From 1afc868b885df277724d0920c0d3f368b39a9405 Mon Sep 17 00:00:00 2001 From: robobun <117481402+robobun@users.noreply.github.com> Date: Fri, 29 May 2026 23:13:18 +0000 Subject: [PATCH] Strip comment blocks longer than 3 lines from .rs files Adds scripts/strip-long-rs-comments.ts and applies it across the tree. The script removes every `//`-style comment block of 4 or more consecutive comment-only lines from git-tracked *.rs files, except: - a block that is the first non-blank content in the file (top-of-file header), - `// HOST_EXPORT(...)` marker lines, which src/codegen/generate-host-exports.ts scrapes to emit `extern "C"` thunks, kept even when surrounding prose is removed, - any block containing SAFETY / a `Safety:`-style marker / a `/// # Safety` heading, since clippy::undocumented_unsafe_blocks is deny workspace-wide. A comment block is a maximal run of lines whose trimmed content starts with `//` (covers `//`, `///`, `//!`). Inline `/* ... */` and trailing `// ...` after code are not touched. vendor/, packages/ and scripts/verify-baseline-static/ are skipped. When a block is removed, adjacent blank lines are collapsed. Four bodies keep a sub-threshold (<=3 line) slice of their original comment because removing it outright would trip a deny-level clippy lint on the resulting construct: - jsc/ConsoleObject.rs: `if token == PercentTag::O {}` (clippy::needless_ifs) - js_parser/p.rs: the two `Substitution::Failure` else-if arms, whose bodies are identical once the explaining comments are gone (clippy::if_same_then_else) - install/lifecycle_script_runner.rs: the `finished_installing` else arm, same body as the if arm (clippy::if_same_then_else) - resolver/resolver.rs: the tsconfig paths-merge else arm (clippy::needless_else) --- scripts/strip-long-rs-comments.ts | 245 +++++ src/analytics/lib.rs | 60 -- src/analytics/schema.rs | 45 - src/api/lib.rs | 27 - src/ast/ast_memory_allocator.rs | 72 -- src/ast/ast_result.rs | 19 - src/ast/b.rs | 41 - src/ast/base.rs | 30 - src/ast/binding.rs | 48 - src/ast/char_freq.rs | 31 - src/ast/e.rs | 221 ----- src/ast/expr.rs | 159 ---- src/ast/fold_string_addition.rs | 46 - src/ast/g.rs | 13 - src/ast/import_record.rs | 24 - src/ast/known_global.rs | 33 +- src/ast/lexer_tables.rs | 61 -- src/ast/lib.rs | 329 ------- src/ast/loader.rs | 17 - src/ast/new_store.rs | 73 -- src/ast/nodes.rs | 164 ---- src/ast/op.rs | 8 - src/ast/runtime.rs | 22 - src/ast/s.rs | 21 - src/ast/scope.rs | 38 - src/ast/server_component_boundary.rs | 5 - src/ast/stmt.rs | 37 - src/ast/symbol.rs | 240 ----- src/ast/transpiler_cache.rs | 12 - src/ast/ts.rs | 111 --- src/base64/lib.rs | 71 -- src/boringssl/lib.rs | 35 - src/boringssl_sys/boringssl.rs | 29 - src/boringssl_sys/lib.rs | 4 - src/brotli/lib.rs | 10 - src/brotli_sys/brotli_c.rs | 5 - src/bun_alloc/MaxHeapAllocator.rs | 4 - src/bun_alloc/MimallocArena.rs | 146 --- src/bun_alloc/NullableAllocator.rs | 4 - src/bun_alloc/ast_alloc.rs | 86 -- src/bun_alloc/c_thunks.rs | 14 - src/bun_alloc/fallback/z.rs | 6 - src/bun_alloc/hashbrown_bridge.rs | 12 - src/bun_alloc/heap_breakdown.rs | 48 - src/bun_alloc/lib.rs | 542 +---------- src/bun_alloc/maybe_owned.rs | 9 - src/bun_alloc/memory.rs | 29 - src/bun_alloc/stack_fallback.rs | 35 - src/bun_bin/lib.rs | 68 -- src/bun_bin/phase_c_exports.rs | 127 --- src/bun_core/Global.rs | 96 -- src/bun_core/Progress.rs | 61 -- src/bun_core/atomic_cell.rs | 39 - src/bun_core/bounded_array.rs | 29 - src/bun_core/build.rs | 8 - src/bun_core/debug.rs | 46 - src/bun_core/deprecated.rs | 62 -- src/bun_core/env.rs | 13 - src/bun_core/env_var.rs | 99 -- src/bun_core/external_shared.rs | 10 - src/bun_core/feature_flags.rs | 46 - src/bun_core/fmt.rs | 322 ------- src/bun_core/heap.rs | 28 - src/bun_core/hint.rs | 16 - src/bun_core/lib.rs | 495 +--------- src/bun_core/output.rs | 354 ------- src/bun_core/result.rs | 74 -- src/bun_core/string/HashedString.rs | 4 - src/bun_core/string/MutableString.rs | 37 - src/bun_core/string/PathString.rs | 20 - src/bun_core/string/SmolStr.rs | 10 - src/bun_core/string/StringBuilder.rs | 28 - src/bun_core/string/StringJoiner.rs | 8 - src/bun_core/string/identifier.rs | 23 - src/bun_core/string/immutable.rs | 273 ------ .../string/immutable/exact_size_matcher.rs | 10 - src/bun_core/string/immutable/unicode.rs | 100 -- src/bun_core/string/immutable/visible.rs | 4 - src/bun_core/string/mod.rs | 332 ------- src/bun_core/string/wtf.rs | 31 - src/bun_core/thread_id.rs | 29 - src/bun_core/util.rs | 734 -------------- src/bun_core/windows_sys.rs | 7 - src/bun_core_macros/lib.rs | 101 -- src/bun_output_tags/lib.rs | 9 - src/bundler/AstBuilder.rs | 63 -- src/bundler/BundleThread.rs | 103 -- src/bundler/Chunk.rs | 161 +--- src/bundler/Graph.rs | 54 -- src/bundler/HTMLImportManifest.rs | 8 - src/bundler/HTMLScanner.rs | 11 - src/bundler/LinkerContext.rs | 423 +-------- src/bundler/LinkerGraph.rs | 133 --- src/bundler/OutputFile.rs | 42 - src/bundler/ParseTask.rs | 299 ------ src/bundler/ServerComponentParseTask.rs | 29 - src/bundler/ThreadPool.rs | 134 +-- src/bundler/analyze_transpiled_module.rs | 59 -- src/bundler/barrel_imports.rs | 106 --- src/bundler/bundle_v2.rs | 624 ------------ src/bundler/bundled_ast.rs | 24 - src/bundler/cache.rs | 85 -- src/bundler/defines.rs | 82 -- src/bundler/entry_points.rs | 46 - src/bundler/lib.rs | 42 - src/bundler/linker.rs | 120 +-- src/bundler/linker_context/MetafileBuilder.rs | 33 - .../linker_context/StaticRouteVisitor.rs | 10 - src/bundler/linker_context/computeChunks.rs | 37 - .../computeCrossChunkDependencies.rs | 74 -- .../linker_context/convertStmtsForChunk.rs | 32 - .../convertStmtsForChunkForDevServer.rs | 30 - src/bundler/linker_context/doStep5.rs | 82 -- .../findAllImportedPartsInJSOrder.rs | 7 - .../findImportedCSSFilesInJSOrder.rs | 23 - .../findImportedFilesInCSSOrder.rs | 229 ----- .../generateChunksInParallel.rs | 68 -- .../generateCodeForFileInChunkJS.rs | 100 -- .../generateCodeForLazyExport.rs | 25 - .../generateCompileResultForCssChunk.rs | 9 - .../generateCompileResultForHtmlChunk.rs | 16 - .../generateCompileResultForJSChunk.rs | 23 - .../linker_context/postProcessCSSChunk.rs | 22 - .../linker_context/postProcessJSChunk.rs | 137 --- .../linker_context/prepareCssAstsForChunk.rs | 98 -- .../linker_context/renameSymbolsInChunk.rs | 39 - .../linker_context/scanImportsAndExports.rs | 184 ---- .../linker_context/writeOutputFilesToDisk.rs | 18 - src/bundler/options.rs | 224 +---- src/bundler/transpiler.rs | 609 ------------ src/bundler_jsc/PluginRunner.rs | 15 - src/bundler_jsc/analyze_jsc.rs | 10 - src/bundler_jsc/lib.rs | 16 - src/bunfig/arguments.rs | 9 - src/bunfig/bunfig.rs | 29 - src/cares_sys/c_ares.rs | 87 -- src/cares_sys/lib.rs | 6 - src/clap/args.rs | 31 - src/clap/comptime.rs | 97 -- src/clap/lib.rs | 160 ---- src/clap/streaming.rs | 7 - src/clap_macros/lib.rs | 22 - src/collections/StaticHashMap.rs | 10 - src/collections/array_hash_map.rs | 306 ------ src/collections/array_list.rs | 56 -- src/collections/bit_set.rs | 150 --- src/collections/comptime_string_map.rs | 41 - src/collections/hive_array.rs | 127 --- src/collections/identity_context.rs | 4 - src/collections/lib.rs | 57 -- src/collections/linear_fifo.rs | 77 -- src/collections/multi_array_list.rs | 157 --- src/collections/pool.rs | 98 -- src/collections/string_map.rs | 4 - src/collections/vec_ext.rs | 50 - src/collections/zig_hash_map.rs | 14 - src/crash_handler/CPUFeatures.rs | 6 - src/crash_handler/handle_oom.rs | 46 - src/crash_handler/lib.rs | 295 ------ src/csrf/lib.rs | 13 - src/css/context.rs | 7 - src/css/css_modules.rs | 25 - src/css/css_parser.rs | 359 ------- src/css/declaration.rs | 57 -- src/css/generics.rs | 127 --- src/css/lib.rs | 72 +- src/css/media_query.rs | 84 -- src/css/printer.rs | 80 -- src/css/properties/align.rs | 12 - src/css/properties/animation.rs | 31 - src/css/properties/background.rs | 15 - src/css/properties/border.rs | 33 - src/css/properties/border_image.rs | 11 - src/css/properties/border_radius.rs | 13 - src/css/properties/box_shadow.rs | 8 - src/css/properties/css_modules.rs | 4 - src/css/properties/custom.rs | 43 - src/css/properties/flex.rs | 48 - src/css/properties/font.rs | 35 - src/css/properties/grid.rs | 13 - src/css/properties/margin_padding.rs | 96 -- src/css/properties/masking.rs | 27 - src/css/properties/mod.rs | 91 -- src/css/properties/outline.rs | 4 - src/css/properties/overflow.rs | 4 - src/css/properties/prefix_handler.rs | 21 - src/css/properties/properties_generated.rs | 60 -- src/css/properties/properties_impl.rs | 17 - src/css/properties/shape.rs | 11 - src/css/properties/size.rs | 12 - src/css/properties/transform.rs | 14 - src/css/properties/transition.rs | 9 - src/css/rules/container.rs | 4 - src/css/rules/document.rs | 4 - src/css/rules/font_face.rs | 54 -- src/css/rules/import.rs | 36 - src/css/rules/keyframes.rs | 18 - src/css/rules/layer.rs | 17 - src/css/rules/media.rs | 5 - src/css/rules/mod.rs | 220 +---- src/css/rules/page.rs | 12 - src/css/rules/property.rs | 6 - src/css/rules/scope.rs | 5 - src/css/rules/style.rs | 76 -- src/css/rules/supports.rs | 43 - src/css/rules/tailwind.rs | 5 - src/css/rules/viewport.rs | 4 - src/css/selectors/builder.rs | 37 - src/css/selectors/mod.rs | 5 - src/css/selectors/parser.rs | 284 ------ src/css/selectors/selector.rs | 197 ---- src/css/small_list.rs | 21 - src/css/targets.rs | 26 - src/css/values/alpha.rs | 4 - src/css/values/angle.rs | 4 - src/css/values/calc.rs | 98 +- src/css/values/color.rs | 135 --- src/css/values/css_string.rs | 8 - src/css/values/easing.rs | 21 - src/css/values/gradient.rs | 25 - src/css/values/ident.rs | 80 -- src/css/values/image.rs | 13 - src/css/values/length.rs | 25 - src/css/values/mod.rs | 19 +- src/css/values/percentage.rs | 16 - src/css/values/rect.rs | 16 - src/css/values/size.rs | 7 - src/css/values/syntax.rs | 32 - src/css/values/time.rs | 14 - src/css/values/url.rs | 4 - src/css_derive/lib.rs | 158 --- src/css_jsc/color_js.rs | 4 - src/css_jsc/css_internals.rs | 16 - src/css_jsc/lib.rs | 5 - src/dispatch/lib.rs | 35 - src/dns/lib.rs | 42 - src/dotenv/env_loader.rs | 138 --- src/errno/lib.rs | 109 --- src/errno/linux_errno.rs | 4 - src/errno/windows_errno.rs | 103 -- src/event_loop/AnyEventLoop.rs | 113 --- src/event_loop/AnyTask.rs | 10 - src/event_loop/AnyTaskWithExtraContext.rs | 31 - src/event_loop/AutoFlusher.rs | 22 - src/event_loop/ConcurrentTask.rs | 62 -- src/event_loop/DeferredTaskQueue.rs | 12 - src/event_loop/EventLoopTimer.rs | 83 -- src/event_loop/ManagedTask.rs | 8 - src/event_loop/MiniEventLoop.rs | 62 -- src/event_loop/SpawnSyncEventLoop.rs | 92 -- src/event_loop/lib.rs | 18 - src/exe_format/elf.rs | 126 --- src/exe_format/lib.rs | 20 - src/exe_format/macho.rs | 24 - src/exe_format/macho_types.rs | 4 - src/exe_format/pe.rs | 12 - src/glob/GlobWalker.rs | 189 ---- src/glob/lib.rs | 4 - src/glob/matcher.rs | 44 - src/hash/lib.rs | 5 - src/hash/xxhash.rs | 5 - src/highway/lib.rs | 48 - src/http/AsyncHTTP.rs | 91 -- src/http/CertificateInfo.rs | 5 - src/http/Decompressor.rs | 29 - src/http/H2Client.rs | 30 - src/http/H3Client.rs | 11 - src/http/HTTPCertError.rs | 12 - src/http/HTTPContext.rs | 225 +---- src/http/HTTPRequestBody.rs | 22 - src/http/HTTPThread.rs | 229 ----- src/http/HeaderBuilder.rs | 5 - src/http/HeaderValueIterator.rs | 4 - src/http/Headers.rs | 12 - src/http/InternalState.rs | 59 -- src/http/ProxyTunnel.rs | 161 ---- src/http/SendFile.rs | 4 - src/http/Signals.rs | 12 - src/http/ThreadSafeStreamBuffer.rs | 8 - src/http/h2_client/ClientSession.rs | 165 ---- src/http/h2_client/PendingConnect.rs | 23 - src/http/h2_client/Stream.rs | 17 - src/http/h2_client/dispatch.rs | 73 -- src/http/h2_client/encode.rs | 5 - src/http/h3_client/AltSvc.rs | 17 - src/http/h3_client/ClientContext.rs | 42 - src/http/h3_client/ClientSession.rs | 93 -- src/http/h3_client/PendingConnect.rs | 25 - src/http/h3_client/Stream.rs | 22 - src/http/h3_client/callbacks.rs | 31 - src/http/lib.rs | 528 +---------- src/http/lshpack.rs | 23 - src/http/ssl_config.rs | 63 +- src/http/websocket.rs | 16 - src/http/zlib.rs | 5 - src/http_jsc/headers_jsc.rs | 22 - src/http_jsc/method_jsc.rs | 5 - src/http_jsc/websocket_client.rs | 151 --- src/http_jsc/websocket_client/CppWebSocket.rs | 17 - .../websocket_client/WebSocketDeflate.rs | 12 - .../websocket_client/WebSocketProxy.rs | 4 - .../websocket_client/WebSocketProxyTunnel.rs | 43 - .../WebSocketUpgradeClient.rs | 127 --- src/http_types/ETag.rs | 27 - src/http_types/FetchRedirect.rs | 10 - src/http_types/Method.rs | 38 - src/http_types/MimeType.rs | 21 - src/http_types/URLPath.rs | 34 - src/http_types/h2.rs | 36 - src/http_types/lib.rs | 10 - src/http_types/mime_type_list_enum.rs | 4 - src/ini/lib.rs | 169 +--- src/install/NetworkTask.rs | 149 --- src/install/PackageInstall.rs | 161 +--- src/install/PackageInstaller.rs | 137 --- src/install/PackageManager.rs | 329 ------- .../PackageManager/CommandLineArguments.rs | 24 - .../PackageManager/PackageJSONEditor.rs | 43 - .../PackageManagerDirectories.rs | 69 -- .../PackageManager/PackageManagerEnqueue.rs | 128 --- .../PackageManager/PackageManagerLifecycle.rs | 40 - .../PackageManager/PackageManagerOptions.rs | 29 - .../PackageManagerResolution.rs | 22 - .../PackageManager/PopulateManifestCache.rs | 31 - src/install/PackageManager/ProgressStrings.rs | 7 - src/install/PackageManager/UpdateRequest.rs | 19 - .../WorkspacePackageJSONCache.rs | 45 +- .../PackageManager/install_with_manager.rs | 126 --- src/install/PackageManager/patchPackage.rs | 66 -- .../PackageManager/processDependencyList.rs | 23 - src/install/PackageManager/runTasks.rs | 150 --- .../PackageManager/security_scanner.rs | 140 --- .../updatePackageJSONAndInstall.rs | 80 -- src/install/PackageManagerTask.rs | 63 -- src/install/PackageManifestMap.rs | 25 - src/install/TarballStream.rs | 152 --- src/install/auto_installer.rs | 41 - src/install/bin.rs | 148 --- src/install/build.rs | 21 - src/install/dependency.rs | 117 --- src/install/extract_tarball.rs | 39 - src/install/hoisted_install.rs | 57 -- src/install/hosted_git_info.rs | 108 --- src/install/integrity.rs | 12 - src/install/isolated_install.rs | 265 +----- src/install/isolated_install/FileCloner.rs | 15 - src/install/isolated_install/FileCopier.rs | 29 - src/install/isolated_install/Hardlinker.rs | 40 +- src/install/isolated_install/Installer.rs | 189 +--- src/install/isolated_install/Store.rs | 53 -- src/install/isolated_install/Symlinker.rs | 7 - src/install/lib.rs | 214 ----- src/install/lifecycle_script_runner.rs | 120 +-- src/install/lockfile.rs | 241 ----- src/install/lockfile/Buffers.rs | 57 -- src/install/lockfile/CatalogMap.rs | 45 +- src/install/lockfile/OverrideMap.rs | 28 - src/install/lockfile/Package.rs | 324 +------ src/install/lockfile/Package/Meta.rs | 10 - src/install/lockfile/Package/Scripts.rs | 36 - src/install/lockfile/Package/WorkspaceMap.rs | 30 - src/install/lockfile/Tree.rs | 100 -- src/install/lockfile/bun.lock.rs | 176 +--- src/install/lockfile/bun.lockb.rs | 83 -- .../lockfile_json_stringify_for_debugging.rs | 24 - src/install/lockfile/printer/Yarn.rs | 8 +- src/install/migration.rs | 56 -- src/install/npm.rs | 198 ---- src/install/padding_checker.rs | 85 -- src/install/patch_install.rs | 36 - src/install/pnpm.rs | 44 - src/install/postinstall_optimizer.rs | 21 +- src/install/repository.rs | 68 -- src/install/resolution.rs | 68 -- src/install/resolvers/folder_resolver.rs | 39 - src/install/windows-shim/BinLinkingShim.rs | 47 - src/install/windows-shim/bun_shim_impl.rs | 204 +--- src/install/windows-shim/main.rs | 76 -- src/install/yarn.rs | 43 +- src/install_jsc/dependency_jsc.rs | 9 - src/install_jsc/ini_jsc.rs | 14 - src/install_jsc/install_binding.rs | 9 - src/install_jsc/lib.rs | 17 - src/install_jsc/npm_jsc.rs | 7 - src/install_jsc/update_request_jsc.rs | 4 - src/install_types/NodeLinker.rs | 51 - src/install_types/resolver_hooks.rs | 157 --- src/io/MaxBuf.rs | 39 - src/io/ParentDeathWatchdog.rs | 114 +-- src/io/PipeReader.rs | 158 --- src/io/PipeWriter.rs | 302 ------ src/io/heap.rs | 38 - src/io/keep_alive.rs | 5 - src/io/lib.rs | 280 ------ src/io/openForWriting.rs | 10 - src/io/pipes.rs | 12 - src/io/posix_event_loop.rs | 108 --- src/io/source.rs | 54 -- src/io/windows_event_loop.rs | 39 - src/io/write.rs | 50 - src/js_parser/benches/string_map_vs_phf.rs | 20 - src/js_parser/defines_table.rs | 9 - src/js_parser/fold.rs | 76 +- src/js_parser/lexer.rs | 284 ------ src/js_parser/lib.rs | 117 --- src/js_parser/lower/lower_decorators.rs | 17 - src/js_parser/lower/lower_esm_exports_hmr.rs | 42 - src/js_parser/p.rs | 896 +----------------- src/js_parser/parse/mod.rs | 46 +- src/js_parser/parse/parse_entry.rs | 319 ------- src/js_parser/parse/parse_fn.rs | 15 - src/js_parser/parse/parse_import_export.rs | 37 - src/js_parser/parse/parse_jsx.rs | 27 - src/js_parser/parse/parse_prefix.rs | 58 -- src/js_parser/parse/parse_skip_typescript.rs | 77 -- src/js_parser/parse/parse_stmt.rs | 126 --- src/js_parser/parse/parse_suffix.rs | 37 - src/js_parser/parse/parse_typescript.rs | 60 -- src/js_parser/parser.rs | 443 +-------- src/js_parser/repl_transforms.rs | 13 - src/js_parser/scan/scan_imports.rs | 120 --- src/js_parser/scan/scan_side_effects.rs | 32 - src/js_parser/scan/scan_symbols.rs | 22 - src/js_parser/typescript.rs | 20 - src/js_parser/visit/mod.rs | 134 --- src/js_parser/visit/visit_binary.rs | 25 - src/js_parser/visit/visit_expr.rs | 171 +--- src/js_parser/visit/visit_stmt.rs | 81 -- src/js_parser_jsc/Macro.rs | 143 --- src/js_parser_jsc/expr_jsc.rs | 18 - src/js_printer/lib.rs | 325 ------- src/js_printer/renamer.rs | 132 --- src/jsc/AbortSignal.rs | 66 -- src/jsc/AnyPromise.rs | 29 - src/jsc/AsyncModule.rs | 152 --- src/jsc/BuildMessage.rs | 6 - src/jsc/CachedBytecode.rs | 27 - src/jsc/CallFrame.rs | 60 -- src/jsc/CommonAbortReason.rs | 8 - src/jsc/ConcurrentPromiseTask.rs | 7 - src/jsc/ConsoleObject.rs | 344 +------ src/jsc/CppTask.rs | 5 - src/jsc/DOMFormData.rs | 18 - src/jsc/DOMURL.rs | 5 - src/jsc/Debugger.rs | 113 --- src/jsc/DeprecatedStrong.rs | 18 - src/jsc/ErrorCode.rs | 61 -- src/jsc/EventType.rs | 5 - src/jsc/FFI.rs | 10 - src/jsc/FetchHeaders.rs | 23 - src/jsc/GarbageCollectionController.rs | 36 - src/jsc/HTTPServerAgent.rs | 23 - src/jsc/JSBigInt.rs | 6 - src/jsc/JSCell.rs | 56 -- src/jsc/JSFunction.rs | 15 - src/jsc/JSGlobalObject.rs | 204 ---- src/jsc/JSMap.rs | 11 - src/jsc/JSModuleLoader.rs | 16 - src/jsc/JSONLineBuffer.rs | 16 - src/jsc/JSObject.rs | 66 -- src/jsc/JSPromise.rs | 68 -- src/jsc/JSPropertyIterator.rs | 21 - src/jsc/JSRef.rs | 110 --- src/jsc/JSSecrets.rs | 16 - src/jsc/JSString.rs | 35 - src/jsc/JSType.rs | 280 ------ src/jsc/JSUint8Array.rs | 4 - src/jsc/JSValue.rs | 251 ----- src/jsc/MarkedArgumentBuffer.rs | 8 - src/jsc/ModuleLoader.rs | 137 --- src/jsc/NodeModuleModule.rs | 13 - src/jsc/PluginRunner.rs | 6 - src/jsc/PosixSignalHandle.rs | 4 - src/jsc/RefString.rs | 9 - src/jsc/RegularExpression.rs | 21 - src/jsc/ResolveMessage.rs | 19 - src/jsc/ResolvedSource.rs | 33 - src/jsc/RuntimeTranspilerCache.rs | 99 -- src/jsc/RuntimeTranspilerStore.rs | 163 ---- src/jsc/SavedSourceMap.rs | 45 - src/jsc/StringBuilder.rs | 16 - src/jsc/Strong.rs | 19 - src/jsc/SystemError.rs | 42 - src/jsc/Task.rs | 20 - src/jsc/TopExceptionScope.rs | 154 --- src/jsc/URL.rs | 24 - src/jsc/VM.rs | 20 - src/jsc/VirtualMachine.rs | 832 ---------------- src/jsc/Weak.rs | 19 - src/jsc/WorkTask.rs | 17 - src/jsc/ZigException.rs | 14 - src/jsc/ZigStackFrame.rs | 16 - src/jsc/ZigStackTrace.rs | 10 - src/jsc/ZigString.rs | 19 - src/jsc/any_task_job.rs | 27 - src/jsc/array_buffer.rs | 85 -- src/jsc/bindgen.rs | 60 -- src/jsc/btjs.rs | 38 - src/jsc/build.rs | 9 - src/jsc/bun_string_jsc.rs | 26 - src/jsc/codegen.rs | 8 - src/jsc/comptime_string_map_jsc.rs | 12 - src/jsc/cpp.rs | 5 - src/jsc/event_loop.rs | 201 ---- src/jsc/ffi_imports.rs | 11 - src/jsc/fmt_jsc.rs | 5 - src/jsc/generated.rs | 205 ---- src/jsc/host_fn.rs | 221 ----- src/jsc/host_object.rs | 15 - src/jsc/hot_reloader.rs | 235 ----- src/jsc/ipc.rs | 177 ---- src/jsc/lib.rs | 322 ------- src/jsc/node_path.rs | 38 - src/jsc/rare_data.rs | 151 --- src/jsc/resolve_path_jsc.rs | 4 - src/jsc/static_export.rs | 16 - src/jsc/virtual_machine_exports.rs | 19 - src/jsc/web_worker.rs | 440 --------- src/jsc/webcore_types.rs | 97 -- src/jsc_macros/lib.rs | 136 --- src/libarchive/lib.rs | 173 ---- src/libdeflate_sys/libdeflate.rs | 40 - src/libuv_sys/lib.rs | 19 - src/libuv_sys/libuv.rs | 172 ---- src/lolhtml_sys/lib.rs | 4 - src/lolhtml_sys/lol_html.rs | 130 --- src/md/ansi_renderer.rs | 191 ---- src/md/autolinks.rs | 5 - src/md/blocks.rs | 8 - src/md/helpers.rs | 4 - src/md/html_renderer.rs | 4 - src/md/inlines.rs | 36 - src/md/line_analysis.rs | 10 - src/md/links.rs | 34 - src/md/parser.rs | 71 -- src/md/ref_defs.rs | 4 - src/md/types.rs | 14 - src/md/unicode.rs | 31 - src/mimalloc_sys/mimalloc.rs | 15 - src/opaque/lib.rs | 86 -- src/options_types/bundle_enums.rs | 18 - src/options_types/command_tag.rs | 9 - src/options_types/compile_target.rs | 6 - src/options_types/context.rs | 53 -- src/options_types/jsx.rs | 37 - src/options_types/lib.rs | 12 - src/options_types/schema.rs | 52 - src/output/lib.rs | 20 - src/parsers/json.rs | 139 --- src/parsers/json_lexer.rs | 72 -- src/parsers/lib.rs | 10 - src/parsers/toml.rs | 16 - src/parsers/toml/lexer.rs | 44 - src/parsers/yaml.rs | 263 ----- src/patch/lib.rs | 118 --- src/patch_jsc/testing.rs | 23 - src/paths/EnvPath.rs | 10 - src/paths/Path.rs | 119 --- src/paths/component_iterator.rs | 29 - src/paths/lib.rs | 193 +--- src/paths/path_char.rs | 19 - src/paths/resolve_path.rs | 158 --- src/paths/string_paths.rs | 20 - src/perf/lib.rs | 13 - src/perf/tracy.rs | 27 - src/picohttp/lib.rs | 16 - src/platform/darwin.rs | 5 - src/platform/lib.rs | 4 - src/platform/linux.rs | 17 - src/ptr/CowSlice.rs | 62 -- src/ptr/lib.rs | 103 -- src/ptr/meta.rs | 30 - src/ptr/owned.rs | 192 ---- src/ptr/parent_ref.rs | 46 - src/ptr/raw_ref_count.rs | 13 - src/ptr/ref_count.rs | 163 ---- src/ptr/shared.rs | 108 --- src/ptr/tagged_pointer.rs | 38 - src/ptr/weak_ptr.rs | 20 - src/resolve_builtins/HardcodedModule.rs | 18 - src/resolve_builtins/node_builtins.rs | 8 - src/resolver/dir_info.rs | 131 --- src/resolver/fs.rs | 378 -------- src/resolver/lib.rs | 323 ------- src/resolver/node_fallbacks.rs | 30 - src/resolver/options.rs | 66 -- src/resolver/package_json.rs | 219 ----- src/resolver/resolver.rs | 733 +------------- src/resolver/result.rs | 30 - src/resolver/standalone_module_graph.rs | 14 - src/resolver/tsconfig_json.rs | 109 --- src/router/lib.rs | 118 --- src/runtime/allocators/LinuxMemFdAllocator.rs | 37 - src/runtime/allocators/mod.rs | 16 - src/runtime/api.rs | 37 - src/runtime/api/Archive.rs | 52 - src/runtime/api/BunObject.rs | 220 ----- src/runtime/api/HashObject.rs | 38 - src/runtime/api/JSBundler.rs | 105 -- src/runtime/api/JSTranspiler.rs | 117 --- src/runtime/api/MarkdownObject.rs | 54 -- src/runtime/api/NativePromiseContext.rs | 52 - src/runtime/api/UnsafeObject.rs | 7 - src/runtime/api/YAMLObject.rs | 18 - src/runtime/api/bun/SSLContextCache.rs | 21 - src/runtime/api/bun/SecureContext.rs | 32 - src/runtime/api/bun/Terminal.rs | 139 --- src/runtime/api/bun/h2_frame_parser.rs | 216 ----- src/runtime/api/bun/js_bun_spawn_bindings.rs | 196 +--- src/runtime/api/bun/spawn.rs | 4 - src/runtime/api/bun/spawn/stdio.rs | 9 - src/runtime/api/bun/subprocess.rs | 147 --- src/runtime/api/bun/subprocess/Readable.rs | 31 - .../bun/subprocess/SubprocessPipeReader.rs | 35 - src/runtime/api/bun/subprocess/Writable.rs | 77 -- src/runtime/api/crash_handler_jsc.rs | 8 - src/runtime/api/cron.rs | 177 +--- src/runtime/api/csrf_jsc.rs | 13 - src/runtime/api/filesystem_router.rs | 78 -- src/runtime/api/glob.rs | 29 - src/runtime/api/html_rewriter.rs | 131 +-- src/runtime/api/js_bundle_completion_task.rs | 83 -- src/runtime/api/lolhtml_jsc.rs | 11 - src/runtime/api/output_file_jsc.rs | 8 - src/runtime/api/standalone_graph_jsc.rs | 11 - src/runtime/bake/DevServer.rs | 284 ------ .../bake/DevServer/ErrorReportRequest.rs | 63 -- src/runtime/bake/DevServer/memory_cost.rs | 60 -- src/runtime/bake/FrameworkRouter.rs | 83 -- src/runtime/bake/bake_body.rs | 101 -- src/runtime/bake/dev_server/assets.rs | 22 - .../bake/dev_server/incremental_graph.rs | 37 - src/runtime/bake/dev_server/lifecycle.rs | 6 - src/runtime/bake/dev_server/mod.rs | 74 -- src/runtime/bake/dev_server/packed_map.rs | 7 - src/runtime/bake/dev_server/route_bundle.rs | 32 - .../bake/dev_server/serialized_failure.rs | 30 - .../bake/dev_server/source_map_store.rs | 64 -- src/runtime/bake/mod.rs | 95 -- src/runtime/bake/production.rs | 110 --- src/runtime/build.rs | 6 - src/runtime/cli/Arguments.rs | 76 -- src/runtime/cli/audit_command.rs | 20 - src/runtime/cli/build_command.rs | 49 +- src/runtime/cli/bunx_command.rs | 125 --- src/runtime/cli/colon_list_type.rs | 9 - .../cli/create/SourceFileProjectGenerator.rs | 22 - src/runtime/cli/create_command.rs | 400 -------- src/runtime/cli/exec_command.rs | 11 - src/runtime/cli/filter_arg.rs | 4 - src/runtime/cli/filter_run.rs | 34 - src/runtime/cli/fuzzilli_command.rs | 4 - src/runtime/cli/init_command.rs | 33 - src/runtime/cli/install_command.rs | 26 - .../cli/install_completions_command.rs | 8 - src/runtime/cli/link_command.rs | 5 - src/runtime/cli/mod.rs | 281 ------ src/runtime/cli/multi_run.rs | 14 - src/runtime/cli/open.rs | 24 - src/runtime/cli/outdated_command.rs | 19 +- src/runtime/cli/pack_command.rs | 97 +- src/runtime/cli/package_manager_command.rs | 23 - src/runtime/cli/pm_pkg_command.rs | 10 - src/runtime/cli/pm_trusted_command.rs | 13 - src/runtime/cli/pm_update_package_json.rs | 25 - src/runtime/cli/pm_why_command.rs | 5 - src/runtime/cli/publish_command.rs | 25 - src/runtime/cli/repl.rs | 38 - src/runtime/cli/repl_command.rs | 18 - src/runtime/cli/run_command.rs | 394 -------- src/runtime/cli/scan_command.rs | 4 - src/runtime/cli/shell_completions.rs | 10 - src/runtime/cli/test/ChangedFilesFilter.rs | 71 -- src/runtime/cli/test/Scanner.rs | 18 - src/runtime/cli/test/parallel/Channel.rs | 74 -- src/runtime/cli/test/parallel/Coordinator.rs | 86 -- src/runtime/cli/test/parallel/FileRange.rs | 5 - src/runtime/cli/test/parallel/Frame.rs | 8 - src/runtime/cli/test/parallel/Worker.rs | 68 -- src/runtime/cli/test/parallel/aggregate.rs | 9 - src/runtime/cli/test/parallel/runner.rs | 41 - src/runtime/cli/test_command.rs | 113 --- src/runtime/cli/unlink_command.rs | 9 - src/runtime/cli/update_interactive_command.rs | 105 +- src/runtime/cli/upgrade_command.rs | 66 -- src/runtime/cli/why_command.rs | 14 - src/runtime/crypto/CryptoHasher.rs | 65 -- src/runtime/crypto/EVP.rs | 33 - src/runtime/crypto/PBKDF2.rs | 10 - src/runtime/crypto/PasswordObject.rs | 36 - src/runtime/crypto/mod.rs | 15 - src/runtime/crypto/pwhash.rs | 34 - src/runtime/dispatch.rs | 108 --- src/runtime/dispatch_js2native.rs | 9 - src/runtime/dns_jsc/cares_jsc.rs | 9 - src/runtime/dns_jsc/dns.rs | 176 ---- src/runtime/dns_jsc/mod.rs | 7 - src/runtime/dns_jsc/options_jsc.rs | 6 +- src/runtime/ffi/FFIObject.rs | 59 +- src/runtime/ffi/abi_type.rs | 12 - src/runtime/ffi/ffi_body.rs | 72 -- src/runtime/ffi/mod.rs | 35 - src/runtime/ffi_imports.rs | 4 - src/runtime/generated_classes.rs | 19 +- src/runtime/hw_exports.rs | 61 -- src/runtime/image/Image.rs | 265 ------ src/runtime/image/backend_coregraphics.rs | 14 - src/runtime/image/backend_wic.rs | 78 -- src/runtime/image/codec_bmp.rs | 10 - src/runtime/image/codec_gif.rs | 39 - src/runtime/image/codec_jpeg.rs | 27 - src/runtime/image/codec_png.rs | 29 - src/runtime/image/codec_webp.rs | 42 - src/runtime/image/codecs.rs | 105 -- src/runtime/image/exif.rs | 4 - src/runtime/image/mod.rs | 17 - src/runtime/image/quantize.rs | 27 - src/runtime/ipc_host.rs | 7 - src/runtime/jsc_hooks.rs | 637 +------------ src/runtime/lib.rs | 17 +- src/runtime/linear_fifo_testing.rs | 10 - src/runtime/napi/mod.rs | 5 - src/runtime/napi/napi_body.rs | 95 -- src/runtime/node.rs | 87 +- src/runtime/node/Stat.rs | 21 - src/runtime/node/StatFS.rs | 13 - src/runtime/node/assert/myers_diff.rs | 101 -- src/runtime/node/buffer.rs | 8 - src/runtime/node/dir_iterator.rs | 66 -- src/runtime/node/fs_events.rs | 69 -- src/runtime/node/net/BlockList.rs | 33 - src/runtime/node/node_assert.rs | 21 - src/runtime/node/node_assert_binding.rs | 9 - src/runtime/node/node_cluster_binding.rs | 20 - src/runtime/node/node_crypto_binding.rs | 54 -- src/runtime/node/node_error_binding.rs | 11 - src/runtime/node/node_fs.rs | 729 +------------- src/runtime/node/node_fs_binding.rs | 35 - src/runtime/node/node_fs_constant.rs | 11 - src/runtime/node/node_fs_stat_watcher.rs | 121 +-- src/runtime/node/node_fs_watcher.rs | 80 -- src/runtime/node/node_net_binding.rs | 12 - src/runtime/node/node_os.rs | 42 - src/runtime/node/node_process.rs | 23 - src/runtime/node/node_util_binding.rs | 13 - src/runtime/node/node_zlib_binding.rs | 71 -- src/runtime/node/nodejs_error_code.rs | 25 - src/runtime/node/path.rs | 180 ---- src/runtime/node/path_watcher.rs | 169 ---- src/runtime/node/time_like.rs | 24 - src/runtime/node/types.rs | 155 --- src/runtime/node/util/parse_args.rs | 19 - src/runtime/node/util/parse_args_utils.rs | 9 - src/runtime/node/util/validators.rs | 14 - src/runtime/node/uv_signal_handle_windows.rs | 5 - src/runtime/node/win_watcher.rs | 33 - src/runtime/node/zlib/NativeBrotli.rs | 58 -- src/runtime/node/zlib/NativeZlib.rs | 18 - src/runtime/node/zlib/NativeZstd.rs | 30 - src/runtime/server/AnyRequestContext.rs | 29 - src/runtime/server/FileResponseStream.rs | 9 - src/runtime/server/FileRoute.rs | 49 - src/runtime/server/HTMLBundle.rs | 68 -- src/runtime/server/NodeHTTPResponse.rs | 143 --- src/runtime/server/RangeRequest.rs | 12 - src/runtime/server/RequestContext.rs | 301 ------ src/runtime/server/ServerConfig.rs | 75 -- src/runtime/server/ServerWebSocket.rs | 65 -- src/runtime/server/StaticRoute.rs | 19 - src/runtime/server/WebSocketServerContext.rs | 20 - src/runtime/server/mod.rs | 307 ------ src/runtime/server/server_body.rs | 209 ---- src/runtime/shell/Builtin.rs | 148 --- src/runtime/shell/EnvStr.rs | 20 - src/runtime/shell/IO.rs | 10 - src/runtime/shell/IOReader.rs | 70 -- src/runtime/shell/IOWriter.rs | 88 -- src/runtime/shell/ParsedShellScript.rs | 29 - src/runtime/shell/Yield.rs | 13 - src/runtime/shell/builtin/cat.rs | 4 - src/runtime/shell/builtin/cp.rs | 48 - src/runtime/shell/builtin/ls.rs | 4 - src/runtime/shell/builtin/mkdir.rs | 15 - src/runtime/shell/builtin/mv.rs | 17 - src/runtime/shell/builtin/rm.rs | 120 --- src/runtime/shell/builtin/seq.rs | 4 - src/runtime/shell/builtin/yes.rs | 13 - src/runtime/shell/dispatch_tasks.rs | 9 - src/runtime/shell/interpreter.rs | 413 -------- src/runtime/shell/mod.rs | 17 - src/runtime/shell/shell_body.rs | 19 - src/runtime/shell/states/Assigns.rs | 4 - src/runtime/shell/states/Async.rs | 6 - src/runtime/shell/states/Base.rs | 8 - src/runtime/shell/states/Cmd.rs | 129 --- src/runtime/shell/states/CondExpr.rs | 6 - src/runtime/shell/states/Expansion.rs | 79 -- src/runtime/shell/states/If.rs | 5 - src/runtime/shell/states/Pipeline.rs | 40 - src/runtime/shell/states/Script.rs | 8 - src/runtime/shell/states/Subshell.rs | 4 - src/runtime/shell/subproc.rs | 196 ---- src/runtime/shell/util.rs | 4 - src/runtime/socket/Handlers.rs | 20 - src/runtime/socket/Listener.rs | 119 --- src/runtime/socket/SSLConfig.rs | 27 - src/runtime/socket/SocketAddress.rs | 118 --- src/runtime/socket/UpgradedDuplex.rs | 30 - src/runtime/socket/WindowsNamedPipe.rs | 243 ----- src/runtime/socket/WindowsNamedPipeContext.rs | 49 - src/runtime/socket/mod.rs | 37 - src/runtime/socket/socket_body.rs | 384 -------- src/runtime/socket/tls_socket_functions.rs | 67 -- src/runtime/socket/udp_socket.rs | 138 --- src/runtime/socket/uws_dispatch.rs | 23 - src/runtime/socket/uws_handlers.rs | 111 --- src/runtime/socket/uws_jsc.rs | 28 - src/runtime/test_runner/Collection.rs | 14 - src/runtime/test_runner/DoneCallback.rs | 5 - src/runtime/test_runner/Execution.rs | 20 - src/runtime/test_runner/Order.rs | 8 - src/runtime/test_runner/ScopeFunctions.rs | 36 - src/runtime/test_runner/bun_test.rs | 124 --- .../test_runner/diff/diff_match_patch.rs | 112 --- src/runtime/test_runner/diff/printDiff.rs | 14 - src/runtime/test_runner/diff_format.rs | 8 - src/runtime/test_runner/expect.rs | 217 ----- src/runtime/test_runner/expect/toBe.rs | 4 - src/runtime/test_runner/expect/toBeCloseTo.rs | 9 - .../test_runner/expect/toBeInstanceOf.rs | 4 - src/runtime/test_runner/expect/toBeObject.rs | 4 - src/runtime/test_runner/expect/toBeOneOf.rs | 4 - src/runtime/test_runner/expect/toBeWithin.rs | 4 - src/runtime/test_runner/expect/toContain.rs | 4 - .../expect/toEqualIgnoringWhitespace.rs | 4 - .../expect/toHaveBeenCalledWith.rs | 8 - .../expect/toHaveLastReturnedWith.rs | 4 - .../test_runner/expect/toHaveProperty.rs | 4 - .../test_runner/expect/toHaveReturnedWith.rs | 16 - .../test_runner/expect/toIncludeRepeated.rs | 4 - src/runtime/test_runner/expect/toMatch.rs | 4 - src/runtime/test_runner/harness/recover.rs | 29 - src/runtime/test_runner/jest.rs | 16 - src/runtime/test_runner/mod.rs | 75 -- src/runtime/test_runner/pretty_format.rs | 136 --- src/runtime/test_runner/snapshot.rs | 10 - src/runtime/test_runner/timers/FakeTimers.rs | 32 - src/runtime/timer/ImmediateObject.rs | 5 - src/runtime/timer/TimeoutObject.rs | 17 - src/runtime/timer/Timer.rs | 49 - src/runtime/timer/WTFTimer.rs | 4 - src/runtime/timer/mod.rs | 164 +--- src/runtime/timer/timer_object_internals.rs | 106 --- src/runtime/valkey_jsc/ValkeyCommand.rs | 11 - src/runtime/valkey_jsc/js_valkey.rs | 159 ---- src/runtime/valkey_jsc/js_valkey_functions.rs | 49 - src/runtime/valkey_jsc/mod.rs | 18 - src/runtime/valkey_jsc/protocol_jsc.rs | 4 - src/runtime/valkey_jsc/valkey.rs | 85 -- src/runtime/webcore.rs | 72 +- src/runtime/webcore/ArrayBufferSink.rs | 4 - src/runtime/webcore/BakeResponse.rs | 4 - src/runtime/webcore/Blob.rs | 403 -------- src/runtime/webcore/Body.rs | 197 ---- src/runtime/webcore/ByteBlobLoader.rs | 17 - src/runtime/webcore/ByteStream.rs | 38 - src/runtime/webcore/CookieMap.rs | 16 - src/runtime/webcore/Crypto.rs | 8 - src/runtime/webcore/FileReader.rs | 97 -- src/runtime/webcore/FileSink.rs | 206 ---- src/runtime/webcore/FormData.rs | 30 +- src/runtime/webcore/ObjectURLRegistry.rs | 7 - src/runtime/webcore/ReadableStream.rs | 112 --- src/runtime/webcore/Request.rs | 97 -- src/runtime/webcore/Response.rs | 130 --- src/runtime/webcore/ResumableSink.rs | 51 - src/runtime/webcore/S3Client.rs | 33 - src/runtime/webcore/S3File.rs | 45 - src/runtime/webcore/S3Stat.rs | 5 - src/runtime/webcore/Sink.rs | 167 ---- src/runtime/webcore/TextDecoder.rs | 29 - .../webcore/TextEncoderStreamEncoder.rs | 8 - src/runtime/webcore/blob/Store.rs | 32 - src/runtime/webcore/blob/copy_file.rs | 47 - src/runtime/webcore/blob/read_file.rs | 118 --- src/runtime/webcore/blob/write_file.rs | 85 -- src/runtime/webcore/encoding.rs | 98 +- src/runtime/webcore/fetch.rs | 95 -- src/runtime/webcore/fetch/FetchTasklet.rs | 234 ----- src/runtime/webcore/prompt.rs | 8 - src/runtime/webcore/s3/client.rs | 31 - src/runtime/webcore/s3/credentials_jsc.rs | 27 - src/runtime/webcore/s3/download_stream.rs | 29 - src/runtime/webcore/s3/error_jsc.rs | 4 - src/runtime/webcore/s3/list_objects.rs | 11 - src/runtime/webcore/s3/multipart.rs | 101 -- src/runtime/webcore/s3/simple_request.rs | 33 - src/runtime/webcore/streams.rs | 152 --- src/runtime/webcore/wasm_streaming.rs | 4 - src/runtime/webview/ChromeProcess.rs | 83 -- src/runtime/webview/HostProcess.rs | 13 - src/s3_signing/credentials.rs | 78 -- src/safety/CriticalSection.rs | 13 - src/safety/alloc.rs | 30 - src/safety/lib.rs | 34 - src/semver/SemverQuery.rs | 24 - src/semver/Version.rs | 32 - src/semver/lib.rs | 34 - src/sha_hmac/sha.rs | 53 -- src/shell_parser/braces.rs | 44 - src/shell_parser/parse.rs | 135 +-- src/simdutf_sys/lib.rs | 5 - src/simdutf_sys/simdutf.rs | 13 - src/sourcemap/Chunk.rs | 159 ---- src/sourcemap/InternalSourceMap.rs | 95 -- src/sourcemap/LineOffsetTable.rs | 75 -- src/sourcemap/Mapping.rs | 29 - src/sourcemap/ParsedSourceMap.rs | 45 - src/sourcemap/lib.rs | 115 --- src/sourcemap_jsc/CodeCoverage.rs | 61 -- src/sourcemap_jsc/JSSourceMap.rs | 25 - src/sourcemap_jsc/source_provider.rs | 11 - src/spawn/lib.rs | 73 -- src/spawn/process.rs | 496 +--------- src/spawn/static_pipe_writer.rs | 44 - src/spawn_sys/lib.rs | 40 - src/spawn_sys/posix_spawn.rs | 63 -- src/spawn_sys/spawn_process.rs | 91 -- src/sql/lib.rs | 4 - src/sql/mysql/MySQLTypes.rs | 14 - src/sql/mysql/StatusFlags.rs | 4 - src/sql/mysql/protocol/AnyMySQLError.rs | 4 - src/sql/mysql/protocol/Auth.rs | 5 - src/sql/mysql/protocol/CharacterSet.rs | 12 - src/sql/mysql/protocol/ColumnDefinition41.rs | 21 - src/sql/mysql/protocol/NewReader.rs | 41 - src/sql/mysql/protocol/NewWriter.rs | 28 - src/sql/mysql/protocol/PreparedStatement.rs | 7 - src/sql/mysql/protocol/Query.rs | 13 - src/sql/mysql/protocol/StackReader.rs | 4 - src/sql/postgres/AnyPostgresError.rs | 9 - src/sql/postgres/CommandTag.rs | 10 - src/sql/postgres/SocketMonitor.rs | 4 - src/sql/postgres/protocol/Authentication.rs | 4 - src/sql/postgres/protocol/Close.rs | 9 - src/sql/postgres/protocol/DataRow.rs | 6 - src/sql/postgres/protocol/DecoderWrap.rs | 8 - src/sql/postgres/protocol/NewReader.rs | 15 - src/sql/postgres/protocol/NewWriter.rs | 11 - src/sql/postgres/protocol/Parse.rs | 4 - src/sql/postgres/protocol/RowDescription.rs | 5 - src/sql/postgres/protocol/WriteWrap.rs | 8 - src/sql/postgres/types/Tag.rs | 43 - src/sql/shared/Data.rs | 4 - src/sql_jsc/jsc.rs | 194 ---- src/sql_jsc/lib.rs | 12 - src/sql_jsc/mysql.rs | 17 - src/sql_jsc/mysql/JSMySQLConnection.rs | 111 +-- src/sql_jsc/mysql/JSMySQLQuery.rs | 39 +- src/sql_jsc/mysql/MySQLConnection.rs | 154 --- src/sql_jsc/mysql/MySQLQuery.rs | 52 - src/sql_jsc/mysql/MySQLRequestQueue.rs | 56 +- src/sql_jsc/mysql/MySQLStatement.rs | 20 - src/sql_jsc/mysql/MySQLValue.rs | 130 +-- .../mysql/protocol/DecodeBinaryValue.rs | 8 - src/sql_jsc/mysql/protocol/ResultSet.rs | 12 - src/sql_jsc/mysql/protocol/Signature.rs | 19 - .../mysql/protocol/any_mysql_error_jsc.rs | 8 - src/sql_jsc/postgres.rs | 6 - src/sql_jsc/postgres/DataCell.rs | 34 - src/sql_jsc/postgres/PostgresRequest.rs | 32 - src/sql_jsc/postgres/PostgresSQLConnection.rs | 202 ---- src/sql_jsc/postgres/PostgresSQLContext.rs | 9 - src/sql_jsc/postgres/PostgresSQLQuery.rs | 65 -- src/sql_jsc/postgres/PostgresSQLStatement.rs | 16 - src/sql_jsc/postgres/SASL.rs | 10 - src/sql_jsc/postgres/Signature.rs | 10 - src/sql_jsc/postgres/error_jsc.rs | 5 - .../postgres/protocol/notice_response_jsc.rs | 5 - src/sql_jsc/postgres/types/PostgresString.rs | 4 - src/sql_jsc/postgres/types/bytea.rs | 7 - src/sql_jsc/postgres/types/date.rs | 4 - src/sql_jsc/postgres/types/tag_jsc.rs | 6 - src/sql_jsc/shared/CachedStructure.rs | 18 - src/sql_jsc/shared/ObjectIterator.rs | 4 - src/sql_jsc/shared/SQLDataCell.rs | 41 - src/standalone_graph/StandaloneModuleGraph.rs | 178 +--- src/sys/Error.rs | 51 - src/sys/PosixStat.rs | 14 - src/sys/SignalCode.rs | 9 - src/sys/copy_file.rs | 43 - src/sys/coreutils_error_map.rs | 11 - src/sys/dir.rs | 25 - src/sys/fd.rs | 49 - src/sys/file.rs | 31 - src/sys/lib.rs | 699 +------------- src/sys/libuv_error_map.rs | 16 - src/sys/linux_syscall.rs | 104 -- src/sys/sys_uv.rs | 53 +- src/sys/walker_skippable.rs | 9 - src/sys/windows/env.rs | 11 - src/sys/windows/mod.rs | 195 ---- src/sys_jsc/error_jsc.rs | 16 - src/sys_jsc/fd_jsc.rs | 10 - src/sys_jsc/lib.rs | 28 - src/tcc_sys/tcc.rs | 61 -- src/threading/Condition.rs | 70 -- src/threading/Futex.rs | 36 - src/threading/Mutex.rs | 55 -- src/threading/RwLock.rs | 17 - src/threading/ThreadPool.rs | 150 --- src/threading/WaitGroup.rs | 6 - src/threading/channel.rs | 15 - src/threading/guarded.rs | 29 - src/threading/lib.rs | 8 - src/threading/unbounded_queue.rs | 13 - src/threading/work_pool.rs | 34 - src/transpiler/lib.rs | 5 - src/url/lib.rs | 110 --- src/uws/lib.rs | 194 ---- src/uws_sys/App.rs | 93 -- src/uws_sys/BodyReaderMixin.rs | 46 - src/uws_sys/ConnectingSocket.rs | 13 - src/uws_sys/InternalLoopData.rs | 18 - src/uws_sys/ListenSocket.rs | 21 - src/uws_sys/Loop.rs | 55 -- src/uws_sys/Request.rs | 6 - src/uws_sys/Response.rs | 56 -- src/uws_sys/SocketContext.rs | 44 - src/uws_sys/SocketGroup.rs | 23 - src/uws_sys/SocketKind.rs | 14 - src/uws_sys/Timer.rs | 8 - src/uws_sys/WebSocket.rs | 66 -- src/uws_sys/h3.rs | 15 - src/uws_sys/lib.rs | 32 - src/uws_sys/quic/Context.rs | 8 - src/uws_sys/quic/Header.rs | 11 - src/uws_sys/quic/Socket.rs | 4 - src/uws_sys/quic/Stream.rs | 4 - src/uws_sys/socket.rs | 64 -- src/uws_sys/thunk.rs | 33 - src/uws_sys/udp.rs | 4 - src/uws_sys/us_socket_t.rs | 26 - src/uws_sys/vtable.rs | 22 - src/valkey/valkey_protocol.rs | 42 - src/watcher/INotifyWatcher.rs | 46 - src/watcher/Watcher.rs | 95 -- src/watcher/WatcherTrace.rs | 13 - src/watcher/WindowsWatcher.rs | 73 -- src/watcher/lib.rs | 19 - src/which/lib.rs | 16 - src/windows_sys/externs.rs | 73 -- src/windows_sys/lib.rs | 4 - src/wyhash/lib.rs | 132 --- src/zlib/lib.rs | 347 ------- src/zlib_sys/shared.rs | 50 - src/zstd/lib.rs | 50 - test/internal/strip-long-rs-comments.test.ts | 180 ++++ 1058 files changed, 646 insertions(+), 70753 deletions(-) create mode 100644 scripts/strip-long-rs-comments.ts create mode 100644 test/internal/strip-long-rs-comments.test.ts diff --git a/scripts/strip-long-rs-comments.ts b/scripts/strip-long-rs-comments.ts new file mode 100644 index 00000000000..73d8d00846c --- /dev/null +++ b/scripts/strip-long-rs-comments.ts @@ -0,0 +1,245 @@ +#!/usr/bin/env bun +// Remove every `//`-style comment block longer than 3 lines from tracked .rs +// files, except a block that is the first non-blank content in the file. +// +// "Comment block" = a maximal run of consecutive lines whose trimmed content +// starts with `//` (this covers `//`, `///`, and `//!`). Inline `/* ... */` +// annotations and trailing `// ...` after code are not comment-only lines and +// are never touched. +// +// Load-bearing comment lines/blocks are preserved: +// - `// HOST_EXPORT(...)` markers (scraped by src/codegen/generate-host-exports.ts) +// → the marker line is kept; surrounding prose in the same block is removed. +// - `// SAFETY:` / `/// # Safety` blocks (clippy::undocumented_unsafe_blocks is +// `deny` workspace-wide, Cargo.toml:216) → the entire block is kept. +// +// Usage: +// bun scripts/strip-long-rs-comments.ts # apply in-place to all tracked *.rs +// bun scripts/strip-long-rs-comments.ts --dry # report only +// bun scripts/strip-long-rs-comments.ts --min N # threshold (default 4) +// bun scripts/strip-long-rs-comments.ts a.rs b.rs # only these files (no git) + +import { $ } from "bun"; +import { readFileSync, writeFileSync } from "node:fs"; + +// Comment lines that must never be removed because codegen scrapes them. +// The matching line is kept; the rest of its block is still removed. +// Matched against the trimmed line. +const PROTECTED_LINE: RegExp[] = [ + /^\/\/\s*HOST_EXPORT\(/, // src/codegen/generate-host-exports.ts +]; + +// If any line in a block matches one of these, the entire block is kept. +// Matched against the trimmed line. +// +// `clippy::undocumented_unsafe_blocks` is `deny` workspace-wide (Cargo.toml) +// and accepts any comment containing /safety:/i immediately before `unsafe`. +// The tree uses many header variants — `// SAFETY:`, `// SAFETY (invariant):`, +// `// SAFETY CONTRACT:`, `/// # Safety`, `/// ## Safety` — so we keep any +// block that mentions SAFETY in caps, opens a line with a `Safety:`-style +// marker in any case, or carries a `/// # Safety` doc heading. Over-preserving +// a few prose blocks that merely reference the pattern is preferable to a +// clippy deny-error. +const PROTECTED_BLOCK: RegExp[] = [ + /\bSAFETY\b/, // `// SAFETY: ...`, `// SAFETY CONTRACT: ...`, any all-caps mention + /^\/\/[/!]*\s*safety\s*:/i, // `// Safety: ...` / `// safety: ...` — clippy matches case-insensitively + /^\/\/\/\s*#+\s*Safety\b/, // `/// # Safety` doc headings +]; + +// Hot-path helpers avoid .trim()/.trimStart() allocation — debug-build JS is +// slow enough that scanning ~3M lines with per-line temp strings takes 30s+. +function firstNonWs(line: string): number { + let j = 0; + const n = line.length; + while (j < n) { + const c = line.charCodeAt(j); + if (c !== 32 && c !== 9) break; // space / tab + j++; + } + return j; +} + +function isCommentOnly(line: string): boolean { + const j = firstNonWs(line); + return line.charCodeAt(j) === 47 /* / */ && line.charCodeAt(j + 1) === 47 /* / */; +} + +function isProtectedLine(line: string): boolean { + const t = line.slice(firstNonWs(line)); + return PROTECTED_LINE.some(re => re.test(t)); +} + +function isProtectedBlockLine(line: string): boolean { + const t = line.slice(firstNonWs(line)); + return PROTECTED_BLOCK.some(re => re.test(t)); +} + +function isBlank(line: string): boolean { + return firstNonWs(line) === line.length; +} + +export type Edit = { start: number; end: number }; // [start, end) line indices to delete + +export function planEdits(lines: string[], min: number): Edit[] { + // First non-blank line index, to identify the top-of-file block. + let firstContent = 0; + while (firstContent < lines.length && isBlank(lines[firstContent])) firstContent++; + + const raw: Edit[] = []; + let i = 0; + while (i < lines.length) { + if (!isCommentOnly(lines[i])) { + i++; + continue; + } + const start = i; + while (i < lines.length && isCommentOnly(lines[i])) i++; + const end = i; // exclusive + const len = end - start; + const isTopOfFile = start === firstContent; + if (len < min || isTopOfFile) continue; + // Keep the whole block if any line in it is a SAFETY/# Safety justification. + let keepBlock = false; + for (let k = start; k < end; k++) { + if (isProtectedBlockLine(lines[k])) { + keepBlock = true; + break; + } + } + if (keepBlock) continue; + // Split around protected single lines; keep those, drop the rest of the block. + let segStart = start; + for (let k = start; k <= end; k++) { + const boundary = k === end || isProtectedLine(lines[k]); + if (boundary) { + if (k > segStart) raw.push({ start: segStart, end: k }); + segStart = k + 1; + } + } + } + + // Merge edits that are separated only by blank lines (e.g. a section banner + // followed by a doc comment) so the intervening blanks are removed too. + const merged: Edit[] = []; + for (const e of raw) { + const prev = merged[merged.length - 1]; + if (prev) { + let gap = prev.end; + while (gap < e.start && isBlank(lines[gap])) gap++; + if (gap === e.start) { + prev.end = e.end; + continue; + } + } + merged.push({ ...e }); + } + + // Extend each edit over trailing blank lines so removal doesn't leave a + // double-blank gap or a stray blank right after `{`. + for (const e of merged) { + let trail = e.end; + while (trail < lines.length && isBlank(lines[trail])) trail++; + if (trail > e.end) { + const before = e.start === 0 ? "" : lines[e.start - 1]; + const blank_before = e.start === 0 || isBlank(before); + const open_before = before.trimEnd().endsWith("{"); + e.end = blank_before || open_before ? trail : trail - 1; + } + } + + return merged; +} + +function applyEdits(lines: string[], edits: Edit[]): string[] { + if (edits.length === 0) return lines; + const keep: string[] = []; + let ei = 0; + for (let i = 0; i < lines.length; i++) { + if (ei < edits.length && i >= edits[ei].start && i < edits[ei].end) { + if (i === edits[ei].end - 1) ei++; + continue; + } + keep.push(lines[i]); + } + return keep; +} + +/** Pure transform: returns the stripped source, or the input unchanged. */ +export function stripLongComments(src: string, min = 4): string { + const hadTrailingNL = src.endsWith("\n"); + const lines = src.split("\n"); + const edits = planEdits(lines, min); + if (edits.length === 0) return src; + const out = applyEdits(lines, edits); + let text = out.join("\n"); + if (hadTrailingNL && !text.endsWith("\n")) text += "\n"; + return text; +} + +// Trees whose .rs files are not processed: +// - vendor/ third-party code +// - packages/ externally-published crates (bun-native-plugin +// renders `///` on docs.rs — user-facing API docs) +// - scripts/verify-baseline-static/ CLAUDE.md cites line ranges into these sources; +// the inline encoding-derivation comments are the +// on-call triage doc +export const SKIP_PREFIXES = ["vendor/", "packages/", "scripts/verify-baseline-static/"]; + +export async function listTrackedRsFiles(cwd?: string): Promise { + const tracked = (await $`git ls-files '*.rs'`.cwd(cwd ?? process.cwd()).text()).split("\n").filter(Boolean); + return tracked.filter(f => !SKIP_PREFIXES.some(p => f.startsWith(p))); +} + +// ─── CLI ────────────────────────────────────────────────────────────────────── + +async function main() { + const args = process.argv.slice(2); + const dry = args.includes("--dry") || args.includes("-n"); + const minIdx = args.findIndex(a => a === "--min"); + const min = minIdx >= 0 ? Number(args[minIdx + 1]) : 4; + if (!Number.isInteger(min) || min < 1) { + console.error(`invalid --min value`); + process.exit(1); + } + const minValueIdx = minIdx >= 0 ? minIdx + 1 : -1; + const explicitFiles = args.filter((a, i) => !a.startsWith("-") && i !== minValueIdx); + const files = explicitFiles.length > 0 ? explicitFiles : await listTrackedRsFiles(); + + let changed = 0; + let blocksRemoved = 0; + let linesRemoved = 0; + + for (const file of files) { + const src = readFileSync(file, "utf8"); + const lines = src.split("\n"); + const edits = planEdits(lines, min); + if (edits.length === 0) continue; + + const removed = edits.reduce((n, e) => n + (e.end - e.start), 0); + blocksRemoved += edits.length; + linesRemoved += removed; + changed++; + + if (dry) { + for (const e of edits) { + console.log(`${file}:${e.start + 1}-${e.end}: ${e.end - e.start} lines`); + } + continue; + } + + // Reuse the edits already planned above instead of re-running the full + // transform (planEdits + applyEdits) a second time inside stripLongComments. + const out = applyEdits(lines, edits); + let text = out.join("\n"); + if (src.endsWith("\n") && !text.endsWith("\n")) text += "\n"; + writeFileSync(file, text); + } + + console.error( + `strip-long-rs-comments: ${dry ? "would change" : "changed"} ${changed} file(s), ` + + `removed ${blocksRemoved} block(s) / ${linesRemoved} line(s) ` + + `(threshold >= ${min} lines; top-of-file, SAFETY, HOST_EXPORT kept)`, + ); +} + +if (import.meta.main) await main(); diff --git a/src/analytics/lib.rs b/src/analytics/lib.rs index 4af975b761d..8604554a98b 100644 --- a/src/analytics/lib.rs +++ b/src/analytics/lib.rs @@ -12,11 +12,6 @@ use bun_core::slice_to_nul; // ────────────────────────────────────────────────────────────────────────── -/// Enables analytics. This is used by: -/// - crash_handler's `report` function to anonymously report crashes -/// -/// Since this field can be `Unknown`, it makes more sense to call `is_enabled` -/// instead of processing this field directly. #[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq)] pub enum TriState { @@ -77,26 +72,9 @@ pub fn is_enabled() -> bool { // Features // ────────────────────────────────────────────────────────────────────────── -/// This answers, "What parts of bun are people actually using?" -/// -/// PORT NOTE: In Zig this is a `struct` used purely as a namespace of `pub var` -/// decls, iterated via `@typeInfo` reflection. Rust has no decl reflection, so -/// the feature list is declared once via `define_features!` and that macro -/// generates the statics, `PACKED_FEATURES_LIST`, `PackedFeatures`, -/// `packed_features()`, and the `Display` body. pub mod features { use super::*; - // PORT NOTE (cyclebreak): the Zig original is - // `EnumSet(bun.jsc.ModuleLoader.HardcodedModule)`. That enum lives in - // `bun_resolve_builtins` (T5) and pulling it here would create a forward - // dep (analytics is T1). The only operations we need are `insert` and - // ordered iteration of the module *names* for the crash-report formatter, - // so store the `&'static str` name (= `@tagName(HardcodedModule)`) instead - // of the enum value. Writers (`runtime/jsc_hooks.rs`) call - // `BUILTIN_MODULES.lock().insert(<&'static str>::from(hardcoded))`. - // PERF(port): Zig used a packed `EnumSet` (bitset); BTreeSet is O(log n) - // insert — fine for ≤~80 entries written once each at module-load time. pub(crate) static BUILTIN_MODULES: bun_core::Mutex> = bun_core::Mutex::new(std::collections::BTreeSet::new()); // PORT NOTE: Zig used a plain mutable global; wrapped in a Mutex here @@ -121,11 +99,6 @@ pub mod features { /// Zig: `pub const packed_features_list = brk: { ... }` pub const PACKED_FEATURES_LIST: &[&str] = &[ $( $name ),* ]; - // Zig: `pub const PackedFeatures = @Type(.{ .@"struct" = .{ .layout = .@"packed", .backing_integer = u64, ... } })` - // All fields are `bool` → bitflags over u64. - // PORT NOTE: nightly `${index()}` (macro_metavar_expr) is unavailable - // on stable, so each feature carries an explicit `$idx` literal at the - // call site. The dense-index assertion below catches gaps/duplicates. ::bitflags::bitflags! { #[repr(transparent)] #[derive(Default, Copy, Clone, PartialEq, Eq)] @@ -214,11 +187,6 @@ pub mod features { }; } - // PORT NOTE: Zig identifiers `@"Bun.stderr"` etc. cannot be Rust idents; - // renamed to `bun_stderr` etc. The string literal preserves the original - // name for output / `PACKED_FEATURES_LIST` (matches `@tagName` semantics). - // The leading integer is the bit index in `PackedFeatures` (must be dense - // 0..N — asserted at compile time inside the macro). define_features! { 0 => (bun_stderr, "Bun.stderr"), 1 => (bun_stdin, "Bun.stdin"), @@ -289,15 +257,6 @@ pub mod features { #[unsafe(export_name = "Bun__Feature__webview_webkit")] 57 => (webview_webkit, "webview_webkit"), } - - // Zig: `comptime { @export(&napi_module_register, .{ .name = "Bun__napi_module_register_count" }); ... }` - // PORT NOTE: C++ declares these as `extern "C" size_t Bun__...;` and - // reads/increments the value directly, so the exported symbol must BE the - // `usize` storage (not a pointer to it). `AtomicUsize` is `#[repr(C)] - // usize`-layout-compatible. Handled via `#[unsafe(export_name = "...")]` - // on the canonical statics inside `define_features!` above — Rust cannot - // alias-export a static under a second symbol name, so the export name is - // attached to the single definition. } // Re-exports to mirror Zig's `Features.packedFeatures()` etc. at module scope. @@ -336,11 +295,6 @@ pub enum EventName { http_build, } -// Zig: `var random: std.rand.DefaultPrng = undefined;` -// PORT NOTE: declared but never read in analytics.zig — dead code. Dropped -// rather than gated; if a future schema-encode path needs a PRNG, seed one -// locally (PORTING.md §Concurrency: OnceLock<...>, no `static mut`). - const PLATFORM_ARCH: analytics::Architecture = { #[cfg(target_arch = "aarch64")] { @@ -409,14 +363,6 @@ pub mod generate_header { // Linux / Android // ────────────────────────────────────────────────────────────────── - // Zig: `pub var linux_os_name: std.c.utsname = undefined;` - // PORT NOTE: Zig's `Environment.isLinux` is true on Android (it checks - // the kernel, not the libc target), so all Linux-gated items below are - // `any(linux, android)` — `for_linux()` itself branches on Android. - // The cached `utsname` itself now lives in T1 at - // `bun_core::ffi::cached_uname()` so `bun_sys` feature probes share the - // same single `uname(2)` syscall. - // ────────────────────────────────────────────────────────────────── // Platform OnceLock // ────────────────────────────────────────────────────────────────── @@ -496,12 +442,6 @@ pub mod generate_header { #[cfg(any(target_os = "linux", target_os = "android"))] pub fn kernel_version() -> semver::Version { - // Route through the T1 canonical probe so the whole binary issues - // a single `uname(2)` for kernel-version detection. The full - // semver `tag` (pre/build) is irrelevant here — `.min()` on the - // old parse path already zeroed it — so a {major,minor,patch} - // lift is behavior-identical for all callers (crash_handler - // formatting, epoll_pwait2 >=5.11 gate, `bun.linuxKernelVersion`). let v = bun_core::linux_kernel_version(); semver::Version { major: u64::from(v.major), diff --git a/src/analytics/schema.rs b/src/analytics/schema.rs index aa521e7e7d3..2679c301fdf 100644 --- a/src/analytics/schema.rs +++ b/src/analytics/schema.rs @@ -4,28 +4,6 @@ use bun_core::Error; -// ────────────────────────────────────────────────────────────────────────── -// Reader / Writer -// ────────────────────────────────────────────────────────────────────────── -// -// Zig's peechy codec exposes a concrete `Reader` struct and a comptime-generic -// `Writer(WritableStream)` struct, but every generated `decode`/`encode` takes -// `reader: anytype` / `writer: anytype` — i.e. structural duck typing. Per -// PORTING.md §Comptime reflection, `anytype` → trait bound: the *protocol* is -// the trait, and the Zig `Reader` struct is one concrete impl (`BufReader` -// below). -// -// Only the primitive-int / byte-slice surface is ported. Zig's -// `readValue(comptime T)` / `writeValue(comptime T, ...)` switch on -// `@typeInfo(T)` to dispatch to enum/packed-struct/`.decode` paths; that -// reflection has no Rust equivalent, so per-type `decode`/`encode` impls call -// the primitive methods directly (which is what the generated schema bodies -// already do). - -/// Zig: `Reader.ReadError = error{EOF}`. -// PORT NOTE: peechy's two error cases (`EOF`, `InvalidValue`) are folded into -// the crate-wide `bun_core::Error` so downstream `decode` signatures stay -// `Result<_, bun_core::Error>` without an extra `From` hop. pub(crate) const EOF: Error = Error::TODO; // TODO(port): Error::from_name("EOF") once name→code table lands /// Primitive integers encodable in the peechy wire format (native-endian raw @@ -33,10 +11,6 @@ pub(crate) const EOF: Error = Error::TODO; // TODO(port): Error::from_name("EOF" /// `std.mem.asBytes`; Rust needs an explicit trait bound. pub use bun_core::NativeEndianInt as SchemaInt; -/// Duck-typed reader protocol for peechy `decode` impls. -/// -/// Zig: `fn decode(reader: anytype) anyerror!T` — the `anytype` becomes a -/// `R: Reader` bound on the Rust side. pub trait Reader { /// Zig: `fn read(this, count: usize) ![]u8` — borrow `count` bytes, /// advancing the cursor. Errors with `EOF` if fewer than `count` remain. @@ -80,17 +54,6 @@ pub trait Reader { } } -// peechy `Writer` lives in `bun_options_types::schema::Writer` (the canonical -// `Vec`-backed struct port of `schema.zig:169 fn Writer(WritableStream)`). -// This crate keeps only the read side; encode users depend on options_types -// directly. - -/// Concrete buffer-backed reader — direct port of Zig's `pub const Reader = struct`. -/// -/// PORT NOTE: the Zig struct also carries `std.mem.Allocator param` for -/// `readArray`'s nested-slice case; per PORTING.md §Allocators (non-AST crate) -/// the allocator param is dropped — callers that need owned sub-arrays -/// allocate at the call site. pub struct BufReader<'a> { pub buf: &'a [u8], pub remain: &'a [u8], @@ -117,15 +80,7 @@ impl<'a> Reader for BufReader<'a> { // ────────────────────────────────────────────────────────────────────────── -// Hand-ported subset of `analytics::*` needed by lib.rs (OperatingSystem, -// Architecture, Platform). The full encode/decode machinery and the rest of -// the schema (EventKind, EventListHeader, …) are unused at runtime today and -// will be filled in by the peechy regen. pub mod analytics { - /// Zig: `pub const OperatingSystem = enum(u8) { _none, linux, macos, windows, wsl, android, freebsd, _ }` - // PORT NOTE: Zig's open enum (`_`) is dropped — Rust enums are closed; the - // schema decoder is the only producer of unknown discriminants and it is - // not yet ported. #[repr(u8)] #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum OperatingSystem { diff --git a/src/api/lib.rs b/src/api/lib.rs index de79a80e96c..e4f6920cea0 100644 --- a/src/api/lib.rs +++ b/src/api/lib.rs @@ -1,26 +1,5 @@ #![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)] #![warn(unused_must_use)] -//! `bun.schema.api` namespace. -//! -//! Ground truth: `src/options_types/schema.zig` (the `pub const api = struct {…}` -//! block — generated from `src/api/schema.peechy`). The full peechy → `.rs` -//! emitter is not landed yet; this crate hand-ports the slice of the schema -//! that downstream crates name today (`bun_ini`, `bun_install`, `bun_runtime` -//! bunfig parser) so they can un-gate against real field shapes. -//! -//! LAYERING: the actual data shapes (`NpmRegistry`, `NpmRegistryMap`, `Ca`, -//! `BunInstall`) were originally hand-ported in two places — here *and* in -//! `bun_options_types::schema::api`. Downstream crates ended up holding values -//! of one and passing them to functions typed against the other (e.g. -//! `bun_options_types::context::install` vs. `bun_ini::load_npmrc_config`), -//! which type-errors despite identical field layout. The canonical definitions -//! now live in `bun_options_types::schema::api` (the lower / shared crate); -//! this crate re-exports them so existing `bun_api::*` paths keep compiling and -//! there is exactly one `BunInstall` in the type graph. -//! -//! When the peechy `.rs` codegen lands it should overwrite/append to -//! `bun_options_types::schema::api` wholesale — keep additions append-only and -//! field-order-faithful so the diff stays reviewable. // ────────────────────────────────────────────────────────────────────────── // Re-exports — canonical definitions live in `bun_options_types::schema::api`. @@ -42,12 +21,6 @@ pub mod npm_registry { pub use super::NpmRegistry; - // PORT NOTE: `Parser` stays generic over `L` (Log) / `S` (Source) so this - // leaf schema crate doesn't need to name `bun_logger`. The lone live body - // (`parse_registry_url_string_impl`) doesn't touch log/source — only the - // not-yet-ported `parse_registry_object` / `parse_registry` paths do, and - // those need `js_ast::Expr` so they belong upstream in the bunfig parser - // anyway. pub struct Parser<'a, L, S> { pub log: &'a mut L, pub source: &'a S, diff --git a/src/ast/ast_memory_allocator.rs b/src/ast/ast_memory_allocator.rs index 536eb987d20..71099b8a40e 100644 --- a/src/ast/ast_memory_allocator.rs +++ b/src/ast/ast_memory_allocator.rs @@ -7,33 +7,6 @@ use bun_alloc::ast_alloc::{self, AstAllocState}; use crate::expr; use crate::stmt; -// PERF(port): Zig used `std.heap.StackFallbackAllocator(@min(8192, std.heap.page_size_min))` -// — a small inline stack buffer with heap fallback. `bun_alloc::Arena` -// (`MimallocArena`) has no stack buffer; instead the owned arena is recycled -// per thread via `ARENA_POOL` below so the per-module callers don't pay a fresh -// `mi_heap_new` + first-segment page faults every file. (The `AstAlloc` side -// *does* have an inline buffer now — see `bun_alloc::ast_alloc::AstAllocState`.) - -// ── Thread-local arena pool ────────────────────────────────────────────── -// -// Zig's `ASTMemoryAllocator` was a `StackFallbackAllocator(8192, fallback)`: -// the 8 KB stack buffer absorbed most per-module AST scratch without touching -// the heap, and the spill went to a long-lived `fallback` arena whose pages -// stayed resident across modules. The Rust port collapsed that to one owned -// `MimallocArena` per `ASTMemoryAllocator`, so a fresh per-module instance -// (`RuntimeTranspilerStore::run`, `Bun.Transpiler.*`, the dev server) paid a -// fresh `mi_heap_new` + first-segment page faults every file, and `enter()`'s -// reset then destroyed-and-recreated that just-created heap before it was even -// used. -// -// Instead, recycle one `MimallocArena` per thread: `Drop` cleans the arena -// (`reset()` bulk-frees this module's nodes — leaving it pristine) and parks -// it here; the next `ASTMemoryAllocator` on this thread reclaims it, reusing -// its committed pages. The pool holds at most one arena (nested scopes — rare -// — fall back to a fresh `Arena::new()`). `#[thread_local]` (not the -// `thread_local!` macro) so there is no destructor: a parked arena at thread -// exit is reclaimed by mimalloc's own thread-teardown, avoiding an unspecified -// destructor-ordering hazard with `mi_heap_destroy`. #[thread_local] static ARENA_POOL: Cell> = Cell::new(None); @@ -58,12 +31,6 @@ pub struct ASTMemoryAllocator { /// `self.arena` and `Drop`/`reset` never destroy or pool anything. Must /// outlive `self` ([`Self::borrowing`] contract). external_arena: *const Arena, - /// `true` once a scope on this instance armed `arena` for allocation (via - /// [`Self::enter`] / [`Self::push`]) since the last reset. Lets - /// [`Self::enter`] / [`Self::reset`] skip the `mi_heap_destroy` + - /// `mi_heap_new` churn when `arena` is already pristine — the common case - /// for the per-module callers, each of which takes a freshly-pooled (clean) - /// arena and arms it exactly once. arena_dirty: bool, /// The `AstAlloc` state for this allocator's scope. Owned here while no /// scope is active; in the `AST_ALLOC` thread-local while pushed @@ -107,15 +74,6 @@ impl Drop for ASTMemoryAllocator { // Borrowed arena: the caller owns its lifecycle. return; } - // Recycle the owned arena for the next `ASTMemoryAllocator` on this - // thread (see `ARENA_POOL`). Clean it first so a pooled arena is always - // pristine — `push()` callers (the bundler workers) allocate straight - // into it with no intervening `reset()`. By the time this runs nothing - // aliases `self.arena`: `enter()`'s returned `Scope` borrows `&mut - // self`, so it drops first and `Scope::exit()` has already restored the - // `Expr/Stmt.Data.Store.memory_allocator` / `data_store_override` / - // `ast_alloc` thread-locals; `push()` callers pair with `pop()` before - // teardown. if self.arena_dirty { self.arena.reset(); } @@ -134,10 +92,6 @@ impl ASTMemoryAllocator { Self::default() } - /// Construct an allocator that routes every allocation into `arena` - /// instead of owning a heap of its own. `arena` must outlive the returned - /// allocator (and every `Scope` derived from it); the caller owns its - /// reset/destroy. pub fn borrowing(arena: &Arena) -> Self { Self { arena: Arena::borrowing_default(), @@ -229,22 +183,6 @@ impl ASTMemoryAllocator { } pub fn enter(&mut self) -> Scope<'_> { - // Zig: this.stack_allocator = SFA{ .fallback_allocator = arena, .. }; - // this.bump_allocator = this.stack_allocator.get(); - // The Zig spec OVERWRITES the entire SFA on every `enter()` (fresh - // 8 KB stack buffer + rewired fallback to the per-call arena), so any - // bytes bump-allocated by the previous `enter()` are released. The - // Rust port collapsed SFA+fallback into a single internal `Arena` - // owned by `self`, so the equivalent re-init is `arena.reset()` — - // otherwise a thread-local `ASTMemoryAllocator` reused across - // `RuntimeTranspilerStore::run()` calls grows unboundedly (one full - // AST worth of nodes per import). - // - // ...but a *pristine* arena (fresh from `new()` / the thread-local - // pool, or just `reset()`) has nothing to discard, so the - // `mi_heap_destroy` + `mi_heap_new` round-trip is skipped in that case - // (the common one — per-module callers create a fresh instance, - // `enter()` once, and drop it). A borrowed arena is never reset here. if self.arena_dirty { self.reset_ast_state(); if self.external_arena.is_null() { @@ -279,11 +217,6 @@ impl ASTMemoryAllocator { } } - /// Per-iteration reset for hot reuse paths (`initialize_mini_store`'s - /// per-workspace-child re-entry). Thin delegate to - /// [`bun_alloc::Arena::reset_retain_with_limit`]; the cold init paths - /// (`bundler::ThreadPool::Worker::init`, `BundleThread::generate_in_new_ - /// thread`) keep calling [`Self::reset`]. pub fn reset_retain_with_limit(&mut self, limit: usize) { if self.arena_dirty { debug_assert!( @@ -461,11 +394,6 @@ impl<'a> Scope<'a> { } } -// Zig callers write `defer ast_scope.exit()` immediately after `enter()`; -// porting that as RAII so `let _scope = alloc.enter();` restores the previous -// `Expr/Stmt.Data.Store.memory_allocator` on every return path. `exit()` is -// idempotent (guarded by `entered`), so an explicit `.exit()` followed by Drop -// is harmless. impl<'a> Drop for Scope<'a> { fn drop(&mut self) { self.exit(); diff --git a/src/ast/ast_result.rs b/src/ast/ast_result.rs index 644b5fd49d6..c23b8464a65 100644 --- a/src/ast/ast_result.rs +++ b/src/ast/ast_result.rs @@ -96,12 +96,6 @@ pub struct Ast<'a> { pub import_meta_ref: Ref, } -// PORT NOTE: Zig field defaults reference named constants (`Ref.None`, `logger.Range.None`, -// `ExportsKind.none`, `Target.browser`) whose equivalence to the Rust types' `Default::default()` -// is unverified across crates, so spell them out here instead of `#[derive(Default)]`. -// -// `parts`/`symbols`/`import_records` are now `ArenaVec`s and need an allocator, -// so `Default` no longer applies; use `Ast::empty_in(arena)`. impl<'a> Ast<'a> { pub fn empty_in(arena: &'a bun_alloc::MimallocArena) -> Self { Self { @@ -161,13 +155,6 @@ impl Default for CommonJSNamedExport { } } -// `Ast` is held in arena-allocated structures whose `Drop` never runs (the -// `BabyList` pattern — bulk-freed on `ASTMemoryAllocator` / `store_ast_alloc_heap` -// reset). Any `Vec`/`Box` field that defaults to the global allocator therefore -// leaks. The `AstAlloc` parameter routes the column vecs and per-key boxes -// into the same thread-local AST `mi_heap` so they're reclaimed by -// `mi_heap_destroy` alongside the AST nodes (same motivation as -// `G::DeclList`/`PropertyList` and `Scope::members`). pub type CommonJSNamedExports = StringArrayHashMap; pub type NamedImports = ArrayHashMap; @@ -185,12 +172,6 @@ impl<'a> Ast<'a> { ..Ast::empty_in(arena) } } - - // Zig `deinit` only freed `parts`, `symbols`, `import_records` via `bun.default_allocator`, - // and was guarded by "Do not call this if it wasn't globally allocated!". - // In Rust those fields own their storage and free on Drop; no explicit body needed. - // TODO(port): Vec Drop semantics must distinguish arena-backed vs heap-backed to - // preserve the Zig conditional-free behavior. Revisit. } pub use crate::g::Class; diff --git a/src/ast/b.rs b/src/ast/b.rs index f2d42ba501e..c6647dc3782 100644 --- a/src/ast/b.rs +++ b/src/ast/b.rs @@ -6,30 +6,6 @@ use crate::{ExprNodeIndex, flags}; // Re-exported so callers can spell `js_ast::b::ArrayBinding` (Zig: `B.Array.Item`). pub use crate::ArrayBinding; -/// B is for Binding! Bindings are on the left side of variable -/// declarations (s_local), which is how destructuring assignments -/// are represented in memory. Consider a basic example. -/// -/// ```text -/// let hello = world; -/// ^ ^ -/// | E.Identifier -/// B.Identifier -/// ``` -/// -/// Bindings can be nested -/// -/// ```text -/// B.Array -/// | B.Identifier -/// | | -/// let { foo: [ bar ] } = ... -/// ---------------- -/// B.Object -/// ``` -// Zig: `union(Binding.Tag)` — tag enum lives on `Binding::Tag`. -// PORT NOTE: arena values are referenced via `StoreRef` (LIFETIMES.tsv: ARENA) -// rather than a threaded `&'bump mut T`. #[derive(Copy, Clone, bun_core::EnumTag)] #[enum_tag(existing = super::binding::Tag)] pub enum B { @@ -49,14 +25,6 @@ impl Default for B { } } -// ── Layout guards ───────────────────────────────────────────────────────── -// Three `StoreRef` variants (`#[repr(transparent)] NonNull`, 8-byte -// payload) + one ZST → 1-byte discriminant + 8-byte payload = 9, align(8) -// rounds to 16. `Binding` = `B` (16, align 8) + `Loc` (i32) → 20 → 24. -// Matches `expr::Data`/`stmt::Data`: every pointer payload is non-nullable, -// so `Option` packs into the same 16 bytes via the NonNull niche (and -// would continue to even under a future `#[repr(u8)]`, unlike the prior -// `*mut T` form which relied solely on spare-tag-value niche). const _: () = assert!(core::mem::size_of::() == 16); const _: () = assert!(core::mem::size_of::() == 24); const _: () = assert!( @@ -130,15 +98,6 @@ impl B { // PORT NOTE: `symbol_table: anytype` — forwarded to `Ref::get_symbol` and // `Expr::Data::write_to_hasher`; bound mirrors `Expr::Data::write_to_hasher`. { - // Local mirror of `bun.writeAnyToHasher`. Zig fed anonymous tuples - // through `std.mem.asBytes`, but Rust tuples have *uninitialized* - // padding bytes (e.g. `(Tag /*u8*/, usize)` has 7 on 64-bit), so - // forming a `&[u8]` over them is UB. Instead we feed each scalar - // field individually and bound on `NoUninit` so the compiler proves - // every byte is initialized — same pattern as `expr::Data::write_to_hasher`. - // The hash is only used in-process for React Fast Refresh, so the - // byte-stream change vs. Zig is immaterial (and the old stream was - // nondeterministic anyway). #[inline(always)] fn raw(h: &mut H, v: T) { h.update(bun_core::bytes_of(&v)); diff --git a/src/ast/base.rs b/src/ast/base.rs index 400ceeecc92..a4fd33ce8f6 100644 --- a/src/ast/base.rs +++ b/src/ast/base.rs @@ -2,13 +2,6 @@ use crate::symbol; // ───────────────────────────────── Index ───────────────────────────────── -/// In some parts of Bun, we have many different IDs pointing to different things. -/// It's easy for them to get mixed up, so we use this type to make sure we don't. -// -// Zig: `packed struct(u32) { value: Int }` — single field fills the whole word, -// so `#[repr(transparent)]` over `u32` is bit-identical. Tuple-struct shape so -// the (many) bundler call sites that wrote `Index(n)` / `.0` keep compiling; -// `.value()` is provided for sites that read the Zig field name. #[repr(transparent)] #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] pub struct Index(pub IndexInt); @@ -118,34 +111,11 @@ impl Default for Index { // ───────────────────────────────── Ref ───────────────────────────────── -/// -- original comment from esbuild -- -/// -/// Files are parsed in parallel for speed. We want to allow each parser to -/// generate symbol IDs that won't conflict with each other. We also want to be -/// able to quickly merge symbol tables from all files into one giant symbol -/// table. -/// -/// We can accomplish both goals by giving each symbol ID two parts: a source -/// index that is unique to the parser goroutine, and an inner index that -/// increments as the parser generates new symbol IDs. Then a symbol map can -/// be an array of arrays indexed first by source index, then by inner index. -/// The maps can be merged quickly by creating a single outer array containing -/// all inner arrays from all parsed files. pub use crate::{Ref, RefInt, RefTag}; // Zig: `comptime { bun.assert(None.isEmpty()); }` const _: () = assert!(Ref::NONE.is_empty()); -// ─────────────── getSymbol `anytype` dispatch → trait ─────────────── -// -// Zig switches on `@TypeOf(symbol_table)`: -// *const ArrayList(Symbol) | *ArrayList(Symbol) | []Symbol → index by -// `ref.innerIndex()` (parser: single flat array, source_index ignored) -// *Symbol.Map → `map.get(ref)` (bundler: 2D, both halves used) -// -// Different parts of the bundler use different formats of the symbol table. -// In the parser you only have one array, and .sourceIndex() is ignored. -// In the bundler, you have a 2D array where both parts of the ref are used. pub trait SymbolTable { fn get_symbol(&mut self, r: Ref) -> &mut symbol::Symbol; } diff --git a/src/ast/binding.rs b/src/ast/binding.rs index 889d8bf546e..750c65ed83d 100644 --- a/src/ast/binding.rs +++ b/src/ast/binding.rs @@ -43,13 +43,6 @@ pub enum Tag { #[cfg(debug_assertions)] pub(crate) static ICOUNT: AtomicUsize = AtomicUsize::new(0); -// ────────────────────────────────────────────────────────────────────────── -// `init` / `alloc` — Zig switched on `@TypeOf(t)` to pick the `B` variant. -// In Rust the comptime type-switch is a pair of small traits implemented for -// each payload type; `Binding::init` / `Binding::alloc` stay monomorphic -// per call-site like the Zig original. -// ────────────────────────────────────────────────────────────────────────── - pub trait BindingInit { fn into_b(self) -> B; } @@ -127,28 +120,8 @@ impl Binding { } } -// ────────────────────────────────────────────────────────────────────────── -// ToExpr — Zig: `fn ToExpr(comptime expr_type: type, comptime func_type: anytype) type` -// returns a struct holding `context: *ExprType` + `arena` whose -// `wrapIdentifier` calls the comptime `func_type`. -// -// Rust cannot store `*mut P<'a, const ..>` in a non-generic field nor take a -// fn item as a const generic, so the wrapper is type-erased: `wrap` is a plain -// fn pointer that casts the erased `ctx` back to the concrete `P` instantiation. -// Unlike Zig's struct, the `*ExprType` context is **not** stored — it is -// supplied at call time (`Binding::to_expr(.., ctx, ..)`) so the raw pointer's -// Stacked-Borrows tag is a child of the *live* `&mut P` at the call site rather -// than a stale tag captured during `prepare_for_visit_pass` (which every later -// `&mut self` retag would invalidate). The struct is `Copy` so the recursive -// `to_expr` can pass it by value like Zig's `wrapper: anytype`. -// ────────────────────────────────────────────────────────────────────────── - #[derive(Copy, Clone)] pub struct ToExprWrapper { - /// Back-reference to `P.arena`. `BackRef` invariant: the arena is owned by - /// `P<'a>` and outlives every `ToExprWrapper` (which is stored on `P` and - /// only used during the visit pass). `None` only for the pre-wire - /// `dangling()` placeholder; niche-packed so layout matches `*const Arena`. arena: Option>, wrap: fn(*mut core::ffi::c_void, crate::Loc, Ref) -> Expr, } @@ -163,12 +136,6 @@ impl ToExprWrapper { } } - /// Zig: `Context.init(context)` — captures `*ExprType` and its arena. - /// `ExprType` is erased to `c_void`; callers (P.rs) supply a trampoline - /// closure that casts back to `*mut P<..>` and dispatches to - /// `P::wrap_identifier_{namespace,hoisting}`. Non-capturing closures - /// coerce to fn pointers, so this stays zero-cost like Zig's comptime fn. - /// The `*mut P` itself is passed per-call via `Binding::to_expr`. #[inline] pub fn new(arena: &Arena, wrap: fn(*mut core::ffi::c_void, crate::Loc, Ref) -> Expr) -> Self { Self { @@ -193,24 +160,9 @@ impl ToExprWrapper { } } -/// Zig: `Binding.ToExpr(expr_type, func_type)` returned a *type*; Rust callers -/// that want the same per-(P, func) nominal type use this alias and construct -/// via `ToExprWrapper::new`. Kept as a type alias (not a generic struct) so -/// `P` can store two of these without threading its own generics through. pub type ToExpr = ToExprWrapper; impl Binding { - /// Zig: `pub fn toExpr(binding: *const Binding, wrapper: anytype) Expr`. - /// - /// `ctx` is the type-erased `*mut P<..>` derived from the *caller's live* - /// `&mut P` (e.g. `core::ptr::addr_of_mut!(*p) as *mut c_void`). Threading - /// it per-call keeps the raw pointer's provenance under the active Unique - /// borrow, avoiding the stale-tag UB of storing it long-term. - /// - /// Accepts the wrapper by `Borrow` so both the by-value call-site in - /// `visitStmt.rs` (`p.to_expr_wrapper_namespace`) and the `&mut` call-site - /// in `maybe.rs` (`&mut p.to_expr_wrapper_hoisted`) type-check without - /// edits — `T: Borrow` and `&mut T: Borrow` are both blanket impls. pub fn to_expr(binding: &Binding, ctx: *mut core::ffi::c_void, wrapper: W) -> Expr where W: core::borrow::Borrow, diff --git a/src/ast/char_freq.rs b/src/ast/char_freq.rs index e329705ea8e..7afcead427f 100644 --- a/src/ast/char_freq.rs +++ b/src/ast/char_freq.rs @@ -74,13 +74,6 @@ impl CharFreq { }; } - // std.sort.pdq → Rust's sort_unstable_by (pattern-defeating quicksort). - // PORT NOTE: do NOT route through `CharAndCount::less_than` and map - // false→Greater — that comparator never returns `Equal`, which - // violates `sort_unstable_by`'s total-order contract (Rust 1.81+ - // is permitted to panic on inconsistent comparators). `index` is - // unique so equality is unreachable in practice, but keep the - // comparator well-formed regardless. arr.sort_unstable_by(|a, b| { // descending by count, then ascending by (index, char) — // matches CharFreq.zig:12 `CharAndCount.lessThan`. @@ -119,10 +112,6 @@ impl CharFreq { } fn scan_big(out: &mut Buffer, text: &[u8], delta: i32) { - // https://zig.godbolt.org/z/P5dPojWGK - // PORT NOTE: Zig copied `out.*` into a stack local and wrote back via `defer` to - // avoid unaligned (`align(1)`) loads in the hot loop. We operate on `out` directly; - // the field is naturally aligned in Rust. let mut deltas: [i32; 256] = [0; 256]; debug_assert!(text.len() >= SCAN_BIG_CHUNK_SIZE); @@ -141,20 +130,6 @@ fn scan_big(out: &mut Buffer, text: &[u8], delta: i32) { deltas[c as usize] += delta; } - // PORT NOTE — INTENTIONAL SPEC DIVERGENCE: CharFreq.zig:64 writes - // `freqs[0..26].* = deltas[...]`, which *overwrites* the accumulator - // (`var freqs = out.*` is dead). That is an upstream bug: every ≥32-byte - // scan discards all prior counts, so the result is last-big-scan-wins - // rather than the histogram the NameMinifier expects. Zig's output is - // stable only because its StringHashMap iteration order is deterministic, - // so the *same* symbol name overwrites last on every run. The Rust - // `scope.members` map is RandomState-seeded, so a faithful overwrite port - // is nondeterministic (the observed `OV`/`OU` flap on three.js), and even - // a deterministic-iteration port wouldn't reproduce Zig's specific hash - // order. We accumulate (`+=`) instead — the algorithm's intent — which - // makes the freq table both correct and run-to-run stable. Minified - // output therefore differs from Zig by design here (three.js: 2 bytes - // smaller); byte-identical-vs-Zig is not a goal for this function. for i in 0..26 { out[i] += deltas[b'a' as usize + i]; } @@ -173,12 +148,6 @@ fn scan_small(out: &mut Buffer, text: &[u8], delta: i32) { // RMWs in the loop. The Rust field is naturally aligned, so operate on `out` directly // (same treatment as `scan_big`). for &c in text { - // Indices follow `NameMinifier::DEFAULT_TAIL` order - // (`a-zA-Z0-9_$` → 0..63), matching `scan_big` which writes digits - // at `out[52 + i]`. The Zig original (`char_freq.zig:79`) used `53`, - // which shifted `'0'` to 53 and made `'9'` collide with `'_'` at 62, - // leaving slot 52 cold for `<32`-byte inputs and slightly skewing - // minified-name ranking when digits/underscores appear. let i: usize = match c { b'a'..=b'z' => c as usize - b'a' as usize, b'A'..=b'Z' => c as usize - (b'A' as usize - 26), diff --git a/src/ast/e.rs b/src/ast/e.rs index d1d101d4fef..2331e401fb2 100644 --- a/src/ast/e.rs +++ b/src/ast/e.rs @@ -16,11 +16,6 @@ use bun_core::strings; use crate::{Expr, ExprNodeIndex, ExprNodeList, G, OptionalChain, Ref, StoreRef}; use bun_alloc::ArenaVecExt as _; -// In Zig: `const string = []const u8;` -// AST string fields are arena-owned (bulk-freed via Store/arena reset; never -// individually freed). `StoreStr` is `StoreRef`'s `[u8]` sibling: a thin -// lifetime-erased pointer with safe construction (no `transmute`) and -// `Deref` under the same valid-until-arena-reset contract. pub use crate::StoreStr as Str; /// This represents an internal property name that can be mangled. The symbol @@ -63,10 +58,6 @@ impl Default for Array { } } } -// TODO(port): Array methods call `Vec::init_capacity(bump, n)` -// (signature mismatch: Vec takes only `n`; AST-crate variant with bump -// arena pending) and `Expr::Data::*` deep matches. Un-gate with parser round. -// Live subset of `Array` accessors needed by downstream crates. impl Array { pub const EMPTY: Array = Array { items: bun_alloc::AstAlloc::vec(), @@ -97,12 +88,6 @@ impl Array { _bump: &Bump, estimated_count: usize, ) -> Result { - // This over-allocates a little but it's fine - // PERF(port): Zig allocated in arena; this Vec uses the global arena. - // `Expr.data` is an enum (validity invariant), so the Zig - // `expandToCapacity` + index-walk pattern would form `&mut [Expr]` - // over invalid bit patterns. Push into reserved capacity instead — - // same allocation profile (one upfront `with_capacity`), no uninit. let mut out: ExprNodeList = ExprNodeList::init_capacity(estimated_count + self.items.len_u32() as usize); // PORT NOTE: reshaped for borrowck — iterate items via index so the &mut @@ -158,38 +143,8 @@ bitflags::bitflags! { #[derive(Clone, Copy, Default, PartialEq, Eq)] #[repr(transparent)] pub struct UnaryFlags: u8 { - /// The expression "typeof (0, x)" must not become "typeof x" if "x" - /// is unbound because that could suppress a ReferenceError from "x". - /// - /// Also if we know a typeof operator was originally an identifier, then - /// we know that this typeof operator always has no side effects (even if - /// we consider the identifier by itself to have a side effect). - /// - /// Note that there *is* actually a case where "typeof x" can throw an error: - /// when "x" is being referenced inside of its TDZ (temporal dead zone). TDZ - /// checks are not yet handled correctly by Bun, so this possibility is - /// currently ignored. const WAS_ORIGINALLY_TYPEOF_IDENTIFIER = 1 << 0; - /// Similarly the expression "delete (0, x)" must not become "delete x" - /// because that syntax is invalid in strict mode. We also need to make sure - /// we don't accidentally change the return value: - /// - /// ```text - /// Returns false: - /// "var a; delete (a)" - /// "var a = Object.freeze({b: 1}); delete (a.b)" - /// "var a = Object.freeze({b: 1}); delete (a?.b)" - /// "var a = Object.freeze({b: 1}); delete (a['b'])" - /// "var a = Object.freeze({b: 1}); delete (a?.['b'])" - /// - /// Returns true: - /// "var a; delete (0, a)" - /// "var a = Object.freeze({b: 1}); delete (true && a.b)" - /// "var a = Object.freeze({b: 1}); delete (false || a?.b)" - /// "var a = Object.freeze({b: 1}); delete (null ?? a?.['b'])" - /// "var a = Object.freeze({b: 1}); delete (true ? a['b'] : a['b'])" - /// ``` const WAS_ORIGINALLY_DELETE_OF_IDENTIFIER_OR_PROPERTY_ACCESS = 1 << 1; } } @@ -281,14 +236,6 @@ pub struct Call { pub is_direct_eval: bool, pub close_paren_loc: crate::Loc, - /// True if there is a comment containing "@__PURE__" or "#__PURE__" preceding - /// this call expression. This is an annotation used for tree shaking, and - /// means that the call can be removed if it's unused. It does not mean the - /// call is pure (e.g. it may still return something different if called twice). - /// - /// Note that the arguments are not considered to be part of the call. If the - /// call itself is removed due to this annotation, the arguments must remain - /// if they have side effects. pub can_be_unwrapped_if_unused: CallUnwrap, /// Used when printing to generate the source prop on the fly @@ -416,22 +363,6 @@ pub struct Function { pub func: G::Fn, } -/// 8-byte identifier expression payload. The three side-effect flags are packed -/// into `Ref`'s user-bit lane (bits 28..31, masked out of `Ref` identity) so -/// this — the most common `expr::Data` variant — fits in a single word, which -/// is what pulls `expr::Data` down to 16 bytes / `Expr` to 24. The Zig layout -/// stores them as discrete bools (16B with padding); the Rust port exploits -/// `noalias` + smaller nodes for the structural perf win. -/// -/// `ref_` remains a public field so the ~100 existing `id.ref_` / -/// `Identifier { ref_, ..Default::default() }` sites stay untouched; flag -/// access goes through the accessor methods below. -/// -/// **Hazard:** assigning a fresh `Ref` to `ref_` *clears the flags*. This is -/// fine for `visit_expr`'s `e_identifier` (sets `ref_` first then re-derives -/// the flags), but any port of Zig `id.ref = new_ref` that expects the -/// surrounding bool fields to survive must instead write -/// `id.ref_ = new_ref.with_user_bits_from(id.ref_)` — see `handle_identifier`. #[derive(Clone, Copy)] pub struct Identifier { pub ref_: Ref, @@ -460,10 +391,6 @@ impl Identifier { self.ref_.set_user_bit(0, v); } - /// If true, this identifier is known to not have a side effect (i.e. to not - /// throw an exception) when referenced. If false, this identifier may or may - /// not have side effects when referenced. This is used to allow the removal - /// of known globals such as "Object" if they aren't used. #[inline] pub const fn can_be_removed_if_unused(self) -> bool { self.ref_.user_bit(1) @@ -507,27 +434,6 @@ impl Identifier { } } -/// This is similar to an `Identifier` but it represents a reference to an ES6 -/// import item. -/// -/// Depending on how the code is linked, the file containing this EImportIdentifier -/// may or may not be in the same module group as the file it was imported from. -/// -/// If it's the same module group than we can just merge the import item symbol -/// with the corresponding symbol that was imported, effectively renaming them -/// to be the same thing and statically binding them together. -/// -/// But if it's a different module group, then the import must be dynamically -/// evaluated using a property access off the corresponding namespace symbol, -/// which represents the result of a require() call. -/// -/// It's stored as a separate type so it's not easy to confuse with a plain -/// identifier. For example, it'd be bad if code trying to convert "{x: x}" into -/// "{x}" shorthand syntax wasn't aware that the "x" in this case is actually -/// "{x: importedNamespace.x}". This separate type forces code to opt-in to -/// doing this instead of opt-out. -/// 8-byte import-identifier payload — `was_originally_identifier` rides in -/// `Ref` user bit 0 (see `Identifier` doc for the packing rationale). #[derive(Clone, Copy)] pub struct ImportIdentifier { pub ref_: Ref, @@ -560,12 +466,6 @@ impl ImportIdentifier { } } -/// This is a dot expression on exports, such as `exports.`. It is given -/// it's own AST node to allow CommonJS unwrapping, in which this can just be -/// the identifier in the Ref -/// 8-byte CJS-export-identifier payload — `base` rides in `Ref` user bit 0 -/// (`Exports` = 0, `ModuleDotExports` = 1; see `Identifier` doc for packing -/// rationale). #[derive(Clone, Copy)] pub struct CommonJSExportIdentifier { pub ref_: Ref, @@ -598,12 +498,6 @@ impl CommonJSExportIdentifier { } } -/// The original variant of the dot expression must be known so that in the case that we -/// - fail to convert this to ESM -/// - ALSO see an assignment to `module.exports` (commonjs_module_exports_assigned_deoptimized) -/// It must be known if `exports` or `module.exports` was written in source -/// code, as the distinction will alter behavior. The fixup happens in the printer when -/// printing this node. #[derive(Clone, Copy, PartialEq, Eq)] pub enum CommonJSExportIdentifierBase { Exports, @@ -618,36 +512,7 @@ pub struct PrivateIdentifier { pub ref_: Ref, } -/// In development mode, the new JSX transform has a few special props -/// - `React.jsxDEV(type, arguments, key, isStaticChildren, source, self)` -/// - `arguments`: -/// `{ ...props, children: children, }` -/// - `source`: https://github.com/babel/babel/blob/ef87648f3f05ccc393f89dea7d4c7c57abf398ce/packages/babel-plugin-transform-react-jsx-source/src/index.js#L24-L48 -/// ```text -/// { -/// fileName: string | null, -/// columnNumber: number | null, -/// lineNumber: number | null, -/// } -/// ``` -/// - `children`: -/// - static the function is React.jsxsDEV, "jsxs" instead of "jsx" -/// - one child? the function is React.jsxDEV, -/// - no children? the function is React.jsxDEV and children is an empty array. -/// `isStaticChildren`: https://github.com/facebook/react/blob/4ca62cac45c288878d2532e5056981d177f9fdac/packages/react/src/jsx/ReactJSXElementValidator.js#L369-L384 -/// This flag means children is an array of JSX Elements literals. -/// The documentation on this is sparse, but it appears that -/// React just calls Object.freeze on the children array. -/// Object.freeze, historically, is quite a bit slower[0] than just not doing that. -/// Given that...I am choosing to always pass "false" to this. -/// This also skips extra state that we'd need to track. -/// If React Fast Refresh ends up using this later, then we can revisit this decision. -/// [0]: https://github.com/automerge/automerge/issues/177 pub struct JSXElement { - /// JSX tag name - /// `
` => E.String.init("div") - /// `` => E.Identifier{.ref = symbolPointingToMyComponent } - /// null represents a fragment pub tag: Option, /// JSX props @@ -687,13 +552,6 @@ pub enum JSXSpecialProp { Any, } impl JSXSpecialProp { - // PERF(port): Zig used `ComptimeStringMap` (length-prefix lookup, all - // resolved at comptime). A `phf::Map` here would compute a full SipHash + - // index + slice compare on every JSX prop name even though the - // overwhelming majority of inputs (`className`, `onClick`, `style`, ...) - // miss. With only 4 keys at 3 distinct lengths, a length-gated `match` - // rejects almost every miss on a single `usize` compare and never hashes. - // See clap::find_param (12577e958d71) for the same pattern. #[inline] pub fn from_bytes(s: &[u8]) -> Option { match s.len() { @@ -737,11 +595,6 @@ const NEG_DOUBLE_DIGIT: [&[u8]; 101] = [ ]; impl Number { - /// String concatenation with numbers is required by the TypeScript compiler for - /// "constant expression" handling in enums. We can match the behavior of a JS VM - /// by calling out to the APIs in WebKit which are responsible for this operation. - /// - /// This can return `None` in wasm builds to avoid linking JSC pub fn to_string(self, bump: &Bump) -> Option { Self::to_string_from_f64(self.value, bump) } @@ -874,24 +727,12 @@ impl Default for Object { } } -/// used in TOML parser to merge properties. -/// -/// Node types are lifetime-free, so `next` is a raw `*mut Rope` -/// into the bump arena (Zig: `next: ?*Rope`). Segments are bulk-freed at -/// arena reset. pub struct Rope { pub head: Expr, pub next: *mut Rope, } impl Rope { pub fn append(&mut self, expr: Expr, bump: &Bump) -> Result<*mut Rope, AllocError> { - // Walk to the tail iteratively: recursing once per node overflows the - // native stack on adversarially deep ropes (e.g. an `.npmrc` section - // header with thousands of dot-separated segments). - // - // Arena-allocated Rope nodes are uniquely owned by the chain at this - // point; route through `StoreRef::DerefMut` (the arena-backed handle - // whose deref is centralised in `nodes.rs`). let mut tail = StoreRef::from_bump(self); while let Some(next) = core::ptr::NonNull::new(tail.next).map(StoreRef::from_non_null) { tail = next; @@ -904,10 +745,6 @@ impl Rope { Ok(rope) } - /// Re-borrow `next` as `Option<&Rope>`. Same `StoreRef` arena contract: - /// the pointee is a bump allocation valid until arena reset. Centralises - /// the one `unsafe` so the `set_rope`/`get_or_put_*`/`get_rope` walkers - /// don't repeat `if !next.is_null() { unsafe { &*next } }` at every hop. #[inline] pub fn next_ref<'a>(&self) -> Option<&'a Rope> { // SAFETY: `next` is either null or a bump-arena allocation valid until @@ -1353,10 +1190,6 @@ pub struct Spread { /// JavaScript string literal type pub struct EString { - // A version of this where `utf8` and `value` are stored in a packed union, with len as a single u32 was attempted. - // It did not improve benchmarks. Neither did converting this from a heap-allocated type to a stack-allocated type. - // TODO: change this to *const anyopaque and change all uses to either .slice8() or .slice16() - // TODO(port): arena-owned slice pub data: Str, pub prefer_template: bool, @@ -1437,17 +1270,7 @@ impl EString { ..Default::default() } } - /// Construct from a UTF-16 slice (arena-owned). The `data` slice's `.len()` - /// stores the **u16 element count** (not byte count) — Zig: - /// `@ptrCast(value.ptr)[0..value.len]`. `slice16()` and friends rely on - /// this. The pointer is reinterpreted to `*const u8` for storage only. pub fn init_utf16(data: &[u16]) -> Self { - // `Str::new` only records `(ptr, len)`; we want the original `*const u16` - // (reinterpreted as bytes) and the **u16 element count**. Safe-cast the - // full `2*len` byte view, then reslice to the first `len` bytes — same - // pointer/length pair as the old raw-slice construction, without an - // `unsafe` block. Consumers must check `is_utf16` and re-slice via - // `slice16`. let bytes = &bytemuck::cast_slice::(data)[..data.len()]; Self { data: Str::new(bytes), @@ -1483,10 +1306,6 @@ impl EString { } } -// ── live EString accessor surface ────────────────────────────────────────── -// Subset of the gated impl below adapted to the current `bun_core` API -// (`eql_long::`, no bump-arena `to_utf8_alloc`). Heavy -// transcode/rope-clone paths stay gated. impl EString { #[inline] pub fn len(&self) -> usize { @@ -1687,10 +1506,6 @@ impl EString { } } - /// Shallow field-wise copy. `EString` is structurally `Copy` (slice ref + - /// `Option` rope links + scalars) but does not derive it to keep - /// rope-ownership intent explicit; Zig sites that did `.* = other.*` use - /// this instead. #[inline] pub fn shallow_clone(&self) -> EString { EString { @@ -1714,11 +1529,6 @@ impl EString { } } - /// Zig `E.String.push` — link `other` onto this string's rope tail. - /// - /// `other` MUST be Store/arena-allocated (callers pass - /// `Expr::init(EString, ...).data.e_string_mut()` or a freshly - /// `Store::append`ed node); its address is captured as a `StoreRef`. pub fn push(&mut self, other: &mut EString) { debug_assert!(self.is_utf8()); debug_assert!(other.is_utf8()); @@ -1755,10 +1565,6 @@ impl EString { pub fn clone_rope_nodes(s: &EString) -> EString { let mut root = s.shallow_clone(); if let Some(first) = root.next { - // Clone the first link, then walk the freshly-cloned chain via - // `StoreRef` (safe `Deref`/`DerefMut`) instead of a raw `*mut` - // cursor. Each cloned node's `next` still points at the original - // chain (shallow clone), so re-clone link-by-link. let mut tail: StoreRef = crate::expr::data::Store::append(first.get().shallow_clone()); root.next = Some(tail); @@ -1851,10 +1657,6 @@ pub struct TemplatePart { pub struct Template { pub tag: Option, - /// Arena-owned mutable slice (Zig: `[]TemplatePart`). Stored as a - /// `StoreSlice` so writers (`substitute_single_use_symbol_in_expr`, the - /// visit pass, `foldStringAddition`) retain mutable provenance. Use - /// `parts()` / `parts_mut()` for ergonomic access; never null. pub parts: crate::StoreSlice, pub head: TemplateContents, } @@ -2089,12 +1891,6 @@ pub struct RegExp { // TODO(port): arena-owned slice pub value: Str, - /// This exists for JavaScript bindings - /// The RegExp constructor expects flags as a second argument. - /// We want to avoid re-lexing the flags, so we store them here. - /// This is the index of the first character in a flag, not the "/" - /// /foo/gim - /// ^ pub flags_offset: Option, } impl RegExp { @@ -2104,10 +1900,6 @@ impl RegExp { }; pub fn pattern(&self) -> &[u8] { - // rewind until we reach the /foo/gim - // ^ - // should only ever be a single character - // but we're being cautious if let Some(i_) = self.flags_offset { let mut i = i_; while i > 0 && self.value[i as usize] != b'/' { @@ -2121,10 +1913,6 @@ impl RegExp { } pub fn flags(&self) -> &[u8] { - // rewind until we reach the /foo/gim - // ^ - // should only ever be a single character - // but we're being cautious if let Some(i) = self.flags_offset { return &self.value[i as usize..]; } @@ -2180,15 +1968,6 @@ pub struct Import { pub expr: ExprNodeIndex, pub options: ExprNodeIndex, pub import_record_index: u32, - // TODO: - // Comments inside "import()" expressions have special meaning for Webpack. - // Preserving comments inside these expressions makes it possible to use - // esbuild as a TypeScript-to-JavaScript frontend for Webpack to improve - // performance. We intentionally do not interpret these comments in esbuild - // because esbuild is not Webpack. But we do preserve them since doing so is - // harmless, easy to maintain, and useful to people. See the Webpack docs for - // more info: https://webpack.js.org/api/module-methods/#magic-comments. - // leading_interior_comments: []G.Comment = &([_]G.Comment{}), } impl Import { pub fn is_import_record_null(&self) -> bool { diff --git a/src/ast/expr.rs b/src/ast/expr.rs index 73b8f92140c..cc6dff4253e 100644 --- a/src/ast/expr.rs +++ b/src/ast/expr.rs @@ -175,11 +175,6 @@ impl Default for Query { } } -// ─────────────────────────────────────────────────────────────────────────── -// ── live Expr accessor surface ───────────────────────────────────────────── -// Subset of the gated impl below; bodies adapted to the live `E::Object` / -// `E::EString` surface in `e.rs`. The full set/get_path/rope helpers -// stay gated. impl Expr { #[inline] pub fn is_array(&self) -> bool { @@ -282,12 +277,6 @@ impl Expr { } } -// Expr — property/object/string accessor methods. -// TODO(port): these call into `E::Object::as_property` / `EString` -// methods that need `bun_core::utf16_eql_string`/`to_utf8_alloc` (track-A -// blocked_on) and `Vec::deep_clone`. Types are real; bodies un-gate with -// the parser round once those land. - impl Expr { pub fn has_any_property_named(&self, names: &'static [&'static [u8]]) -> bool { let Data::EObject(obj) = &self.data else { @@ -368,18 +357,6 @@ impl Expr { } } - /// This supports lookups like: - /// - `foo` - /// - `foo.bar` - /// - `foo[123]` - /// - `foo[123].bar` - /// - `foo[123].bar[456]` - /// - `foo[123].bar[456].baz` - /// - `foo[123].bar[456].baz.qux` // etc. - /// - /// This is not intended for use by the transpiler, instead by pretty printing JSON. - // PORT NOTE: Zig passed `bun.default_allocator` to getByIndex; Rust threads the arena - // explicitly because get_by_index allocates an E.String slice into &Bump. pub fn get_path_may_be_index(&self, bump: &Bump, name: &[u8]) -> Option { if name.is_empty() { return None; @@ -429,10 +406,6 @@ impl Expr { self.get(name) } - /// Don't use this if you care about performance. - /// - /// Sets the value of a property, creating it if it doesn't exist. - /// `self` must be an object. pub fn set(&mut self, _bump: &Bump, name: &[u8], value: Expr) -> Result<(), AllocError> { debug_assert!(self.is_object()); let Data::EObject(obj) = &mut self.data else { @@ -467,10 +440,6 @@ impl Expr { Ok(()) } - /// Don't use this if you care about performance. - /// - /// Sets the value of a property to a string, creating it if it doesn't exist. - /// `expr` must be an object. pub fn set_string( expr: &mut Expr, _bump: &Bump, @@ -707,11 +676,6 @@ impl ArrayIterator { } } -// PORT NOTE: earlier drafts of `as_array`/`is_string`/`as_utf8_string_literal`/ -// `as_string`/`as_string_cloned`/`as_bool`/`as_number` duplicated the live `&self` -// implementations above (lines ~231-315) with worse signatures (`expr: &Expr`, -// raw-ptr returns). Those drafts were dropped; only the methods without a live -// counterpart remain. impl Expr { #[inline] pub fn as_string_literal<'b>(&self, bump: &'b Bump) -> Option<&'b [u8]> { @@ -771,16 +735,6 @@ pub enum EFlags { // `is_missing` lives in the `init`/`allocate` impl block below. impl Expr { - /// The goal of this function is to "rotate" the AST if it's possible to use the - /// left-associative property of the operator to avoid unnecessary parentheses. - /// - /// When using this, make absolutely sure that the operator is actually - /// associative. For example, the "-" operator is not associative for - /// floating-point numbers. - // - // PERF(port): Zig took `comptime op: Op.Code`. `Op::Code` does not derive - // `ConstParamTy` (Op.rs owns the enum); pass at runtime here. Revisit once - // `Code` gains `ConstParamTy` — call sites are a handful of literal ops. pub fn join_with_left_associative_op(op: Op::Code, a: Expr, b: Expr) -> Expr { Self::join_with_left_associative_op_with_check(op, a, b, bun_core::StackCheck::init()) } @@ -977,21 +931,9 @@ impl Expr { // Static state // ─────────────────────────────────────────────────────────────────────────── -// Zig: `pub var icount: usize = 0;` — a plain non-atomic global, never read -// (debug counter). Kept for parity but **debug-only**: in release the -// `lock xadd` per node was a contended cache line bouncing across the bundler -// worker pool on every Expr allocation. Zig's increment is a non-atomic store -// (i.e. racy garbage under threads) so a debug-gated atomic is strictly more -// faithful than the old unconditional one. #[cfg(debug_assertions)] pub(crate) static ICOUNT: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0); -// PORT NOTE: Zig `expr.zig` declares `true_bool`/`false_bool`/`bool_values` -// statics but never references them — `E.Boolean` is stored by value in -// `Data.e_boolean` (both `allocate` and `init` arms), not as a pointer to a -// pooled singleton. Dropped here; the comment "We don't need to dynamically -// allocate booleans" already holds because `E::Boolean` is inline in `Data`. - // ─────────────────────────────────────────────────────────────────────────── // Expr::allocate / Expr::init — comptime-type dispatch → trait // ─────────────────────────────────────────────────────────────────────────── @@ -1250,10 +1192,6 @@ pub enum Tag { EPrivateIdentifier, ECommonjsExportIdentifier, EBoolean, - /// Like e_boolean, but produced by `feature()` from `bun:bundle`. - /// This tag ensures feature() can only be used directly in conditional - /// contexts (if statements, ternaries). Invalid usage is caught during - /// the visit phase when this expression appears outside a branch condition. EBranchBoolean, ENumber, EBigInt, @@ -1487,10 +1425,6 @@ impl Expr { Expr::init(t, self.loc) } - // Wraps the provided expression in the "!" prefix operator. The expression - // will potentially be simplified to avoid generating unnecessary extra "!" - // operators. For example, calling this with "!!x" will return "!x" instead - // of returning "!!!x". pub fn not(&self, bump: &Bump) -> Expr { self.maybe_simplify_not(bump).unwrap_or_else(|| { Expr::init( @@ -1514,11 +1448,6 @@ impl Expr { self.has_value_for_this_in_call() } - /// The given "expr" argument should be the operand of a "!" prefix operator - /// (i.e. the "x" in "!x"). This returns a simplified expression for the - /// whole operator (i.e. the "!x") if it can be simplified, or false if not. - /// It's separate from "Not()" above to avoid allocation on failure in case - /// that is undesired. pub fn maybe_simplify_not(&self, bump: &Bump) -> Option { let expr = self; match expr.data { @@ -1550,14 +1479,6 @@ impl Expr { } } Data::EBinary(mut ex) => { - // TODO: evaluate whether or not it is safe to do this mutation since it's modifying in-place. - // Make sure that these transformations are all safe for special values. - // For example, "!(a < b)" is not the same as "a >= b" if a and/or b are - // NaN (or undefined, or null, or possibly other problem cases too). - // - // PORT: Zig captured `*E.Binary` and wrote through it; `StoreRef` is a - // `Copy` `NonNull` handle, so copying it out of the (immutable) `Data` - // and `DerefMut`-ing reaches the same arena slot. match ex.op { crate::OpCode::BinLooseEq => { // "!(a == b)" => "a != b" @@ -1698,11 +1619,6 @@ impl PrimitiveType { // Data // ─────────────────────────────────────────────────────────────────────────── -/// Tagged union of expression payloads. Pointer variants are arena-allocated -/// `StoreRef` (thin `NonNull` into `expr::data::Store` / a bump arena); -/// inline variants are stored by value. `StoreRef` is `Copy` + `Deref`, so -/// `Data` is `Copy` and `let Data::EBinary(b) = data; b.op` works (matching -/// Zig's `data.e_binary.op`). #[derive(Clone, Copy, bun_core::EnumTag)] #[enum_tag(existing = Tag)] pub enum Data { @@ -1764,19 +1680,6 @@ pub enum Data { ENameOfSymbol(StoreRef), } -// ── Layout guards ───────────────────────────────────────────────────────── -// Zig: `bun.assert_eql(@sizeOf(Data), 24)` (Expr.zig:2189). Rust packs the -// identifier-family flags into `Ref`'s spare bits (see `E::Identifier` doc), -// so every inline payload is ≤ 8 bytes; with the repr(Rust) discriminant -// that rounds to 16. `Expr` = `Data` (16, align 8) + `Loc` (i32) → 20 → 24 -// after tail padding — 25% smaller than the Zig layout, which is the -// structural noalias-shrink win this port targets. -// -// The `Option` assert proves Rust's niche optimization fires: the enum -// has spare discriminant values (47 variants < 256, and every pointer variant -// contributes a NonNull niche), so `None` packs into an unused bit-pattern -// rather than adding a word. If a future variant adds `#[repr(C)]`/`#[repr(u32)]` -// or a nullable `*mut T` payload, this assert catches the size regression. const _: () = assert!(core::mem::size_of::() == 16); // Do not increase the size of Expr const _: () = assert!(core::mem::size_of::() == 24); const _: () = assert!( @@ -1900,10 +1803,6 @@ impl Data { matches!(self, Data::ENumber(_)) } - // ── Remaining StoreRef field-style accessors ────────────────── - // visitExpr / maybe.rs port from Zig's `data.e_dot.*` etc., which are - // unchecked union field reads. Rust callers `.unwrap()` (or pattern-match) - // — the `Option` is the cheapest sound encoding of Zig's UB-on-mismatch. #[inline] pub fn e_unary(&self) -> Option> { if let Data::EUnary(v) = *self { @@ -2304,22 +2203,7 @@ impl Data { } } -// ─────────────────────────────────────────────────────────────────────────── -// Data — heavy transform/analysis methods (clone/deep_clone/fold/etc). -// TODO(port): these reference `Vec::deep_clone`/`E::*::Clone` -// surfaces, `bun_core::write_any_to_hasher`, and parser-state types that land -// with `P.rs`/`Parser.rs`. The *types* (`Data`/`Expr`/`Tag`/`Store`) are real; -// only these method bodies wait. - impl Data { - /// Shallow clone: re-allocate the boxed payload (so the caller owns a fresh - /// arena slot) but don't recurse into children. Zig: `Data.clone`. - /// - /// PORT NOTE: the `E::*` payloads do not derive `Clone` (they hold raw arena - /// pointers / `Vec`). Zig copied struct bytes (`el.*`); we mirror that - /// with a `core::ptr::read` of the payload, which is sound because every - /// payload is `Copy`-shaped (no `Drop`, no owned heap state — `Vec` - /// stores a raw pointer + len/cap into the arena). pub fn clone_in(this: Data, bump: &Bump) -> Result { // TODO(port): narrow error set macro_rules! shallow { @@ -2359,17 +2243,6 @@ impl Data { } } - /// Deep-clone this subtree into `bump`. - /// - /// Nodes go into `bump`; embedded `AstVec`s (`items`/`properties`/…) - /// allocate via `AstAlloc`, which reads the thread's active allocation - /// state. If a per-parse `ASTMemoryAllocator` scope is active that state - /// is bulk-freed while the cloned tree (e.g. `WorkspacePackageJSONCache`) - /// still references the buffers — UAF. This entry point installs a - /// [`DetachAstHeap`] guard so - /// those vecs land on global mimalloc. The guard is installed once here - /// and at [`Expr::deep_clone`]; the recursive body goes through - /// `*_no_detach` so we don't pay 3 TLS ops per node. pub fn deep_clone(&self, bump: &Bump) -> Result { let _g = bun_alloc::ast_alloc::DetachAstHeap::new(); self.deep_clone_no_detach(bump) @@ -2615,24 +2488,11 @@ impl Data { } // end `impl Data` (clone_in/deep_clone) impl Data { - /// `hasher` should be something with `fn update(&[u8])`; - /// symbol table is passed to serialize `Ref` as identifier names instead of nondeterministic numbers. - /// - /// Port of `Expr.Data.writeToHasher`. Zig fed raw bytes of anonymous tuples - /// (`std.mem.asBytes(&.{a, b, c})`) — including padding, which is undefined - /// in both languages. The Rust port hashes each scalar individually so the - /// output is deterministic (this is only consumed by React Refresh signature - /// generation; byte-for-byte parity with Zig is not required, only stability). pub fn write_to_hasher(&self, hasher: &mut H, symbol_table: &mut S) where H: bun_core::Hasher + ?Sized, S: crate::base::SymbolTable + ?Sized, { - // Local mirror of `bun.writeAnyToHasher` for padding-free POD — - // `bun_core::write_any_to_hasher` is bound by `AsBytes` (ints only) and - // we cannot extend that trait from this crate-file scope. `NoUninit` - // bound lets `bytemuck::bytes_of` view the value's bytes safely - // (mirrors Zig `hasher.update(std.mem.asBytes(&thing))`). #[inline(always)] fn raw(h: &mut H, v: T) { h.update(bytemuck::bytes_of(&v)); @@ -2801,10 +2661,6 @@ impl Data { /// outside of a module wrapper (__esm/__commonJS). pub fn can_be_moved(&self) -> bool { match self { - // TODO: identifiers can be removed if unused, however code that - // moves expressions around sometimes does so incorrectly when - // doing destructures. test case: https://github.com/oven-sh/bun/issues/14027 - // Data::EIdentifier(id) => id.can_be_removed_if_unused, Data::EClass(class) => class.can_be_moved(), Data::EArrow(_) @@ -3118,12 +2974,6 @@ pub struct Equality { pub equal: bool, pub ok: bool, - /// This extra flag is unfortunately required for the case of visiting the expression - /// `require.main === module` (and any combination of !==, ==, !=, either ordering) - /// - /// We want to replace this with the dedicated import_meta_main node, which: - /// - Stops this module from having p.require_ref, allowing conversion to ESM - /// - Allows us to inline `import.meta.main`'s value, if it is known (bun build --compile) pub is_require_main_and_module: bool, } @@ -3162,10 +3012,6 @@ impl EqlKindT for StrictEql { const STRICT: bool = true; } -/// Minimal parser surface needed by `Data::eql` — Zig wrote `p: anytype` and -/// touched only `p.arena` + `p.module_ref`. Kept separate from -/// `ast::p::ParserLike` so this file does not grow that trait (out of scope); -/// blanket-impl'd for every `P<...>` instantiation below. pub trait EqlParser { fn arena(&self) -> &Bump; fn module_ref(&self) -> Ref; @@ -3414,11 +3260,6 @@ pub use data::Store; // StoredData / helpers // ─────────────────────────────────────────────────────────────────────────── -// Zig: `pub fn StoredData(tag: Tag) type` — comptime type-level function. -// Rust cannot return types from runtime tags. Callers should match on `Data` -// directly. -// TODO(port): if needed, expose as a macro mapping Tag → payload type. - fn string_to_equivalent_number_value(str: &[u8]) -> f64 { // +"" -> 0 if str.is_empty() { diff --git a/src/ast/fold_string_addition.rs b/src/ast/fold_string_addition.rs index a578a27ed58..e65c2cb6a7f 100644 --- a/src/ast/fold_string_addition.rs +++ b/src/ast/fold_string_addition.rs @@ -2,11 +2,6 @@ use crate::expr::{Data, PrimitiveType, data}; use crate::{E, Expr, StoreRef, e}; use bun_alloc::Arena; // bumpalo::Bump re-export -// ── local rope helpers ───────────────────────────────────────────────────── -// `EString::push` / `EString::clone_rope_nodes` are still gated in E.rs -// (round-C draft); inline the minimal surface here so this file can un-gate -// without touching E.rs. These mirror the Zig bodies 1:1. - #[inline] fn store_append_string(s: E::EString) -> StoreRef { data::Store::append(s) @@ -46,10 +41,6 @@ fn estring_push(lhs: &mut E::EString, mut other: StoreRef) { fn clone_rope_nodes(s: &E::EString) -> E::EString { let mut root = s.shallow_clone(); if let Some(first) = root.next { - // Clone the first link, then walk the freshly-cloned chain via - // `StoreRef` (safe `Deref`/`DerefMut`) instead of a raw `*mut` - // cursor. Each cloned node's `next` still points at the original - // chain (shallow clone), so re-clone link-by-link. let mut tail: StoreRef = store_append_string(first.get().shallow_clone()); root.next = Some(tail); while let Some(next) = tail.next { @@ -62,43 +53,17 @@ fn clone_rope_nodes(s: &E::EString) -> E::EString { root } -/// Concatenate two `E::String`s, mutating BOTH inputs -/// unless `has_inlined_enum_poison` is set. -/// -/// Currently inlined enum poison refers to where mutation would cause output -/// bugs due to inlined enum values sharing `E::String`s. If a new use case -/// besides inlined enums comes up to set this to true, please rename the -/// variable and document it. fn join_strings( left: &E::EString, right: &E::EString, has_inlined_enum_poison: bool, ) -> E::EString { let mut new = if has_inlined_enum_poison { - // Inlined enums can be shared by multiple call sites. In - // this case, we need to ensure that the ENTIRE rope is - // cloned. In other situations, the lhs doesn't have any - // other owner, so it is fine to mutate `lhs.data.end.next`. - // - // Consider the following case: - // const enum A { - // B = "a" + "b", - // D = B + "d", - // }; - // console.log(A.B, A.D); clone_rope_nodes(left) } else { left.shallow_clone() }; - // Similarly, the right side has to be cloned for an enum rope too. - // - // Consider the following case: - // const enum A { - // B = "1" + "2", - // C = ("3" + B) + "4", - // }; - // console.log(A.B, A.C); let rhs_clone = store_append_string(if has_inlined_enum_poison { clone_rope_nodes(right) } else { @@ -111,10 +76,6 @@ fn join_strings( new } -/// `std.mem.concat(arena, E.TemplatePart, &.{a, b})` — concat into the bump -/// arena. `TemplatePart` is POD-shaped (no Drop) but not `Copy` because -/// `EString` opted out; mirror `Template::fold`'s field-wise copy via -/// `shallow_clone` instead of raw `copy_nonoverlapping`. fn concat_parts( bump: &Arena, a: &[e::TemplatePart], @@ -237,13 +198,6 @@ pub fn fold_string_addition( // `foo${bar}` + "baz" => `foo${bar}baz` Data::EString(right) => { if right.is_utf8() { - // Mutation of this node is fine because it will be not - // be shared by other places. Note that e_template will - // be treated by enums as strings, but will not be - // inlined unless they could be converted into - // .e_string. - // `parts` is `StoreSlice` (arena-owned, mutable - // provenance) — write through `parts_mut()`. if !left.parts().is_empty() { let i = left.parts().len() - 1; let last_tail = &left.parts()[i].tail; diff --git a/src/ast/g.rs b/src/ast/g.rs index 9b557a9405e..b54745a2a03 100644 --- a/src/ast/g.rs +++ b/src/ast/g.rs @@ -142,15 +142,6 @@ impl Default for ClassStaticBlock { } pub struct Property { - /// This is used when parsing a pattern that uses default values: - /// - /// [a = 1] = []; - /// ({a = 1} = {}); - /// - /// It's also used for class fields: - /// - /// class Foo { a = 1 } - /// pub initializer: Option, pub kind: PropertyKind, pub flags: flags::PropertySet, @@ -195,10 +186,6 @@ impl Property { self.class_static_block.as_deref() } - /// Mutable sibling of [`class_static_block_ref`]. Routes through - /// `StoreRef::DerefMut` (same arena contract: callers must not hold an - /// overlapping `&`/`&mut` to the same `ClassStaticBlock` — upheld by the - /// single-threaded visitor pass). #[inline] pub fn class_static_block_mut(&mut self) -> Option<&mut ClassStaticBlock> { self.class_static_block.as_deref_mut() diff --git a/src/ast/import_record.rs b/src/ast/import_record.rs index 859236a64fe..2130baa56a6 100644 --- a/src/ast/import_record.rs +++ b/src/ast/import_record.rs @@ -24,18 +24,8 @@ pub struct ImportRecord { pub source_index: Index, - /// `js_printer::printBundledImport` reads this. The Zig field was removed - /// from `ImportRecord` but the printer body referencing it is dead (never - /// analysed by Zig's lazy compilation). Kept here so the eagerly-compiled - /// Rust port of that body type-checks; always 0 in practice. - // TODO(port): delete once `printBundledImport` is confirmed dead and removed. pub module_id: u32, - /// The original import specifier as written in source code (e.g., "./foo.js"). - /// This is preserved before resolution overwrites `path` with the resolved path. - /// Used for metafile generation. - // TODO(port): lifetime — Zig `[]const u8` defaulting to "", never freed in this file. - // Likely a borrow into parser-owned source text; using &'static [u8] as a placeholder. pub original_path: &'static [u8], /// Pack all boolean flags into 2 bytes to reduce padding overhead. @@ -46,16 +36,6 @@ pub struct ImportRecord { bitflags::bitflags! { #[derive(Copy, Clone, Eq, PartialEq, Default, Debug)] pub struct Flags: u16 { - /// True for the following cases: - /// - /// try { require('x') } catch { handle } - /// try { await import('x') } catch { handle } - /// try { require.resolve('x') } catch { handle } - /// import('x').catch(handle) - /// import('x').then(_, handle) - /// - /// In these cases we shouldn't generate an error if the path could not be - /// resolved. const HANDLES_IMPORT_ERRORS = 1 << 0; const IS_INTERNAL = 1 << 1; @@ -95,10 +75,6 @@ bitflags::bitflags! { /// If true, this import can be removed if it's unused const IS_EXTERNAL_WITHOUT_SIDE_EFFECTS = 1 << 11; - /// Tell the printer to print the record as "foo:my-path" instead of "path" - /// where "foo" is the namespace - /// - /// Used to prevent running resolve plugins multiple times for the same path const PRINT_NAMESPACE_IN_PATH = 1 << 12; const WRAP_WITH_TO_ESM = 1 << 13; diff --git a/src/ast/known_global.rs b/src/ast/known_global.rs index d50ac3bdd05..abeaa43405d 100644 --- a/src/ast/known_global.rs +++ b/src/ast/known_global.rs @@ -30,17 +30,6 @@ pub enum KnownGlobal { RegExp, } -// `pub const map = bun.ComptimeEnumMap(KnownGlobal);` -// -// PERF(port): Zig's `ComptimeEnumMap` lowers to a comptime-generated switch. -// An earlier port used `phf::Map<&[u8], _>`, which on every probe computes a 128-bit -// SipHash of the name, two modular reductions, a bounds check, and a final -// slice compare. `minify_global_constructor` calls this for every `new Ident` -// expression in the input, and the overwhelming majority of probes are -// *misses* (any user-defined class). A length-gated match rejects those on a -// single `usize` compare and at most 1-3 fixed-size byte compares — no hash, -// no indirection. 21 keys, ≤3 per length bucket: well within the range where -// open-coded dispatch beats `phf`. #[inline] pub(crate) fn lookup(name: &[u8]) -> Option { match name.len() { @@ -295,14 +284,7 @@ impl KnownGlobal { // Just remove 'new' for Function Some(Self::call_from_new(e, loc)) } - KnownGlobal::RegExp => { - // Don't optimize RegExp - the semantics are too complex: - // - new RegExp(re) creates a copy, but RegExp(re) returns the same instance - // - This affects object identity and lastIndex behavior - // - The difference only applies when flags are undefined - // Keep the original new RegExp() call to preserve correct semantics - None - } + KnownGlobal::RegExp => None, KnownGlobal::WeakSet | KnownGlobal::WeakMap => { let n = e.args.len_u32(); @@ -352,12 +334,6 @@ impl KnownGlobal { | js_ast::expr::PrimitiveType::Boolean | js_ast::expr::PrimitiveType::Number | js_ast::expr::PrimitiveType::String => { - // "new Date('')" is pure - // "new Date(0)" is pure - // "new Date(null)" is pure - // "new Date(true)" is pure - // "new Date(false)" is pure - // "new Date(undefined)" is pure e.can_be_unwrapped_if_unused = js_ast::CanBeUnwrapped::IfUnused; } _ => { @@ -424,13 +400,6 @@ impl KnownGlobal { | js_ast::expr::PrimitiveType::Boolean | js_ast::expr::PrimitiveType::Number | js_ast::expr::PrimitiveType::String => { - // "new Response('')" is pure - // "new Response(0)" is pure - // "new Response(null)" is pure - // "new Response(true)" is pure - // "new Response(false)" is pure - // "new Response(undefined)" is pure - e.can_be_unwrapped_if_unused = js_ast::CanBeUnwrapped::IfUnused; } _ => { diff --git a/src/ast/lexer_tables.rs b/src/ast/lexer_tables.rs index a7a8487593a..62245836778 100644 --- a/src/ast/lexer_tables.rs +++ b/src/ast/lexer_tables.rs @@ -158,19 +158,6 @@ impl T { } } -/// Pack `N <= 16` bytes into a native-endian `u128` (zero-padded). `const` so -/// the literal arms in the `by_len!` macros below fold to integer immediates -/// at compile time; the runtime call (post-monomorphization, fixed `N`) lowers -/// to one or two unaligned loads. -/// -/// The `N <= 8` branch routes through a `u64` and widens with `as u128` so the -/// upper half is the *literal* `0` rather than a stack-buffer read — LLVM -/// InstCombine then narrows the resulting `icmp eq i128 (zext %lo), C` back to -/// a single `i64` compare. This is the codegen Zig's `ComptimeStringMap` emits -/// (`mov (%rsi),%rax; movabs $imm,%rcx; cmp %rcx,%rax`). Matching on -/// `&[u8; N]` directly does **not** get this: rustc lowers array patterns to a -/// per-byte `cmpb`+`jne` decision tree (8 branches for `b"function"`), which -/// is what the previous revision of `by_len!` produced. #[inline(always)] const fn kw_pack(arr: &[u8; N]) -> u128 { assert!(N <= 16); @@ -198,32 +185,8 @@ const fn kw_pack(arr: &[u8; N]) -> u128 { } } -/// Hot-path keyword classifier — called once per identifier in the lexer. -/// -/// Replaces the `phf::Map` lookup for `KEYWORDS` (which hashes through -/// SipHash13 and showed up as ~4% self-time under `phf_shared::hash` in -/// `perf record` on the three.js bundle). Mirrors Zig's `ComptimeStringMap` -/// strategy: bucket by length, then load the candidate once as a wide integer -/// and compare against const-folded immediates — one `cmp` per candidate, no -/// hash, no bounds checks, no `memcmp`, no per-byte ladder. -/// -/// All JS keywords are 2..=10 ASCII bytes; the length dispatch rejects the -/// overwhelming majority of identifiers (which are not keywords) with one -/// branch when `len > 10`. #[inline] pub fn keyword(s: &[u8]) -> Option { - /// View `s` as `&[u8; $n]` (length already proven by the outer - /// `match s.len()`), pack it into a single native-endian integer via - /// [`kw_pack`], and compare against const-folded integer immediates. Each - /// arm is one wide `cmp`. (Matching on `&[u8; N]` directly lowers to a - /// per-byte `cmpb` chain — see [`kw_pack`] doc.) - /// - /// Spelled as an `if`/`else` chain rather than a `match`: inline-`const` - /// in *pattern* position is unstable (`inline_const_pat`), but in - /// *expression* position it has been stable since 1.79 and forces the RHS - /// to a compile-time immediate. The lowered IR is identical — a `match` - /// over scattered `u128` constants is a sequential `cmp`+`je` ladder - /// either way (no jump table for sparse 128-bit keys). macro_rules! by_len { ($n:literal: $($lit:literal => $tok:expr,)*) => {{ let arr: &[u8; $n] = s.try_into().unwrap(); @@ -288,10 +251,6 @@ pub fn keyword(s: &[u8]) -> Option { } } -// Strict-mode reserved-word table sunk to `bun_core::lexer_tables` (single -// source of truth shared with `MutableString::ensure_valid_identifier`). -// `STRICT_MODE_RESERVED_WORDS` is now `[&[u8]; 9]` — `.len()`/`.iter()`- -// compatible with the former `phf::Set` callers (renamer.rs). pub use bun_core::lexer_tables::{ STRICT_MODE_RESERVED_WORDS, is_strict_mode_reserved_word, strict_mode_reserved_word_remap, }; @@ -369,12 +328,6 @@ impl PropertyModifierKeyword { b"static" => PropertyModifierKeyword::PStatic, }; - /// Hot path: queried in `parse_property` once per identifier-keyed - /// property (every method/field name in a class body). Same length-bucket - /// strategy as [`keyword`] — avoids the SipHash round-trip inside - /// `phf::Map::get`. All entries are 3..=9 ASCII bytes; class-heavy inputs - /// like three.js have property names that are overwhelmingly *not* in this - /// set, so the `match s.len()` rejects most lookups in one branch. #[inline] pub fn find(s: &[u8]) -> Option { macro_rules! by_len { @@ -415,10 +368,6 @@ impl PropertyModifierKeyword { } } -/// TypeScript "parameter property" modifier check (constructor args). Same -/// strategy as [`is_strict_mode_reserved_word`]: length-bucketed fixed-array -/// compare to avoid the SipHash inside `phf::Set::contains`. All entries are -/// 6..=9 ASCII bytes and lengths are unique except 8 (override/readonly). #[inline] pub fn is_type_script_accessibility_modifier(s: &[u8]) -> bool { macro_rules! by_len { @@ -601,12 +550,6 @@ pub enum TypescriptStmtKeyword { } impl TypescriptStmtKeyword { - /// Length-gated match. Same strategy as [`keyword`]: 7 entries, max 2 per - /// length bucket, so gating on `len()` first lets LLVM lower each inner - /// compare to a fixed-width integer compare instead of phf's SipHash + - /// index + slice-compare. Almost every miss (every non-TS-keyword - /// identifier at statement position) falls out on the single `usize` - /// compare without touching bytes. #[inline] pub fn from_bytes(s: &[u8]) -> Option { macro_rules! by_len { @@ -1009,10 +952,6 @@ pub fn is_latin1_identifier>(name: B) -> bool { true } -/// `JSLexer.isLatin1Identifier(comptime []const u16, name)` — UTF-16 overload -/// of [`is_latin1_identifier`]. Walks code units exactly as the Zig generic -/// does (no narrowing/alloc): any unit `> 0xFF` fails the predicate, otherwise -/// the byte rules apply. pub fn is_latin1_identifier_u16(name: &[u16]) -> bool { if name.is_empty() { return false; diff --git a/src/ast/lib.rs b/src/ast/lib.rs index 2d00a3afdfa..f4967e980fd 100644 --- a/src/ast/lib.rs +++ b/src/ast/lib.rs @@ -1,21 +1,5 @@ #![feature(allocator_api)] -// `#[thread_local]` for the per-node-allocation hot-path TLS -// (`DATA_STORE_OVERRIDE`, `Expr/Stmt::data::Store::{INSTANCE, -// MEMORY_ALLOCATOR, DISABLE_RESET}`, `store_ast_alloc_heap::ARENA`): bare -// `__thread` slot like Zig's `threadlocal var`, vs the `thread_local!` -// macro's `LocalKey` wrapper. All are `Cell<*mut _>` / `Cell` (no -// destructor, const init). #![feature(thread_local)] -//! Port of `src/logger/logger.zig`. -//! -//! TODO(port): OWNERSHIP — almost every `[]const u8` field in this module has -//! mixed/ambiguous ownership in the Zig original (see the comment on -//! `Location::deinit`: "don't really know what's safe to deinit here!"). Strings -//! are sometimes literals, sometimes `allocator.dupe` results, sometimes slices -//! into `Source.contents` or a `StringBuilder` arena. They are kept as -//! `&'static [u8]` to mirror the Zig `[]const u8` shape without lifetime params; -//! a real ownership story (likely `bun_core::String` or a `'source` lifetime -//! threaded through `Location`/`Data`/`Msg`) is still needed. use core::fmt; use std::borrow::Cow; @@ -83,15 +67,6 @@ pub enum ImportKind { Internal = 11, } -// E0015: EnumMap indexing isn't const; Zig's `comptime brk: { ... }` initializer -// is folded into match arms inside label()/error_label() below — same lookup -// table, zero runtime init (PORTING.md §Concurrency: prefer no-lock over OnceLock -// when the data is pure const). -// -// If these are changed, make sure to update -// - src/js/builtins/codegen/replacements.ts -// - packages/bun-types/bun.d.ts - impl ImportKind { #[inline] pub fn label(self) -> &'static [u8] { @@ -145,11 +120,6 @@ impl ImportKind { // `schema::api::ImportKind` which sits in a higher-tier crate. } -// ─────────────────────────────────────────────────────────────────────────── -// Ref / Symbol -// Zig: src/js_parser/ast/{base,Symbol,G}.zig + js_parser.zig (ImportItemStatus). -// ─────────────────────────────────────────────────────────────────────────── - /// Tag bits of `Ref` (Zig: anonymous `enum(u2)` field). #[repr(u8)] #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, strum::IntoStaticStr)] @@ -161,26 +131,6 @@ pub enum RefTag { Symbol = 3, } -/// Packed-u64 symbol reference: `{inner_index: u28, user: u3, tag: u2, source_index: u31}`. -/// -/// Layout matches `src/js_parser/ast/base.zig:Ref` LSB-first packing for the -/// `tag`/`source_index` fields so `as_u64()` hashes identically to the Zig -/// original for all normally-constructed refs (user bits = 0). The Rust port -/// steals 3 bits from `inner_index` (Zig u31 → u28, max 268M symbols/file — -/// three.js peaks at ~50K) so that `E::Identifier` / `E::ImportIdentifier` / -/// `E::CommonJSExportIdentifier` can pack their boolean side-flags inline, -/// shrinking `expr::Data` from 24→16 bytes and `Expr` from 32→24. This is the -/// structural noalias-shrink advantage Rust has over the Zig layout: the -/// rarely-set flags (`with`-stmt guard, known-pure-global hints) ride in -/// otherwise-dead bits instead of forcing 8 bytes of struct padding on every -/// identifier node. -/// -/// User bits are *not* part of the ref's identity: `eq`/`hash`/`eql`/`as_u64` -/// all mask them off, so `id.ref_` (which may carry flags) compares/hashes -/// identically to the symbol-table key it indexes. `pack()` always writes 0 -/// into the user-bit lane, so for every `Ref` constructed via `new`/`init` -/// the masking is a no-op and hashing is bit-identical to the pre-shrink -/// layout — preserving output sha-identity. #[repr(transparent)] #[derive(Clone, Copy)] pub struct Ref(u64); @@ -194,10 +144,6 @@ impl Ref { /// `debug_assert!` in `pack()` catches any source large enough to overflow /// (would require >268M symbols or a >268MB source-contents-slice offset). const INNER_BITS: u64 = (1u64 << 28) - 1; - /// Bits 28..31 — opaque per-node flags (E::Identifier side-effect hints, - /// E::ImportIdentifier `was_originally_identifier`, E::CommonJSExportIdentifier - /// `base`). Never set by `pack()`; only via `set_user_bit`. Masked out of - /// identity (`eq`/`hash`/`as_u64`). const USER_BITS_MASK: u64 = 0b111 << 28; const SRC_SHIFT: u32 = 33; @@ -256,10 +202,6 @@ impl Ref { #[inline] pub const fn is_empty(self) -> bool { - // Mask user bits so a flagged `Ref::NONE` (e.g. the define-template - // `E::Identifier::init(Ref::NONE).with_can_be_removed_if_unused(true)`) - // still reports null — keeps `is_empty`/`is_null` consistent with - // `eq`/`hash`/`eql`/`as_u64`, which all ignore the user-bit lane. (self.0 & !Self::USER_BITS_MASK) == 0 } #[inline] @@ -310,13 +252,6 @@ impl Ref { bun_wyhash::hash(&self.as_u64().to_ne_bytes()) } - // ── User bits (E::Identifier-family side flags) ────────────────────── - // Three spare bits at 28..31, freed by narrowing `inner_index` to u28. - // These ride along on the inline `pub ref_: Ref` field of identifier - // expression nodes so the node fits in 8 bytes (the niche-free size of - // every other inline `expr::Data` payload). They are masked out of - // identity (eq/hash/as_u64/inner_index) so `id.ref_` remains a valid - // symbol-map key regardless of flag state. #[inline] pub const fn user_bit(self, n: u32) -> bool { debug_assert!(n < 3); @@ -335,20 +270,10 @@ impl Ref { self.0 = (self.0 & !bit) | ((v as u64) << (28 + n)); self } - /// Identity bits only (user/flag lane zeroed). Use when handing a - /// flag-carrying `E::Identifier.ref_` to a context that stores its own - /// flags in the same lane (e.g. `E::ImportIdentifier::new`), so stale - /// `can_be_removed_if_unused`/`call_can_be_unwrapped_if_unused` bits don't - /// leak across node kinds. #[inline] pub const fn without_user_bits(self) -> Ref { Ref(self.0 & !Self::USER_BITS_MASK) } - /// Replace the identity bits with those of `self` while keeping `src`'s - /// user-bit lane. Used by `handle_identifier`'s `id_clone.ref_ = result.ref` - /// port — in Zig the flags are separate struct fields and survive the ref - /// assignment; here they ride in `ref_` and would be silently zeroed by a - /// whole-word write. #[inline] pub const fn with_user_bits_from(self, src: Ref) -> Ref { Ref((self.0 & !Self::USER_BITS_MASK) | (src.0 & Self::USER_BITS_MASK)) @@ -375,11 +300,6 @@ impl Default for Ref { } } -// Identity excludes the user-bit lane (bits 28..31). For every Ref produced by -// `pack()` those bits are 0, so this is bit-identical to `#[derive(...)]` on -// the raw u64 — the mask only matters for `E::Identifier.ref_` & friends where -// flag bits may be set, and there it ensures `HashMap` lookups via -// `id.ref_` resolve to the same bucket as the flag-free symbol-table key. impl PartialEq for Ref { #[inline] fn eq(&self, other: &Self) -> bool { @@ -412,12 +332,6 @@ impl fmt::Debug for Ref { } } -// TODO(port): bun_paths must define `PathContentsPair` (TYPE_ONLY from bun_resolver::fs). -// Local mirror so init_file / init_recycled_file resolve until paths' move-in lands. -// `pub` so `bun_bundler::Transpiler::parse_maybe` can construct it for -// `Source::init_recycled_file` (transpiler.zig:852). -/// A [`Source`]'s path paired with its raw bytes (used by virtual-module -/// injection: `BundleV2`'s `additional_files`, `Bun.build` inputs). #[derive(Clone, Copy)] pub struct PathContentsPair { pub path: bun_paths::fs::Path<'static>, @@ -430,12 +344,6 @@ type Str = &'static [u8]; // TODO(port): lifetime — see module-level note. `Str` is a stand-in for the Zig // `[]const u8` struct-field pattern; TODO(port): replace with the real type. -// ─────────────────────────────────────────────────────────────────────────── -// api — hand-ported slice of `bun.schema.api` (src/options_types/schema.zig -// :2295–2509) consumed by `Kind/Location/Data/Msg/Log::to_api`. The full -// peechy → .rs codegen (`bun_api`) will supersede this; field shapes are kept -// faithful so the generated diff stays reviewable. Lives here (not `bun_api`) -// ─────────────────────────────────────────────────────────────────────────── pub mod api { /// schema.zig:2295 `MessageLevel` (u32 enum, 1-based; `_none` = 0). #[repr(u32)] @@ -493,10 +401,6 @@ pub mod api { } } -/// `[]const u8` parameter shim — accepts `&str` / `&[u8]` (any lifetime) -/// and erases to the crate-wide `Str` (`&'static [u8]`) lie so callers in either -/// string flavour compile against the same Zig-shaped signatures. -/// TODO(port): lifetime — remove with `Str` once `'source` is threaded through. pub trait IntoStr { fn into_str(self) -> Str; } @@ -706,18 +610,6 @@ impl Loc { // ─────────────────────────────────────────────────────────────────────────── pub struct Location { - // Field ordering optimized to reduce padding: - // - 16-byte fields first: string (ptr+len), ?string (ptr+len+null flag) - // - 8-byte fields next: usize - // - 4-byte fields last: i32 - // This eliminates padding between differently-sized fields. - // - // PORT NOTE: `file` / `line_text` are `Cow` (not `Str`) because - // `Location::clone()` must deep-dupe them (Zig: `allocator.dupe(u8, ..)`, - // logger.zig:113) so a `BuildMessage`/`ResolveMessage` that outlives the - // `Source.contents` it borrowed from doesn't read poisoned memory. The - // borrowed arm covers the common case where the slice points into - // arena-owned source text. pub file: Cow<'static, [u8]>, pub namespace: Str, /// Text on the line, avoiding the need to refetch the source code @@ -738,14 +630,6 @@ pub struct Location { pub column: i32, } -// PORT NOTE: NOT `#[derive(Clone)]`. `file` / `line_text` are -// `Cow<'static, [u8]>` whose `Borrowed` arm may carry a lifetime-erased view -// into `Source.contents` (see `init_or_null`, `css_parser.rs`, `error.rs`, -// `JSBundler.rs`). The derived `Cow::clone` would re-borrow that pointer, so a -// `BuildMessage` cloned via `Option::clone()` / `Vec::clone()` -// could outlive the source buffer and read poisoned memory. Mirror the Zig -// `Location.clone` (`allocator.dupe`, logger.zig:113) for the trait impl too — -// every `Clone` of a `Location` deep-dupes its borrowed bytes. impl Clone for Location { fn clone(&self) -> Self { Location { @@ -794,21 +678,10 @@ impl Location { } pub fn clone(&self) -> Location { - // Zig (logger.zig:113): `allocator.dupe(u8, this.file)` / - // `allocator.dupe(u8, this.line_text.?)` — the duped bytes outlive the - // original `Source.contents`. The trait `Clone` impl above does the - // deep-dupe; this inherent shim forwards to it. ::clone(self) } pub fn clone_with_builder(&self, _string_builder: &mut StringBuilder) -> Location { - // PORT NOTE: Zig's `string_builder.append` copies into a buffer owned - // by the destination `Log`'s allocator (StringBuilder.zig). The local - // `StringBuilder` stub above is a no-op that returns its input, so a - // `Cow::Borrowed(append(s))` would alias `self`'s storage and dangle - // after `self.msgs.clear()` in `append_to_with_recycled`. Deep-copy - // here instead — same end-state as the real builder, just without the - // single-buffer packing. Location { file: Cow::Owned(self.file.to_vec()), namespace: self.namespace, @@ -906,14 +779,6 @@ impl Location { } else { 1 }, - // PORT NOTE: Zig borrows `source.contents` here and relies on the - // arena outliving the `Log` (transpiler.zig:853 — `entry.contents` - // is arena-allocated and never explicitly freed on `return null`). - // Rust's `source_backing` in `Transpiler::parse_*` is RAII and - // drops on the parse-error path *before* `process_fetch_log` - // clones the `Msg` into a `BuildMessage`, so own the bytes here - // instead. `full_line` is bounded (≤ ~120 bytes) and only - // materialized on diagnostic paths. line_text: Some(Cow::Owned(bun_core::trim_left(full_line, b"\n\r").to_vec())), offset: usize::try_from(r.loc.start.max(0)).expect("int cast"), }); @@ -951,11 +816,6 @@ impl Data { cost } - // Zig `deinit` frees `text` and calls `location.deinit()` (no-op). - // `text` is `Cow<'static, [u8]>`: `Owned` frees on `Drop` (matches Zig - // `allocator.free(d.text)`), `Borrowed` is a `&'static` literal — nothing to - // free. No explicit `Drop` body needed. - pub fn clone_line_text(&self, should: bool) -> Data { if !should || self.location.is_none() || self.location.as_ref().unwrap().line_text.is_none() { @@ -982,10 +842,6 @@ impl Data { pub fn clone(&self) -> Data { Data { text: if !self.text.is_empty() { - // Zig (logger.zig:231): `try allocator.dupe(u8, this.text)`. - // `Cow::clone` only deep-copies the `Owned` arm; force the dupe - // so a `Borrowed` `text` (rare today, but the type permits it) - // can't alias recycled storage in the cloned `Msg`. Cow::Owned(self.text.to_vec()) } else { Cow::Borrowed(b"") @@ -997,13 +853,6 @@ impl Data { pub fn clone_with_builder(&self, builder: &mut StringBuilder) -> Data { Data { text: if !self.text.is_empty() { - // Zig: `builder.append(this.text)` copies into the destination - // `Log`'s arena (StringBuilder.zig). The local `StringBuilder` - // is a no-op stub (returns its input), so a bare `Cow::clone` - // would leave a `Borrowed` arm aliasing `self`'s storage and - // dangle after `self.msgs.clear()` in - // `append_to_with_recycled`. Deep-copy — same end-state as the - // real builder, just without the single-buffer packing. Cow::Owned(self.text.to_vec()) } else { Cow::Borrowed(b"") @@ -1039,10 +888,6 @@ impl Data { return Ok(()); } - // Local wrapper around `bun_core::pretty_fmt!` so the const-generic - // `ENABLE_ANSI_COLORS` selects the right comptime template at each call - // site (the macro pattern-matches a literal `true`/`false` token). - // PERF(port): was comptime bool dispatch — profile. macro_rules! pretty_write { ($fmt:literal $(, $arg:expr)* $(,)?) => { if ENABLE_ANSI_COLORS { @@ -1152,11 +997,6 @@ impl Data { } if cfg!(debug_assertions) { - // TODO(port): the Zig gates this on - // `std.mem.indexOf(u8, @typeName(@TypeOf(to)), "fs.file") != null` — - // i.e. comptime reflection on the writer's type name to detect - // a real file writer (vs Bun.inspect). No Rust equivalent; - // TODO(port): plumb an explicit flag. if false && Output::ENABLE_ANSI_COLORS_STDERR .load(core::sync::atomic::Ordering::Relaxed) @@ -1309,10 +1149,6 @@ impl Msg { resolve: if let Metadata::Resolve(r) = &self.metadata { Some(r.specifier.slice(&self.data.text).to_vec()) } else { - // Zig (logger.zig:457): `else ""` — coerces to a NON-NULL - // `?[]const u8`, so peechy `MessageMeta.encode` still emits - // field-ID 1 with an empty string. `None` would skip the - // field entirely on the wire. Some(Vec::new()) }, build: Some(matches!(self.metadata, Metadata::Build)), @@ -1441,11 +1277,6 @@ impl Default for Range { } } -/// Was `bun_js_parser::lexer::rangeOfIdentifier`. -/// Moved into logger to break logger→js_parser. Mirrors lexer.zig:3113-3148. -/// TODO(port): full Unicode `isIdentifierStart/Continue` tables — currently -/// ASCII + `#`/`\` only; non-ASCII identifiers get a Range with len up to the -/// first non-ASCII byte (only affects error-highlight width, not correctness). pub fn range_of_identifier(contents: &[u8], loc: Loc) -> Range { if loc.start < 0 || (loc.start as usize) >= contents.len() { return Range::NONE; @@ -1518,23 +1349,8 @@ pub struct Log { pub clone_line_text: bool, - /// Owned backing storage for `Location.{file,line_text}` (and similar) - /// that came from transient buffers (e.g. native-plugin C strings). Zig - /// `log.msgs.allocator.dupe(u8, …)` allocates from the Log's allocator and - /// stores a raw slice in `Location` (logger.zig `Location.deinit` is a - /// no-op, so the bytes live as long as the Log). Rust models that as a - /// side-vector of `Box<[u8]>` owned by the `Log`; [`Log::dupe`] returns a - /// lifetime-erased borrow into the just-pushed box. The borrow is valid - /// for the life of `self` because `Box<[u8]>` is heap-stable across `Vec` - /// growth. See PORTING.md §Allocators (arena pattern). pub owned_strings: Vec>, - /// Incremental line/column scanner for the messages this log creates, so - /// a flood of diagnostics against one source doesn't rescan it from byte - /// 0 for every message; see [`LineColumnTracker`]. Boxed and allocated - /// lazily by the first located diagnostic: most logs never report - /// anything, and `Log` is embedded by value in types that are sensitive - /// to its size (e.g. `TaskError` in the isolated installer). pub line_column_tracker: Option>, } @@ -1557,12 +1373,6 @@ impl Default for Log { } impl Log { - /// Port of Zig's `log.msgs.allocator.dupe(u8, s)` pattern: copy `s` into - /// storage owned by this `Log` and return a `&'static [u8]` view. The - /// returned slice is valid for as long as `self` lives (the box is never - /// moved out of `owned_strings`); `'static` is a lifetime erasure matching - /// the `Str` alias used by `Location`/`Msg`. NOT a leak — the bytes free - /// when the `Log` drops. pub fn dupe(&mut self, s: &[u8]) -> &'static [u8] { if s.is_empty() { return b""; @@ -1621,10 +1431,6 @@ impl Level { // → deleted; lives in `bun_logger_jsc`. } -// Zig: `pub var default_log_level = Level.warn;` -// PORTING.md §Global mutable state: written by CLI startup, read by every -// `Log::init()` (including from bundler worker threads). `AtomicCell` -// — Acquire/Release, no `unsafe` at call sites. pub static DEFAULT_LOG_LEVEL: bun_core::AtomicCell = bun_core::AtomicCell::new(Level::Warn); impl Log { @@ -1883,11 +1689,6 @@ impl Log { }) } - /// Shared, non-generic tail for the `add*Fmt` family. The public wrappers - /// are `inline` and only do the per-call-site `allocPrint(fmt, args)`; the - /// rest (counter bump, rangeData, cloneLineText, addMsg) lives here so it - /// isn't re-stamped for every distinct format string. ~165 callers of - /// `addErrorFmt` alone used to duplicate this body. #[cold] #[inline(never)] fn add_formatted_msg( @@ -1929,10 +1730,6 @@ impl Log { err: bun_core::Error, ) { let text = alloc_print(args); - // TODO: fix this. this is stupid, it should be returned in allocPrint. - // PORT NOTE: Zig reads `args.@"0"` (first tuple element) for the - // specifier; with `fmt::Arguments` that's opaque, so callers must pass - // `specifier_arg` explicitly. let specifier = BabyString::r#in(&text, specifier_arg); if IS_ERR { self.errors += 1; @@ -2485,11 +2282,6 @@ impl Log { ) -> fmt::Result { let mut needs_newline = false; if self.warnings > 0 && self.errors > 0 { - // Print warnings at the top - // errors at the bottom - // This is so if you're reading from a terminal - // and there are a bunch of warnings - // You can more easily see where the errors are for msg in &self.msgs { if msg.kind != Kind::Err { if msg.kind.should_print(self.level) { @@ -2540,20 +2332,8 @@ pub struct AddErrorOptions<'a> { pub redact_sensitive_information: bool, } -/// Downstream-compat alias: some callers (`bunfig.rs`, `PnpmMatcher.rs`) spell -/// the option-struct as `bun_ast::ErrorOpts { .. }` (Zig: `Log.addError*` opts -/// param). Same layout as `AddErrorOptions`; the canonical name is kept while -/// the Zig side still calls it `addErrorOpts`. pub type ErrorOpts<'a> = AddErrorOptions<'a>; -/// Call-site helper that mirrors Zig `allocPrint`: rewrites `..` markup -/// in the *literal* format string via `bun_core::pretty_fmt!` (compile-time), -/// then formats. Expands to a `fmt::Arguments` so it drops in wherever a -/// pre-built `fmt::Arguments` was previously passed to `alloc_print`. -/// -/// Callers that build messages with markup must use this (or `alloc_print!`) so -/// the tags are converted/stripped; passing a raw `format_args!` through the -/// function form below leaves the markup verbatim. #[macro_export] macro_rules! pretty_format_args { ($fmt:literal $(, $arg:expr)* $(,)?) => {{ @@ -2575,13 +2355,6 @@ macro_rules! alloc_print { }; } -/// `add_error_pretty!(log, source, loc, "...", args..)` — call-site form -/// of Zig `addErrorFmt`: rewrites `` markup in the *literal* format string -/// at compile time (via `bun_core::pretty_fmt!`) before interpolation, then -/// calls `Log::add_error_fmt`. Use this instead of -/// `add_error_fmt(.., format_args!("..."))` so markup is converted/stripped -/// rather than stored literally in the message text. Only one branch executes, -/// so each `$arg` evaluates exactly once. #[macro_export] macro_rules! add_error_pretty { ($log:expr, $src:expr, $loc:expr, $fmt:literal $(, $arg:expr)* $(,)?) => { @@ -2627,15 +2400,6 @@ macro_rules! add_warning_pretty { #[inline] pub fn alloc_print(args: fmt::Arguments<'_>) -> Cow<'static, [u8]> { - // Zig `allocPrint` runs `Output.prettyFmt(fmt, enable_ansi_colors)` at - // comptime over the *format-string literal only*, then interpolates args - // afterward — interpolated values are never inspected for `<..>` markup. - // With `fmt::Arguments` the literal is opaque, so callers that need markup - // conversion must go through `pretty_format_args!` / `alloc_print!` above - // (which do the rewrite at the macro call site). The function form here - // renders `args` verbatim: do NOT run a runtime markup pass over the - // rendered bytes, or user-supplied argument values containing `<` - // (``, `Array`, JSX/HTML snippets) get mangled. use std::io::Write; let mut v = Vec::new(); let _ = write!(&mut v, "{}", args); @@ -2660,21 +2424,9 @@ pub fn usize2loc(loc: usize) -> Loc { pub struct Source { pub path: bun_paths::fs::Path<'static>, - /// PORT NOTE: `Cow` so `source_from_file` / `File::to_source_at` can hand - /// back a heap buffer without leaking (PORTING.md §Forbidden). Borrowed - /// arm covers the Zig `[]const u8`-field default (parser/transpiler feed - /// arena slices via `IntoStr`). Prefer the `.contents()` accessor at - /// call-sites — it derefs to `&[u8]` regardless of arm. pub contents: Cow<'static, [u8]>, pub contents_is_recycled: bool, - /// Lazily-generated human-readable identifier name that is non-unique - /// Avoid accessing this directly most of the time - /// - /// PORT NOTE: `Cow` because the cached value is produced by - /// `MutableString::ensure_valid_identifier` (owned `Box<[u8]>`); the Zig - /// freed it in `deinit`, so per PORTING.md §Forbidden this cannot be - /// `&'static [u8]` + leak. pub identifier_name: Cow<'static, [u8]>, pub index: Index, @@ -2723,10 +2475,6 @@ impl Default for ErrorPositionState { } impl ErrorPositionState { - /// Consume the codepoints of `contents[from..to]`, updating line/column - /// exactly the way `Source::init_error_position` always has (`\r` resets - /// the column to 0, `\r\n` counts as a single line break, U+2028/U+2029 - /// are line breaks). Returns `true` if a line break was crossed. fn advance(&mut self, contents: &[u8], from: usize, to: usize) -> bool { use bun_core::immutable::{CodepointIterator, Cursor}; let iter_ = CodepointIterator::init(&contents[from..to]); @@ -2807,15 +2555,6 @@ fn clamp_error_offset(contents: &[u8], offset_loc: Loc) -> usize { (usize::try_from(offset_loc.start).expect("int cast")).min(contents.len().max(1) - 1) } -/// Incremental line/column scanner for diagnostics (the esbuild -/// `LineColumnTracker` approach). [`Source::init_error_position`] walks the -/// source from byte 0 on every call, so a parse that reports many diagnostics -/// against one file pays O(diagnostics × file size) just computing their -/// positions — a few hundred KB of malformed JSONC that errors on nearly -/// every token used to take minutes. The [`Log`] owns one of these: -/// diagnostics for the same source at non-decreasing offsets resume the -/// previous scan, so N diagnostics cost O(file size + N) in total. Results -/// are identical to `Source::init_error_position`. #[derive(Default)] pub struct LineColumnTracker { /// Identity of the tracked source (`contents` and `path.text` pointers + @@ -3142,11 +2881,6 @@ pub fn range_data(source: Option<&Source>, r: Range, text: impl IntoText) -> Dat } } -// ─────────────────────────────────────────────────────────────────────────── -// File → Source helpers — `bun_sys` (T1) cannot name `Source` (this crate), -// so the body of `src/sys/File.zig:toSourceAt/toSource` lives here as free fns. -// ─────────────────────────────────────────────────────────────────────────── - #[derive(Default, Clone, Copy)] pub struct ToSourceOptions { pub convert_bom: bool, @@ -3156,18 +2890,10 @@ pub struct ToSourceOptions { /// option-struct as `bun_ast::ToSourceOpts { convert_bom: true }`. pub type ToSourceOpts = ToSourceOptions; -/// Read `path` (rooted at cwd) into memory and wrap it in a `Source`. -/// -/// MOVE_DOWN from `bun_sys::File::to_source` (T1 cannot name T2). Zig source: -/// `src/sys/File.zig:toSource`. pub fn source_from_file(path: &bun_core::ZStr, opts: ToSourceOptions) -> bun_sys::Maybe { source_from_file_at(bun_sys::Fd::cwd(), path, opts) } -/// Read `path` (relative to `dir_fd`) into memory and wrap it in a `Source`. -/// -/// MOVE_DOWN from `bun_sys::File::to_source_at`. Zig source: -/// `src/sys/File.zig:toSourceAt`. pub(crate) fn source_from_file_at( dir_fd: bun_sys::Fd, path: &bun_core::ZStr, @@ -3190,12 +2916,6 @@ pub fn to_source(path: &bun_core::ZStr, opts: ToSourceOptions) -> bun_sys::Resul source_from_file(path, opts) } -// ─────────────────────────────────────────────────────────────────────────── -// AST type modules — the full Expr/Stmt/Binding/Symbol/Scope/Op tree. -// One canonical definition; bun_parsers/bun_css/bun_js_parser/bun_js_printer -// all consume these. -// ─────────────────────────────────────────────────────────────────────────── - pub mod ast_memory_allocator; pub mod b; pub mod base; @@ -3324,11 +3044,6 @@ pub mod flags { pub const FUNCTION_NONE: FunctionSet = EnumSet::empty(); } -/// Detected indentation of a [`Source`] (tab vs N-space). The JSON/TOML lexers -/// record this so a `package.json` round-trip preserves the user's formatting; -/// `bun_js_printer::Options.indent` consumes it. Default: 2 spaces. -/// -/// Zig: `src/js_printer/js_printer.zig:434` `Options.Indentation`. #[derive(Clone, Copy)] pub struct Indentation { pub scalar: usize, @@ -3354,11 +3069,6 @@ pub enum IndentationCharacter { // ported from: src/logger/logger.zig -// ─────────────────────────────────────────────────────────────────────────── -// Store helpers — debug guards + thread-local side-arena lifecycle for the -// AST `NewStore` slabs. -// ─────────────────────────────────────────────────────────────────────────── - /// `bun.DebugOnlyDisabler(T)` — debug-build re-entrancy guard around Store /// access. No-op in release; in debug, asserts `!disabled`. pub struct DebugOnlyDisabler(core::marker::PhantomData); @@ -3391,10 +3101,6 @@ impl Drop for DebugOnlyDisablerScope { } } -/// Per-thread side [`bun_alloc::ast_alloc::AstAllocState`] that backs -/// `AstAlloc` while the bundler's `Stmt.Data.Store` / `Expr.Data.Store` -/// block-store is active and **no** `ASTMemoryAllocator` scope is in effect. -/// See `NewStore::reset` for the leak this closes. pub mod store_ast_alloc_heap { use core::cell::Cell; use core::ptr; @@ -3463,13 +3169,6 @@ pub mod store_ast_alloc_heap { } } -// ── DATA_STORE_OVERRIDE ──────────────────────────────────────────────────── -// Thread-local override arena for `Expr`/`Stmt` boxed payloads. -// -// Zig: `Expr.Data.Store.memory_allocator` — when non-null, `Expr::init` -// allocates boxed payloads into this arena instead of the long-lived block -// store, so a scoped caller (YAML/TOML/JSONC parse) can bulk-free the whole -// tree by dropping the arena. Set/restored by `ASTMemoryAllocator::Scope`. #[thread_local] static DATA_STORE_OVERRIDE: core::cell::Cell<*const bun_alloc::Arena> = core::cell::Cell::new(core::ptr::null()); @@ -3483,14 +3182,6 @@ pub(crate) fn set_data_store_override(p: *const bun_alloc::Arena) { DATA_STORE_OVERRIDE.set(p); } -/// Copy `bytes` into the active AST arena so the slice shares the same -/// lifetime as the `StoreRef`-backed `Expr` nodes that reference it -/// (bulk-freed on Store reset). Mirrors Zig call sites that write -/// `Expr.init(E.String, .{ .data = try allocator.dupe(u8, …) }, …)`: callers -/// building an `EString` from a scratch buffer must intern the bytes here, not -/// into a function-local bump, or `EString.data` dangles when that bump drops. -/// The lifetime is erased per the `StoreStr` convention — arena ownership, not -/// a leak. pub fn data_store_dupe_str(bytes: &[u8]) -> &'static [u8] { let ov = DATA_STORE_OVERRIDE.get(); if !ov.is_null() { @@ -3506,12 +3197,6 @@ pub fn data_store_dupe_str(bytes: &[u8]) -> &'static [u8] { &*dup }; } - // No override arena: allocate in the thread-local AST heap (`AstAlloc`), - // which is reset alongside the Expr/Stmt stores. `AstAlloc` is a `'static` - // ZST, so `Vec::leak` already yields `&'static mut [u8]` — no `transmute` - // needed. Storage lives until `store_ast_alloc_heap::reset()`; callers must - // not hold the slice across that boundary (same contract as every - // `StoreRef`/`StoreStr`). let mut v: Vec = bun_alloc::AstAlloc::vec(); v.extend_from_slice(bytes); v.leak() @@ -3557,26 +3242,12 @@ impl Drop for StoreResetGuard { } } -/// Idempotently create both thread-local AST node stores (`Expr.Data.Store` -/// + `Stmt.Data.Store`). Safe to call repeatedly — `Store::create()` is a -/// no-op once the slab (or an `ASTMemoryAllocator` override) is installed, -/// so no `Once` guard is needed (and a process-global `Once` would be wrong -/// anyway: the backing `INSTANCE` is `#[thread_local]`). -/// -/// Zig: open-coded `Expr.Data.Store.create(); Stmt.Data.Store.create();` -/// at every CLI entry point (transpiler.zig, run_command.zig, …). #[inline] pub fn initialize_store() { expr::data::Store::create(); stmt::data::Store::create(); } -/// Create both AST node stores on first call, **reset** them on every -/// subsequent call. Maps to `Store::begin()` (create-or-reset) on each -/// slab, so callers that re-enter — e.g. the install pipeline parsing many -/// `package.json`s — get a fresh arena each time without re-allocating. -/// -/// Zig: install.zig `initializeStore()` (`if (initialized_store) reset else create`). #[inline] pub fn initialize_store_or_reset() { expr::data::Store::begin(); diff --git a/src/ast/loader.rs b/src/ast/loader.rs index 3759ed20baf..09c66288e2d 100644 --- a/src/ast/loader.rs +++ b/src/ast/loader.rs @@ -9,10 +9,6 @@ use bun_core::strings; use enum_map::Enum; use phf; -/// The max integer value in this enum can only be appended to. -/// It has dependencies in several places: -/// - bun-native-bundler-plugin-api/bundler_plugin.h -/// - src/jsc/bindings/headers-handwritten.h #[repr(u8)] #[derive(Copy, Clone, Eq, PartialEq, Debug, Hash, Enum, strum::IntoStaticStr)] // Zig field names are lower_snake — `@tagName` is exposed to JS (HTMLImportManifest @@ -42,11 +38,6 @@ pub enum Loader { Md = 20, } -// Crosses FFI as `uint8_t default_loader` / `uint8_t loader` in -// `OnBeforeParseArguments` / `OnBeforeParseResult` (`bundler_plugin.h`); lock -// the discriminant width and the values native plugins observe. NB: the C -// header's `BUN_LOADER_TOML = 7` etc. predate `Jsonc`'s insertion at 7 and are -// known-stale — Zig `options.zig` is the source of truth, which Rust matches. bun_core::assert_ffi_discr!( Loader, u8; Jsx = 0, Js = 1, Ts = 2, Tsx = 3, Css = 4, File = 5, Json = 6, @@ -246,10 +237,6 @@ impl Loader { matches!(self, Loader::Jsx | Loader::Js | Loader::Ts | Loader::Tsx) } - // PORT NOTE: spelling-aliases for the canonical `is_typescript` / - // `is_javascript_like*` (acronym-collapsing rule). Hoisted from - // `bun_bundler::options::LoaderExt` so cross-crate callers (bun_jsc, - // bun_runtime) resolve them as inherent methods without a trait import. #[inline] pub fn is_type_script(self) -> bool { self.is_typescript() @@ -312,10 +299,6 @@ pub enum SideEffects { /// This file was loaded using a data-oriented loader (e.g. "text") that is /// known to not have side effects. NoSideEffectsPureData, - // /// Same as above but it came from a plugin. We don't want to warn about - // /// unused imports to these files since running the plugin is a side effect. - // /// Removing the import would not call the plugin which is observable. - // NoSideEffectsPureDataFromPlugin, } // ported from: src/options_types/BundleEnums.zig (Loader, SideEffects) diff --git a/src/ast/new_store.rs b/src/ast/new_store.rs index a28ec01048a..9633ebc0bfe 100644 --- a/src/ast/new_store.rs +++ b/src/ast/new_store.rs @@ -10,16 +10,6 @@ // Scope name distinct from the macro-generated `struct Store`. ::bun_core::declare_scope!(STORE_LOG, hidden); -/// Zig: `pub fn NewStore(comptime types: []const type, comptime count: usize) type` -/// -/// Rust cannot take a slice of types as a generic parameter, and the body -/// derives array sizes and alignment from that list (which would require -/// `generic_const_exprs`). Per PORTING.md this falls under the -/// `macro_rules!` type-generator exception: heterogeneous type-list -/// iteration that determines struct layout. -/// -/// Usage: `new_store!(ExprStore, [EArray, EBinary, /* ... */], 256);` -/// emits `pub mod ExprStore { pub struct Store { ... } /* Block, ... */ }`. #[macro_export] macro_rules! new_store { ($mod_name:ident, [$($T:ty),+ $(,)?], $count:expr) => { @@ -60,33 +50,12 @@ macro_rules! new_store { // (declared once at crate level: `bun_output::declare_scope!(Store, hidden);`) pub struct Store { - /// Lazily-allocated head of the block chain — `None` until the - /// first [`Store::allocate`]. Owns the entire `Box` - /// `next`-linked list; `Store`'s `Drop` walks it iteratively. - /// - /// PERF(port): Zig co-allocated `Store` + the first `Block` in a - /// single `PreAlloc` so `create()` always paid one `~BLOCK_SIZE` - /// malloc. Splitting them lets a store that is `create()`d but - /// never written to (e.g. the `Stmt` store during - /// `Transpiler::configure_defines`, which only emits `E::String` - /// expression nodes for `--define` / `NODE_ENV`) cost nothing - /// beyond this small header. head: Option>, - /// Bump-pointer target for the active block. Null iff `head` is - /// `None` (no allocation has happened on this thread yet); - /// otherwise points into the `head` chain and stays valid until - /// `destroy()`. current: *mut Block, #[cfg(debug_assertions)] debug_lock: ::core::cell::Cell, } - /// Zig: `pub const Block = struct { ... }` - // PORT NOTE: `buffer` needs `align(LARGEST_ALIGN)` but `#[repr(align(N))]` - // requires a literal. Over-approximate with align(16) — every AST payload - // type is `<= 16` aligned (asserted below). Switch to a - // `#[repr(C)] union AlignUnion { $($T),+ }` element type if a >16-aligned - // payload is ever introduced. const _: () = assert!(LARGEST_ALIGN <= 16, "NewStore payload type with align>16; bump Block repr(align)"); /// Zig: `pub const size = largest_size * count * 2;` pub(crate) const BLOCK_SIZE: usize = LARGEST_SIZE * $count * 2; @@ -161,10 +130,6 @@ macro_rules! new_store { // Zig: `pub const Size = std.math.IntFittingRange(0, size + largest_size);` type BlockSize = u32; - /// `Store` owns its `Box` chain (`head` → `next` → …). The - /// derived drop glue for `Box` is recursive (`Block.next: - /// Option>`); a long parse can build a deep chain, so - /// dismantle it iteratively here to keep `Drop` O(1)-stack. impl Drop for Store { fn drop(&mut self) { let mut it = self.head.take(); @@ -190,11 +155,6 @@ macro_rules! new_store { impl Store { pub fn init() -> *mut Store { /* scoped_log elided — debug_logs feature only */ - // PERF(port): the first `Block`'s ~`BLOCK_SIZE` heap buffer - // is *not* allocated here — only the small `Store` header. - // `allocate()` lazily mallocs the first `Block` on the first - // `append()` (see the `head` field doc). Box aborts on OOM - // (matches Zig `bun.handleOom`). bun_core::heap::into_raw(Box::new(Store { head: None, current: ::core::ptr::null_mut(), @@ -340,28 +300,6 @@ macro_rules! new_store { }; } -// ─────────────────────────────────────────────────────────────────────────── -// thread_local_ast_store! — the per-thread *front-end* wrapper around a -// `new_store!`-generated slab. -// -// `Expr` and `Stmt` each need three `#[thread_local]` slots (instance ptr, -// optional `ASTMemoryAllocator` override, `disable_reset` flag) plus the -// twelve identical accessor/lifecycle fns. The two hand-written copies in -// expr.rs / stmt.rs were byte-for-byte twins modulo the backing type and the -// "Expr"/"Stmt" panic-string label — and so are the Zig originals -// (expr.zig:3117-3196 vs stmt.zig:300-382). This macro stamps out one -// `pub mod Store { … }` per call site so the duplication lives here once. -// -// Why a macro and not a generic struct: `#[thread_local] static` cannot be -// generic over `T`, so a `struct Front` with `static INSTANCE: Cell<*mut B>` -// is rejected; the storage must be monomorphised at the item level. -// -// Usage (inside `pub mod data { use super::*; … }`): -// crate::thread_local_ast_store!(expr_store::Store, "Expr"); -// -// Expects in scope via `use super::*;`: the `$Backing` path and a -// `type Disabler = DebugOnlyDisabler<…>;` alias for the debug re-entrancy -// guard called from `append()`. #[macro_export] macro_rules! thread_local_ast_store { ($Backing:path, $label:literal) => { @@ -371,19 +309,8 @@ macro_rules! thread_local_ast_store { use ::core::cell::Cell; type Backing = $Backing; - // `#[thread_local]` (bare `__thread` slot) — `memory_allocator()` is - // read on every node `alloc` (the hottest TLS in the parser), and - // the `thread_local!` macro's `LocalKey` wrapper showed up in - // next-lint profiles. All three are `Cell` (no destructor, - // const init); matches Zig `threadlocal var`. #[thread_local] pub(crate) static INSTANCE: Cell<*mut Backing> = Cell::new(::core::ptr::null_mut()); - /// Back-reference to the `ASTMemoryAllocator` installed by the - /// enclosing `ASTMemoryAllocatorScope` stack frame. Stored as - /// `Option` (vs. raw `*mut`) so `append()` can read it via - /// safe `Deref`; the back-reference invariant (pointee outlives every - /// copy) is upheld by `ASTMemoryAllocatorScope::{enter,exit}`, which - /// always restores the previous value before its frame returns. #[thread_local] pub(crate) static MEMORY_ALLOCATOR: Cell< Option<::bun_ptr::BackRef<$crate::ASTMemoryAllocator>>, diff --git a/src/ast/nodes.rs b/src/ast/nodes.rs index 5122c8b0c8e..579a74b1c48 100644 --- a/src/ast/nodes.rs +++ b/src/ast/nodes.rs @@ -14,14 +14,6 @@ use crate::{Binding, E, Expr, Index, Ref, Scope, Stmt, symbol}; pub use crate::flags as Flags; -// ─────────────────────────────────────────────────────────────────────────── -// StoreRef — arena-owned pointer into a node Store / bump arena. -// -// Thin `NonNull` newtype — `Copy`, `Deref`/`DerefMut`. The pointee lives -// until the owning Store/arena is `reset()`; callers must not hold a `StoreRef` -// across that boundary. Matches Zig's `*T` payloads in `Expr.Data`. -// ─────────────────────────────────────────────────────────────────────────── - #[repr(transparent)] pub struct StoreRef(NonNull); @@ -45,10 +37,6 @@ impl StoreRef { pub const fn from_non_null(p: NonNull) -> Self { StoreRef(p) } - /// Wrap a raw pointer. Panics if `p` is null. Alignment and arena-lifetime - /// are caller-tracked just like the already-safe `from_non_null` / - /// `From>` constructors — the only invariant `unsafe` was - /// guarding here was non-null, which we now check. #[inline] pub fn from_raw(p: *mut T) -> Self { StoreRef(NonNull::new(p).expect("StoreRef::from_raw: null pointer")) @@ -58,11 +46,6 @@ impl StoreRef { pub fn from_bump(r: &mut T) -> Self { StoreRef(NonNull::from(r)) } - /// Consume a `Box` whose payload must outlive every Store reset - /// (Zig `deepClone(default_allocator)` semantics). Ownership transfers to - /// the returned `StoreRef`; the allocation is process-lifetime by design - /// and is never dropped — mirrors `bun.default_allocator.create(T)` with - /// no paired `destroy`. Prefer `from_bump` for arena-backed nodes. #[inline] pub fn from_box(b: Box) -> Self { StoreRef(bun_core::heap::into_raw_nn(b)) @@ -133,10 +116,6 @@ pub type ExprNodeIndex = Expr; pub type StmtNodeIndex = Stmt; pub type BindingNodeIndex = Binding; -// ─── arena-slice helpers ──────────────────────────────────────────────────── -// Legacy alias: AST string fields now uniformly use `StoreStr` (safe `Deref` -// wrapper around an arena `[u8]`). Kept as a type alias so existing field -// declarations / call sites that spell `ArenaStr` continue to compile. pub(crate) type ArenaStr = StoreStr; #[inline] pub(crate) const fn empty_arena_str() -> ArenaStr { @@ -144,16 +123,6 @@ pub(crate) const fn empty_arena_str() -> ArenaStr { } // (former `empty_arena_slice_mut()` removed — use `StoreSlice::::EMPTY`.) -// ─── StoreStr — arena-owned string slice (StoreRef's [u8] sibling) ────────── -// -// AST string fields (`E::Dot.name`, `E::String.data`, …) borrow from the parse -// arena and are bulk-freed at `Store::reset()`. `StoreStr` mirrors -// `StoreRef` (raw `NonNull`) and `StmtNodeList` (`StoreSlice`): a -// thin lifetime-erased pointer with safe construction and `Deref` -// under the same callers-must-not-outlive-the-arena contract that `StoreRef` -// already imposes. Avoids cascading `<'arena>` through `Expr`/`Stmt`/`Data` -// (~100 types, 12 downstream crates) — that cascade is the follow-up round -// once `StoreRef` itself carries `'arena`. #[derive(Copy, Clone)] #[repr(C)] pub struct StoreStr { @@ -199,10 +168,6 @@ impl StoreStr { self.len } - /// Re-borrow as `&[u8]`. Same safety contract as `StoreRef::get`: the - /// pointee lives until arena reset, which the caller must not cross. - /// Takes `self` by value (it's `Copy`) so the returned borrow is not tied - /// to a stack temporary — mirrors `StoreRef::Deref`'s arena contract. #[inline] pub fn slice<'a>(self) -> &'a [u8] { // SAFETY: StoreStr invariant — `ptr` is non-null, points at `len` @@ -215,11 +180,6 @@ impl StoreStr { pub fn as_raw(self) -> *const [u8] { core::ptr::slice_from_raw_parts(self.ptr.as_ptr(), self.len) } - - // (former `from_raw(*const [u8])` removed — the StoreSlice migration is - // complete; `js_printer::renamer::NameStr` now constructs via the safe - // `StoreStr::new(&[u8])`, so the raw-fat-pointer back-door has no - // remaining callers.) } impl Default for StoreStr { @@ -312,15 +272,6 @@ impl core::fmt::Debug for StoreStr { } } -// ─── StoreSlice — arena-owned typed slice (StoreStr's generic sibling) ─── -// -// Generalizes `StoreStr` to `[T]` for AST list fields (`E::Arrow.args`, -// per-node `[Stmt]`/`[Expr]` views, …) that borrow from the parse arena. -// Same contract as `StoreRef`/`StoreStr`: safe `::new`, -// raw `NonNull` + `u32` length, `Deref`, valid until the -// owning arena resets. The `u32` length matches Zig's `[]T` (`u32` len under -// `-Dwasm32` and the AST's practical bounds) and keeps the field at 12 bytes -// on 64-bit instead of 16 — relevant for hot AST nodes. #[repr(C)] pub struct StoreSlice { ptr: core::ptr::NonNull, @@ -393,10 +344,6 @@ impl StoreSlice { self.len } - /// Re-borrow as `&[T]`. Same safety contract as `StoreStr::slice` / - /// `StoreRef::get`: the pointee lives until arena reset, which the caller - /// must not cross. Takes `self` by value (Copy) so the returned borrow is - /// not tied to a stack temporary. #[inline] pub fn slice<'a>(self) -> &'a [T] { // SAFETY: StoreSlice invariant — `ptr` is non-null, points at `len` @@ -405,14 +352,6 @@ impl StoreSlice { unsafe { core::slice::from_raw_parts(self.ptr.as_ptr(), self.len as usize) } } - /// Re-borrow as `&mut [T]`. Same `StoreRef` contract as [`slice`]: the - /// pointee lives until arena reset, and the single-threaded parser/visitor - /// pass holds at most one live `&mut` per node (mirrors `StoreRef`'s safe - /// `DerefMut`, which already encodes this invariant). The arena hands out - /// unique allocations and `StoreSlice` is `Copy`, so aliasing cannot be - /// *statically* checked — but neither can `StoreRef::deref_mut`'s, and the - /// two share one safety story. Callers must not overlap a `slice_mut()` - /// borrow with another `slice()`/`slice_mut()` of the same allocation. #[inline] pub fn slice_mut<'a>(self) -> &'a mut [T] { // SAFETY: StoreSlice invariant — `ptr` is non-null, points at `len` @@ -490,41 +429,8 @@ impl core::fmt::Debug for StoreSlice { // ───────────────────────────────────────────────────────────────────────────── -/// This is the index to the automatically-generated part containing code that -/// calls "__export(exports, { ... getters ... })". This is used to generate -/// getters on an exports object for ES6 export statements, and is both for -/// ES6 star imports and CommonJS-style modules. All files have one of these, -/// although it may contain no statements if there is nothing to export. pub const NAMESPACE_EXPORT_PART_INDEX: u32 = 0; -// There are three types. -// 1. Expr (expression) -// 2. Stmt (statement) -// 3. Binding -// Q: "What's the difference between an expression and a statement?" -// A: > Expression: Something which evaluates to a value. Example: 1+2/x -// > Statement: A line of code which does something. Example: GOTO 100 -// > https://stackoverflow.com/questions/19132/expression-versus-statement/19224#19224 - -// Expr, Binding, and Stmt each wrap a Data: -// Data is where the actual data where the node lives. -// There are four possible versions of this structure: -// [ ] 1. *Expr, *Stmt, *Binding -// [ ] 1a. *Expr, *Stmt, *Binding something something dynamic dispatch -// [ ] 2. *Data -// [x] 3. Data.(*) (The union value in Data is a pointer) -// I chose #3 mostly for code simplification -- sometimes, the data is modified in-place. -// But also it uses the least memory. -// Since Data is a union, the size in bytes of Data is the max of all types -// So with #1 or #2, if S.Function consumes 768 bits, that means Data must be >= 768 bits -// Which means "true" in code now takes up over 768 bits, probably more than what v8 spends -// Instead, this approach means Data is the size of a pointer. -// It's not really clear which approach is best without benchmarking it. -// The downside with this approach is potentially worse memory locality, since the data for the node is somewhere else. -// But it could also be better memory locality due to smaller in-memory size (more likely to hit the cache) -// only benchmarks will provide an answer! -// But we must have pointers somewhere in here because can't have types that contain themselves - /// Slice that stores capacity and length in the same space as a regular slice. pub type ExprNodeList = Vec; @@ -576,26 +482,10 @@ impl Default for LocRef { } pub struct ClauseItem { - /// The local alias used for the imported/exported symbol in the current module. - /// For imports: `import { foo as bar }` - "bar" is the alias - /// For exports: `export { foo as bar }` - "bar" is the alias - /// For re-exports: `export { foo as bar } from 'path'` - "bar" is the alias pub alias: ArenaStr, pub alias_loc: crate::Loc, - /// Reference to the actual symbol being imported/exported. - /// For imports: `import { foo as bar }` - ref to the symbol representing "foo" from the source module - /// For exports: `export { foo as bar }` - ref to the local symbol "foo" - /// For re-exports: `export { foo as bar } from 'path'` - ref to an intermediate symbol pub name: LocRef, - /// This is the original name of the symbol stored in "Name". It's needed for - /// "SExportClause" statements such as this: - /// - /// export {foo as bar} from 'path' - /// - /// In this case both "foo" and "bar" are aliases because it's a re-export. - /// We need to preserve both aliases in case the symbol is renamed. In this - /// example, "foo" is "OriginalName" and "bar" is "Alias". pub original_name: ArenaStr, } @@ -850,21 +740,8 @@ pub enum ExportsKind { // allowed but may return undefined. Cjs, - // All export names are known explicitly. Calling "require()" on this module - // generates an exports object (stored in "exports") with getters for the - // export names. Named imports to this module are only allowed if they are - // in the set of export names. Esm, - // Some export names are known explicitly, but others fall back to a dynamic - // run-time object. This is necessary when using the "export * from" syntax - // with either a CommonJS module or an external module (i.e. a module whose - // export names are not known at compile-time). - // - // Calling "require()" on this module generates an exports object (stored in - // "exports") with getters for the export names. All named imports to this - // module are allowed. Direct named imports reference the corresponding export - // directly. Other imports go through property accesses on "exports". EsmWithDynamicFallback, // Like "EsmWithDynamicFallback", but the module was originally a CommonJS @@ -1049,11 +926,6 @@ pub type BindingList = Vec; // PERF(port): Zig `std.array_list.Managed` — these may be arena-backed in // callers; revisit with bumpalo::collections::Vec if profiling shows churn. -/// Each file is made up of multiple parts, and each part consists of one or -/// more top-level statements. Parts are used for tree shaking and code -/// splitting analysis. Individual parts of a file can be discarded by tree -/// shaking and can be assigned to separate chunks (i.e. output files) by code -/// splitting. pub struct Part { pub stmts: StoreSlice, pub scopes: StoreSlice<*mut Scope>, // TODO(port): &'bump mut [&'bump mut Scope] @@ -1061,20 +933,11 @@ pub struct Part { /// Each is an index into the file-level import record list pub import_record_indices: PartImportRecordIndices, - /// All symbols that are declared in this part. Note that a given symbol may - /// have multiple declarations, and so may end up being declared in multiple - /// parts (e.g. multiple "var" declarations with the same name). Also note - /// that this list isn't deduplicated and may contain duplicates. pub declared_symbols: DeclaredSymbolList, /// An estimate of the number of uses of all symbols used within this part. pub symbol_uses: PartSymbolUseMap, - /// This tracks property accesses off of imported symbols. We don't know - /// during parsing if an imported symbol is going to be an inlined enum - /// value or not. This is only known during linking. So we defer adding - /// a dependency on these imported symbols until we know whether the - /// property access is an inlined enum value or not. pub import_symbol_property_uses: PartSymbolPropertyUseMap, /// The indices of the other parts in this file that are needed if this part @@ -1182,22 +1045,11 @@ pub struct NamedImport { /// Parts within this file that use this import pub local_parts_with_uses: bun_alloc::AstVec, - /// The original export name from the source module being imported. - /// Examples: - /// - `import { foo } from 'module'` → alias = "foo" - /// - `import { foo as bar } from 'module'` → alias = "foo" (original export name) - /// - `import * as ns from 'module'` → alias_is_star = true, alias = "" - /// This field is used by the bundler to match imports with their corresponding - /// exports and for error reporting when imports can't be resolved. pub alias: Option, pub alias_loc: Option, pub namespace_ref: Option, pub import_record_index: u32, - /// If true, the alias refers to the entire export namespace object of a - /// module. This is no longer represented as an alias called "*" because of - /// the upcoming "Arbitrary module namespace identifier names" feature: - /// https://github.com/tc39/ecma262/pull/2154 pub alias_is_star: bool, /// It's useful to flag exported imports because if they are in a TypeScript @@ -1260,11 +1112,6 @@ bun_core::impl_tag_error!(ToJSError); bun_core::named_error_set!(ToJSError); -/// Say you need to allocate a bunch of tiny arrays -/// You could just do separate allocations for each, but that is slow -/// With std.ArrayList, pointers invalidate on resize and that means it will crash. -/// So a better idea is to batch up your allocations into one larger allocation -/// and then just make all the arrays point to different parts of the larger allocation pub struct Batcher { pub head: StoreSlice, } @@ -1319,13 +1166,6 @@ impl Batcher { // Zig: `pub fn NewBatcher(comptime Type: type) type` → Rust generic struct above. pub type NewBatcher = Batcher; -// ═════════════════════════════════════════════════════════════════════════ -// Symbols pulled DOWN from higher-tier -// crates so lower-tier callers (css, interchange, js_parser itself) can -// resolve them here without forming a cycle. Ground truth for each port is -// the named .zig file, NOT the sibling .rs (which may already forward-ref). -// ═════════════════════════════════════════════════════════════════════════ - // ─── from bun_jsc::math (src/jsc/jsc.zig) ─────────────────────────────────── pub mod math { /// `Number.MAX_SAFE_INTEGER` (2^53 - 1) @@ -1346,8 +1186,4 @@ pub mod math { Bun__JSC__operationMathPow(x, y) } } -// ─── from bun_bundler::v2::MangledProps (src/bundler/bundle_v2.zig) ───────── -// Zig: `std.AutoArrayHashMapUnmanaged(Ref, []const u8)` -// LIFETIMES.tsv: value slices point into the parser arena → `StoreStr` -// (arena-owned, no `'bump` cascade). pub type MangledProps = ArrayHashMap; diff --git a/src/ast/op.rs b/src/ast/op.rs index d8f1cd522ff..7f8bd7d294e 100644 --- a/src/ast/op.rs +++ b/src/ast/op.rs @@ -212,10 +212,6 @@ impl Level { #[inline] const fn from_raw(n: u8) -> Level { - // Callers only pass values derived from a valid `Level` discriminant - // ±1 (`sub`/`add_f`); decode by exhaustive match so an out-of-range - // shift traps in release too (matches Zig's safety-checked - // `@enumFromInt`) instead of fabricating an invalid discriminant. match n { 0 => Level::Lowest, 1 => Level::Comma, @@ -274,10 +270,6 @@ impl Op { } } -// Zig: `pub const TableType: std.EnumArray(Op.Code, Op) = undefined;` -// This declared an `undefined` value (vestigial / used only for @TypeOf at callsites). -// Ported as a type alias since Rust statics cannot be uninitialized. -// TODO(port): verify no callsite reads TableType as a value. pub type TableType = Table; /// `.rodata` `[Op; Code::COUNT]` indexed by [`Code`] discriminant. Exposes the diff --git a/src/ast/runtime.rs b/src/ast/runtime.rs index b234450197f..b5bedb24964 100644 --- a/src/ast/runtime.rs +++ b/src/ast/runtime.rs @@ -1,13 +1,5 @@ #![allow(unexpected_cfgs)] // `bun_codegen_embed` is set via RUSTFLAGS (scripts/build/rust.ts) for release/CI builds. -// REFACTOR_BUN_AST: this module holds only the data-shaped pieces of -// `runtime.zig` that the AST crate (and `bun_js_printer::Options`) need: -// `Runtime::source_code`, `Imports`, `ReplaceableExport*`, `ServerComponentsMode`. -// The `Features` struct (carries `&mut RuntimeTranspilerCache`) and -// `Fallback` HTML rendering (needs `bun_options_types::schema`, `bun_io`, -// `bun_base64`) live in `bun_js_parser::parser::Runtime` to avoid the -// `bun_options_types → bun_ast → bun_options_types` cycle. - use bun_collections::StringArrayHashMap; use bun_wyhash::Wyhash11; @@ -44,18 +36,8 @@ impl ReplaceableExport { } } -/// Zig: `bun.StringArrayHashMapUnmanaged(ReplaceableExport)`. -/// -/// Newtype (not a bare alias) so we can hang `get_ptr` (Zig spelling for -/// `getPtr`, which borrows immutably) and expose a `.entries` accessor that -/// satisfies the `replace_exports.entries.len` shape `visitStmt` ported -/// verbatim from Zig's `ArrayHashMap.entries`. #[derive(Default)] pub struct ReplaceableExportMap { - /// Backing map. Named `entries` so `replace_exports.entries.len()` — - /// the literal Zig spelling — resolves (Zig's `ArrayHashMap.entries` - /// is a `MultiArrayList` with `.len`; here `StringArrayHashMap` derefs - /// to `ArrayHashMap` which has `.len()`). pub entries: StringArrayHashMap, } @@ -78,10 +60,6 @@ impl ReplaceableExportMap { pub fn count(&self) -> usize { self.entries.count() } - /// Zig `getPtr` returns `?*V` from a `*const Self` — i.e. immutable - /// lookup yielding a (logically-mutable) pointer. Rust splits this into - /// `get_ptr` (`&V`) and `get_ptr_mut` (`&mut V`); call sites in the - /// visitor only read through it. #[inline] pub fn get_ptr(&self, key: &[u8]) -> Option<&ReplaceableExport> { self.entries.get(key) diff --git a/src/ast/s.rs b/src/ast/s.rs index a8ce4377c39..7c850c453f6 100644 --- a/src/ast/s.rs +++ b/src/ast/s.rs @@ -184,24 +184,7 @@ pub struct Switch { pub cases: StoreSlice, // arena-owned } -/// This object represents all of these types of import statements: -/// -/// import 'path' -/// import {item1, item2} from 'path' -/// import * as ns from 'path' -/// import defaultItem, {item1, item2} from 'path' -/// import defaultItem, * as ns from 'path' -/// import defer * as ns from 'path' -/// -/// Many parts are optional and can be combined in different ways. The only -/// restriction is that you cannot have both a clause and a star namespace. pub struct Import { - /// If this is a star import: This is a Ref for the namespace symbol. The Loc - /// for the symbol is StarLoc. - /// - /// Otherwise: This is an auto-generated Ref for the namespace representing - /// the imported file. In this case StarLoc is nil. The NamespaceRef is used - /// when converting this module to a CommonJS module. pub namespace_ref: Ref, pub default_name: Option, // = None pub items: StoreSlice, // arena-owned; = &[] @@ -262,10 +245,6 @@ impl Default for Local { impl Local { pub fn can_merge_with(&self, other: &Local) -> bool { - // Don't merge "using" / "await using" declarations. Merging them is - // spec-compliant but matches esbuild's behavior of keeping them - // separate, and avoids any downstream pass assuming one decl per - // `using` statement. if self.kind.is_using() { return false; } diff --git a/src/ast/scope.rs b/src/ast/scope.rs index 74672d9f992..4ad67d36a7e 100644 --- a/src/ast/scope.rs +++ b/src/ast/scope.rs @@ -7,19 +7,8 @@ use crate::nodes::StoreRef; use crate::symbol::{self, Symbol}; use crate::ts::TSNamespaceScope; -/// Backed by `AstAlloc` so the table allocation *and* the per-key boxes land -/// in the thread-local AST `mi_heap` and are reclaimed by the same -/// `mi_heap_destroy` that frees the arena-allocated `Scope` holding the map. -/// In Zig this was `bun.StringHashMapUnmanaged(Member)` whose backing array -/// lived in the parser arena; the original Rust port placed both on the -/// global heap, and since `Scope` itself sits in an arena slot whose `Drop` -/// never runs, every member map leaked. pub(crate) type MemberHashMap = StringHashMap; -// PORT NOTE: Zig `Scope` is a value type — `Ast.module_scope` / `BundledAst.module_scope` -// hold it by value and `toAST` / `init` bitwise-copy it (`this.module_scope`). Vec no -// longer derives `Clone` (private `origin` field); callers that need a shallow copy must -// `core::mem::take` or `core::ptr::read` instead. pub struct Scope { pub id: usize, pub kind: Kind, @@ -58,14 +47,6 @@ pub struct Scope { } impl Scope { - /// All-empty `Scope` as a `const`. Used with struct-update syntax in the - /// parser's per-scope allocation hot path (`push_scope_for_parse_pass` - /// runs once per `{}` / function / class body) so the unspecified fields - /// are filled by a compile-time bit pattern instead of the runtime - /// `Default::default()` chain — i.e. no temporary `Scope` is constructed - /// and partially dropped, and `members`/`children`/`generated` come from a - /// const-folded zero header rather than three out-of-line `default()` - /// calls. `AstAlloc::vec` and `StringHashMap::new_in` are both `const fn`. pub const EMPTY: Self = Self { id: 0, kind: Kind::Block, @@ -91,10 +72,6 @@ impl Default for Scope { } impl Scope { - // Must agree with `StringHashMap`'s `BuildHasher` (`bun_wyhash::BuildHasher`, - // i.e. `BuildHasherDefault`) so the precomputed hash can be - // fed to `get_hashed` without a rehash per scope level. If the map's hasher - // ever changes, this must change with it (the debug_assert below catches it). pub fn get_member_hash(name: &[u8]) -> u64 { bun_wyhash::auto_hash::<[u8]>(name) } @@ -148,11 +125,6 @@ impl Scope { Self::can_merge_symbol_kinds::(self.kind, existing, new) } - /// Associated-fn form of [`can_merge_symbols`] taking the scope's [`Kind`] - /// by value instead of `&self`. Lets the parser hold a single-probe - /// `members.entry()` borrow across the merge decision without re-borrowing - /// the whole `Scope` (which would alias the live entry under Stacked - /// Borrows). The method body only ever read `self.kind`. pub fn can_merge_symbol_kinds( scope_kind: Kind, existing: symbol::Kind, @@ -165,12 +137,6 @@ impl Scope { } if IS_TYPESCRIPT_ENABLED { - // In TypeScript, imports are allowed to silently collide with symbols within - // the module. Presumably this is because the imports may be type-only: - // - // import {Foo} from 'bar' - // class Foo {} - // if existing == Sk::Import { return SymbolMergeResult::ReplaceWithNew; } @@ -196,10 +162,6 @@ impl Scope { } } - // "var foo; var foo;" - // "var foo; function foo() {}" - // "function foo() {} var foo;" - // "function *foo() {} function *foo() {}" but not "{ function *foo() {} function *foo() {} }" if Symbol::is_kind_hoisted_or_function(new) && Symbol::is_kind_hoisted_or_function(existing) && (scope_kind == Kind::Entry diff --git a/src/ast/server_component_boundary.rs b/src/ast/server_component_boundary.rs index 9528108ff2e..6ec02da849f 100644 --- a/src/ast/server_component_boundary.rs +++ b/src/ast/server_component_boundary.rs @@ -28,11 +28,6 @@ pub struct ServerComponentBoundary { /// server's code. For server actions, this is the client's code. pub reference_source_index: IndexInt, - /// When `bake.Framework.ServerComponents.separate_ssr_graph` is enabled this - /// points to the separated module. When the SSR graph is not separate, this is - /// equal to `reference_source_index` - // - // TODO: Is this used for server actions. pub ssr_source_index: IndexInt, } diff --git a/src/ast/stmt.rs b/src/ast/stmt.rs index bb1b4aaae0b..f67294207dc 100644 --- a/src/ast/stmt.rs +++ b/src/ast/stmt.rs @@ -101,13 +101,6 @@ const NONE: S::Empty = S::Empty {}; #[cfg(debug_assertions)] pub static ICOUNT: AtomicUsize = AtomicUsize::new(0); -/// Trait absorbing the Zig `switch (comptime StatementType)` tables in -/// `init` / `alloc` / `allocate`. Each `S::*` payload type implements this to -/// map itself onto the corresponding `Data` variant. -/// -/// The Zig used three near-identical 32-arm comptime switches; in Rust the -/// dispatch is the trait impl and the arm list is the `impl_statement_data!` -/// invocation below — diff that list against the Zig switch. pub trait StatementData: Sized { /// Wrap an already-allocated payload (Zig `Stmt.init` / `comptime_init`). fn wrap_ref(ptr: StoreRef) -> Data; @@ -152,11 +145,6 @@ impl Stmt { } } - // Zig `comptime_init` — `@unionInit(Data, tag_name, origData)`. In Rust the - // variant constructor IS the union-init; this helper collapses to identity - // and is absorbed by `StatementData::wrap_ref`. - // TODO(port): no direct equivalent; callers use the trait. - #[inline] pub fn alloc(orig_data: T, loc: crate::Loc) -> Stmt { data::Store::assert(); @@ -359,16 +347,6 @@ pub enum Data { SLazyExport(StoreRef), } -// ── Layout guards ───────────────────────────────────────────────────────── -// Zig: `if (@sizeOf(Stmt) > 24) @compileLog(...)` (Stmt.zig:295). Every payload -// variant is either a `StoreRef` (`#[repr(transparent)] NonNull`, 8 bytes, -// niche-carrying) or a ZST, so the union is one pointer word and the repr(Rust) -// discriminant packs alongside it for `Data` = 16. `Stmt` = `Data` (16, align 8) -// + `Loc` (i32) → 20 → 24 after tail padding. The `Option` assert proves -// the niche fires (33 variants < 256 + every pointer variant contributes a -// NonNull niche), so `Option` / `Option` add no discriminant word. -// Adding `#[repr(C)]`/`#[repr(u8)]` to `Data` or a nullable `*mut T` payload -// would break this — the asserts catch it. const _: () = assert!(core::mem::size_of::() == 16); const _: () = assert!( core::mem::size_of::() <= 24, @@ -430,12 +408,6 @@ impl PartialEq for Data { } impl Eq for Data {} -// Zig field-style union accessors (`data.s_function`, `data.s_local`, …). -// visitStmt and the printer port from Zig's `data.s_local.*` etc., which are -// unchecked union field reads. Rust callers `.unwrap()` (or pattern-match) — -// the `Option` is the cheapest sound encoding of Zig's UB-on-mismatch. -// Mirrors `expr::Data::e_*()`. Returns `Option>` (Copy) for -// pointer-payload variants and `Option` by value for inline ZST variants. impl Data { // ── StoreRef field-style accessors ──────────────────────────── #[inline] @@ -919,10 +891,6 @@ impl Data { } } - // ── Inline (by-value) payload accessors ───────────────────────────── - // These variants store the payload directly (no `StoreRef`); all are - // zero-sized `Copy` types. Returned by value for symmetry with - // `expr::Data::e_boolean()` etc. #[inline] pub fn s_type_script(&self) -> Option { if let Data::STypeScript(v) = *self { @@ -992,11 +960,6 @@ pub mod data { crate::thread_local_ast_store!(stmt_store::Store, "Stmt"); } -// Zig `pub fn StoredData(tag: Tag) type` — returns the payload type for a tag, -// dereferencing pointer variants. Rust has no type-returning fns. -// TODO(port): callers should use the `StatementData` trait or a per-variant -// associated type; revisit once call sites are known. - impl Stmt { pub fn cares_about_scope(&self) -> bool { match &self.data { diff --git a/src/ast/symbol.rs b/src/ast/symbol.rs index aef56f39de7..126b42601b9 100644 --- a/src/ast/symbol.rs +++ b/src/ast/symbol.rs @@ -6,71 +6,16 @@ use crate::base::Ref; use crate::g as G; pub struct Symbol { - /// This is the name that came from the parser. Printed names may be renamed - /// during minification or to avoid name collisions. Do not use the original - /// name during printing. - // Arena-owned slice (parser/AST crate). `StoreStr` is the lifetime-erased - // `[u8]` wrapper used uniformly across AST string fields; it derefs to - // `[u8]` and is valid until the owning arena resets. pub original_name: crate::StoreStr, - /// This is used for symbols that represent items in the import clause of an - /// ES6 import statement. These should always be referenced by EImportIdentifier - /// instead of an EIdentifier. When this is present, the expression should - /// be printed as a property access off the namespace instead of as a bare - /// identifier. - /// - /// For correctness, this must be stored on the symbol instead of indirectly - /// associated with the Ref for the symbol somehow. In ES6 "flat bundling" - /// mode, re-exported symbols are collapsed using MergeSymbols() and renamed - /// symbols from other files that end up at this symbol must be able to tell - /// if it has a namespace alias. pub namespace_alias: Option, - /// Used by the parser for single pass parsing. - /// - /// `Cell` because union-find (`merge`/`follow`) mutates this through - /// `&Symbol` while other shared refs to the same table are live. `Ref` is - /// `Copy`, so `Cell` is zero-cost and lets those algorithms run - /// without raw-pointer writes. pub link: Cell, - /// An estimate of the number of uses of this symbol. This is used to detect - /// whether a symbol is used or not. For example, TypeScript imports that are - /// unused must be removed because they are probably type-only imports. This - /// is an estimate and may not be completely accurate due to oversights in the - /// code. But it should always be non-zero when the symbol is used. pub use_count_estimate: u32, - /// This is for generating cross-chunk imports and exports for code splitting. - /// - /// Do not use this directly. Use `chunkIndex()` instead. - /// - /// `AtomicU32` (not plain `u32`) because [`Map::assign_chunk_index`] is - /// invoked from worker threads in - /// `compute_cross_chunk_dependencies::walk()` while other threads hold a - /// shared `&LinkerGraph` (and thus `&Symbol`). The linker invariant is - /// that all declarations of a given top-level symbol are placed in a - /// single chunk, so cross-thread writes target disjoint slots — but the - /// invariant is data-dependent, not type-checked, and a plain `u32` write - /// through a slot reachable from `&` is UB regardless. Relaxed ordering - /// is sufficient: the worker-pool join supplies the happens-before edge - /// to the post-pass reader (`compute_cross_chunk_dependencies_with_chunk_metas`). pub chunk_index: AtomicU32, - /// This is used for minification. Symbols that are declared in sibling scopes - /// can share a name. A good heuristic (from Google Closure Compiler) is to - /// assign names to symbols from sibling scopes in declaration order. That way - /// local variable names are reused in each global function like this, which - /// improves gzip compression: - /// - /// function x(a, b) { ... } - /// function y(a, b, c) { ... } - /// - /// The parser fills this in for symbols inside nested scopes. There are three - /// slot namespaces: regular symbols, label symbols, and private symbols. - /// - /// Do not use this directly. Use `nestedScopeSlot()` instead. pub nested_scope_slot: u32, pub did_keep_name: bool, @@ -86,78 +31,8 @@ pub struct Symbol { /// Renaming can also break any identifier used inside a "with" statement. pub must_not_be_renamed: bool, - /// We automatically generate import items for property accesses off of - /// namespace imports. This lets us remove the expensive namespace imports - /// while bundling in many cases, replacing them with a cheap import item - /// instead: - /// - /// import * as ns from 'path' - /// ns.foo() - /// - /// That can often be replaced by this, which avoids needing the namespace: - /// - /// import {foo} from 'path' - /// foo() - /// - /// However, if the import is actually missing then we don't want to report a - /// compile-time error like we do for real import items. This status lets us - /// avoid this. We also need to be able to replace such import items with - /// undefined, which this status is also used for. pub import_item_status: ImportItemStatus, - /// --- Not actually used yet ----------------------------------------------- - /// Sometimes we lower private symbols even if they are supported. For example, - /// consider the following TypeScript code: - /// - /// class Foo { - /// #foo = 123 - /// bar = this.#foo - /// } - /// - /// If "useDefineForClassFields: false" is set in "tsconfig.json", then "bar" - /// must use assignment semantics instead of define semantics. We can compile - /// that to this code: - /// - /// class Foo { - /// constructor() { - /// this.#foo = 123; - /// this.bar = this.#foo; - /// } - /// #foo; - /// } - /// - /// However, we can't do the same for static fields: - /// - /// class Foo { - /// static #foo = 123 - /// static bar = this.#foo - /// } - /// - /// Compiling these static fields to something like this would be invalid: - /// - /// class Foo { - /// static #foo; - /// } - /// Foo.#foo = 123; - /// Foo.bar = Foo.#foo; - /// - /// Thus "#foo" must be lowered even though it's supported. Another case is - /// when we're converting top-level class declarations to class expressions - /// to avoid the TDZ and the class shadowing symbol is referenced within the - /// class body: - /// - /// class Foo { - /// static #foo = Foo - /// } - /// - /// This cannot be converted into something like this: - /// - /// var Foo = class { - /// static #foo; - /// }; - /// Foo.#foo = Foo; - /// - /// --- Not actually used yet ----------------------------------------------- pub private_symbol_must_be_lowered: bool, pub remove_overwritten_function_declaration: bool, @@ -166,12 +41,6 @@ pub struct Symbol { pub has_been_assigned_to: bool, } -// TODO(port): Zig asserts @sizeOf(Symbol) == 88 and @alignOf(Symbol) == @alignOf([]const u8). -// Rust default repr reorders fields and Option niche may differ -// (likely needs #[repr(C)] or manual packing if the size is load-bearing). -// const _: () = assert!(core::mem::size_of::() == 88); -// const _: () = assert!(core::mem::align_of::() == core::mem::align_of::()); - const INVALID_CHUNK_INDEX: u32 = u32::MAX; pub const INVALID_NESTED_SCOPE_SLOT: u32 = u32::MAX; @@ -264,35 +133,9 @@ pub enum Kind { /// in. For example, using "window" without declaring it will be unbound. Unbound, - /// This has special merging behavior. You're allowed to re-declare these - /// symbols more than once in the same scope. These symbols are also hoisted - /// out of the scope they are declared in to the closest containing function - /// or module scope. These are the symbols with this kind: - /// - /// - Function arguments - /// - Function statements - /// - Variables declared using "var" Hoisted, HoistedFunction, - /// There's a weird special case where catch variables declared using a simple - /// identifier (i.e. not a binding pattern) block hoisted variables instead of - /// becoming an error: - /// - /// var e = 0; - /// try { throw 1 } catch (e) { - /// print(e) // 1 - /// var e = 2 - /// print(e) // 2 - /// } - /// print(e) // 0 (since the hoisting stops at the catch block boundary) - /// - /// However, other forms are still a syntax error: - /// - /// try {} catch (e) { let e } - /// try {} catch ({e}) { var e } - /// - /// This symbol is for handling this weird special case. CatchIdentifier, /// Generator and async functions are not hoisted, but still have special @@ -376,10 +219,6 @@ pub struct Use { } pub type List<'a> = bun_alloc::ArenaVec<'a, Symbol>; -/// `Map.symbols_for_source` storage. Decoupled from [`List`] (which is -/// arena-backed): the linker clones every per-source symbol table here so it -/// can mutate them independently of the parsed `BundledAst.symbols`, and those -/// clones are owned for the link lifetime — global allocator, no arena tag. pub type NestedList = Vec>; impl Symbol { @@ -396,13 +235,6 @@ impl Symbol { #[derive(Default)] pub struct Map { - // This could be represented as a "map[Ref]Symbol" but a two-level array was - // more efficient in profiles. This appears to be because it doesn't involve - // a hash. This representation also makes it trivial to quickly merge symbol - // maps from multiple files together. Each file only generates symbols in a - // single inner array, so you can join the maps together by just make a - // single outer array containing all of the inner arrays. See the comment on - // "Ref" for more detail. pub symbols_for_source: NestedList, } @@ -435,15 +267,6 @@ impl Map { bun_core::output::flush(); } - // Takes `&self` (not `&mut self`) — the only caller - // (`computeCrossChunkDependencies::walk`) runs concurrently across worker - // threads. `Symbol.chunk_index` is `AtomicU32`, so the per-slot write is a - // sound interior mutation through `&Symbol`; no raw-pointer or `&mut Map` - // escape is needed. Relaxed ordering: see the field doc — the worker-pool - // join is the only required happens-before edge, and the linker invariant - // places all declarations of a given symbol in a single chunk (same - // worker), so cross-thread writes target disjoint slots. A - // `debug_assert!` documents that invariant. pub fn assign_chunk_index(&self, decls_: &crate::DeclaredSymbolList, chunk_index: u32) { use crate::DeclaredSymbol; struct Iterator<'a> { @@ -454,11 +277,6 @@ impl Map { impl Iterator<'_> { pub(crate) fn next(&mut self, ref_: Ref) { let symbol = self.map.get_const(ref_).unwrap(); - // Thread-confinement invariant: a top-level symbol's - // declarations are all assigned to one chunk, so any prior - // value is either INVALID or this same chunk (overwrite from a - // sibling `var` decl in the same chunk — see esbuild comment in - // `walk`). If this fires, two chunks raced on one symbol. debug_assert!( { let prev = symbol.chunk_index.load(Ordering::Relaxed); @@ -486,11 +304,6 @@ impl Map { return new; } - // Union-find with path compression. `link` is `Cell`, so all link - // reads/writes go through safe shared access (`get_const`). Backing - // storage is never reallocated during merge, so re-looking-up after the - // recursive call is cheap and the borrow ends before the `&mut self` - // recursion. let old_link = self.get_const(old).unwrap().link.get(); if old_link.is_valid() { let merged = self.merge(old_link, new); @@ -520,17 +333,6 @@ impl Map { new } - // Returns a raw *mut Symbol because callers (merge/follow/assign_chunk_index/ - // get_with_link) hold aliasing pointers into the NestedList and/or recurse through - // &mut self while holding the pointer. Mirrors Zig's `*const Map -> ?*Symbol` - // (interior mutability via Vec's raw `[*]T` ptr field). - // - // SOUNDNESS: the *mut is derived directly from `Vec.ptr: NonNull` — a raw - // pointer field whose provenance is independent of the `&self` borrow used to read - // it. We deliberately do NOT go through `.slice()`/`.at()` (which produce `&[T]`/`&T` - // and would yield read-only provenance, making any later write UB). Callers may write - // through the result as long as the backing storage is not reallocated and they do - // not materialize overlapping `&mut`. pub fn get(&self, ref_: Ref) -> Option<*mut Symbol> { if Ref::is_source_index_null(ref_.source_index()) || ref_.is_source_contents_slice() { return None; @@ -583,13 +385,6 @@ impl Map { } } - // PORT NOTE: Zig aliased the caller's stack `[1]List` slot directly; that's - // unsound in Rust (would dangle on return). Take ownership of `list` and - // box it into a one-element NestedList instead. - // PERF(port): one extra allocation vs Zig — profile (single - // caller is the printer one-shot, cold). - // OWNERSHIP: returned `Map` is *owned*; the `Vec` allocated here leaks if a - // consumer parks it in `ManuallyDrop` (e.g. renamer.rs `MinifyRenamer.symbols`). pub fn init_with_one_list(list: Vec) -> Map { Self::init_list(vec![list]) } @@ -600,10 +395,6 @@ impl Map { } } - /// Safe `&mut` lookup via the `Vec`/`Vec` backing storage. Mirrors - /// [`get_const`] but returns a unique borrow tied to `&mut self`, so callers - /// that only need to flip a flag (e.g. `must_not_be_renamed`) don't need the - /// raw `*mut Symbol` from [`get`] + an open-coded `(*ptr).field = ...`. pub fn get_mut(&mut self, ref_: Ref) -> Option<&mut Symbol> { if Ref::is_source_index_null(ref_.source_index()) || ref_.is_source_contents_slice() { return None; @@ -633,10 +424,6 @@ impl Map { } pub fn follow_all(&mut self) { - // TODO(port): bun_perf::trace("Symbols.followAll") — RAII guard - // `link` is `Cell`, so we can iterate the table by shared ref and - // mutate `link` in place; `follow()` only takes `&self` and only touches - // `link`, so the nested shared borrows coexist. for symbols in self.symbols_for_source.iter() { for symbol in symbols.iter() { if !symbol.has_link() { @@ -648,14 +435,6 @@ impl Map { } } - /// Equivalent to followSymbols in esbuild. - /// - /// PORT NOTE: Zig's body is naturally recursive (`follow(symbol.link)`). - /// Reshaped to an iterative two-phase walk so the per-hop work is just two - /// raw pointer adds and a load — no call frame, no `Option` unwrap, no - /// repeated tag/null guards. Semantics are identical to Zig's: every node - /// on the path from `ref_` to the union-find root has its `link` rewritten - /// to the root (full path compression). pub fn follow(&self, ref_: Ref) -> Ref { // Entry guard — `ref_` may be `Ref::None` / a SourceContentsSlice ref // (callers pass arbitrary Refs read out of AST nodes). After this, @@ -671,15 +450,6 @@ impl Map { return ref_; } - // Phase 1: find the root. `link.is_valid()` holds here. The only - // writers of `Symbol::link` are (a) the default `Ref::NONE` - // (tag=Invalid — rejected by `is_valid()` above), (b) `merge()`, - // which stores a Ref that came from `declare_symbol` / `new_symbol` / - // `LinkerGraph::generate_symbol`, and (c) prior `follow()` path - // compression, which stores a `root` that itself satisfied (b). All - // such refs satisfy the in-bounds contract (see `get_const`): - // `(source_index, inner_index)` with tag ∈ {Symbol, AllocatedName}, - // never `SourceContentsSlice` and never the null source sentinel. let outer = self.symbols_for_source.as_slice(); let lookup = |r: Ref| -> &Symbol { debug_assert!(!r.is_source_contents_slice()); @@ -695,21 +465,11 @@ impl Map { root = next; } - // Phase 2: path compression. Rewrite `link` on the entry node and every - // intermediate node to point directly at `root` (matches the Zig - // recursion's post-order `symbol.link = link` writes). The `!=` gate - // mirrors Zig's `if (!symbol.link.eql(link))` to avoid a redundant - // store when the chain was already length-1. `link` is `Cell`, so - // writes go through `&Symbol` safely. if !link.eql(root) { symbol.link.set(root); loop { let p = lookup(link); let next = p.link.get(); - // `next.eql(root)` ⇔ `p.link` already points at root — - // mirrors Zig's post-order `if (!symbol.link.eql(link))` gate - // and saves a redundant store on the last intermediate plus - // the otherwise-wasted lookup of `root` itself. if next.eql(root) || !next.is_valid() { break; } diff --git a/src/ast/transpiler_cache.rs b/src/ast/transpiler_cache.rs index 44d8e998d52..360bcfeaf8d 100644 --- a/src/ast/transpiler_cache.rs +++ b/src/ast/transpiler_cache.rs @@ -45,10 +45,6 @@ impl Default for RuntimeTranspilerCache { } } -// Low tier (`bun_ast`) names no `bun_jsc` types; `bun_jsc` provides the -// `Jsc` arm. Cold path: at most twice per parse; callee does file I/O. -// `parser_options` is `*const ()` because `parser::Options` would be a forward -// edge here; the jsc impl casts it back. bun_dispatch::link_interface! { pub TranspilerCacheImpl[Jsc] { fn get(source: &Source, parser_options: NonNull<()>, used_jsx: bool) -> bool; @@ -58,14 +54,6 @@ bun_dispatch::link_interface! { } impl RuntimeTranspilerCache { - /// Build the dispatch handle for the set-once `r#impl` slot. - /// - /// Centralises the raw-pointer obligation so the three public entry - /// points below stay safe. - /// `this` is always derived from a live `&self` / `&mut self` in those - /// callers and the returned `Copy` handle is consumed immediately, so - /// the `link_interface!` liveness contract (owner valid for every - /// dispatch through the handle) is upheld. #[inline] fn handle(kind: TranspilerCacheImplKind, this: *mut Self) -> TranspilerCacheImpl { // SAFETY: `this` is non-null, aligned, and live for the immediate diff --git a/src/ast/ts.rs b/src/ast/ts.rs index b528bf9f481..a1464c0bbec 100644 --- a/src/ast/ts.rs +++ b/src/ast/ts.rs @@ -6,110 +6,13 @@ use bun_collections::array_hash_map::StringContext; use crate::base::Ref; use crate::e::String as EString; -/// This is for TypeScript "enum" and "namespace" blocks. Each block can -/// potentially be instantiated multiple times. The exported members of each -/// block are merged into a single namespace while the non-exported code is -/// still scoped to just within that block: -/// -/// let x = 1; -/// namespace Foo { -/// let x = 2; -/// export let y = 3; -/// } -/// namespace Foo { -/// console.log(x); // 1 -/// console.log(y); // 3 -/// } -/// -/// Doing this also works inside an enum: -/// -/// enum Foo { -/// A = 3, -/// B = A + 1, -/// } -/// enum Foo { -/// C = A + 2, -/// } -/// console.log(Foo.B) // 4 -/// console.log(Foo.C) // 5 -/// -/// This is a form of identifier lookup that works differently than the -/// hierarchical scope-based identifier lookup in JavaScript. Lookup now needs -/// to search sibling scopes in addition to parent scopes. This is accomplished -/// by sharing the map of exported members between all matching sibling scopes. -// PORT NOTE: 'arena lifetime dropped — `EnumString` payload is a `StoreRef` -// rather than an arena-borrowed reference (see the TODO on that variant). pub struct TSNamespaceScope { - /// This is specific to this namespace block. It's the argument of the - /// immediately-invoked function expression that the namespace block is - /// compiled into: - /// - /// var ns; - /// (function (ns2) { - /// ns2.x = 123; - /// })(ns || (ns = {})); - /// - /// This variable is "ns2" in the above example. It's the symbol to use when - /// generating property accesses off of this namespace when it's in scope. pub arg_ref: Ref, - /// This is shared between all sibling namespace blocks - // LIFETIMES.tsv: ARENA — p.arena.create(Pair); &pair.map; shared across - // sibling scopes. `StoreRef` (arena back-pointer with safe `Deref`) so - // callers don't open-code `unsafe { &mut *exported_members }` at every use. pub exported_members: crate::nodes::StoreRef, - /// This is a lazily-generated map of identifiers that actually represent - /// property accesses to this namespace's properties. For example: - /// - /// namespace x { - /// export let y = 123 - /// } - /// namespace x { - /// export let z = y - /// } - /// - /// This should be compiled into the following code: - /// - /// var x; - /// (function(x2) { - /// x2.y = 123; - /// })(x || (x = {})); - /// (function(x3) { - /// x3.z = x3.y; - /// })(x || (x = {})); - /// - /// When we try to find the symbol "y", we instead return one of these lazily - /// generated proxy symbols that represent the property access "x3.y". This - /// map is unique per namespace block because "x3" is the argument symbol that - /// is specific to that particular namespace block. pub property_accesses: StringArrayHashMap, - /// Even though enums are like namespaces and both enums and namespaces allow - /// implicit references to properties of sibling scopes, they behave like - /// separate, er, namespaces. Implicit references only work namespace-to- - /// namespace and enum-to-enum. They do not work enum-to-namespace. And I'm - /// not sure what's supposed to happen for the namespace-to-enum case because - /// the compiler crashes: https://github.com/microsoft/TypeScript/issues/46891. - /// So basically these both work: - /// - /// enum a { b = 1 } - /// enum a { c = b } - /// - /// namespace x { export let y = 1 } - /// namespace x { export let z = y } - /// - /// This doesn't work: - /// - /// enum a { b = 1 } - /// namespace a { export let c = b } - /// - /// And this crashes the TypeScript compiler: - /// - /// namespace a { export let b = 1 } - /// enum a { c = b } - /// - /// Therefore we only allow enum/enum and namespace/namespace interactions. pub is_enum_scope: bool, } @@ -148,11 +51,6 @@ impl Data { } } -// ── TypeScript::Metadata ─────────────────────────────────────────────────── -// Decorator-metadata type tag attached to `G.Property` / `G.FnArg` / `G.Fn`. -// Data-only; the parser-state predicates that depend on `P` stay in -// `bun_js_parser::typescript`. - #[derive(Clone, Default)] pub enum Metadata { #[default] @@ -187,11 +85,6 @@ impl Metadata { // translated from: // https://github.com/microsoft/TypeScript/blob/e0a324b0503be479f2b33fd2e17c6e86c94d1297/src/compiler/transformers/typeSerializer.ts#L402 - /// Return the final union type if possible, or return None to continue merging. - /// - /// If the current type is MNever, MNull, or MUndefined assign the current type - /// to MNone and return None to ensure it's always replaced by the next type. - /// `load_name`: closure form of `p.load_name_from_ref` to avoid coupling Metadata to P. pub fn finish_union<'b, F: Fn(Ref) -> &'b [u8]>(&mut self, load_name: F) -> Option { let current = self; match current { @@ -238,10 +131,6 @@ impl Metadata { } } - /// Return the final intersection type if possible, or return None to continue merging. - /// - /// If the current type is MUnknown, MNull, or MUndefined assign the current type - /// to MNone and return None to ensure it's always replaced by the next type. pub fn finish_intersection<'b, F: Fn(Ref) -> &'b [u8]>( &mut self, load_name: F, diff --git a/src/base64/lib.rs b/src/base64/lib.rs index cd49c96a05f..cd2d67ca6d7 100644 --- a/src/base64/lib.rs +++ b/src/base64/lib.rs @@ -55,17 +55,6 @@ pub const fn decode_lenient_len(source_len: usize) -> usize { source_len.div_ceil(4) * 3 } -/// Decode base64 the way Node.js `Buffer.from(str, "base64" | "base64url")` -/// and `buf.write(str, "base64" | "base64url")` do: both the standard and the -/// URL-safe alphabets are accepted, whitespace and any other non-alphabet -/// bytes are skipped, and decoding stops at the first `'='`. Invalid input -/// never fails — as much data as possible is decoded. -/// -/// Like Node.js, strictly valid input for the requested alphabet -/// (`is_urlsafe`) is decoded with simdutf's fastest kernel; everything else is -/// decoded with simdutf's `base64_default_or_url_accept_garbage` mode. -/// -/// Returns the number of bytes written to `destination`. pub fn decode_lenient(destination: &mut [u8], source: &[u8], is_urlsafe: bool) -> usize { // Fast path: the common case is strictly valid base64 for the requested // alphabet (possibly with whitespace and padding), which simdutf decodes @@ -75,11 +64,6 @@ pub fn decode_lenient(destination: &mut [u8], source: &[u8], is_urlsafe: bool) - return strict.count; } - // simdutf only honors the accept-garbage stop-at-'=' rule when the - // destination can hold the worst-case decode; with a smaller destination - // (e.g. `buf.write` into a short buffer) it switches to a chunked strategy - // that keeps decoding past the '='. Apply the rule up front in that case - // so both strategies agree. let source = if destination.len() < decode_lenient_len(source.len()) { match source.iter().position(|&c| c == b'=') { Some(index) => &source[..index], @@ -135,12 +119,6 @@ pub(crate) fn simdutf_encode_len_url_safe(source_len: usize) -> usize { simdutf::base64::encode_len(source_len, true) } -/// Encode with the following differences from regular `encode` function: -/// -/// * No padding is added (the extra `=` characters at the end) -/// * `-` and `_` are used instead of `+` and `/` -/// -/// See the documentation for simdutf's `binary_to_base64` function for more details (simdutf_impl.h). pub(crate) fn simdutf_encode_url_safe(destination: &mut [u8], source: &[u8]) -> usize { simdutf::base64::encode(source, destination, true) } @@ -217,12 +195,6 @@ pub fn encode_url_safe(dest: &mut [u8], source: &[u8]) -> usize { unsafe { WTF__base64URLEncode(source.as_ptr(), source.len(), dest.as_mut_ptr(), dest.len()) } } -// ────────────────────────────────────────────────────────────────────────── -// VLQ — moved from bun_sourcemap. Ground truth: src/sourcemap/VLQ.zig. -// Lives here because the encoding is pure -// base64-alphabet bit-packing with zero sourcemap-specific deps; bun_sourcemap -// re-exports this for its own consumers. -// ────────────────────────────────────────────────────────────────────────── pub use vlq::{VLQ, VLQResult}; /// Variable-length quantity encoding, limited to i32 as per source map spec. @@ -296,18 +268,6 @@ pub mod vlq { entries }; - // A single base 64 digit can contain 6 bits of data. For the base 64 variable - // length quantities we use in the source map spec, the first bit is the sign, - // the next four bits are the actual value, and the 6th bit is the continuation - // bit. The continuation bit tells us whether there are more digits in this - // value following this digit. - // - // Continuation - // | Sign - // | | - // V V - // 101011 - // const fn encode_slow_path(value: i32) -> VLQ { let mut len: u8 = 0; let mut bytes: [u8; VLQ_MAX_IN_BYTES] = [0; VLQ_MAX_IN_BYTES]; @@ -365,11 +325,6 @@ pub mod vlq { bytes }; - // Shared body for `decode` / `decode_assume_valid`. The two .zig originals - // (src/sourcemap/VLQ.zig:104/135) differ only by two `bun.assert` lines; - // const-generic `ASSERT_VALID` is const-folded so codegen matches the - // hand-duplicated bodies. - // PERF(port): loop was `inline for` (unrolled) — profile if hot. #[inline(always)] fn decode_impl(encoded: &[u8], start: usize) -> VLQResult { let mut shift: u8 = 0; @@ -406,11 +361,6 @@ pub mod vlq { } } - // Reached when the input is empty or ends mid-VLQ (the last byte's - // continuation bit is set with no following byte, or all 8 bytes have - // it set — both malformed). No value was decoded; return `start` - // unchanged so callers' no-progress checks treat the truncated - // mapping as a parse failure instead of silently accepting `value: 0`. VLQResult { start, value: 0 } } @@ -720,10 +670,6 @@ pub mod zig_base64 { result } - /// Invalid characters that are not ignored result in Error::InvalidCharacter. - /// Invalid padding results in Error::InvalidPadding. - /// Decoding more data than can fit in dest results in Error::NoSpaceLeft. See also ::calc_size_upper_bound. - /// Returns the number of bytes written to dest. pub(crate) fn decode( &self, dest: &mut [u8], @@ -942,10 +888,6 @@ pub mod zig_base64 { assert_eq!(expected_decoded, &decoded[0..written]); } - /// `expected_with_ignore` is the error `decoder_with_ignore` reports - /// for the same input, or `None` if it accepts the input. Differs from - /// `expected_err` when the codec's `decoder_with_ignore` doesn't share - /// its `pad_char` (URL-safe family). fn test_error( codecs: &Codecs, encoded: &[u8], @@ -987,19 +929,6 @@ pub mod zig_base64 { } } -// ────────────────────────────────────────────────────────────────────────── -// LAYERING: hoisted from `bun_css::css_modules::hash` so `bun_bundler` can -// call the *same* implementation without taking a hard dep on `bun_css` (and -// without re-implementing the hash, which would diverge — see review of -// `LinkerContext.rs::css_modules_hash_shim`). `bun_css` re-exports this as -// `css_modules::hash` for its in-crate callers. -// -// Spec: `src/css/css_modules.zig:hash` — wyhash(u64) of the formatted args, -// truncated to u32, url-safe-base64-encoded into a bump-allocated slice. If -// `at_start` and the first encoded byte is a digit, prefix `_` (CSS idents -// can't start with a digit). -// ────────────────────────────────────────────────────────────────────────── - // TODO: replace with bun's hash pub fn wyhash_url_safe<'a>( bump: &'a bun_alloc::Arena, diff --git a/src/boringssl/lib.rs b/src/boringssl/lib.rs index 60c17a526ce..3749f4db99b 100644 --- a/src/boringssl/lib.rs +++ b/src/boringssl/lib.rs @@ -19,12 +19,6 @@ pub mod x509 { pub fn is_safe_alt_name(name: &[u8], utf8: bool) -> bool { for &c in name { match c { - // These mess with encoding rules. - // Commas make it impossible to split the list of subject - // alternative names unambiguously, which is why we escape. - // Single quotes are unlikely to appear in any legitimate values, - // but they could be used to make a value look like it was escaped - // (i.e., enclosed in single/double quotes). b'"' | b'\\' | b',' | b'\'' => return false, _ => { if utf8 { @@ -75,12 +69,6 @@ pub fn load() { }} } -// ────────────────────────────────────────────────────────────────────────── -// Extra FFI surface not yet exposed by `bun_boringssl_sys` (hand-curated -// subset). Ground truth: src/boringssl_sys/boringssl.zig + openssl/ssl.h. -// Remove once the bindgen pipeline lands these in the sys crate. -// ────────────────────────────────────────────────────────────────────────── - /// `enum ssl_verify_result_t` is `BORINGSSL_ENUM_INT`-backed; `ssl_verify_ok == 0`. #[allow(non_camel_case_types)] type ssl_verify_result_t = c_int; @@ -166,10 +154,6 @@ pub fn init_client() -> *mut boring::SSL { } let ctx = CTX_STORE .get_or_init(|| { - // Zig: `SSL_CTX.init()` — see boringssl.zig:19197. Three steps: - // 1. SSL_CTX_new(TLS_with_buffers_method()) - // 2. setCustomVerify(noop_custom_verify) → SSL_CTX_set_custom_verify(ctx, 0, cb) - // 3. setup() → CRYPTO_BUFFER_POOL_new + set0_buffer_pool + set_cipher_list("ALL") let ctx = boring::SSL_CTX_new(boring::TLS_with_buffers_method()); SSL_CTX_set_custom_verify(ctx, 0, Some(noop_custom_verify)); ssl_ctx_setup(ctx); @@ -191,25 +175,6 @@ pub fn init_client() -> *mut boring::SSL { // void, OPENSSL_memory_free, (void *ptr) // size_t, OPENSSL_memory_get_size, (void *ptr) -// The following three functions can be defined to override default heap -// allocation and freeing. If defined, it is the responsibility of -// |OPENSSL_memory_free| to zero out the memory before returning it to the -// system. |OPENSSL_memory_free| will not be passed NULL pointers. -// -// WARNING: These functions are called on every allocation and free in -// BoringSSL across the entire process. They may be called by any code in the -// process which calls BoringSSL, including in process initializers and thread -// destructors. When called, BoringSSL may hold pthreads locks. Any other code -// in the process which, directly or indirectly, calls BoringSSL may be on the -// call stack and may itself be using arbitrary synchronization primitives. -// -// As a result, these functions may not have the usual programming environment -// available to most C or C++ code. In particular, they may not call into -// BoringSSL, or any library which depends on BoringSSL. Any synchronization -// primitives used must tolerate every other synchronization primitive linked -// into the process, including pthreads locks. Failing to meet these constraints -// may result in deadlocks, crashes, or memory corruption. - #[unsafe(no_mangle)] pub extern "C" fn OPENSSL_memory_alloc(size: usize) -> *mut c_void { bun_alloc::mimalloc::mi_malloc(size) diff --git a/src/boringssl_sys/boringssl.rs b/src/boringssl_sys/boringssl.rs index ffa1a3ef806..7ca113819b3 100644 --- a/src/boringssl_sys/boringssl.rs +++ b/src/boringssl_sys/boringssl.rs @@ -10,13 +10,6 @@ use core::ffi::{c_char, c_int, c_long, c_uint, c_void}; -// ═══════════════════════════════════════════════════════════════════════════ -// Opaque-type helper — thin sugar over the canonical -// `bun_opaque::opaque_ffi!` (see its crate doc for the `UnsafeCell<[u8;0]>` / -// `PhantomPinned` rationale). Local alias just bakes in `pub` so the 21 -// `opaque!(/// doc \n Name)` call sites below stay one-arg. -// ═══════════════════════════════════════════════════════════════════════════ - macro_rules! opaque { ($($(#[$m:meta])* $name:ident),+ $(,)?) => { ::bun_opaque::opaque_ffi!($($(#[$m])* pub $name),+); @@ -375,10 +368,6 @@ unsafe extern "C" { out_len: *mut c_uint, ) -> *mut u8; - // ── SHA-1 ──────────────────────────────────────────────────────────── - // `*_Init` are write-only initialisers but stay `*mut`: callers feed - // `MaybeUninit::as_mut_ptr()`, and forcing `&mut CTX` would require a - // valid (initialised) `CTX` first — defeating the point. pub fn SHA1_Init(sha: *mut SHA_CTX) -> c_int; pub fn SHA1_Update(sha: *mut SHA_CTX, data: *const c_void, len: usize) -> c_int; pub fn SHA1_Final(out: *mut u8, sha: *mut SHA_CTX) -> c_int; @@ -435,14 +424,6 @@ unsafe extern "C" { pub safe fn X509V3_EXT_get_nid(nid: c_int) -> *const X509V3_EXT_METHOD; } -// ═══════════════════════════════════════════════════════════════════════════ -// Typed STACK_OF(...) inline wrappers -// -// BoringSSL defines these as `static inline` in C, so they have no exported -// symbol — they bottom out on the untyped `sk_*` ABI above. Mirrors the -// translate-c bodies in `boringssl.zig`. -// ═══════════════════════════════════════════════════════════════════════════ - /// Per-stack free callback type used by `sk_GENERAL_NAME_pop_free` /// (matches Zig's `stack_GENERAL_NAME_free_func`). pub(crate) type sk_GENERAL_NAME_free_func = unsafe extern "C" fn(*mut struct_stack_st_GENERAL_NAME); @@ -524,11 +505,6 @@ pub unsafe fn sk_GENERAL_NAME_pop_free( } } -// ═══════════════════════════════════════════════════════════════════════════ -// SSL / TLS — error codes, verify modes, shutdown flags, renegotiate modes -// (`vendor/boringssl/include/openssl/ssl.h`) -// ═══════════════════════════════════════════════════════════════════════════ - pub const SSL_ERROR_SSL: c_int = 1; pub const SSL_ERROR_WANT_READ: c_int = 2; pub const SSL_ERROR_WANT_WRITE: c_int = 3; @@ -561,11 +537,6 @@ pub const SSL_OP_LEGACY_SERVER_CONNECT: u32 = 0; /// `#define RSA_PKCS1_OAEP_PADDING 4` (`openssl/rsa.h`). pub const RSA_PKCS1_OAEP_PADDING: c_int = 4; -// ═══════════════════════════════════════════════════════════════════════════ -// BIO — opaque-ish handle + method vtable -// (`vendor/boringssl/include/openssl/bio.h`) -// ═══════════════════════════════════════════════════════════════════════════ - /// `CRYPTO_refcount_t` (`openssl/thread.h`) — atomic-ish u32 in BoringSSL. pub(crate) type CRYPTO_refcount_t = u32; diff --git a/src/boringssl_sys/lib.rs b/src/boringssl_sys/lib.rs index ed669086127..c652f13d782 100644 --- a/src/boringssl_sys/lib.rs +++ b/src/boringssl_sys/lib.rs @@ -3,10 +3,6 @@ pub mod boringssl; pub use boringssl::*; -/// Constant-time byte-slice equality via BoringSSL `CRYPTO_memcmp`. -/// -/// Returns `false` when lengths differ (the length comparison itself is NOT -/// constant-time — matches all existing call sites, which already early-out on len). #[inline] pub fn constant_time_eq(a: &[u8], b: &[u8]) -> bool { if a.len() != b.len() { diff --git a/src/brotli/lib.rs b/src/brotli/lib.rs index 01c9b0c73f3..bca662ac214 100644 --- a/src/brotli/lib.rs +++ b/src/brotli/lib.rs @@ -49,10 +49,6 @@ impl Default for DecoderOptions { pub struct BrotliReaderArrayList<'a> { pub input: &'a [u8], - // PORT NOTE: reshaped for borrowck — Zig kept a by-value copy of the - // ArrayListUnmanaged in `list` and wrote it back to `*list_ptr` on every - // `readAll` (defer). `Vec` is not `Copy`, so we operate on `list_ptr` - // directly and drop the redundant `list` + `list_allocator` fields. pub list_ptr: &'a mut Vec, pub brotli: *mut c::BrotliDecoder, pub state: ReaderState, @@ -372,12 +368,6 @@ impl BrotliCompressionStream { } pub fn end(&mut self) -> Result<&[u8], Error> { - // TODO(port): narrow error set - // Zig: `defer this.state = .End` — runs on BOTH ok and error paths. - // PORT NOTE: reshaped for borrowck — `compress_stream`'s output borrows - // `&mut *self.brotli`, so we set `self.state` first and inline - // write/write_chunk("", true). Net state matches Zig (defer overrides - // any intermediate `Error` back to `End`). if matches!(self.state, CompressionState::End | CompressionState::Error) { self.state = CompressionState::End; return Ok(b""); diff --git a/src/brotli_sys/brotli_c.rs b/src/brotli_sys/brotli_c.rs index a8c78320334..29c68badad8 100644 --- a/src/brotli_sys/brotli_c.rs +++ b/src/brotli_sys/brotli_c.rs @@ -192,11 +192,6 @@ pub enum BrotliDecoderResult { needs_more_output = 3, } -// NOTE: brotli_c.zig (translate-c output) defines this error-code table three times -// (loose `BROTLI_DECODER_*` consts, a subset `BrotliDecoderErrorCode` enum, and the -// full `BrotliDecoderErrorCode2` enum). They are intentionally collapsed here into the -// single complete enum below; `BrotliDecoderErrorCode` is kept as an alias for FFI -// signatures. Do not re-add the duplicates on a mechanical re-port. pub type BrotliDecoderErrorCode = BrotliDecoderErrorCode2; #[repr(i32)] // Zig: enum(c_int) diff --git a/src/bun_alloc/MaxHeapAllocator.rs b/src/bun_alloc/MaxHeapAllocator.rs index 71546a6b600..521cc3a333e 100644 --- a/src/bun_alloc/MaxHeapAllocator.rs +++ b/src/bun_alloc/MaxHeapAllocator.rs @@ -6,10 +6,6 @@ use core::ptr::NonNull; use crate::MAX_ALIGN_T as MAX_ALIGN; use crate::{Alignment, Allocator}; -/// Zig backed `array_list` with `std.array_list.AlignedManaged(u8, .of(std.c.max_align_t))` -/// so the returned pointer is guaranteed aligned to `max_align_t`. Rust `Vec` -/// allocates with align 1, which would violate the `alignment <= MAX_ALIGN` -/// contract. Store a raw `MAX_ALIGN`-aligned buffer instead. pub struct MaxHeapAllocator { ptr: Option>, capacity: usize, diff --git a/src/bun_alloc/MimallocArena.rs b/src/bun_alloc/MimallocArena.rs index 2b0b1795e1d..72f163a7bbf 100644 --- a/src/bun_alloc/MimallocArena.rs +++ b/src/bun_alloc/MimallocArena.rs @@ -27,11 +27,6 @@ use core::sync::atomic::{AtomicU64, Ordering}; use crate::mimalloc; -// ── Debug-only mi_heap accounting ───────────────────────────────────────── -// -// Tracks `mi_heap_new`/`mi_heap_destroy` calls so leak tests can assert the -// live-heap count is bounded. Gated on `debug_assertions` (zero cost in -// release); nothing reads these counters in production. #[cfg(debug_assertions)] pub(crate) static HEAP_NEW_COUNT: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0); @@ -39,45 +34,16 @@ pub(crate) static HEAP_NEW_COUNT: core::sync::atomic::AtomicUsize = pub(crate) static HEAP_DESTROY_COUNT: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0); -// ── Debug-only thread-ownership guard (Zig: `bun.safety.ThreadLock`) ────── -// -// `bun_alloc` sits below `bun_core` in the crate graph, so we cannot reuse -// `bun_core::ThreadLock`. This is the minimal subset needed to mirror Zig's -// `ci_assert` same-thread check on the `mi_heap_*` allocation paths: a per- -// thread monotone id stamped at `MimallocArena::new()` and asserted on every -// alloc/realloc. `mi_free` is documented thread-safe and is left unchecked. - #[cfg(debug_assertions)] #[inline] fn debug_thread_stamp() -> u64 { - // Intentionally NOT `bun_threading::current_thread_id()` / - // `bun_safety::thread_id::current()`: `bun_alloc` is tier-0 and sits below - // both in the crate graph (they depend on us), so routing there would - // create a cycle. The contract here is only "any nonzero per-thread-unique - // u64 for an ownership debug-assert", which a counter satisfies. - // - // Portable thread-unique id without `ThreadId::as_u64` (unstable) or - // platform syscalls: each thread takes a fresh nonzero counter value the - // first time it asks. static NEXT: AtomicU64 = AtomicU64::new(1); std::thread_local!(static ID: u64 = NEXT.fetch_add(1, Ordering::Relaxed)); ID.with(|id| *id) } -/// A mimalloc heap. Owns a `mi_heap_t`; all allocations are bulk-freed on -/// `Drop` (Zig: `MimallocArena.deinit` → `mi_heap_destroy`). -/// -/// Implements [`core::alloc::Allocator`] for `&MimallocArena`, so it can back -/// `Vec` / `Box` with real per-allocation -/// free + realloc — the thing `bumpalo::Bump` cannot do. pub struct MimallocArena { heap: NonNull, - /// `true` when `heap` came from `mi_heap_new()` and must be - /// `mi_heap_destroy`ed on `Drop`/`reset()`. `false` when borrowing the - /// process-wide `mi_heap_main()` (see [`Self::borrowing_default`]) — Drop - /// is then a no-op and allocations live for the process lifetime, matching - /// Zig's `default_allocator` shape for callers that just need an `&Arena` - /// without paying `mi_heap_new` + `mi_heap_destroy`. owns: bool, /// Zig: `thread_lock: bun.safety.ThreadLock` (debug-only). Stamped on /// `new()`/`reset()`; asserted on every `mi_heap_*` alloc/realloc path. @@ -126,16 +92,6 @@ impl MimallocArena { } } - /// Borrow the process-wide default mimalloc heap (`mi_heap_main()`) instead - /// of creating a fresh one. `Drop` is a no-op; `reset()` is forbidden. - /// Allocations made through this arena are equivalent to global - /// `mi_malloc`/`mi_free` and live until individually freed (or process - /// exit for `into_bump_slice`-style leaks). - /// - /// Use this where Zig threads `bun.default_allocator` through an - /// `Allocator`-shaped parameter and the Rust port needs an `&Arena` but - /// the `mi_heap_new` + `mi_heap_destroy` pair is measurable overhead on a - /// hot, short-lived path (e.g. `Bunfig::parse` on `bun -e ''` startup). #[inline] pub fn borrowing_default() -> Self { // SAFETY: FFI call with no preconditions; `mi_heap_main()` returns the @@ -153,10 +109,6 @@ impl MimallocArena { } } - /// Zig: `Borrowed.assertThreadLock()` — debug-only check that the calling - /// thread is the one that constructed (or last `reset()`) this arena. - /// Guards every `mi_heap_*` allocation path so the over-broad `Sync` impl - /// cannot silently corrupt mimalloc's per-heap free lists. #[inline(always)] fn assert_owning_thread(&self) { #[cfg(debug_assertions)] @@ -199,17 +151,6 @@ impl MimallocArena { self.heap.as_ptr() } - /// Destroy the current heap (bulk-freeing all live allocations) and - /// allocate a fresh one. Mirrors `bumpalo::Bump::reset` semantics for - /// callers that reuse one arena per work item. - /// - /// Any pointers previously returned by this arena are invalidated. - /// - /// `#[cold]` + `#[inline(never)]`: `mi_heap_destroy` does per-page - /// free-list/bitmap teardown (and, when mimalloc's stats are compiled in, - /// an `_mi_stats_merge_from` walk over `mi_stats_t`), so this is the slow - /// path. The hot path is `reset_retain_with_limit`'s retain branch — keep - /// that lean by not letting this body inline up into it. #[cold] #[inline(never)] pub fn reset(&mut self) { @@ -230,76 +171,14 @@ impl MimallocArena { // SAFETY: FFI call with no preconditions. let heap = unsafe { mimalloc::mi_heap_new() }; self.heap = NonNull::new(heap).unwrap_or_else(|| crate::out_of_memory()); - // `&mut self` proves exclusive access; re-stamp the debug thread-lock - // so an arena `Send`-moved to a worker and then reset there may - // allocate on that worker (Zig has no equivalent because its - // `MimallocArena` is not moved post-init). #[cfg(debug_assertions)] self.owning_thread .store(debug_thread_stamp(), Ordering::Relaxed); } - /// Zig: `std.heap.ArenaAllocator.reset(.{.retain_with_limit = limit})`. - /// - /// Retains the warm heap while its in-use footprint is `<= limit`, and - /// only then falls back to a full [`Self::reset`] (`mi_heap_destroy` + - /// `mi_heap_new`). Returns `true` when the heap was retained, `false` when - /// it was recycled — mirroring `ArenaAllocator.reset`'s "reuse succeeded" - /// boolean. - /// - /// **Why this isn't a no-op-or-full-reset.** Zig's `std.heap.ArenaAllocator` - /// is a bump allocator: `.retain_with_limit` rewinds the bump cursor to - /// offset 0 (logically freeing every allocation) but keeps up to `limit` - /// bytes of *backing buffer* committed, so the next cycle's allocations - /// reuse those warm pages instead of faulting in (and zeroing) fresh ones. - /// A `mi_heap_t` has no "free all blocks but keep the pages" primitive — - /// the only bulk-free is `mi_heap_destroy`, which also hands the pages back - /// to mimalloc (which may purge/decommit them). So the faithful mapping is - /// "keep the *whole* heap — blocks and all — while it is still small; - /// recycle it once it grows past `limit`". The retained blocks are dead - /// (arena callers never `mi_free`; `AstAlloc::deallocate` is a no-op by - /// design), so they *are* garbage — but `limit` bounds that garbage, and - /// in exchange the per-cycle `mi_heap_destroy` + `mi_heap_new` (and the - /// re-commit + memset its first allocation pays when mimalloc has since - /// purged the arena page) is amortised away for the common case of many - /// small cycles. `limit` is the RSS/CPU knob: a tighter cap trades warm - /// pages for lower steady-state RSS; a looser one does the reverse. - /// - /// (An earlier port made this an unconditional `reset()` on the theory - /// that mimalloc's per-thread segment cache already keeps pages warm - /// across `mi_heap_destroy`/`mi_heap_new`. In practice, with a lower RSS - /// ceiling and mimalloc's purge timer, the recycled page is often already - /// decommitted by the time the next cycle touches it, so the round-trip - /// re-commits and re-zeroes it — exactly the cost `.retain_with_limit` - /// exists to avoid. Hence the cap-gated retain.) - /// - /// **Why `mi_heap_collect` is not an alternative to the cap.** When the - /// caller's allocations are individually freed before the cycle ends (most - /// `Vec` users — the bundler, renamer, installer), the - /// heap footprint is already near zero at this point, so the limit is never - /// hit and the heap is retained for free. The one caller where the limit - /// *does* fire is the transpiler's per-module AST arena (`jsc_hooks`): it - /// also backs `AstAlloc`, whose `deallocate` is a deliberate no-op (the AST - /// graph is abandoned, not freed), so that heap's footprint only ever - /// grows. `mi_heap_collect(force=true)` cannot reclaim it — it returns only - /// *empty* pages, and the dead AST blocks pin every page. The only bulk - /// reclaim is `mi_heap_destroy` (the `reset()` path). So for that caller the - /// `limit` is purely a "how much dead AST do we tolerate before paying a - /// `mi_heap_destroy`" knob; raising it trades steady-state RSS for fewer - /// destroys per transpile batch. #[inline] pub fn reset_retain_with_limit(&mut self, limit: usize) -> bool { - // `borrowing_default()` arenas (`!owns`) wrap `mi_heap_main()`, whose - // footprint is the whole process — they always fall through to - // `reset()`, which debug-asserts `owns` (recycling the main heap is a - // bug). `allocated_bytes()` walks heap *areas* (O(areas)), so the - // check is cheap. if self.owns && self.allocated_bytes() <= limit { - // `&mut self` proves exclusive access; re-stamp the debug - // thread-lock so an arena `Send`-moved to a worker and then - // retain-reset there may keep allocating on that worker — same as - // the full-reset path below (`reset()` re-stamps after - // `mi_heap_new`). #[cfg(debug_assertions)] self.owning_thread .store(debug_thread_stamp(), Ordering::Relaxed); @@ -359,10 +238,6 @@ impl MimallocArena { total } - /// Zig: `MimallocArena.ownsPtr()` → `mi_heap_contains(heap, p)`. - /// `mi_heap_contains` only tests address-range membership and never - /// dereferences `addr`, so this takes the address as `usize` (not a raw - /// pointer — there is no caller precondition to uphold). #[inline] pub fn owns_ptr(&self, addr: usize) -> bool { // SAFETY: `self.heap` is a live heap; `addr` is only range-tested. @@ -401,12 +276,6 @@ impl MimallocArena { } } - // ── bumpalo-compatible surface ─────────────────────────────────────── - // These exist so `pub type Arena = MimallocArena` is source-compatible - // with the previous `Arena = bumpalo::Bump` alias. They allocate from - // this heap and hand back `&'arena mut` borrows; memory is reclaimed on - // `reset()`/`Drop` (or earlier via the `Allocator` impl's `deallocate`). - /// `bumpalo::Bump::alloc_layout` parity. #[inline] pub fn alloc_layout(&self, layout: Layout) -> NonNull { @@ -526,11 +395,6 @@ impl MimallocArena { /// the Zig-style allocator handle. #[inline] pub fn std_allocator(&self) -> crate::StdAllocator { - // `ctx` is `*const MimallocArena` (not the inner `*mut Heap`) so the - // vtable thunks can reach `assert_owning_thread()`. They load - // `heap_ptr()` from it on every call; this is one extra indirection vs - // Zig (`ctx == heap`). The only consumer of `ctx` is this vtable; - // `is_instance()` compares the *vtable* pointer, not `ctx`. crate::StdAllocator { ptr: ptr::from_ref(self).cast_mut().cast(), vtable: &HEAP_ALLOCATOR_VTABLE, @@ -545,10 +409,6 @@ impl MimallocArena { || core::ptr::eq(alloc.vtable, &raw const GLOBAL_MIMALLOC_VTABLE) } - /// Zig: `MimallocArena.getThreadLocalDefault()` — a `StdAllocator` that - /// routes through the process-wide `mi_malloc`/`mi_free` (no per-heap ctx). - /// In mimalloc v3 these are already thread-local-fast, so there is no - /// separate per-thread default heap to cache. #[inline] pub fn get_thread_local_default() -> crate::StdAllocator { crate::StdAllocator { @@ -576,12 +436,6 @@ impl Drop for MimallocArena { } } -// ── core::alloc::Allocator ──────────────────────────────────────────────── -// -// Implemented on `&MimallocArena` (not the owned value) so that -// `Vec` borrows the arena for `'a` — matching -// `bumpalo`'s `&'bump Bump: Allocator` shape and the `ArenaVec<'a, T>` alias. - /// Wrap a raw mimalloc pointer in the `Result, AllocError>` shape /// the `Allocator` trait wants. `#[inline(always)]` keeps codegen identical to /// the open-coded `match` this replaced (hot path). diff --git a/src/bun_alloc/NullableAllocator.rs b/src/bun_alloc/NullableAllocator.rs index f3ee890f10c..50bc245f242 100644 --- a/src/bun_alloc/NullableAllocator.rs +++ b/src/bun_alloc/NullableAllocator.rs @@ -4,10 +4,6 @@ use core::ffi::c_void; use crate::{Alignment, AllocatorVTable, StdAllocator}; -/// PORT NOTE: Zig stored `{ ptr: *anyopaque, vtable: ?*const VTable }` and -/// recovered the `Allocator` by null-checking the vtable. Rust models the same -/// thing directly — `vtable: Option<&'static AllocatorVTable>` carries the -/// niche, so the struct is identical in size to `StdAllocator`. #[derive(Clone, Copy)] pub struct NullableAllocator { ptr: *mut c_void, diff --git a/src/bun_alloc/ast_alloc.rs b/src/bun_alloc/ast_alloc.rs index dc3e38bca46..a7b6ab8de21 100644 --- a/src/bun_alloc/ast_alloc.rs +++ b/src/bun_alloc/ast_alloc.rs @@ -35,12 +35,6 @@ use core::ptr::NonNull; use crate::{MimallocArena, mimalloc}; -// The parser builds thousands of tiny `AstVec`s; allocations `<= BUMP_MAX` are -// carved from a 16 KB buffer stored inline in the state (mirroring Zig's -// `StackFallbackAllocator`), so the small case never touches mimalloc. The -// cursor, the buffer, and the spill target live in one struct, so none of them -// can outlive the others. - /// Largest allocation served from the inline bump chunk; above this, requests /// go straight to the spill heap. const BUMP_MAX: usize = 512; @@ -49,17 +43,9 @@ const BUMP_MAX: usize = 512; /// through to the spill heap. const BUMP_CHUNK: usize = 16 * 1024; -/// Per-scope allocation state for [`AstAlloc`]. Owned by whichever component -/// opened the AST allocation scope and moved into the [`AST_ALLOC`] -/// thread-local while the scope is active; the owner decides when the -/// contents are bulk-freed. pub struct AstAllocState { /// Offset of the next free byte in `bump_chunk`. bump_cursor: usize, - /// Spill target for allocations the chunk can't serve. Set by the - /// installing scope from its own arena ([`Self::set_spill_heap`]); the - /// installer guarantees the heap outlives the installed window. Null when - /// the installer has no arena — `owned_spill` is then created lazily. spill: *mut mimalloc::Heap, /// Backing storage for `spill` when no borrowed target was provided. owned_spill: Option, @@ -151,18 +137,9 @@ impl AstAllocState { // ── Thread-local active state ──────────────────────────────────────────────── -/// The active [`AstAllocState`], or `None` when no AST scope is installed -/// (allocations then fall back to global mimalloc). -/// -/// `#[thread_local]` (not `thread_local!`): read on every `AstAlloc` -/// allocation, so it must stay a bare `__thread` slot. #[thread_local] static AST_ALLOC: Cell>> = Cell::new(None); -// One-slot recycler so a per-job `acquire_state`/`release_state` pair doesn't -// pay a 16 KB malloc each time. Uses `thread_local!` (unlike `AST_ALLOC`) so -// the destructor frees a parked box at thread exit; only touched on scope -// entry/exit, never on the allocation hot path. std::thread_local! { static AST_ALLOC_SPARE: Cell>> = const { Cell::new(None) }; } @@ -230,14 +207,6 @@ pub fn set_active_spill_heap(heap: *mut mimalloc::Heap) { } } -/// RAII guard: for its lifetime, [`AstAlloc`] allocates on **global** mimalloc -/// instead of the active per-parse state. Use when constructing -/// `AstVec`/`StoreRef` data that must outlive the current parse arena -/// (e.g. `Expr::deep_clone` for `WorkspacePackageJSONCache`). Without this, -/// the next `ASTMemoryAllocator::reset()` frees buffers the cache still holds. -/// -/// Restores the prior state on drop, so it nests correctly inside an -/// `ASTMemoryAllocator` scope. pub struct DetachAstHeap(Option>); impl DetachAstHeap { #[inline] @@ -282,11 +251,6 @@ impl ScopedAstAlloc { } } - /// Uninstall the scope's state and return it **without** bulk-freeing it, - /// restoring the previous occupant exactly as `drop` would. For callers - /// that hand the parsed AST to an async consumer: small `AstVec`s live in - /// the state's inline chunk, so the returned box must be kept alive until - /// the consumer is done with the AST. #[inline] pub fn take_state(self) -> Option> { let mut this = core::mem::ManuallyDrop::new(self); @@ -316,12 +280,6 @@ impl Drop for ScopedAstAlloc { } } -/// Zero-sized `Allocator` that routes to the active [`AstAllocState`] when one -/// is installed, else to global mimalloc. `deallocate` is a no-op (the state's -/// owner reclaims everything in bulk). -/// -/// Use as `Vec` (see [`AstVec`]). The ZST means the `Vec` stays -/// 24 bytes — same size as `Vec` — so AST node layouts are unchanged. #[derive(Clone, Copy, Default)] pub struct AstAlloc; @@ -337,11 +295,6 @@ fn heap_alloc(layout: Layout) -> *mut u8 { // `size == 0` (unique non-null pointer), so no special-casing. return mimalloc::mi_malloc_auto_align(layout.size(), layout.align()).cast(); }; - // Small, normally-aligned requests: carve from the state's inline chunk so - // a burst of tiny `AstVec`s costs zero mallocs (and stays out of - // `_mi_malloc_generic`). Zero-size layouts and over-aligned ones (no AST - // list type needs `> MI_MAX_ALIGN_SIZE`) fall through to mimalloc, which - // handles both. if layout.size() != 0 && layout.size() <= BUMP_MAX && layout.align() <= mimalloc::MI_MAX_ALIGN_SIZE @@ -400,11 +353,6 @@ unsafe impl Allocator for AstAlloc { #[inline] fn allocate_zeroed(&self, layout: Layout) -> Result, AllocError> { - // `mi_*zalloc` lets mimalloc skip the `memset` for blocks carved from - // freshly-`mmap`ed (already-zero) OS pages, which the default - // `allocate` + `ptr::write_bytes(0)` cannot. Never bump-carved (the - // chunk is uninitialised); same lifetime semantics as `heap_alloc`. - // Mirrors `MimallocArena::allocate_zeroed`. let p: *mut u8 = match active_state() { None => mimalloc::mi_zalloc_auto_align(layout.size(), layout.align()).cast(), // SAFETY: `heap_ptr` returns the live spill heap owned by the @@ -433,25 +381,6 @@ unsafe impl Allocator for AstAlloc { old: Layout, new: Layout, ) -> Result, AllocError> { - // Fast path: mimalloc rounds every allocation up to a size class, so the - // block behind `ptr` frequently already has room for `new.size()`. - // `mi_expand` reports that (and fixes up mimalloc's own padding - // bookkeeping) *without* moving the block — so it stays in whatever heap - // owns it and never thrashes the `heap → theap` TLS lookup. When it - // succeeds there is no allocation, no `memcpy`, and no abandoned block, - // matching `MimallocArena`'s `resize_in_place` (Zig's arena `remap` is - // `mi_expand`-then-`mi_realloc`). - // - // Gated on: - // - `old.size() > BUMP_MAX`: smaller blocks may be bump-chunk interior - // pointers (see `heap_alloc`), and `mi_expand` on those would treat - // the *whole chunk* as the block — corrupting its bookkeeping. A - // `> BUMP_MAX` block always came straight from - // `mi_[heap_]malloc[_aligned]`, so this is the only safe slice to - // use it. - // - `new.align() <= old.align()`: the block was aligned for `old`, - // `mi_expand` cannot raise that, and for `Vec` (the only `AstVec` - // shape) the alignment never changes across grows. if old.size() > BUMP_MAX && new.align() <= old.align() { // SAFETY: `ptr` is a live block from this allocator (the `grow` // contract) and — given `old.size() > BUMP_MAX` — a real mimalloc @@ -492,13 +421,6 @@ unsafe impl Allocator for AstAlloc { } } -// ── AstVec construction helpers ────────────────────────────────────────── -// `Vec` has no `Default` / `From<&[T]>` for non-`Global` `A`, so the -// 81 `DeclList::default()` / `::from_slice()` etc. call sites need these. -// Kept as free fns (not a trait) so `bun_collections::VecExt` can add a -// blanket `impl VecExt for Vec` that forwards here without -// a `bun_alloc → bun_collections` cycle. - impl AstAlloc { /// `Vec::new()` parity. `const` so it is usable in `Default` impls. #[inline] @@ -520,10 +442,6 @@ impl AstAlloc { v } - /// Move `items` element-wise into a fresh AST-heap allocation. Replaces - /// both `VecExt::from_owned_slice` (`Box<[T]>` → `Vec`) and - /// `VecExt::from_bump_slice` (leaked `&mut [T]` → `Vec`): in either case - /// the source storage is on the wrong heap, so a copy is unavoidable. #[inline] pub fn vec_from_iter>(iter: I) -> AstVec { let iter = iter.into_iter(); @@ -534,10 +452,6 @@ impl AstAlloc { } } -// NOTE: `impl Default for Vec` is rejected by orphan rules -// (`T` is an uncovered type param appearing before the local `AstAlloc` in -// `Vec`'s parameter list). `core::mem::take` therefore cannot be used on -// `AstVec`; call [`AstAlloc::take`] instead. impl AstAlloc { /// `core::mem::take` for [`AstVec`] (whose `Default` impl is blocked by /// orphan rules). Replaces `*v` with an empty vec and returns the old diff --git a/src/bun_alloc/c_thunks.rs b/src/bun_alloc/c_thunks.rs index c8cfc8f5940..8a13a8d1d99 100644 --- a/src/bun_alloc/c_thunks.rs +++ b/src/bun_alloc/c_thunks.rs @@ -45,20 +45,6 @@ pub unsafe extern "C" fn mi_free_bytes(bytes: *mut c_void, _ctx: *mut c_void) { // Zone-tagged thunks // ────────────────────────────────────────────────────────────────────────── -/// Expands a set of `extern "C"` allocator thunks bound to a named -/// heap-breakdown zone. When `heap_breakdown::ENABLED` is false (release / -/// non-macOS) the thunks fall straight through to the default allocator. -/// -/// Generated items: -/// - `malloc_size(_, len: usize) -> *mut c_void` — brotli-shape, non-zeroing. -/// Safe `extern "C" fn` (opaque cookie ignored; body is all-safe). -/// - `calloc_items(_, items: c_uint, len: c_uint) -> *mut c_void` — zlib-shape, -/// zeroing. Safe `extern "C" fn` (same rationale). -/// - `free(_, ptr: *mut c_void)` — paired with either alloc. `unsafe` -/// (precondition: `ptr` was allocated by this zone / the default allocator). -/// -/// Intended to be invoked inside a `mod XxxAllocator { … }` so call sites can -/// keep referring to `XxxAllocator::alloc` / `::free` via a local `pub use`. #[macro_export] macro_rules! c_thunks_for_zone { ($name:literal) => { diff --git a/src/bun_alloc/fallback/z.rs b/src/bun_alloc/fallback/z.rs index 5fae0a21981..4b54403638a 100644 --- a/src/bun_alloc/fallback/z.rs +++ b/src/bun_alloc/fallback/z.rs @@ -5,12 +5,6 @@ use crate::{Alignment, Allocator}; // `std.heap.c_allocator` — the libc-malloc-backed allocator. use super::C_ALLOCATOR as c_allocator; -/// A fallback zero-initializing allocator. -// -// Zig: `pub const allocator = Allocator{ .ptr = undefined, .vtable = &vtable };` -// `std.mem.Allocator` is a `{ptr, vtable}` fat struct — the Rust mapping is -// `&dyn bun_alloc::Allocator`, so the public export is a ZST implementing the -// trait. Consumers borrow `&ALLOCATOR` (coerces to `&dyn Allocator`). pub static ALLOCATOR: Z = Z; #[derive(Clone, Copy, Default)] diff --git a/src/bun_alloc/hashbrown_bridge.rs b/src/bun_alloc/hashbrown_bridge.rs index 1694b2f7328..992c5b653c2 100644 --- a/src/bun_alloc/hashbrown_bridge.rs +++ b/src/bun_alloc/hashbrown_bridge.rs @@ -24,10 +24,6 @@ use core::alloc::{Allocator, Layout}; use core::ptr::NonNull; -/// Implement `allocator_api2::alloc::Allocator` for a type that already -/// implements `core::alloc::Allocator`, by delegation. Only the two methods -/// hashbrown actually calls are forwarded; the rest keep the polyfill's -/// default bodies (which themselves call back into `allocate`/`deallocate`). macro_rules! bridge_allocator_api2 { ($t:ty) => { // SAFETY: every method is a 1:1 forward to the corresponding @@ -65,14 +61,6 @@ macro_rules! bridge_allocator_api2 { }; } -// `crate::DefaultAlloc` is the existing ZST marker for `bun.default_allocator` -// (Zig); here it gains `core::alloc::Allocator` (forwarding to -// `std::alloc::Global`, since `#[global_allocator] = Mimalloc` makes that the -// process default) plus the api2 bridge, so a single `A` type can back both a -// `hashbrown::HashMap<_, _, _, A>` table and its `Box<[u8], A>` keys. -// `std::alloc::Global` can't be used directly: orphan rules forbid us -// implementing the polyfill trait on it, and `allocator_api2::alloc::Global` -// doesn't implement `core::alloc::Allocator`. use crate::DefaultAlloc; // SAFETY: thin forwarder to `std::alloc::Global`, which upholds the diff --git a/src/bun_alloc/heap_breakdown.rs b/src/bun_alloc/heap_breakdown.rs index 36a0784559c..d5e19be5e65 100644 --- a/src/bun_alloc/heap_breakdown.rs +++ b/src/bun_alloc/heap_breakdown.rs @@ -11,18 +11,6 @@ type vm_size_t = usize; // TODO(port): `enable_asan` mapped to a cargo feature; verify the build wires this the same way. pub const ENABLED: bool = cfg!(debug_assertions) && cfg!(target_os = "macos") && !cfg!(bun_asan); -/// Zig: `pub fn getZone(comptime name: [:0]const u8) *Zone` -/// -/// Each comptime instantiation in Zig gets its own `static var zone` + `std.once`. -/// The faithful Rust translation is the crate-root `get_zone!` macro in lib.rs -/// that expands a fresh `OnceLock` per call site (per literal name). Not -/// duplicated here to avoid path-export collisions on macOS. - -/// Runtime `getZone(name)` — looks up (or creates) the per-name zone. The -/// `get_zone!` macro is the zero-cost form. This runtime path keys a -/// process-global map for callers that pass a non-literal name. -// TODO(port): could be replaced with a `#[heap_label]` derive that expands -// `get_zone!` directly. #[allow(clippy::assertions_on_constants)] pub fn get_zone(name: &[u8]) -> &'static Zone { debug_assert!( @@ -31,10 +19,6 @@ pub fn get_zone(name: &[u8]) -> &'static Zone { ); use core::cell::UnsafeCell; - // Map key = `name` (no NUL) so lookups match inserts. The NUL-terminated - // label handed to `malloc_set_zone_name` is stored as the map *value* - // (alongside the zone) to keep its allocation alive for 'static - // (PORTING.md §Forbidden: never `Box::leak`). struct ZoneTable(UnsafeCell, Vec, &'static Zone)>>); // SAFETY: the inner `Vec` is only accessed while `LOCK` is held. unsafe impl Sync for ZoneTable {} @@ -63,16 +47,6 @@ pub fn get_zone(name: &[u8]) -> &'static Zone { } bun_opaque::opaque_ffi! { - /// Zig: `pub const Zone = opaque { ... };` - /// - /// Opaque FFI handle for a macOS `malloc_zone_t`. - /// - /// The `UnsafeCell` field makes `Zone: !Freeze`, so a `&Zone` does not assert - /// immutability of the pointee. This is required because every malloc-zone FFI - /// call (`malloc_zone_memalign`, `malloc_zone_free`, …) mutates the zone's - /// internal state, and Zig models the handle as a freely-aliasing `*Zone`. - /// Without `UnsafeCell`, casting `&Zone as *const _ as *mut _` and writing - /// through it (via FFI) is UB under Stacked Borrows. pub struct Zone; } @@ -150,15 +124,6 @@ impl Zone { Zone::aligned_alloc(unsafe { &*(zone.cast::()) }, len, alignment) } - // Zig exposed a `pub const vtable: std.mem.Allocator.VTable` with - // { alloc, resize, remap = noRemap, free }. In Rust the equivalent is an - // `impl crate::Allocator for Zone` (see below); the raw vtable struct is a - // Zig-ism and is not materialized here, so the `resize`/`free` vtable thunks - // (and the `malloc_size` helper they used) are not ported. - // TODO(port): if `bun_alloc::Allocator` ever becomes a literal vtable struct - // (to match `std.mem.Allocator` ABI), reintroduce a `pub static VTABLE` - // along with the `resize`/`raw_free` thunks. - /// Zig: `pub fn allocator(zone: *Zone) std.mem.Allocator` pub fn allocator(&'static self) -> &'static dyn crate::Allocator { self @@ -202,19 +167,11 @@ impl Zone { unsafe { malloc_zone_free(self.as_mut_ptr(), ptr.cast()) }; } - /// Zig: `pub fn isInstance(allocator_: std.mem.Allocator) bool` - /// - /// Zig: `return allocator_.vtable == &vtable;` — implemented as a `TypeId` - /// identity check via the `Allocator::type_id()` hook. pub fn is_instance(allocator_: &dyn crate::Allocator) -> bool { allocator_.is::() } } -// `crate::Allocator` is a marker trait carrying `type_id()`; the Zig vtable -// methods (`alloc`/`resize`/`free`) are inherent on `Zone` above (`raw_alloc`, -// `resize`, `raw_free`). This impl makes `Zone` usable as `&dyn Allocator` -// for `is_instance` identity checks. impl crate::Allocator for Zone {} // TODO(port): move to bun_alloc_sys (or keep here gated `#[cfg(target_os = "macos")]` @@ -226,11 +183,6 @@ unsafe extern "C" { /// No preconditions; allocates a new zone (process-lifetime). pub(crate) safe fn malloc_create_zone(start_size: vm_size_t, flags: c_uint) -> *mut Zone; pub fn malloc_destroy_zone(zone: *mut Zone); - // `&Zone` is ABI-identical to libmalloc's `malloc_zone_t *` (thin non-null - // pointer to an `opaque_ffi!` `!Freeze` struct — interior mutation by C is - // sound). The reference type encodes the only pointer-validity precondition, - // so `safe fn` discharges the link-time proof for the pure-allocation entry - // points (alloc/calloc/valloc/memalign return null on failure). pub(crate) safe fn malloc_zone_malloc(zone: &Zone, size: usize) -> *mut c_void; pub(crate) safe fn malloc_zone_calloc( zone: &Zone, diff --git a/src/bun_alloc/lib.rs b/src/bun_alloc/lib.rs index a19e3febdd0..cdfbe4c662b 100644 --- a/src/bun_alloc/lib.rs +++ b/src/bun_alloc/lib.rs @@ -5,11 +5,6 @@ #![allow(clippy::disallowed_types)] #![feature(arbitrary_self_types_pointers)] #![feature(allocator_api)] -// `#[thread_local]` (vs the `thread_local!` macro) compiles to a bare -// `__thread` slot — single `mov reg, fs:[OFFSET]` access, no `LocalKey` -// `__getit()` wrapper, no lazy-init flag check, no dtor-registration probe. -// Used for the per-allocation hot-path TLS in `ast_alloc::AST_ALLOC`; matches -// Zig's `threadlocal var` semantics exactly. #![feature(thread_local)] use core::fmt::Write as _; @@ -44,12 +39,6 @@ impl Alignment { } } -// ── `std.c.max_align_t` alignment ───────────────────────────────────────── -// The `libc` crate does not expose `max_align_t` on every target Bun ships -// (missing on Windows MSVC and on FreeBSD aarch64), so those targets carry a -// local mirror of the Zig definition. Remaining non-Windows targets keep -// `libc::max_align_t` (which carries `long double`, align 16 on x86_64/aarch64; -// the {f64,i64,*const ()} fallback would silently downgrade to 8). #[cfg(windows)] #[repr(C)] struct MaxAlignT { @@ -59,10 +48,6 @@ struct MaxAlignT { } #[cfg(windows)] pub const MAX_ALIGN_T: usize = core::mem::align_of::(); -// Zig: `extern struct { a: c_longlong, b: c_longdouble }` — on AArch64 -// AAPCS64 `long double` is IEEE binary128, 16-byte aligned. The `libc` crate -// only defines `max_align_t` for FreeBSD on x86_64, so hardcode the ABI value -// for the aarch64 port. #[cfg(all(target_os = "freebsd", target_arch = "aarch64"))] pub const MAX_ALIGN_T: usize = 16; #[cfg(not(any(windows, all(target_os = "freebsd", target_arch = "aarch64"))))] @@ -75,11 +60,6 @@ pub struct AllocatorVTable { pub free: unsafe fn(*mut core::ffi::c_void, &mut [u8], Alignment, usize), } impl AllocatorVTable { - /// `alloc` impl that always fails. For vtables that only ever `free` an - /// externally-produced buffer (mmap region, plugin-owned memory, refcounted - /// foreign string) and never allocate or grow it. Zig has no `std.mem. - /// Allocator.noAlloc`; every Zig site hand-rolls `fn alloc(...) ?[*]u8 { - /// return null; }`. This is the Rust-side improvement. pub const NO_ALLOC: unsafe fn(*mut core::ffi::c_void, usize, Alignment, usize) -> *mut u8 = |_, _, _, _| core::ptr::null_mut(); pub const NO_RESIZE: unsafe fn( @@ -258,25 +238,12 @@ impl<'a> FixedBufferAllocator<'a> { } } -// PORTING.md §Allocators: AST crates thread an `Arena`; non-AST use Vec/Box -// (global mimalloc). `Arena` is now the real per-heap `MimallocArena` (matching -// Zig's `bun.allocators.MimallocArena`) — unlike `bumpalo::Bump`, it supports -// per-allocation free + realloc, so `ArenaVec` no longer leaks on grow. -// -// `bumpalo::Bump` is kept as `Bump` for genuinely bump-only scratch (parser -// node stores that are never resized and where the no-op `deallocate` is the -// point). pub use mimalloc_arena::MimallocArena; pub type Arena = MimallocArena; /// `bumpalo::Bump` — kept for genuinely bump-only scratch that's never resized. pub type Bump = bumpalo::Bump; mod baby_vec; pub use baby_vec::BabyVec; -/// Arena-backed `Vec` with `u32` length/capacity — port of Zig's -/// `BabyList(T)`. 24 B (vs 32 B for `Vec`); the -/// allocator handle is kept inline for lifetime checking. Growth/free route -/// through `<&MimallocArena as Allocator>` (= `mi_heap_realloc_aligned` / -/// `mi_free`); reclaimed on arena `reset`/`Drop`. pub type ArenaVec<'a, T> = BabyVec<'a, T>; pub use mimalloc_arena::{ArenaString, ArenaVecExt}; @@ -293,23 +260,6 @@ where v } -/// Re-tag an [`ArenaVec`]'s allocator handle to `dst` without copying data. -/// -/// Zig parity: `BabyList.transferOwnership` (collections/baby_list.zig). Zig's -/// `BabyList` is allocator-erased — the linker passes a different allocator at -/// each `append(allocator, ..)` call site; the Rust port stores `&'a Arena` in -/// the `Vec`, so the equivalent is swapping that field. -/// -/// Sound because `<&MimallocArena as Allocator>` is heap-agnostic on the -/// existing buffer: -/// - `deallocate` → `mi_free(ptr)`: looks up the owning heap from the pointer's -/// page metadata; works from any thread on any heap's allocation. -/// - `grow`/`shrink` → `mi_heap_realloc_aligned(dst, ptr, ..)`: returns `ptr` -/// in-place if it fits (read-only `mi_usable_size`), else allocs on `dst`, -/// `memcpy`s, then `mi_free(ptr)`. -/// -/// The original arena is never `mi_heap_malloc`-ed from again via this `Vec`, -/// so the [`MimallocArena`] single-thread-alloc contract is preserved. #[inline] pub fn transfer_arena<'a, T>(v: &mut ArenaVec<'a, T>, dst: &'a MimallocArena) { v.set_allocator(dst); @@ -330,27 +280,6 @@ macro_rules! arena_format { /// `bun.use_mimalloc` — false under ASAN, where the global allocator is `std::alloc::System`. pub const USE_MIMALLOC: bool = cfg!(not(bun_asan)); -// ── Allocator-vtable modules: per-module disposition (PORTING.md §Allocators) ── -// -// These modelled Zig's `std.mem.Allocator` vtable. With `#[global_allocator]` -// + `Arena = bumpalo::Bump`, most callers should drop the allocator param -// PORTING.md §Forbidden) so the .zig↔.rs diff pass has a real body to compare; -// callers are migrated incrementally. -// -// MimallocArena → prefer `bun_alloc::Arena` (= bumpalo::Bump) -// NullableAllocator → prefer `Option<&Arena>` or drop the param -// MaxHeapAllocator → debug-only cap (single-allocation arena) -// BufferFallbackAllocator → PORTING.md "StackFallbackAllocator → just use the heap" -// fallback → libc-malloc + zeroing wrapper (Zig std.heap.c_allocator) -// maybe_owned → prefer `std::borrow::Cow` / `bun_ptr::Owned` -// heap_breakdown → macOS malloc_zone_* per-tag heaps (debug builds) -// basic → `impl GlobalAlloc for Mimalloc` above is the canonical impl -// -// LinuxMemFdAllocator, MimallocArena (the vtable impl) -// import bun_core/sys/runtime/collections and so live in -// `bun_runtime::allocators`; callers import from -// there directly. -// #[path = "BufferFallbackAllocator.rs"] pub mod buffer_fallback_allocator; pub mod fallback; @@ -430,11 +359,6 @@ pub mod default_alloc { if ptr.is_null() { return 0; } - // Under `bun_asan` the global allocator is `std::alloc::System`, so the - // size must come from libc, not mimalloc — and the symbol differs per - // OS (`malloc_usable_size` on Linux, `malloc_size` on macOS). `bun_asan` - // is only ever set on Linux or macOS, so the catch-all (non-asan, every - // `check-all` target including Windows) stays on mimalloc. #[cfg(all(bun_asan, target_os = "linux"))] return unsafe { libc::malloc_usable_size(ptr.cast_mut()) }; #[cfg(all(bun_asan, target_os = "macos"))] @@ -537,11 +461,6 @@ mod hashbrown_bridge; /// `allocator-api2`. pub use allocator_api2::alloc::Allocator as HashbrownAllocator; -// ── tier-0 local primitives ─────────────────────────────────────────────── -// Real, self-contained helpers used by the BSS containers below. These are the -// canonical tier-0 definitions, re-exported by higher tiers (`bun_paths::SEP_STR`, -// `bun_core::strings::trim_right`, `bun_core::strings::trim_right`). - /// Zig: `std.fs.path.sep_str` — `"\\"` on Windows, `"/"` elsewhere. /// Canonical tier-0 definition; re-exported by `bun_paths::SEP_STR`. pub const SEP_STR: &str = if cfg!(windows) { "\\" } else { "/" }; @@ -579,17 +498,6 @@ pub fn trim<'a>(s: &'a [u8], chars: &[u8]) -> &'a [u8] { trim_right(trim_left(s, chars), chars) } -// ─── ascii-lowercase helpers ────────────────────────────────────────────── -// Sunk from bun_core::strings so bun_alloc::BSSList::append_lower_case can call -// it without a dep cycle (bun_core → bun_alloc, not the reverse). bun_core -// re-exports both names so all existing callers of -// `bun_core::strings::copy_lowercase` / `bun_core::immutable::copy_lowercase` -// keep compiling unchanged. - -/// Zig: `strings.copyLowercase` (src/string/immutable.zig). ASCII-lowercase -/// `in_` into `out` (which must be at least `in_.len()`), returning the -/// written prefix. Memcpy-runs + per-uppercase-byte fixup; identical output -/// to a byte-at-a-time `to_ascii_lowercase` zip. pub fn copy_lowercase<'a>(in_: &[u8], out: &'a mut [u8]) -> &'a [u8] { let mut in_slice = in_; // PORT NOTE: reshaped for borrowck — track output offset instead of reslicing &mut. @@ -614,11 +522,6 @@ pub fn copy_lowercase<'a>(in_: &[u8], out: &'a mut [u8]) -> &'a [u8] { &out[0..in_.len()] } -/// Zig: `strings.copyLowercaseIfNeeded` (src/string/immutable.zig:664). If -/// `in_` contains no ASCII uppercase byte, returns `in_` unchanged and leaves -/// `out` UNTOUCHED. Otherwise identical to [`copy_lowercase`]: writes the -/// lowercased bytes into `out[..in_.len()]` and returns that prefix. Both -/// borrows share `'a` so the return may alias either. pub fn copy_lowercase_if_needed<'a>(in_: &'a [u8], out: &'a mut [u8]) -> &'a [u8] { if in_.iter().any(u8::is_ascii_uppercase) { copy_lowercase(in_, out) @@ -627,10 +530,6 @@ pub fn copy_lowercase_if_needed<'a>(in_: &'a [u8], out: &'a mut [u8]) -> &'a [u8 } } -/// Lowercase `input` into a fresh `[u8; N]` stack buffer, returning -/// `Some((buf, input.len()))` or `None` if `input.len() > N`. The unused tail -/// of `buf` is zero-filled. Covers the ubiquitous "lowercase a short key into -/// a stack buffer, then look it up in a phf/length-gated map" pattern. #[inline] pub fn ascii_lowercase_buf(input: &[u8]) -> Option<([u8; N], usize)> { if input.len() > N { @@ -654,12 +553,6 @@ pub(crate) fn alloc_result( .ok_or(core::alloc::AllocError) } -/// Port of `std.fmt.count`: number of bytes the formatted args would produce. -/// -/// Drives a discarding `fmt::Write` that only sums `s.len()` — no allocation, -/// no UTF-8 validation beyond what the formatter already did. Lives here in -/// T0 so higher tiers (`bun_core::fmt::count` re-exports this) and `bun_alloc` -/// itself can share the single implementation. #[inline] pub fn fmt_count(args: core::fmt::Arguments<'_>) -> usize { struct Discarding(usize); @@ -677,13 +570,6 @@ pub fn fmt_count(args: core::fmt::Arguments<'_>) -> usize { w.0 } -/// `core::fmt::Write` adapter over a borrowed `&mut [u8]` — the engine behind -/// [`buf_print`] / [`buf_print_len`] (and `bun_core::fmt::buf_print_z`). -/// -/// This is the single port of Zig `std.fmt.bufPrint`'s internal cursor. It -/// lives at T0 so `bun_alloc` itself can use it (`BSSStringList::print`); T1 -/// `bun_core::fmt` re-exports it and adds an `io::Write` impl so the same -/// struct also serves as Zig's `std.io.fixedBufferStream` for write-only sites. pub struct SliceCursor<'a> { pub buf: &'a mut [u8], pub at: usize, @@ -731,18 +617,6 @@ pub fn buf_print_len( Ok(c.at) } -// ── RAII Mutex ──────────────────────────────────────────────────────────── -// Zig's `bun.Mutex` exposes bare `lock()`/`unlock()` (no guard). The BSS -// containers below need to hold the lock across `&mut self` method calls, so -// the returned [`MutexGuard`] deliberately erases its borrow of `self` — it -// stores the `std::sync::MutexGuard` lifetime-extended to `'static` (lifetimes -// are erased at codegen, so this is a layout no-op). This is sound because -// every `Mutex` here lives inside a `'static` BSS singleton (see `instance()` -// below), so the pointee always outlives the guard. -// -// LAYERING: `bun_alloc` is below `bun_threading` in the crate graph, so the -// futex-backed `bun_threading::Mutex` is unavailable here; `std::sync` (itself -// futex-backed since Rust 1.62) is the dependency-free stand-in. pub struct Mutex(std::sync::Mutex<()>); impl Mutex { pub const fn new() -> Self { @@ -805,15 +679,6 @@ macro_rules! oom_from_alloc { )+ }; } -/// The mimalloc-backed `#[global_allocator]` payload. -/// -/// Per PORTING.md "Prereq for every crate": -/// `#[global_allocator] static ALLOC: bun_alloc::Mimalloc = bun_alloc::Mimalloc;` -/// must be set at the binary root before any `Box`/`Rc`/`Arc`/`Vec` mapping is valid. -/// -/// Mirrors `src/bun_alloc/basic.zig` `c_allocator` vtable, using mimalloc's -/// `MI_MAX_ALIGN_SIZE` (16) fast-path: alignments ≤16 go through `mi_malloc`, -/// larger through `mi_malloc_aligned`. `mi_free` handles both. pub struct Mimalloc; use mimalloc::MI_MAX_ALIGN_SIZE; @@ -916,16 +781,6 @@ pub fn usable_size(ptr: *const u8) -> usize { // Symbols hoisted DOWN into T0 so higher tiers can re-import without cycles. // ────────────────────────────────────────────────────────────────────────── -// ── out_of_memory ───────────────────────────────────────────────────────── -// Source: src/bun.zig `outOfMemory()` → `crash_handler.crashHandler(.out_of_memory, ..)`. -// -// `bun_alloc` is T0 and cannot depend on `bun_crash_handler`, so the upward -// call is routed through a link-time `extern "Rust"` symbol defined by -// `bun_crash_handler`. Resolved at link time → the target lives in read-only -// `.text`, so memory corruption cannot redirect it (the previous `AtomicPtr` -// slot was writable). Under `cfg(test)` (this crate's standalone test binary -// does not link `bun_crash_handler`) the fallback is a direct abort. - #[cold] #[inline(never)] pub fn out_of_memory() -> ! { @@ -967,10 +822,6 @@ pub fn page_size() -> usize { } #[cfg(windows)] { - // Local `#[repr(C)]` mirror so this crate stays leaf (no - // `windows-sys` dep — see PORTING.md §Crate map). Only - // `dwPageSize` is read; the rest is opaque padding sized to - // `sizeof(SYSTEM_INFO)` (48 bytes on both x86 and x64). #[repr(C)] struct SystemInfo { _w_processor_architecture: u16, @@ -980,10 +831,6 @@ pub fn page_size() -> usize { _ints: [u32; 5], } unsafe extern "system" { - // `&mut SystemInfo` is ABI-identical to `LPSYSTEM_INFO` (thin - // non-null pointer to a `#[repr(C)]` struct); kernel32 fully - // initialises every field. No other preconditions, so `safe fn` - // discharges the link-time proof and the caller needs no `unsafe`. safe fn GetSystemInfo(lpSystemInfo: &mut SystemInfo); } let mut info = SystemInfo { @@ -1016,13 +863,6 @@ pub mod wtf { } } -// ── String (bun.String) — TYPE_ONLY landing ─────────────────────────────── -// Source: src/string/string.zig + src/jsc/ZigString.zig + src/string/wtf.zig. -// Layout-only (#[repr(C)]) so T0/T1 crates can name the type; rich methods -// (toJS, toUTF8, WTF refcounting) remain in bun_str via extension traits. -// PORTING.md: "#[repr(C)] struct { tag: u8, value: StringValue } — NOT a Rust -// enum (C++ mutates tag and value independently across FFI)." - /// Port of `bun.String.Tag`. #[repr(u8)] #[derive(Clone, Copy, PartialEq, Eq, Debug)] @@ -1042,17 +882,6 @@ pub const ZS_GLOBAL_BIT: usize = 1usize << 62; pub const ZS_16BIT_BIT: usize = 1usize << 63; pub const ZS_UNTAG_MASK: usize = (1usize << 53) - 1; -/// Port of `jsc.ZigString` — extern struct `{ ptr: [*]const u8, len: usize }`. -/// -/// **Canonical storage layout.** `bun_core::string::ZigString` is a -/// `#[repr(transparent)]` newtype over this struct (so the FFI layout has ONE -/// source of truth) and adds the encoding-aware/allocating methods via -/// `Deref`/`DerefMut`. The pointer-tag accessors (`is_*` / `mark_*` / -/// `untagged` / `slice` / `utf16_slice_aligned`) live HERE so the T0 -/// `bun_alloc::String` union and `WTFStringImplStruct::to_zig_string` can use -/// them without an upward dep on `bun_core`. Higher-tier callers should name -/// `bun_core::ZigString`; reaching the inherent methods through `Deref` is the -/// intended path. #[repr(C)] #[derive(Clone, Copy)] pub struct ZigString { @@ -1195,15 +1024,6 @@ impl ZigString { } } -/// Port of `WTFStringImplStruct` — must match WebKit's `WTF::StringImpl` layout. -/// -/// `m_ref_count` / `m_hash_and_flags` are `Cell` (not bare `u32`) because -/// `r#ref`/`deref`/`ensure_hash` hand a `*const Self` derived from `&self` to -/// C++ FFI that **writes** those fields. Without `UnsafeCell` the struct is -/// `Freeze`, the `&self` borrow asserts the whole pointee is read-only, and -/// the FFI write is a Stacked-Borrows violation (LLVM may also CSE the -/// pre-/post-FFI `ref_count()` loads). `Cell` is `repr(transparent)` over -/// `UnsafeCell`, so the C ABI layout is unchanged. #[repr(C)] pub struct WTFStringImplStruct { pub m_ref_count: core::cell::Cell, @@ -1267,27 +1087,12 @@ impl WTFStringImplStruct { // WTF::StringImpl::hasAtLeastOneRef self.m_ref_count.get() > 0 } - /// Atomic view of `m_ref_count`. The C++ field is - /// `std::atomic m_refCount` (StringImpl.h:163); we model it as - /// `Cell` for the read-only accessors above but `ref`/`deref` must - /// issue real atomic RMWs to match `WTF::StringImpl::ref`/`deref` exactly. - /// `Cell` is `repr(transparent)` over `UnsafeCell` and - /// `AtomicU32` is `repr(C, align(4))` over `UnsafeCell`: same size, - /// same alignment (`m_ref_count` is the first field of a `#[repr(C)]` - /// struct so it is 4-aligned), so the in-place reborrow is sound. #[inline(always)] fn ref_count_atomic(&self) -> &AtomicU32 { // SAFETY: layout-compatible reborrow of `UnsafeCell` as // `AtomicU32`; see doc comment above. unsafe { AtomicU32::from_ptr(self.m_ref_count.as_ptr()) } } - /// Inline port of `WTF::StringImpl::ref()` (StringImpl.h:1181). - /// - /// Cross-language LTO does not inline the `Bun__WTFStringImpl__ref` C++ - /// shim into Rust callers (2151 out-of-line `callq` sites in the release - /// binary vs 0 in the Zig build), so the one-instruction body is - /// reimplemented here. `Relaxed` matches WebKit's - /// `m_refCount.fetch_add(s_refCountIncrement, std::memory_order_relaxed)`. #[inline] pub fn r#ref(&self) { let old = self @@ -1301,14 +1106,6 @@ impl WTFStringImplStruct { ); let _ = old; } - /// Inline port of `WTF::StringImpl::deref()` (StringImpl.h:1193). - /// - /// Hot path is a single `lock xadd`; only the last-ref branch crosses FFI - /// to `StringImpl::destroy`. `Relaxed` matches WebKit's - /// `m_refCount.fetch_sub(s_refCountIncrement, std::memory_order_relaxed)`; - /// WTF relies on the static-string flag bit (0x1) to keep static strings' - /// counters from ever equalling `s_refCountIncrement`, so no separate - /// `isStatic()` check is needed. #[inline] pub fn deref(&self) { let old = self @@ -1331,10 +1128,6 @@ impl WTFStringImplStruct { vtable: StringImplAllocator::VTABLE_PTR, } } - /// Borrow `len` raw bytes from `m_ptr`. The `latin1` arm of the `repr(C)` - /// union is a valid byte pointer regardless of encoding (both arms share - /// the same offset). Centralises the `from_raw_parts(m_ptr.latin1, …)` used - /// by `byte_slice` / `latin1_slice` / `utf8_slice`. #[inline(always)] pub fn raw_bytes(&self, len: usize) -> &[u8] { // SAFETY: `m_ptr.latin1` points at the impl's character buffer for the @@ -1395,14 +1188,6 @@ impl WTFStringImplStruct { } unsafe extern "C" { - // `&WTFStringImplStruct` is ABI-identical to the C++ `StringImpl*` (thin - // non-null pointer to a `#[repr(C)]` struct). C++-side mutation lands in - // `m_ref_count` / `m_hash_and_flags`, both `Cell`, so writes through - // a `&`-derived pointer are sound. The type encodes the only validity - // precondition, so `safe fn` discharges the link-time proof. - // `ref`/`deref` are inlined in Rust above; only the cold last-ref - // `destroy` path crosses FFI. `*const` + `unsafe`: it frees the - // allocation backing the pointer. pub fn Bun__WTFStringImpl__destroy(this: *const WTFStringImplStruct); // Kept for Zig callers (`src/string/wtf.zig`); Rust no longer calls these. pub safe fn Bun__WTFStringImpl__ref(this: &WTFStringImplStruct); @@ -1416,13 +1201,6 @@ unsafe extern "C" { ) -> bool; } -/// Port of `bun.String.StringImplAllocator` (src/string/wtf.zig). -/// -/// A `std.mem.Allocator` vtable whose `ptr` is a `WTFStringImpl`; `alloc` bumps -/// the refcount, `free` derefs. Hoisted into `bun_alloc` (which already owns -/// `AllocatorVTable` and the `WTFStringImplStruct` layout) so the -/// `is_wtf_allocator` vtable-identity check is a local pointer compare — no -/// upward dependency on `bun_string` and no runtime fn-ptr hook. #[allow(non_snake_case)] // Zig namespace `bun.String.StringImplAllocator` pub mod StringImplAllocator { use super::{Alignment, AllocatorVTable, WTFStringImplStruct}; @@ -1474,10 +1252,6 @@ pub union StringImpl { // .StaticZigString aliases .zig_string; .Dead/.Empty are zero-width. } -/// Port of `bun.String` (a.k.a. `BunString` in C++). -/// -/// 5-variant tagged union over WTF-backed and Zig-slice-backed strings. NOT a -/// Rust `enum` because C++ mutates `tag` and `value` independently across FFI. #[repr(C)] #[derive(Clone, Copy)] pub struct String { @@ -1508,11 +1282,6 @@ impl String { }, }; - /// Borrow the live `WTF::StringImpl` backing this string. - /// - /// Centralises the union-field read + raw-ptr deref that `to_zig_string` / - /// `length` / `is_8bit` each open-coded. Callers branch on - /// `self.tag == WTFStringImpl` first (debug-asserted). #[inline(always)] fn wtf_impl(&self) -> &WTFStringImplStruct { debug_assert_eq!(self.tag, Tag::WTFStringImpl); @@ -1562,10 +1331,6 @@ impl String { } } - /// Zig `eqlComptime` — compare against a (typically literal) byte slice. - /// PERF(port): Zig dispatched to SIMD `bun.strings.eqlComptime*`; this T0 - /// version uses scalar `==` / widening compare. Re-route to - /// `bun_core::strings` via inlining if it shows up on a hot path. pub fn eql_comptime(&self, other: &[u8]) -> bool { let zs = self.to_zig_string(); if zs.is_16bit() { @@ -1664,13 +1429,6 @@ pub unsafe fn default_free(ptr: *mut u8, len: usize) { basic::C_ALLOCATOR.raw_free(buf, Alignment::from_byte_units(1), 0); } -/// Zig: `bun.default_allocator.dupe(u8, src)` for raw `[]u8` not owned by a -/// `Vec`/`Box` — symmetric with [`default_free`]. Returns a `&'static [u8]` -/// view onto a fresh mimalloc allocation; caller is responsible for pairing -/// with `default_free(ptr, len)`. -/// -/// Empty input borrows the static empty slice (no allocation; `default_free` -/// no-ops on `len == 0`). pub fn default_dupe(src: &[u8]) -> &'static [u8] { if src.is_empty() { return b""; @@ -1708,13 +1466,6 @@ pub unsafe fn secure_zero(p: *mut u8, len: usize) { core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst); } -/// Memory is typically not decommitted immediately when freed. Sensitive -/// information kept in memory can be read until the OS decommits it or the -/// allocator reuses it. Zero it before dropping. -/// -/// Zig used `std.crypto.secureZero` then `allocator.free`; Rust drops the -/// allocator param (global mimalloc) and uses [`secure_zero`] so the zeroing -/// cannot be elided by the optimizer. pub fn free_sensitive(mut slice: Box<[T]>) { // SAFETY: `slice` is exclusively owned; writing `size_of_val` zero bytes // over its storage is sound for `T: Copy` (no drop glue, no invariants on @@ -1748,11 +1499,6 @@ pub unsafe fn free_sensitive_cstr(p: *const core::ffi::c_char) { } } -// ────────────────────────────────────────────────────────────────────────── -// IndexType — `packed struct(u32) { index: u31, is_overflow: bool = false }` -// Zig packed-struct fields are LSB-first: bits 0..=30 = index, bit 31 = is_overflow. -// ────────────────────────────────────────────────────────────────────────── - #[repr(transparent)] #[derive(Clone, Copy, PartialEq, Eq, Default)] pub struct IndexType(u32); @@ -1795,63 +1541,16 @@ pub enum ItemStatus { NotFound, } -// ────────────────────────────────────────────────────────────────────────── -// BSSList / BSSStringList / BSSMapInner — real method bodies follow below. -// Per-monomorphization statics are emitted at the declare site via the -// `bss_list!` / `bss_string_list!` / `bss_map_inner!` / `bss_map!` macros -// (`SyncUnsafeCell>` + `Once` + `init_at`). `init()` is a -// thin heap-allocating wrapper for callers that manage their own once-guard. -// ────────────────────────────────────────────────────────────────────────── - -// ────────────────────────────────────────────────────────────────────────── -// `bun.allocators` namespace shim -// -// Zig exposed this file as `bun.allocators.*`; downstream crates were ported -// against that path (`use bun_alloc::allocators;`). Re-export the crate root -// so `allocators::IndexType`, `allocators::BSSMapInner`, etc. resolve without -// rewriting every callsite. -// ────────────────────────────────────────────────────────────────────────── pub mod allocators { pub use super::*; } -// ────────────────────────────────────────────────────────────────────────── -// Per-monomorphization singleton macros -// -// Zig defines `pub var instance: *Self = undefined; pub var loaded = false;` -// *inside* the generic type, giving one static per instantiation. Rust forbids -// generic statics, so the storage is emitted at the *declare site* instead: -// -// bss_string_list! { pub dirname_store: 4096, 129 } -// // → static STORAGE: SyncUnsafeCell>> -// // pub fn dirname_store() -> *mut BSSStringList<4096,129> -// -// The accessor lazily field-initializes via `init_at` under `std::sync::Once`. -// Returning `&'static mut` is the same aliasing contract as Zig's global -// `instance` pointer — callers must not hold overlapping unique borrows. -// ────────────────────────────────────────────────────────────────────────── - -/// Emit a process-lifetime singleton accessor for any type with an -/// `unsafe fn init_at(*mut Self)` in-place initializer. Storage is a single -/// `AtomicPtr` (8 bytes) per declare site; the value itself is heap-allocated -/// on first call (Zig spec: `default_allocator.create(Self)`). #[macro_export] macro_rules! bss_singleton { ($(#[$m:meta])* $vis:vis fn $name:ident() -> $ty:ty) => { $(#[$m])* #[inline(always)] $vis fn $name() -> *mut $ty { - // Zig's spec is `default_allocator.create(Self)` on first access - // (heap, process-lifetime). Store an 8-byte heap pointer and - // allocate on first call, matching the spec. - // - // Hot path: this accessor is hit per-append/get from the resolver - // (`DirnameStore::append`, `EntriesMap::get`, …). Zig reads a - // plain `*Self` global; the previous `Once::call_once` fast-path - // is an Acquire load + cmp + branch + Relaxed load that *cannot* - // inline across crates (it's a call into `std::sys::sync::once`). - // Open-code the double-checked-init so the post-init path is one - // Acquire load + null-test inlined into every caller. static STORAGE: ::core::sync::atomic::AtomicPtr<$ty> = ::core::sync::atomic::AtomicPtr::new(::core::ptr::null_mut()); let p = STORAGE.load(::core::sync::atomic::Ordering::Acquire); @@ -1865,11 +1564,6 @@ macro_rules! bss_singleton { #[inline(never)] fn slow() -> *mut $ty { let p = $crate::bss_heap_init::<$ty>(<$ty>::init_at).as_ptr(); - // Race: two threads may both reach here. The mmap'd region is - // process-lifetime and never freed, so the loser is leaked - // (≤ one per declare site, which in practice is single-threaded - // — `FileSystem::init` runs once on the main thread). The CAS - // is the publication barrier. match STORAGE.compare_exchange( ::core::ptr::null_mut(), p, @@ -1885,12 +1579,6 @@ macro_rules! bss_singleton { }; } -/// Heap-allocate a fresh `T` via mimalloc and run its in-place `init_at` initializer. -/// -/// Shared body of the `BSSList`/`BSSStringList`/`BSSMapInner`/`BSSMap` `init()` shims — -/// Zig's `default_allocator.create(Self)` followed by field-init. The once-guard -/// (Zig's `loaded` flag) is the *caller's* responsibility; use the `bss_*!` macros -/// for the canonical per-monomorphization singleton. #[doc(hidden)] // Public only for the `bss_singleton!` macro expansion in dependent crates. #[inline] pub fn bss_heap_init(init_at: unsafe fn(*mut T)) -> NonNull { @@ -1904,29 +1592,6 @@ pub fn bss_heap_init(init_at: unsafe fn(*mut T)) -> NonNull { ptr } -/// Reserve `size` bytes of demand-zero-faulted, process-lifetime storage. -/// -/// On unix this carves a sub-range out of a single process-wide -/// `mmap(MAP_PRIVATE|MAP_ANONYMOUS|MAP_NORESERVE)` arena: pages are not -/// committed until first written to, so a 532 KiB `BSSStringList` backing -/// buffer that only ever sees a handful of filenames touches one or two pages -/// instead of all 130. On Windows this falls back to `mi_zalloc_aligned` -/// (eager commit, but still all-zeros so callers may rely on that uniformly). -/// -/// The mapping is **never freed** — these are Zig-port `.bss`-semantics -/// singletons. Do not call from code paths that need to release the storage. -/// -/// **Coalesced arena.** In Zig these singletons are linker-adjacent `.bss` -/// globals: one VMA, demand-faulted page-by-page. The original Rust port -/// `mmap`ed each one separately, costing 6 `mmap` syscalls + 6 VMAs on the -/// `bun run ` path (≈2 MiB total across `entry_store_backing`, -/// `dirname_store_backing`, `hash_map_instance`, …) before any user code -/// runs. We instead bump-allocate every request out of one lazily-mapped -/// [`BSS_ARENA_SIZE`] region, restoring the single-VMA `.bss` locality and -/// dropping the syscall count to 1. Requests that overflow the arena (none -/// today; the headroom is ~2×) fall through to a dedicated `mmap`. -/// -/// Returned pointer is `align`-aligned (`align ≤ 4096`). #[doc(hidden)] #[inline] pub fn bss_lazy_bytes(size: usize, align: usize) -> NonNull { @@ -1937,30 +1602,13 @@ pub fn bss_lazy_bytes(size: usize, align: usize) -> NonNull { bss_arena_bump(size, align) }; #[cfg(not(unix))] - let ptr = { - // Windows: `VirtualAlloc(MEM_RESERVE)`-only would require commit-on-touch - // plumbing through a guard-page handler. The largest singleton is ~1.3 MiB - // and Windows already faults `.bss` eagerly per-page on first write anyway, - // so the simpler eager allocation is kept. Use `mi_zalloc_aligned` (not - // `mi_malloc`) so callers can uniformly rely on all-zeros — `init_at` - // bodies skip writing zero-valued fields. - mimalloc::mi_zalloc_aligned(size, align).cast::() - }; + let ptr = { mimalloc::mi_zalloc_aligned(size, align).cast::() }; NonNull::new(ptr).expect("OOM") } -/// Size of the shared demand-zero arena backing every `bss_*!` singleton on -/// unix. Sum of all live monomorphizations on the `bun run` path is ≈2 MiB -/// (`entry_store_backing` 1,216,560 B + `dirname_store_backing` 528,384 B + -/// `hash_map_instance` 229,440 B + slice/key buffers); 4 MiB leaves ~2× -/// headroom. `MAP_NORESERVE` means the unused tail costs only address space. #[cfg(unix)] const BSS_ARENA_SIZE: usize = 4 * 1024 * 1024; -/// Bump-allocate `size` bytes at `align` out of the process-wide `.bss` arena, -/// mapping it on first call. Returns a pointer into a `MAP_ANONYMOUS|MAP_NORESERVE` -/// region (zero-on-read, demand-faulted). Falls back to a dedicated `mmap` if -/// the arena is exhausted. Never returns null. #[cfg(unix)] fn bss_arena_bump(size: usize, align: usize) -> *mut u8 { use core::sync::atomic::{AtomicPtr, AtomicUsize, Ordering}; @@ -1968,10 +1616,6 @@ fn bss_arena_bump(size: usize, align: usize) -> *mut u8 { static BASE: AtomicPtr = AtomicPtr::new(core::ptr::null_mut()); static CURSOR: AtomicUsize = AtomicUsize::new(0); - // Resolve the arena base. Fast path is one Acquire load; the cold path - // maps the 4 MiB region once and publishes via CAS. A losing racer's - // mapping is leaked (≤ one per process; `MAP_NORESERVE` so it costs no - // committed memory) — same race policy as `bss_singleton!`. let mut base = BASE.load(Ordering::Acquire); if base.is_null() { #[cold] @@ -1991,10 +1635,6 @@ fn bss_arena_bump(size: usize, align: usize) -> *mut u8 { }; } - // Bump the cursor: round up to `align`, reserve `size`. CAS loop because - // alignment padding makes the increment input-dependent. Contention is - // ~nil (called a handful of times from `Transpiler::init` on the main - // thread); the loop is for correctness, not throughput. let mut cur = CURSOR.load(Ordering::Relaxed); loop { let aligned = (cur + align - 1) & !(align - 1); @@ -2046,12 +1686,6 @@ fn bss_mmap_noreserve(len: usize) -> *mut u8 { if p == libc::MAP_FAILED { crate::out_of_memory(); } - // LSan only scans data/BSS, stacks, and malloc-tracked heap for live - // pointers. This anonymous mapping is none of those, so any `Box`/`Vec` - // whose owning pointer lives inside a `bss_*!` singleton (e.g. the - // resolver's `EntriesOption` cache) is reported as a leak — which then - // forces every subprocess to spend ~5s in llvm-symbolizer matching the - // suppression. Register the mapping as a root region so LSan walks it. #[cfg(bun_asan)] { unsafe extern "C" { @@ -2062,11 +1696,6 @@ fn bss_mmap_noreserve(len: usize) -> *mut u8 { p.cast::() } -/// Reserve `count` elements of `T` as a lazy-faulted slice. See [`bss_lazy_bytes`]. -/// -/// Returns `NonNull<[MaybeUninit]>`: bytes are zero-on-read but treated as -/// logically uninitialized — callers must gate reads on a separate `used` -/// counter (Zig leaves the array `undefined` and never reads past `used`). #[doc(hidden)] #[inline] pub fn bss_lazy_slice(count: usize) -> NonNull<[MaybeUninit]> { @@ -2116,13 +1745,6 @@ mod __bss_macro_smoke { crate::bss_map! { _m : u32, 4, 8, false } } -// ────────────────────────────────────────────────────────────────────────── -// heap_breakdown — macOS `malloc_zone_*` per-tag heaps (debug-only) -// -// Full port lives in `heap_breakdown.rs`. It compiles on all targets: on -// non-macOS the FFI surface is `unreachable!()` behind `ENABLED == false`. -// ────────────────────────────────────────────────────────────────────────── - #[path = "heap_breakdown.rs"] pub mod heap_breakdown; @@ -2144,11 +1766,6 @@ macro_rules! get_zone { }}; } -// ────────────────────────────────────────────────────────────────────────── -// IndexMap / Result -// (`IndexType`, `ItemStatus`, `NOT_FOUND`, `UNASSIGNED` defined above.) -// ────────────────────────────────────────────────────────────────────────── - type HashKeyType = u64; /// Zig `IndexMapContext` — identity hash on a u64 key. Keys here are already @@ -2414,24 +2031,6 @@ impl OverflowList { // BSSList // ────────────────────────────────────────────────────────────────────────── -/// "Formerly-BSSList" -/// It's not actually BSS anymore. -/// -/// We do keep a pointer to it globally, but because the data is not zero-initialized, it ends up -/// taking space in the object file. We don't want to spend 1-2 MB on these structs. -/// -/// TODO(port): const-generic arithmetic (`COUNT = _COUNT * 2`) and per-monomorphization -/// a raw mutable INSTANCE static are not expressible on stable Rust. Instantiate per use-site -/// via `macro_rules!` or pin concrete `COUNT` constants. -/// -/// `#[repr(C)]` with the small mutated scalars (`mutex`, `head`, `used`, -/// `tail`'s header) laid out *before* the giant `backing_buf` array. Storage -/// comes from [`bss_lazy_bytes`] (anonymous mmap, demand-zero), so each page -/// faults only on first write. With default repr rustc placed `used: u32` -/// *after* `backing_buf` (~1.2 MB into the largest instantiation), so -/// `init_at`'s startup writes faulted tail pages Zig never touches. With this -/// layout every startup write lands in page 0 of the mapping; subsequent pages -/// fault only as `append` actually fills them. #[repr(C)] pub struct BSSList { pub mutex: Mutex, @@ -2453,17 +2052,8 @@ unsafe impl Sync for BSSList { pub used: AtomicU16, @@ -2522,14 +2112,6 @@ impl BSSList { pub const CHUNK_SIZE: usize = BSS_LIST_CHUNK_SIZE; const MAX_INDEX: usize = COUNT - 1; - // Zig: `pub var instance: *Self = undefined; pub var loaded = false;` - // Rust cannot define generic statics, so the per-monomorphization storage is - // emitted at the *declare site* via `bss_list! { name: T, N }` (see macro - // below), which owns a `SyncUnsafeCell>` + `Once` and - // calls `init_at` on first access. `init()` is kept for callers that manage - // their own once-guard (e.g. `dir_info::hash_map_instance`); it heap-allocs - // a fresh instance each call. - #[inline] pub fn block_index(index: u32 /* u31 */) -> usize { index as usize / BSS_LIST_CHUNK_SIZE @@ -2575,10 +2157,6 @@ impl BSSList { } pub fn exists(&self, value: &[u8]) -> bool { - // Zig: `isSliceInBuffer(value, &instance.backing_buf)` — pointer-range check - // against the backing storage as raw bytes. Done with addresses rather - // than forming a `&[u8]` over `MaybeUninit` storage (which would - // assert byte-validity of uninitialized memory). let base = self.backing_buf.as_ptr() as usize; let end = base + core::mem::size_of_val(&self.backing_buf); let p = value.as_ptr() as usize; @@ -2697,11 +2275,6 @@ impl BSSList { impl Drop for BSSList { fn drop(&mut self) { - // Zig `deinit`: `self.head.deinit()` walks `prev` and frees each heap block. - // The inline `self.tail` is not Boxed and must not be Box-dropped; the - // `prev: Option>` chain stops at `None` before reaching it - // (see `append_overflow_uninit`). Singleton `loaded = false` reset belongs to the - // `bss_list!` singleton wrapper, not here. if let Some(head) = self.head.take() { let tail_ptr: *const BSSListOverflowBlock = core::ptr::addr_of!(self.tail); if !core::ptr::eq(head.as_ptr().cast_const(), tail_ptr) { @@ -2723,26 +2296,10 @@ pub struct BSSListPair { // BSSStringList<_COUNT, _ITEM_LENGTH> // ────────────────────────────────────────────────────────────────────────── -/// Append-only list. -/// Stores an initial count in .bss section of the object file. -/// Overflows to heap when count is exceeded. -/// -/// TODO(port): same const-generic-arithmetic and per-type-static caveats as `BSSList`. pub struct BSSStringList< const COUNT: usize, /* = _COUNT * 2 */ const ITEM_LENGTH: usize, /* = _ITEM_LENGTH + 1 */ > { - // Zig keeps both arrays *inline* in the struct (`[count*item_length]u8`, - // `[count][]const u8`) so they live in the same demand-faulted allocation - // as the rest of the singleton and `init()` writes only the four scalar - // fields — pages are committed lazily as `append` writes bytes. Stable - // Rust can't spell `[u8; COUNT*ITEM_LENGTH]` without `generic_const_exprs`, - // so we store fat pointers to *separate* `bss_lazy_bytes` mappings instead. - // Same laziness guarantee (MAP_NORESERVE), same lifetime (process-static, - // never freed), no eager memset. - // - // `MaybeUninit` because Zig leaves both arrays `undefined`; only - // `[..backing_buf_used]` / `[..slice_buf_used]` are ever read. pub backing_buf: NonNull<[MaybeUninit]>, // len == COUNT * ITEM_LENGTH pub backing_buf_used: u64, // TODO(port): Overflow = OverflowList<&'static [u8], COUNT / 4> (generic_const_exprs). @@ -2914,19 +2471,6 @@ impl BSSStringList, ) -> core::result::Result<&'a [u8], AllocError> { - // Zig's `std.fmt.count` + `std.fmt.bufPrint` are both comptime-expanded - // straight-line writes, so the count-then-write double pass is free - // there. Rust's `core::fmt::write` drives a `dyn fmt::Write` vtable per - // argument piece, so a literal port pays that dispatch *twice* — the - // dominant cost in `extract_tarball::build_url`, which is called once - // per lockfile package with 6+ args. - // - // Single-pass instead: format into a stack scratch (one `core::fmt` - // drive), then memcpy the exact bytes into the store via `append` - // (which adds the trailing NUL itself, matching the original `len + 1` - // reservation). 512 B covers every current caller (npm tarball URLs, - // interned dirnames); longer outputs fall through to the original - // count-then-reserve path below. const STACK: usize = 512; let mut scratch = [MaybeUninit::::uninit(); STACK]; // SAFETY: `SliceCursor::write_str` only *writes* into `buf[at..end]` @@ -3009,10 +2553,6 @@ impl BSSStringList BSSStringList( &mut self, @@ -3069,10 +2604,6 @@ impl BSSStringList(); if ptr.is_null() { return Err(AllocError); @@ -3129,12 +2660,6 @@ impl BSSStringList // ────────────────────────────────────────────────────────────────────────── -// Zig returns one of two *different* struct types depending on `comptime store_keys: bool`. -// Rust cannot return different types from one generic; we expose both: -// - `BSSMapInner` (the `store_keys = false` shape) -// - `BSSMap` (the `store_keys = true` wrapper) -// TODO(port): callers that passed `store_keys=false` should name `BSSMapInner` directly. - pub struct BSSMapInner { pub index: IndexMap, // TODO(port): Overflow = OverflowList (generic_const_exprs). @@ -3183,10 +2708,6 @@ impl instance.backing_buf_used as usize >= COUNT } - /// Normalize `denormalized_key` per `REMOVE_TRAILING_SLASHES` and hash it. - /// Shared prelude of `get_or_put` / `get` / `remove`; the trimmed slice itself - /// is never needed by callers, only the hash. `#[inline(always)]` + the - /// const-generic branch fold to identical codegen at each monomorphization. #[inline(always)] fn key_hash(denormalized_key: &[u8]) -> u64 { let key = if REMOVE_TRAILING_SLASHES { @@ -3231,10 +2752,6 @@ impl pub fn get(&mut self, denormalized_key: &[u8]) -> Option<&mut ValueType> { let _key = Self::key_hash(denormalized_key); - // Hold the lock across `at_index` (Zig: `defer self.mutex.unlock()` at fn scope) — - // a concurrent `put()` could otherwise mutate `overflow_list`/`backing_buf` while - // we dereference `index`. `MutexGuard` holds a raw pointer (see [`Mutex`] docs), - // so it does not conflict with the `&mut self` borrow in `at_index`. let _guard = self.mutex.lock(); let index = self.index.get(&_key).copied()?; self.at_index(index) @@ -3325,11 +2842,6 @@ pub struct BSSMap< const ESTIMATED_KEY_LENGTH: usize, const REMOVE_TRAILING_SLASHES: bool, > { - // Inner map lives in its own `bss_heap_init` mapping (lazy-faulted; its - // inline `[MaybeUninit; COUNT]` + 32 KiB overflow ptrs stay - // uncommitted until written). Process-lifetime → never freed → raw - // `NonNull` rather than `Box` (avoids tying mmap storage to the global - // allocator's `dealloc`). map: NonNull>, // Same lazy-fault treatment as `BSSStringList::backing_buf` — see the // struct-level comment there. Zig keeps these inline; we map separately @@ -3337,10 +2849,6 @@ pub struct BSSMap< pub key_list_buffer: NonNull<[MaybeUninit]>, // len == COUNT * ESTIMATED_KEY_LENGTH pub key_list_buffer_used: usize, pub key_list_slices: NonNull<[MaybeUninit<&'static [u8]>]>, // len == COUNT - // TODO(port): Zig declares this as `OverflowList([]u8, count / 4)` but then calls - // `.items[...]` and `.append(allocator, slice)` on it — those are `std.ArrayListUnmanaged` - // methods, NOT `OverflowList` methods. Likely dead code or a latent bug upstream. - // Ported as `Vec<&'static [u8]>` to match the *called* API. pub key_list_overflow: Vec<&'static [u8]>, } @@ -3489,10 +2997,6 @@ impl< // SAFETY: points into self.key_list_buffer (singleton-static lifetime). slice = unsafe { core::slice::from_raw_parts(dst.as_ptr(), dst.len()) }; } else { - // Zig: `slice = try self.map.allocator.dupe(u8, key);` — propagate OOM. Route - // through mimalloc directly (PORTING.md forbids `Box::leak`) so the - // size-agnostic `mi_free` below stays valid even after `trim_right` shortens - // the stored slice. let ptr = mimalloc::mi_malloc(key.len().max(1)).cast::(); if ptr.is_null() { return Err(AllocError); @@ -3562,29 +3066,6 @@ impl< } } -// ────────────────────────────────────────────────────────────────────────── -// Allocator-trait surface — OBSOLETE per PORTING.md §Allocators -// ────────────────────────────────────────────────────────────────────────── -// -// Zig's `std.mem.Allocator` / `GenericAllocator` interface threaded an allocator -// param through every fn because Zig has no global allocator. Rust does -// (`#[global_allocator] = Mimalloc` above), so per PORTING.md: -// -// - Non-AST crates: DELETE the `allocator` param. `Box`/`Vec`/`String` use -// global mimalloc. -// - AST crates: thread `&'bump bumpalo::Bump` (= `Arena`) directly. -// -// The trait below is kept ONLY as an empty marker so downstream code that -// still says `&dyn bun_alloc::Allocator` continues to parse. Do not implement -// it; do not add methods. Callers should be rewritten to drop the param -// entirely. - -/// Marker trait standing in for Zig `std.mem.Allocator`. See module note. -/// -/// Provides a `type_id()` hook so `is_instance`-style checks (Zig: -/// `allocator.vtable == &vtable`) can be expressed as concrete-type identity -/// on the trait object — every implementor gets a default `type_id()` that -/// returns its monomorphized `TypeId`. pub trait Allocator: 'static { #[inline] fn type_id(&self) -> core::any::TypeId { @@ -3593,24 +3074,12 @@ pub trait Allocator: 'static { } impl dyn Allocator { - /// Is the concrete type behind this `&dyn Allocator` exactly `T`? - /// - /// Zig's `allocator.vtable == &T.vtable` check, expressed as `TypeId` - /// identity via the trait's `type_id()` hook (dynamic dispatch on the - /// dyn receiver — NOT `Any::type_id`). All per-type - /// `Foo::is_instance(alloc)` associated fns delegate here. #[inline] pub fn is(&self) -> bool { Allocator::type_id(self) == core::any::TypeId::of::() } } -/// Checks whether `allocator` is the default allocator. -/// -/// Zig: `return allocator.vtable == c_allocator.vtable;` — compare identity -/// against the global mimalloc-backed allocator. With `#[global_allocator] = -/// Mimalloc`, the Rust default is `DefaultAlloc`; vtable-identity becomes a -/// `TypeId` comparison. #[inline] pub fn is_default(alloc: &dyn Allocator) -> bool { alloc.is::() @@ -3624,20 +3093,11 @@ impl Allocator for DefaultAlloc {} static DEFAULT_ALLOC: DefaultAlloc = DefaultAlloc; -/// Zig: `bun.default_allocator` — global mimalloc-backed allocator. With -/// `#[global_allocator] = Mimalloc`, this is a marker handle; callers that -/// thread it should be rewritten to use `Box`/`Vec` directly. Kept so ported -/// call sites that still pass an `&dyn Allocator` resolve. #[inline] pub fn default_allocator() -> &'static dyn Allocator { &DEFAULT_ALLOC } -// `GenericAllocator` / `Borrowed` / `Nullable` are dropped — they modelled -// Zig's allocator-borrowing discipline (avoid double-deinit), which Rust's -// ownership already enforces. Drafts that referenced them are gated under -// `` and will be rewritten to drop the param when un-gated. - // ────────────────────────────────────────────────────────────────────────── // `basic` module selection // ────────────────────────────────────────────────────────────────────────── diff --git a/src/bun_alloc/maybe_owned.rs b/src/bun_alloc/maybe_owned.rs index 65411d46868..f785e809163 100644 --- a/src/bun_alloc/maybe_owned.rs +++ b/src/bun_alloc/maybe_owned.rs @@ -44,10 +44,6 @@ impl MaybeOwned { /// will occur. pub const BORROWED: Self = Self::init_borrowed(); - /// Creates a `MaybeOwned` allocator that owns memory, and forwards to a specific - /// allocator. - /// - /// Allocations are forwarded to `parent_alloc`. pub fn init_owned(parent_alloc: A) -> Self { Self { _parent: Some(parent_alloc), @@ -78,9 +74,4 @@ impl MaybeOwned { } } -// Zig `deinit` only forwarded to `bun.memory.deinit(parent_alloc)` on the owned field. -// Per PORTING.md (Idiom map: `pub fn deinit`), that is exactly field drop glue on -// `_parent: Option`, so no explicit `Drop` impl — keeping one would also forbid -// moving `self._parent` out in `into_parent(self)`. - // ported from: src/bun_alloc/maybe_owned.zig diff --git a/src/bun_alloc/memory.rs b/src/bun_alloc/memory.rs index bc97888d15d..972283bb4ba 100644 --- a/src/bun_alloc/memory.rs +++ b/src/bun_alloc/memory.rs @@ -1,34 +1,5 @@ //! Basic utilities for working with memory and objects. -// ────────────────────────────────────────────────────────────────────────────── -// PORT NOTE: `exemptedFromDeinit`, `deinitIsVoid`, and `deinit` are intentionally -// NOT ported as functions. -// -// Zig's `bun.memory.deinit(ptr_or_slice)` walked `@typeInfo` to: -// - recurse into slices/arrays/optionals/error-unions, -// - call `.deinit()` on struct / tagged-union pointees (unless the type set -// `pub const deinit = void;` or was in an exemption list), and -// - finally write `undefined` over the memory if the pointer was mutable. -// -// Rust's `Drop` already does the recursive part automatically: dropping a value -// drops every field, every `Vec`/`Box` element, every `Option`/`Result` payload. -// The "write undefined" poisoning has no safe Rust equivalent (and is a debug aid, -// not semantics). -// -// Call sites: -// - `bun.memory.deinit(&x)` → delete (let `x` drop at scope exit). -// - `bun.memory.deinit(slice)` → delete (slice elements drop with their owner). -// - explicit early release → `drop(x)` or a type-specific `close(self)`. -// -// `@typeInfo` has no Rust equivalent (§Comptime reflection), so a faithful generic -// port is not possible — and per §Idiom map, `deinit` definitions become `impl Drop` -// on the target type, not a free function here. -// -// TODO(port): if any caller relied on the `*x = undefined` poisoning to catch UAF in -// debug, add `#[cfg(debug_assertions)] unsafe { ptr::write_bytes(p, 0xAA, 1) }` at -// that call site. -// ────────────────────────────────────────────────────────────────────────────── - /// Rebase a slice from one memory buffer to another buffer. /// /// Given a slice which points into a memory buffer with base `old_base`, return a diff --git a/src/bun_alloc/stack_fallback.rs b/src/bun_alloc/stack_fallback.rs index 69880418b7b..200d77281cf 100644 --- a/src/bun_alloc/stack_fallback.rs +++ b/src/bun_alloc/stack_fallback.rs @@ -32,18 +32,6 @@ use core::ptr::{self, NonNull}; use crate::{MimallocArena, alloc_result, mimalloc}; -/// `std.heap.StackFallbackAllocator(N)` — bump-allocate from an inline -/// `[u8; N]` stack buffer; spill to `fallback` when it doesn't fit. -/// `deallocate`/`grow` dispatch by address-range check ([`Self::owns`]). -/// -/// Lives on the caller's stack frame; single-threaded by construction -/// (`Cell` ⇒ `!Sync`, so `&StackFallback: !Send` — a `Vec<_, &Self>` cannot -/// cross threads with a stack pointer inside it). -/// -/// `N` guidance: default to **1024** for "format a small string / build a -/// short list" (modal Zig choice; well under the 8 KB Windows `__chkstk` -/// threshold). **4096** for path-ish buffers. Cap at **16 KB** — anything -/// larger should go straight to `MimallocArena`/`Global`. #[repr(C)] // keep `buf` at a fixed offset; `align_of::() == align_of::().max(word)` pub struct StackFallback { /// Bump cursor into `buf`. `Cell` so `Allocator::allocate(&self)` can advance it. @@ -74,11 +62,6 @@ impl StackFallback { } } - /// Zig: `StackFallbackAllocator.get()` — reset the bump region and return - /// the allocator handle. Debug-asserts single call (heap.zig:404). In Rust - /// the "handle" is just `&self` (blanket `impl Allocator for &Self` below), - /// so callers may equivalently write `Vec::new_in(&sf)` directly and skip - /// this. #[inline] pub fn get(&self) -> &Self { #[cfg(debug_assertions)] @@ -274,19 +257,6 @@ unsafe impl Allocator for &StackFallback { } } -// ── ArenaPtr ───────────────────────────────────────────────────────────────── -// -// `*const MimallocArena` as an [`Allocator`]. Exists so `StackFallback` can -// borrow a caller-owned `MimallocArena` without a lifetime parameter: -// `ASTMemoryAllocator` is published into raw thread-locals and may outlive any -// nameable `'a`, so `&'a MimallocArena` (which already implements `Allocator`) -// cannot be used directly. The caller guarantees the pointee outlives every -// allocation — same invariant the `ast_alloc` install/uninstall protocol -// already requires. -// -// `arena == null` routes to global `mi_malloc`/`mi_free`, matching -// [`crate::ast_alloc::AstAlloc`] when no AST scope is active. - /// Borrowed `*const MimallocArena` as an [`Allocator`]. See section doc above. #[derive(Clone, Copy)] pub struct ArenaPtr { @@ -316,11 +286,6 @@ impl ArenaPtr { pub fn set_arena(&mut self, arena: *const MimallocArena) { self.arena = arena; } - /// Shared borrow of the wrapped arena, or `None` for the global path. - /// - /// Single backref-deref site for the `arena: *const MimallocArena` field; - /// the [`Allocator`] impl below branches on the result instead of - /// open-coding the null-check + raw-pointer deref at every method. #[inline] fn arena_ref(&self) -> Option<&MimallocArena> { // SAFETY: `arena` is either null (→ `None`) or, per [`ArenaPtr::new`]'s diff --git a/src/bun_bin/lib.rs b/src/bun_bin/lib.rs index 37395b82980..37434bdfa08 100644 --- a/src/bun_bin/lib.rs +++ b/src/bun_bin/lib.rs @@ -56,83 +56,24 @@ static ALLOC: bun_alloc::Mimalloc = bun_alloc::Mimalloc; #[global_allocator] static ALLOC: std::alloc::System = std::alloc::System; -/// ASAN runtime options override. Lives in the binary crate so it is a direct -/// link input — the ASAN runtime weak-defines this symbol, and an rlib/archive -/// member that only provides it would never be extracted, so the override in -/// `bun_safety::asan` silently didn't apply (manifesting as a -/// `Thread::currentSingleton().stack().contains(this)` assert in -/// `JSGlobalObject::GlobalPropertyInfo` because `detect_stack_use_after_return` -/// puts C++ stack locals on a heap-backed fake stack JSC's conservative GC -/// can't see). Unconditional: harmless dead symbol when ASAN isn't linked. -/// -/// `#[cold]`: read once by the ASAN runtime during its own init (never linked -/// in release / on the `bun run` path) — keep it off the startup `.text` pages. #[cold] #[inline(never)] #[unsafe(no_mangle)] pub extern "C" fn __asan_default_options() -> *const core::ffi::c_char { - // detect_stack_use_after_return=0: keep stack locals on the real stack so - // JSC's conservative GC scan and `StackBounds::contains` see them. - // detect_leaks=0: off by default (Linux defaults it on); CI opts in via - // ASAN_OPTIONS with a suppressions file. - // - // PORT NOTE: matches `src/safety/asan.zig` exactly. Do NOT add `symbolize=0` - // here — LSAN's function-name suppression matching (`test/leaksan.supp`) - // requires symbolized stacks; with symbolization disabled every entry like - // `leak:uws_create_app` silently stops matching and CI reports the - // suppressed allocations as leaks. If local debug crashes feel slow to - // print, set `ASAN_OPTIONS=symbolize=0` in your shell instead. c"detect_stack_use_after_return=0:detect_leaks=0".as_ptr() } -/// LSAN built-in suppressions, merged with whatever `LSAN_OPTIONS=suppressions=` -/// the CI runner passes (`test/leaksan.supp`). That file's entries were written -/// against Zig's symbol mangling (`runtime.node.zlib.NativeZlib.Context.init`, -/// `jsc.web_worker.create`, …); LSAN matches by *substring on a symbolized -/// frame*, so after the Rust port renamed every frame to `bun_::` -/// none of the Zig-named rules fire and CI reports the same intentionally- -/// leaked-at-exit allocations the suppressions were authored for. Baking the -/// Rust spellings into the binary keeps `leaksan.supp` as the C/C++/JSC list -/// and lets the Rust list ride with the code that produces the symbols. -/// -/// Also covers one Rust-only false positive that has no Zig analogue: -/// `std::thread::Builder::spawn` allocates an `Arc` that the -/// detached thread holds in TLS for its lifetime; LSAN does not scan other -/// threads' TLS roots at exit, so every long-lived detached thread (HTTP -/// client, debugger, FSEvents) reports a 48-byte "leak". -/// -/// Weak-defined by the ASAN runtime, so this strong definition wins. Harmless -/// dead symbol when ASAN isn't linked (same linkage story as -/// `__asan_default_options` above). -/// -/// `#[cold]`: read once by the LSAN runtime during its own init (never linked -/// in release / on the `bun run` path) — keep it off the startup `.text` pages. #[cold] #[inline(never)] #[unsafe(no_mangle)] pub extern "C" fn __lsan_default_suppressions() -> *const core::ffi::c_char { - // One rule per line. Substring match on any frame in the allocation stack. - // - // Every entry below is a structural / process-lifetime allocation that has - // been investigated and is intentionally suppressed — not a leak. New - // entries here require a comment naming the owner and why it cannot be - // freed before exit. Do NOT add a suppression to silence a CI flake; fix - // the lifecycle instead. concat!( // Rust std false positive — a detached thread's `Arc` // is held by the OS thread's TLS, which LSan does not scan as a root. "leak:std::thread::thread::Thread>::new\n", - // macOS-only `dlopen("CoreFoundation")` / `dlopen("CoreServices")` - // and the per-process `FSEventStream` / `CFRunLoop` they require. - // These are platform singletons by design (CF objects are not safely - // disposable while the dylib remains loaded). "leak:bun_runtime::node::fs_events::init_core_foundation\n", "leak:bun_runtime::node::fs_events::init_core_services\n", "leak:bun_runtime::node::fs_events::FSEventsLoop\n", - // Process-lifetime inspector thread. The debugger handles SIGINT and - // serves the WebSocket protocol up to (and during) `process.exit()`; - // joining it from `global_exit` would deadlock when the user is - // mid-breakpoint. The thread's stack/Arc are reclaimed by the OS. "leak:bun_jsc::debugger::Debugger>::start_js_debugger_thread\n", "\0", ) @@ -176,10 +117,6 @@ pub unsafe extern "C" fn main(argc: c_int, argv: *const *const c_char) -> c_int libc::signal(libc::SIGXFSZ, libc::SIG_IGN); } - // main.zig:40-50 — Windows-only startup. Must run BEFORE the first libuv - // call (uv allocator) and before anything reads `Bun.env`/`process.env` - // (env conversion). The Zig spec orders these between sigaction and - // `start_time`/`initArgv`. #[cfg(windows)] { // SAFETY: mimalloc fns match the libuv allocator signatures; called @@ -227,11 +164,6 @@ pub unsafe extern "C" fn main(argc: c_int, argv: *const *const c_char) -> c_int StackCheck::configure_thread(); bun_io::ParentDeathWatchdog::install(); - // 6. Push high-tier allocator vtable addresses into the - // `bun_safety::alloc::has_ptr` registry so debug-only allocator-mismatch - // checks can identify `LinuxMemFdAllocator`/`MimallocArena` instances - // (Zig: inline `isInstance` chain in `safety/alloc.zig:hasPtr`). - // Runs once; reads are lock-free Relaxed. bun_runtime::allocators::register_safety_vtables(); // 7. CLI dispatch. diff --git a/src/bun_bin/phase_c_exports.rs b/src/bun_bin/phase_c_exports.rs index 164efec8047..1c7ecb3db10 100644 --- a/src/bun_bin/phase_c_exports.rs +++ b/src/bun_bin/phase_c_exports.rs @@ -39,17 +39,6 @@ type VirtualMachine = c_void; // Exported variables (Zig: `export var` / `@export(&var, …)`) // ════════════════════════════════════════════════════════════════════════════ -// REAL: now provided by bun_jsc (src/jsc/VirtualMachine.rs). -// isBunTest -// Bun__stringSyntheticAllocationLimit -// Bun__defaultRemainingRunsUntilSkipReleaseAccess -// Bun__getDefaultGlobalObject - -// REAL: now provided by bun_runtime (src/runtime/cli/Arguments.rs). -// Bun__Node__ProcessNoDeprecation -// Bun__Node__ProcessThrowDeprecation -// Bun__Node__UseSystemCA - // REAL: now provided by bun_analytics (src/analytics/lib.rs). // Bun__napi_module_register_count // Bun__isEpollPwait2SupportedOnLinuxKernel @@ -92,22 +81,6 @@ pub(crate) extern "C" fn Bun__panic(msg: *const u8, len: usize) -> ! { // REAL: now provided by bun_runtime (src/runtime/api/bun/SSLContextCache.rs). // bun_ssl_ctx_cache_on_free -// ════════════════════════════════════════════════════════════════════════════ -// Resolved stubs — real `#[no_mangle]` bodies live in bun_jsc / bun_runtime / -// bun_http_jsc / bun_bundler_jsc. Stub deleted; linker resolves to the crate -// definition (or flags it if the upstream gate hasn't been lifted yet). -// ════════════════════════════════════════════════════════════════════════════ - -// ── VM bridge ─────────────────────────────────────────────────────────────── -// REAL: src/jsc/virtual_machine_exports.rs -// Bun__getVM -// Bun__VirtualMachine__exitDuringUncaughtException -// Bun__queueTask -// Bun__queueTaskConcurrently -// Bun__readOriginTimer -// Bun__readOriginTimerStart -// Bun__reportUnhandledError - // REAL: src/jsc/JSCScheduler.rs // Bun__eventLoop__incrementRefConcurrently @@ -140,20 +113,10 @@ pub(crate) extern "C" fn Bun__VM__scriptExecutionStatus(_vm: *const VirtualMachi // REAL: now provided by bun_jsc (src/jsc/VirtualMachine.rs). // Bun__VM__useIsolationSourceProviderCache -// ── Host fns: `(global, callframe) -> JSValue` ────────────────────────────── -// REAL: src/runtime/webcore/fetch.rs -// Bun__fetch -// Bun__fetchPreconnect - // REAL: src/runtime/webcore/ObjectURLRegistry.rs // Bun__createObjectURL // Bun__revokeObjectURL -// REAL: src/runtime/webcore/prompt.rs -// WebCore__alert -// WebCore__confirm -// WebCore__prompt - // REAL: src/runtime/webcore/FormData.rs // FormData__jsFunctionFromMultipartData @@ -161,87 +124,6 @@ pub(crate) extern "C" fn Bun__VM__scriptExecutionStatus(_vm: *const VirtualMachi // REAL: src/http_jsc/websocket_client/WebSocketUpgradeClient.rs // Bun__WebSocket__freeSSLConfig -// ── WebWorker ─────────────────────────────────────────────────────────────── -// REAL: src/jsc/web_worker.rs -// WebWorker__create -// WebWorker__destroy -// WebWorker__notifyNeedTermination -// WebWorker__setRef -// WebWorker__getParentWorker - -// ── encoding ──────────────────────────────────────────────────────────────── -// REAL: src/runtime/webcore/encoding.rs -// Bun__encoding__writeLatin1 -// Bun__encoding__writeUTF16 -// Bun__encoding__byteLengthLatin1AsUTF8 -// Bun__encoding__byteLengthUTF16AsUTF8 -// Bun__encoding__toString - -// ── TextEncoder ───────────────────────────────────────────────────────────── -// REAL: src/runtime/webcore/TextEncoder.rs -// TextEncoder__encode8 -// TextEncoder__encode16 -// TextEncoder__encodeRopeString -// TextEncoder__encodeInto16 -// TextEncoder__encodeInto8 - -// ── Blob ──────────────────────────────────────────────────────────────────── -// REAL: src/runtime/webcore/Blob.rs -// Blob__dupeFromJS -// Blob__dupe -// Blob__deref -// Blob__setAsFile -// Blob__getFileNameString -// Blob__getDataPtr -// Blob__getSize -// Bun__Blob__getSizeForBindings - -// .classes.ts hooks (build/debug/codegen/ZigGeneratedClasses.zig) -// REAL: now provided by bun_runtime::generated_classes -// (build/debug/codegen/generated_classes.rs via generateRust()). -// Blob__estimatedSize -// BlobClass__finalize -// Blob__onStructuredCloneSerialize -// Blob__onStructuredCloneDeserialize -// BlockList__estimatedSize -// BlockList__onStructuredCloneSerialize -// BlockList__onStructuredCloneDeserialize - -// ── WebView process control ───────────────────────────────────────────────── -// REAL: src/runtime/webview/{ChromeProcess,HostProcess}.rs (`mod webview` not -// (Bun__Chrome__kill / Bun__WebViewHost__kill now defined in -// src/runtime/webview/{ChromeProcess,HostProcess}.rs.) - -// ── napi ──────────────────────────────────────────────────────────────────── -// REAL: src/runtime/napi/napi_body.rs -// napi_create_string_latin1 -// napi_create_string_utf8 -// napi_create_string_utf16 -// napi_internal_enqueue_finalizer - -// ── usockets dispatch ─────────────────────────────────────────────────────── -// REAL: src/runtime/socket/uws_dispatch.rs -// us_dispatch_open -// us_dispatch_close -// us_dispatch_timeout -// us_dispatch_long_timeout -// us_dispatch_handshake -// us_dispatch_data -// us_dispatch_fd -// us_dispatch_writable -// us_dispatch_end -// us_dispatch_connect_error -// us_dispatch_connecting_error -// us_dispatch_ssl_raw_tap - -// ── DNS addrinfo (usockets → bun_runtime::dns_jsc) ────────────────────────── -// REAL: src/runtime/dns_jsc/dns.rs -// Bun__addrinfo_get -// Bun__addrinfo_set -// Bun__addrinfo_cancel -// Bun__addrinfo_freeRequest -// Bun__addrinfo_getRequestResult - // ── bundler analyze ───────────────────────────────────────────────────────── // REAL: src/bundler/analyze_transpiled_module.rs // zig__ModuleInfoDeserialized__deinit @@ -250,11 +132,6 @@ pub(crate) extern "C" fn Bun__VM__scriptExecutionStatus(_vm: *const VirtualMachi // zig__ModuleInfoDeserialized__toJSModuleRecord // zig__renderDiff -// ════════════════════════════════════════════════════════════════════════════ -// Genuinely unimplemented — no Zig `export fn`, no C++ body. Kept so the -// extern ref in the rlib resolves; loud crash if ever called. -// ════════════════════════════════════════════════════════════════════════════ - // Declared `CPP_DECL` in headers.h:279 but bindings.cpp never defines it. #[unsafe(no_mangle)] pub(crate) extern "C" fn JSC__JSValue__parseJSON( @@ -275,10 +152,6 @@ pub(crate) extern "C" fn BunString__toErrorInstance( unreachable!("BunString__toErrorInstance: not implemented in Zig either (no C++ body)") } -// Declared `extern` in InspectorLifecycleAgent.cpp:47-48 but never defined in -// C++ nor Zig (Debugger.zig declares it `extern "c"` too). The agent's -// preventExit/stopPreventingExit protocol commands are no-ops in the inspector -// build today. #[unsafe(no_mangle)] pub(crate) extern "C" fn Bun__LifecycleAgentPreventExit(_agent: *mut c_void) {} #[unsafe(no_mangle)] diff --git a/src/bun_core/Global.rs b/src/bun_core/Global.rs index b43c3096bd2..22edeac204d 100644 --- a/src/bun_core/Global.rs +++ b/src/bun_core/Global.rs @@ -15,19 +15,6 @@ use crate::debug_allocator_data; // MOVE_DOWN: bun_core::ZStr → bun_core (move-in pass). use crate::ZStr; -// ────────────────────────────────────────────────────────────────────────── -// Process-wide top-level directory (cwd at startup). Storage lives at T0 so -// `bun_sys::File::read_from_user_input` reads it directly; the resolver's -// `FileSystem::init` writes it once via `set_top_level_dir`. -// ────────────────────────────────────────────────────────────────────────── - -// Stored behind a `RwLock<&'static [u8]>` rather than a split (AtomicPtr, -// AtomicUsize) pair: `install::PackageManager` calls `fs.set_top_level_dir()` -// during workspace discovery (potentially after worker threads exist), so a -// reader could otherwise observe an OLD len with a NEW ptr (or vice-versa) and -// build an out-of-bounds `from_raw_parts`. The read path is cold (display / -// path-relative formatting) so one uncontended read-lock is cheaper than a UB -// window; writes are rare and serial. static TOP_LEVEL_DIR: crate::RwLock<&'static [u8]> = crate::RwLock::new(b"."); /// Record the top-level directory (interned `'static` slice). Idempotent; @@ -55,12 +42,6 @@ pub static CRASH_HANDLER_INSTALLED: AtomicBool = AtomicBool::new(false); pub static WINDOWS_SEGFAULT_HANDLE: core::sync::atomic::AtomicPtr = core::sync::atomic::AtomicPtr::new(core::ptr::null_mut()); -// ────────────────────────────────────────────────────────────────────────── -// crash_handler primitives (moved from ptr/safety/collections/sys) -// StoredTrace + dump_stack_trace + panicking state are pure data + libc; the -// platform-specific symbolication / SEH bits stay in bun_crash_handler (T>core). -// ────────────────────────────────────────────────────────────────────────── - /// Zig: `std.builtin.StackTrace` — slice of return addresses + cursor. #[derive(Clone, Copy)] pub struct StackTrace<'a> { @@ -153,18 +134,6 @@ impl Default for DumpStackTraceOptions { /// Zig-spec name (`crash_handler.WriteStackTraceLimits`); also re-exported from `bun_crash_handler`. pub type WriteStackTraceLimits = DumpStackTraceOptions; -/// Zig: `crash_handler.dumpStackTrace`. T0 fallback prints raw return -/// addresses — **no symbolication** (the `backtrace` crate is not a T0 dep, -/// and `std::backtrace` cannot resolve a stored address list). This is a -/// deliberate debug-UX downgrade vs the Zig spec for the *stored*-trace path -/// (ref_count leak reports); the *current*-stack path below -/// uses `std::backtrace` and stays symbolicated. Crash-report paths that need -/// llvm-symbolizer / pdb-addr2line call `bun_crash_handler::dump_stack_trace` -/// directly — that crate sits above us so it owns the rich impl without a hook. -/// -/// `limits.stop_at_jsc_llint` / `skip_stdlib` / `skip_*_patterns` are accepted -/// for signature parity but **ignored** here (they require symbol names to -/// match against). Only `frame_count` is honoured. pub fn dump_stack_trace(trace: &StackTrace<'_>, limits: DumpStackTraceOptions) { crate::output::flush(); let n = trace @@ -179,14 +148,6 @@ pub fn dump_stack_trace(trace: &StackTrace<'_>, limits: DumpStackTraceOptions) { } } -/// Capture and dump the current call stack. Dispatches to -/// `bun_crash_handler::dump_current_stack_trace` (matching Zig -/// `fd.zig`/`ref_count.zig` which call `bun.crash_handler.dumpCurrentStackTrace` -/// directly). The upward call is routed through a link-time `extern "Rust"` -/// symbol defined by `bun_crash_handler` so the function pointer lives in -/// read-only `.text` instead of a writable `AtomicPtr` slot — memory corruption -/// cannot redirect it. Under `cfg(test)` (this crate's standalone test binary -/// does not link `bun_crash_handler`) a stub note is printed instead. pub fn dump_current_stack_trace(first_address: Option, limits: DumpStackTraceOptions) { #[cfg(not(test))] { @@ -230,12 +191,6 @@ pub fn sleep_forever_if_another_thread_is_crashing() { } } -// ─── SignalCode — single source of truth (Zig: src/sys/SignalCode.zig) ──── -// Zig declares ONE `enum(u8) { …, _ }` and derives the name table via -// `@tagName` + `ComptimeEnumMap`. Rust has no enum reflection, so the 31 -// (name,number) pairs live in ONE X-macro below; every consumer — the closed -// enum here, the open newtype in `bun_sys`, `SIGNAL_NAMES`, `from_raw`, -// `from_name` — is generated from it. Never re-spell a signal pair elsewhere. #[macro_export] macro_rules! for_each_signal { ($cb:ident) => { @@ -288,11 +243,6 @@ macro_rules! __define_signal_code { } for_each_signal!(__define_signal_code); -// ─── analytics::features (MOVE_DOWN from bun_analytics) ─────────────────── -// Zig: src/analytics/analytics.zig::Features — bag of `pub var X: usize`. -// Port as atomic counters so cross-thread `.fetch_add` is sound. Only the -// counters are tier-0; `builtin_modules` (EnumSet over jsc HardcodedModule) -// stays in bun_analytics (depends on tier-6). pub mod features { use core::sync::atomic::AtomicUsize; macro_rules! feat { ($($name:ident),* $(,)?) => { $(pub static $name: AtomicUsize = AtomicUsize::new(0);)* } } @@ -371,11 +321,6 @@ macro_rules! mark_binding { $crate::mark_binding!(::core::panic::Location::caller().file()) }; ($fn_name:expr) => { - // Zig: `Output.scoped(.JSC, .hidden)` (jsc.zig:169) — opt-in via - // BUN_DEBUG_JSC=1. The `JSC` scope is owned by bun_core. Gate on - // `debug_assertions` (== `Environment::ENABLE_LOGS`) — never on a Cargo - // feature, since `cfg!(feature = ..)` is resolved against the *calling* - // crate and would warn (or silently no-op) in crates without it. if cfg!(debug_assertions) && $crate::Global::JSC_SCOPE.is_visible() { $crate::Global::JSC_SCOPE.log(::core::format_args!( "[JSC] {} ({}:{})\n", @@ -451,10 +396,6 @@ pub const package_json_version_with_canary: &str = if cfg!(debug_assertions) { version_string }; -// PORT NOTE: Zig sliced `git_sha[0..@min(len, 8)]` inline; we use the -// pre-computed `GIT_SHA_SHORT` (same value) since const slicing of a const -// `&str` by a runtime-ish min() is awkward in stable Rust. -/// The version and a short hash in parenthesis. pub const package_json_version_with_sha: &str = if env::GIT_SHA.is_empty() { package_json_version } else if cfg!(debug_assertions) { @@ -487,10 +428,6 @@ pub const package_json_version_with_revision: &str = if env::GIT_SHA.is_empty() formatcp!("{}+{}", version_string, env::GIT_SHA_SHORT) }; -// Node-style platform string. Distinct from Environment.os.nameString() on -// Android: the kernel-level OS enum stays .linux (so syscall switches keep -// working), but user-facing strings — npm user-agent, process.platform — -// must be "android" so native-addon postinstalls don't fetch glibc binaries. pub const os_name: &str = if cfg!(target_os = "android") { "android" } else { @@ -590,11 +527,6 @@ pub fn add_exit_callback(function: ExitFn) { Bun__atexit(function); } -/// Callbacks `Bun__onExit` runs BEFORE `run_exit_callbacks()`. Spec -/// `Global.zig:220` hard-codes `bun.jsc.Node.FSEvents.closeAndWait()` ahead of -/// `runExitCallbacks()`; that crate sits above us, so it pushes its callback -/// here at first-loop creation (data moved down — same `Vec` shape as -/// `ON_EXIT_CALLBACKS`, no fn-ptr type-erase). static PRE_EXIT_CALLBACKS: crate::Mutex> = crate::Mutex::new(Vec::new()); pub fn add_pre_exit_callback(function: ExitFn) { @@ -623,11 +555,6 @@ pub(crate) fn is_exiting() -> bool { IS_EXITING.load(Ordering::Relaxed) } -// libc process-termination entry points used by `exit` / -// `raise_ignoring_panic_handler_raw` below. All take by-value `c_int` or no -// args and are `noreturn`/kernel-validated — no memory-safety preconditions, -// so `safe fn` discharges the link-time proof and the call sites are plain -// calls. `#[link_name]` avoids colliding with this module's own `pub fn exit`. unsafe extern "C" { #[link_name = "abort"] safe fn libc_abort() -> !; @@ -684,20 +611,10 @@ pub fn raise_ignoring_panic_handler(sig: crate::SignalCode) -> ! { raise_ignoring_panic_handler_raw(sig as c_int) } -/// Re-raise `sig` (raw `c_int`) after restoring TTY/crash state. Zig's -/// `SignalCode` is a *non-exhaustive* `enum(u8)`, so callers may forward any -/// signal byte (incl. Linux RT signals 32..=64) that has no `crate::SignalCode` -/// discriminant. Mirrors `raiseIgnoringPanicHandler(@enumFromInt(sig))`. pub fn raise_ignoring_panic_handler_raw(sig: c_int) -> ! { Output::flush(); Output::source::stdio::restore(); - // Clear the crash handler's segfault hooks so the re-raised signal goes to - // SIG_DFL instead of recursing into the panic handler. Storage moved down - // from `bun_crash_handler` — it sets `CRASH_HANDLER_INSTALLED` on init and - // we do the libc reset ourselves (no fn-ptr hook). Mirrors - // `crash_handler.zig::resetSegfaultHandler`: skip when ASAN owns the - // signals (we never installed over them); on Windows remove the VEH. #[cfg(unix)] if CRASH_HANDLER_INSTALLED.load(Ordering::Relaxed) && !crate::env::ENABLE_ASAN { // SAFETY: zeroed sigaction with SIG_DFL is a valid disposition. @@ -714,10 +631,6 @@ pub fn raise_ignoring_panic_handler_raw(sig: c_int) -> ! { if CRASH_HANDLER_INSTALLED.load(Ordering::Relaxed) && !crate::env::ENABLE_ASAN { let handle = WINDOWS_SEGFAULT_HANDLE.swap(core::ptr::null_mut(), Ordering::Relaxed); if !handle.is_null() { - // `Handle` is an opaque cookie returned by - // `AddVectoredExceptionHandler`; the kernel validates it and - // returns 0 on a stale/garbage value — no memory-safety - // preconditions, so `safe fn` discharges the link-time proof. unsafe extern "system" { safe fn RemoveVectoredExceptionHandler(Handle: *mut core::ffi::c_void) -> u32; } @@ -758,10 +671,6 @@ pub fn mimalloc_cleanup(force: bool) { } // Versions are now handled by build-generated header (bun_dependency_versions.h) -// Enabling huge pages slows down bun by 8x or so -// Keeping this code for: -// 1. documentation that an attempt was made -// 2. if I want to configure allocator later #[inline] pub fn configure_allocator(_: AllocatorConfiguration) {} @@ -793,11 +702,6 @@ unsafe impl Sync for SyncCStr {} pub(crate) static Bun__userAgent: SyncCStr = SyncCStr(concatcp!(user_agent, "\0").as_ptr().cast::()); -/// Prevent the linker from dead-code-eliminating `#[no_mangle]` symbols that are -/// only ever called from C/C++ (so rustc sees no Rust caller). Port of Zig's -/// `std.mem.doNotOptimizeAway` pattern (Global.zig:224). Expands to one -/// `core::hint::black_box(f as *const ())` per path — purely a side-effect, so -/// invoke inside a `fix_dead_code_elimination()` fn wired from `run_command`. #[macro_export] macro_rules! keep_symbols { ($($f:path),* $(,)?) => { diff --git a/src/bun_core/Progress.rs b/src/bun_core/Progress.rs index d19b2b48e7f..c16baeef63d 100644 --- a/src/bun_core/Progress.rs +++ b/src/bun_core/Progress.rs @@ -24,12 +24,6 @@ use crate::Mutex; #[cfg(windows)] use crate::windows_sys as windows; -// `HANDLE` is an opaque kernel handle (kernel32 validates and returns 0/FALSE -// on a non-console handle); every out-param is `&mut T` to a `#[repr(C)]` POD, -// ABI-identical to the Win32 `LP*` pointer (thin non-null). The reference type -// encodes the only pointer-validity precondition, so `safe fn` discharges the -// link-time proof. (`bun_windows_sys::kernel32` declares these with `*mut`; -// redeclared locally so the legacy-conhost cursor path below is plain calls.) #[cfg(windows)] #[link(name = "kernel32")] unsafe extern "system" { @@ -61,30 +55,14 @@ unsafe extern "system" { ) -> windows::BOOL; } -// Progress's terminal handle is the canonical `output::File` (vtable-backed -// stderr/File from `OutputSinkVTable`). The duplicate `ProgressTerminalVTable` -// from B-0 round 1 is removed; tty/ansi/winsize route through the new -// `OutputSinkVTable` slots so `bun_core` stays T0 (no `bun_sys` dep). pub use crate::output::File; use crate::output::output_sink; impl File { - /// `std.io.tty.supportsAnsiEscapeCodes()` — on unix this is `isatty()`; - /// on Windows it requires `ENABLE_VIRTUAL_TERMINAL_PROCESSING` (set by - /// `Output.Source.init`). We route through the sink so the platform check - /// lives in `bun_sys`. #[inline] pub fn supports_ansi_escape_codes(self) -> bool { #[cfg(windows)] { - // Zig std.fs.File.supportsAnsiEscapeCodes(): query the live console - // mode for ENABLE_VIRTUAL_TERMINAL_PROCESSING — a *capability* - // check. Do NOT proxy through ENABLE_ANSI_COLORS_STDERR: that is a - // color-*preference* flag (NO_COLOR/FORCE_COLOR/tty) and never - // inspects whether SetConsoleMode(VT) actually succeeded, so it - // would pick the wrong branch in `Progress::start` on legacy - // conhost (emit raw escapes) or under NO_COLOR on a VT terminal - // (force the SetConsoleCursorPosition path). let mut mode: windows::DWORD = 0; GetConsoleMode(self.console_handle(), &mut mode) != 0 && (mode & windows::ENABLE_VIRTUAL_TERMINAL_PROCESSING) != 0 @@ -124,11 +102,6 @@ pub struct Progress { /// Whether the terminal supports ANSI escape codes. pub supports_ansi_escape_codes: bool, - /// If the terminal is "dumb", don't print output. - /// This can be useful if you don't want to print all - /// the stages of code generation if there are a lot. - /// You should not use it if the user should see output - /// for example showing the user what tests run. pub dont_print_on_dumb: bool, pub root: Node, @@ -228,23 +201,11 @@ impl Default for Node { } impl Node { - /// Raw pointer to the owning `Progress`. - /// - /// A `&`/`&mut`-returning accessor is intentionally **not** provided: - /// `Progress` embeds `root: Node` and `refresh_with_held_lock` walks the - /// `recently_updated_child` chain, so materializing a `&mut Progress` while - /// any `&Node`/`&mut Node` is live would alias. Callers must go through the - /// raw pointer and keep each access narrowly scoped. #[inline] pub fn context_ptr(&self) -> *mut Progress { self.context } - /// Shared reference to the parent node, or `None` for the root. - /// - /// Safe for read-only use (atomic field access, walking `parent.parent()`). - /// For paths that must call `&mut self` methods on the parent (e.g. - /// `complete_one`), use [`parent_ptr`](Self::parent_ptr) instead. #[inline] pub fn parent(&self) -> Option<&Node> { // SAFETY: parent backref points into caller-provided storage that @@ -261,12 +222,6 @@ impl Node { self.parent } - /// Create a new child progress node. Thread-safe. - /// Call `Node.end` when done. - /// TODO solve https://github.com/ziglang/zig/issues/2765 and then change this - /// API to set `self.parent.recently_updated_child` with the return value. - /// Until that is fixed you probably want to call `activate` on the return value. - /// Passing 0 for `estimated_total_items` means unknown. pub fn start(&mut self, name: &'static [u8], estimated_total_items: usize) -> Node { Node { context: self.context, @@ -410,11 +365,6 @@ impl Node { } impl Progress { - /// Create a new progress node. - /// Call `Node.end` when done. - /// TODO solve https://github.com/ziglang/zig/issues/2765 and then change this - /// API to return Progress rather than accept it as a parameter. - /// `estimated_total_items` value of 0 means unknown. pub fn start(&mut self, name: &'static [u8], estimated_total_items: usize) -> &mut Node { // TODO(port): std.fs.File.stderr() / supportsAnsiEscapeCodes() / isTty() — // map to bun_sys::File equivalents. @@ -703,17 +653,6 @@ impl Progress { self.columns_written = 0; } - /// Allows the caller to freely write to stderr until `unlock_stderr()` is - /// called. During the lock, the progress information is cleared from the - /// terminal. - /// - /// PORT NOTE: Zig splits the lock/unlock across fn boundaries. - /// `crate::Mutex` (std::sync wrapper) has no raw `unlock()`, and storing a - /// guard on `self` is self-referential. There are currently **no callers** - /// of `lock_stderr`/`unlock_stderr` in either the Zig or Rust trees, so - /// this clears the terminal under a scoped lock and `unlock_stderr` is a - /// no-op. If a caller materializes, refactor to return the guard (or move - /// `update_mutex` to a raw `bun_threading::Mutex` once layering allows). pub fn lock_stderr(&mut self) { let ctx_ptr = std::ptr::from_mut::(self); let _g = self.update_mutex.lock(); diff --git a/src/bun_core/atomic_cell.rs b/src/bun_core/atomic_cell.rs index 5a1137b204c..43b07ef43ee 100644 --- a/src/bun_core/atomic_cell.rs +++ b/src/bun_core/atomic_cell.rs @@ -26,32 +26,8 @@ use core::sync::atomic::{AtomicPtr, AtomicU8, AtomicU16, AtomicU32, AtomicU64, O // AtomicCell // ═══════════════════════════════════════════════════════════════════════════ -/// Lock-free atomic cell for any `Copy` type up to 8 bytes. -/// -/// This is the cross-thread counterpart to [`RacyCell`](crate::RacyCell): -/// where `RacyCell` documents "single-threaded by construction", `AtomicCell` -/// documents "actually shared; every load/store is an atomic op with -/// Acquire/Release ordering". Use this for flags, counters, small enums, -/// handles, and `Option>` that more than one thread touches. -/// -/// `T` must implement [`Atom`] (no padding, `size_of::() ∈ {1,2,4,8}`). -/// Larger or padded `T` is a compile error — use `bun_threading::RwLock` or -/// restructure. There is **no** seqlock fallback (unlike crossbeam's -/// `AtomicCell`): if it doesn't fit in a native atomic word, it doesn't -/// compile. -/// -/// Default ordering is **Acquire/Release**, not Relaxed — at least six of the -/// data-race findings that motivated this type were "Relaxed gives no -/// happens-before for the init it guards". Telemetry / best-effort hints can -/// opt out via [`load_relaxed`](Self::load_relaxed) / -/// [`store_relaxed`](Self::store_relaxed), named so grep finds every site -/// that opted out of ordering. #[repr(C)] pub struct AtomicCell { - // ZST that forces 8-byte alignment so `inner`'s address is valid for - // `AtomicU64` (the widest backing we cast to). Smaller widths need ≤8, so - // this covers all sizes. With `repr(C)` the ZST occupies offset 0 and - // `inner` is also at offset 0. _align: [AtomicU64; 0], inner: UnsafeCell, } @@ -239,12 +215,6 @@ const unsafe fn xmute(a: A) -> B { }) } -/// Implement [`Atom`] for a non-pointer `Copy` type by routing to the -/// `AtomicU{8,16,32,64}` of matching width. -/// -/// `unsafe` because the caller asserts the [`Atom`] safety contract (size is -/// a power of two ≤ 8, no padding bytes). A `const _` assert checks the size -/// half at compile time; the no-padding half is on you. #[macro_export] macro_rules! unsafe_impl_atom { ($($T:ty),+ $(,)?) => {$( @@ -588,10 +558,6 @@ impl ThreadCell { } impl ThreadCell { - /// Bind this cell to the calling thread. Idempotent on the owner; panics - /// if a *different* thread has already claimed it. Call once from the - /// owning thread's entry point (e.g. `HTTPThread::on_start`, - /// `IoRequestLoop::on_spawn_io_thread`). #[inline] pub fn claim(&self) { #[cfg(debug_assertions)] @@ -635,11 +601,6 @@ impl ThreadCell { self.inner.get() } - /// Raw pointer **without** the owner assertion. Use only on paths that - /// touch fields which are *themselves* cross-thread-safe (lock-free - /// queue + waker), pending a refactor that moves those fields out of the - /// thread-confined struct. Every call site must say which fields it - /// touches and why that's sound. #[inline] pub fn get_unchecked(&self) -> *mut T { self.inner.get() diff --git a/src/bun_core/bounded_array.rs b/src/bun_core/bounded_array.rs index 6bc4a06e03c..d9386f03185 100644 --- a/src/bun_core/bounded_array.rs +++ b/src/bun_core/bounded_array.rs @@ -27,34 +27,13 @@ pub enum OverflowError { crate::named_error_set!(OverflowError); -/// A structure with an array and a length, that can be used as a slice. -/// -/// Useful to pass around small arrays whose exact size is only known at -/// runtime, but whose maximum size is known at comptime, without requiring -/// an `Allocator`. pub type BoundedArray = BoundedArrayAligned; // PORT NOTE: Zig's `BoundedArray` delegates to `BoundedArrayAligned` with `@alignOf(T)`. // In Rust the natural alignment of `[T; N]` is already `align_of::()`, so the alias is // transparent. The explicit `alignment` const-param is dropped (see below). -/// A structure with an array, length and alignment, that can be used as a -/// slice. -/// -/// Useful to pass around small explicitly-aligned arrays whose exact size is -/// only known at runtime, but whose maximum size is known at comptime, without -/// requiring an `Allocator`. -// TODO(port): Zig takes `comptime alignment: Alignment` and applies it via -// `align(alignment.toByteUnits())` on the buffer field. Stable Rust cannot express -// `#[repr(align(N))]` with a const-generic `N`. All in-tree callers use the default -// `@alignOf(T)` via `BoundedArray`, so the param is dropped. Revisit if a -// caller needs over-alignment (would require a wrapper type per alignment). pub struct BoundedArrayAligned { buffer: [MaybeUninit; BUFFER_CAPACITY], - // TODO(port): Zig uses `Length = std.math.ByteAlignedInt(std.math.IntFittingRange(0, buffer_capacity))` - // (smallest byte-aligned uint that fits `0..=BUFFER_CAPACITY`) to shrink this field. - // Stable Rust const generics cannot pick an integer type from a const value without - // `generic_const_exprs`. Using `usize` for now. - // PERF(port): was size-optimized integer field — profile if it shows up on a hot path len: usize, } @@ -217,10 +196,6 @@ impl BoundedArrayAligned { Some(unsafe { self.buffer[i].assume_init_read() }) } - /// Return a slice of only the extra capacity after items. - /// This can be useful for writing directly into it. - /// Note that such an operation must be followed up with a - /// call to `resize()` pub fn unused_capacity_slice(&mut self) -> &mut [MaybeUninit] { &mut self.buffer[self.len..] } @@ -331,10 +306,6 @@ impl BoundedArrayAligned { self.buffer[i].write(item); } - /// Remove the element at index `i`, shift elements after index - /// `i` forward, and return the removed element. - /// Asserts the slice has at least one item. - /// This operation is O(N). pub fn ordered_remove(&mut self, i: usize) -> T where T: Copy, diff --git a/src/bun_core/build.rs b/src/bun_core/build.rs index 5ef9d8badf9..bad7111020b 100644 --- a/src/bun_core/build.rs +++ b/src/bun_core/build.rs @@ -4,14 +4,6 @@ clippy::disallowed_types, clippy::disallowed_macros )] -//! Export `BUN_CODEGEN_DIR` and fingerprint `build_options.rs` for -//! `include!(concat!(env!("BUN_CODEGEN_DIR"), "/build_options.rs"))`. -//! -//! `build_options.rs` is written at configure time by -//! `scripts/build/buildOptionsRs.ts` from the resolved `Config` (sha, -//! version, baseline, …). This script does NOT run the generator — it just -//! resolves the path and tells cargo to track the file so a sha/version -//! change recompiles `bun_core`. Mirrors `src/{jsc,runtime}/build.rs`. use std::env; use std::path::{Path, PathBuf}; diff --git a/src/bun_core/debug.rs b/src/bun_core/debug.rs index c1dd9ef92be..037e6552143 100644 --- a/src/bun_core/debug.rs +++ b/src/bun_core/debug.rs @@ -18,19 +18,6 @@ pub struct SymbolInfo { pub source_location: Option, } -// ────────────────────────────────────────────────────────────────────── -// Frame-pointer stack unwinder (port of the `std.debug` subset Zig used: -// `@frameAddress`, `MemoryAccessor`, `StackIterator`). The Rust port had -// briefly routed capture through libc `backtrace()` / `RtlCaptureStackBackTrace`, -// which are CFI/unwind-table based — but release builds strip the unwind tables -// (`-fno-asynchronous-unwind-tables` + `--no-eh-frame-hdr`) and the POSIX -// signal handler runs on an `SA_ONSTACK` altstack, so those APIs captured only -// the handler's own frames (or nothing). Frame pointers are force-enabled -// (`-Cforce-frame-pointers=yes`, `-fno-omit-frame-pointer`), so FP walking is -// the correct mechanism. Lives in `bun_core` (libc/std/bun_alloc only) so the -// crash handler, `StoredTrace`, and `btjs` can all share one implementation. -// ────────────────────────────────────────────────────────────────────── -/// Port of Zig `@frameAddress()`. Reads the frame-pointer register directly. #[inline(always)] pub fn frame_address() -> usize { #[cfg(target_arch = "x86_64")] @@ -231,11 +218,6 @@ impl StackIterator { core::mem::size_of::() }; - /// `fp` is required: this function is not `#[inline(always)]`, so a - /// `frame_address()` call from inside it would read this frame's own rbp — - /// a frame that no longer exists by the time `next()` dereferences it. Pass - /// `frame_address()` from the caller (where it inlines) or a context-seeded - /// value. pub fn init(fp: usize) -> StackIterator { StackIterator { fp, @@ -268,15 +250,6 @@ impl StackIterator { pub(crate) const PC_OFFSET: usize = StackIterator::PC_OFFSET; -/// Capture the current thread's call stack. -/// -/// POSIX: walk frame pointers. Windows: `RtlCaptureStackBackTrace` via -/// `.pdata` (rbp is not a reliable frame pointer across all linked code). -/// -/// `first_address`, when present, trims every frame above (and including) the -/// capture machinery: frames are dropped until one matches `first_address`. -/// If no frame matches (e.g. inlining moved the boundary), the full untrimmed -/// trace is returned rather than an empty one — a noisier trace beats none. #[inline(never)] pub(crate) fn capture_current(first_address: Option, out: &mut [usize]) -> usize { #[cfg(windows)] @@ -319,20 +292,6 @@ pub(crate) fn capture_current(first_address: Option, out: &mut [usize]) - n } -/// Capture a faulting thread's call stack from the fault context. `pc` is the -/// exact faulting instruction (`ExceptionAddress` / `mcontext` PC) and becomes -/// frame 0. -/// -/// POSIX: walk frame pointers from `fp` (the saved frame pointer register). -/// No trimming is needed — the walk starts on the faulting stack, so the -/// signal handler's own frames (on the altstack) are never in the chain. -/// -/// Windows: `rbp` is not a reliable frame pointer across all linked code (the -/// prebuilt JavaScriptCore and LLInt assembly do not maintain it), so an -/// fp-walk derails at the C++ boundary. Use the native `.pdata`-based -/// `RtlCaptureStackBackTrace` instead — it works with or without unwind tables -/// since `.pdata` is always emitted — and trim the handler's own frames by -/// scanning for `pc`. `fp` is unused on Windows. pub fn capture_from_context(pc: usize, fp: usize, out: &mut [usize]) -> usize { if out.is_empty() { return 0; @@ -352,11 +311,6 @@ pub fn capture_from_context(pc: usize, fp: usize, out: &mut [usize]) -> usize { core::ptr::null_mut(), ) } as usize; - // VEH runs on the faulting thread's stack, so the captured trace is - // [handler frames…][fault frame][callers…]. Trim everything above the - // first frame whose return address sits within a small tolerance of - // the fault `pc` (the call-site/return-address may be a few bytes - // off). If no match, keep the full trace rather than discard it. const TOLERANCE: usize = 256; let frames = &out[1..1 + got]; let skip = frames diff --git a/src/bun_core/deprecated.rs b/src/bun_core/deprecated.rs index ebb085c7b07..d7305d67571 100644 --- a/src/bun_core/deprecated.rs +++ b/src/bun_core/deprecated.rs @@ -19,12 +19,6 @@ where // TODO(port): replace with the real reader trait once it exists. R: DeprecatedRead, { - // Zig: `pub const Error = R.Error;` — inherent assoc types are nightly-only - // (E0658). Callers name `R::Error` directly; this alias was sugar. - // TODO(port): `pub const Reader = std.Io.GenericReader(*Self, Error, read);` — - // depends on the Rust port of `std.Io.GenericReader`. Left unported; `reader()` - // below is stubbed accordingly. - pub fn read(&mut self, dest: &mut [u8]) -> Result { // First try reading from the already buffered data onto the destination. let current = &self.buf[self.start..self.end]; @@ -74,31 +68,10 @@ pub fn buffered_reader(reader: R) -> BufferedReader<4096, R> } } -// ────────────────────────────────────────────────────────────────────────── -// SinglyLinkedList -// ────────────────────────────────────────────────────────────────────────── -// -// DEDUP(D050): the Rust port of `SinglyLinkedList` / `SinglyLinkedNode` was -// removed — the canonical implementation lives at -// `bun_collections::pool::{SinglyLinkedList, Node}`. The two had diverged -// (`data: T` vs `data: MaybeUninit`, `*mut`-null vs `Option<*mut>` returns) -// and this copy had zero callers outside its own unit test. New consumers -// should depend on `bun_collections::pool` directly. - // ────────────────────────────────────────────────────────────────────────── // DoublyLinkedList // ────────────────────────────────────────────────────────────────────────── -/// A doubly-linked list has a pair of pointers to both the head and -/// tail of the list. List elements have pointers to both the previous -/// and next elements in the sequence. The list can be traversed both -/// forward and backward. Some operations that take linear O(n) time -/// with a singly-linked list can be done without traversal in constant -/// O(1) time with a doubly-linked list: -/// -/// - Removing an element. -/// - Inserting a new element before an existing element. -/// - Pushing or popping an element from the end of the list. pub struct DoublyLinkedList { pub first: *mut DoublyLinkedNode, pub last: *mut DoublyLinkedNode, @@ -124,11 +97,6 @@ impl Default for DoublyLinkedList { } impl DoublyLinkedList { - /// Insert a new node after an existing one. - /// - /// Arguments: - /// node: Pointer to a node in the list. - /// new_node: Pointer to the new node to insert. pub unsafe fn insert_after( &mut self, node: *mut DoublyLinkedNode, @@ -153,11 +121,6 @@ impl DoublyLinkedList { self.len += 1; } - /// Insert a new node before an existing one. - /// - /// Arguments: - /// node: Pointer to a node in the list. - /// new_node: Pointer to the new node to insert. pub unsafe fn insert_before( &mut self, node: *mut DoublyLinkedNode, @@ -182,11 +145,6 @@ impl DoublyLinkedList { self.len += 1; } - /// Concatenate list2 onto the end of list1, removing all entries from the former. - /// - /// Arguments: - /// list1: the list to concatenate onto - /// list2: the list to be concatenated pub unsafe fn concat_by_moving(&mut self, list2: &mut Self) { let l2_first = list2.first; if l2_first.is_null() { @@ -211,10 +169,6 @@ impl DoublyLinkedList { list2.len = 0; } - /// Insert a new node at the end of the list. - /// - /// Arguments: - /// new_node: Pointer to the new node to insert. pub unsafe fn append(&mut self, new_node: *mut DoublyLinkedNode) { let last = self.last; if !last.is_null() { @@ -228,10 +182,6 @@ impl DoublyLinkedList { } } - /// Insert a new node at the beginning of the list. - /// - /// Arguments: - /// new_node: Pointer to the new node to insert. pub unsafe fn prepend(&mut self, new_node: *mut DoublyLinkedNode) { let first = self.first; if !first.is_null() { @@ -252,10 +202,6 @@ impl DoublyLinkedList { } } - /// Remove a node from the list. - /// - /// Arguments: - /// node: Pointer to the node to be removed. pub unsafe fn remove(&mut self, node: *mut DoublyLinkedNode) { // SAFETY: caller guarantees `node` is a valid node currently in this list. unsafe { @@ -282,10 +228,6 @@ impl DoublyLinkedList { debug_assert!(self.len == 0 || (!self.first.is_null() && !self.last.is_null())); } - /// Remove and return the last node in the list. - /// - /// Returns: - /// A pointer to the last node in the list. pub unsafe fn pop(&mut self) -> *mut DoublyLinkedNode { let last = self.last; if last.is_null() { @@ -296,10 +238,6 @@ impl DoublyLinkedList { last } - /// Remove and return the first node in the list. - /// - /// Returns: - /// A pointer to the first node in the list. pub unsafe fn pop_first(&mut self) -> *mut DoublyLinkedNode { let first = self.first; if first.is_null() { diff --git a/src/bun_core/env.rs b/src/bun_core/env.rs index 85fe5aadd21..dc243f43d9f 100644 --- a/src/bun_core/env.rs +++ b/src/bun_core/env.rs @@ -30,11 +30,6 @@ pub const IS_WINDOWS: bool = cfg!(windows); pub(crate) const IS_POSIX: bool = !IS_WINDOWS && !IS_WASM; pub const IS_DEBUG: bool = cfg!(debug_assertions); pub(crate) const IS_TEST: bool = cfg!(test); -// Zig's `Environment.isLinux` is `builtin.target.os.tag == .linux`, which is -// TRUE on Android (Zig models Android as `os.tag == .linux, abi == .android`). -// Rust splits them into two `target_os` values, so this const has to OR them -// to keep the Zig semantics — otherwise `OS` (below) panics at const-eval on -// the `*-linux-android` cross targets and Linux-only code paths are skipped. pub const IS_LINUX: bool = cfg!(any(target_os = "linux", target_os = "android")); pub(crate) const IS_FREEBSD: bool = cfg!(target_os = "freebsd"); /// kqueue-based event loop (macOS + FreeBSD share most of this path). @@ -241,14 +236,6 @@ pub const ARCH: Architecture = if IS_WASM { // Helper for const &str slicing (Rust stable lacks const range indexing on str). const fn const_str_slice(s: &'static str, start: usize, end: usize) -> &'static str { - // `[u8]::split_at` and `str::from_utf8` are both `const` on stable, so the - // sub-slice + UTF-8 check happens entirely at compile time without raw - // pointer arithmetic. - // - // ALLOWED `core::str::from_utf8`: this is the ONE permitted call site in - // the codebase. simdutf is the runtime validator (`bun_core::strings:: - // {is_valid_utf8, str_utf8}`); `const fn` cannot call FFI, and this body - // const-evals at compile time only (git-SHA slicing). let (head, _) = s.as_bytes().split_at(end); let (_, sub) = head.split_at(start); match core::str::from_utf8(sub) { diff --git a/src/bun_core/env_var.rs b/src/bun_core/env_var.rs index 9ddfaf7b9a4..ff14c859f91 100644 --- a/src/bun_core/env_var.rs +++ b/src/bun_core/env_var.rs @@ -26,15 +26,6 @@ //! everything. This means that we potentially scan through envp a lot of //! times, even though we could only do it once. -// TODO(port): The Zig original uses comptime type-returning functions (`New`, `PlatformSpecificNew`) -// that take comptime string keys + option structs and return a unique type per env var with an -// embedded `static` cache. Rust cannot parameterize a generic type on `&'static str` + a struct -// value in stable, so this port models `New`/`PlatformSpecificNew` as `macro_rules!` that emit a -// module per env var. In Zig the declarations come first and the type-generator fns come last; -// here the macros must be defined (or `#[macro_use]`d) before the declarations. The macro -// definitions could move into a sibling `env_var_impl.rs` and be `#[macro_use]`d to restore Zig -// declaration order in this file. - use core::sync::atomic::{AtomicPtr, AtomicU8, AtomicU64, AtomicUsize, Ordering}; // MOVE_DOWN: bun_core::ZStr → bun_core (move-in pass). @@ -49,16 +40,7 @@ new!(pub BUN_AGENT_RULE_DISABLED: boolean, "BUN_AGENT_RULE_DISABLED", { default: new!(pub BUN_COMPILE_TARGET_TARBALL_URL: string, "BUN_COMPILE_TARGET_TARBALL_URL", {}); new!(pub BUN_CONFIG_DISABLE_COPY_FILE_RANGE: boolean, "BUN_CONFIG_DISABLE_COPY_FILE_RANGE", { default: false }); new!(pub BUN_CONFIG_DISABLE_ioctl_ficlonerange: boolean, "BUN_CONFIG_DISABLE_ioctl_ficlonerange", { default: false }); -// TODO(markovejnovic): Legacy usage had the default at 30, even though a the attached comment -// quoted: Amazon Web Services recommends 5 seconds: -// https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/jvm-ttl-dns.html -// -// It's unclear why this was done. new!(pub BUN_CONFIG_DNS_TIME_TO_LIVE_SECONDS: unsigned, "BUN_CONFIG_DNS_TIME_TO_LIVE_SECONDS", { default: 30 }); -// Idle timeout for HTTP client sockets (fetch / `bun install`), in seconds. -// The timer is armed when the socket opens and re-armed on every read/write; -// if it fires the request fails with `error.Timeout`. Covers the TLS -// handshake through the response body. 0 disables. See `src/http/lib.rs`. new!(pub BUN_CONFIG_HTTP_IDLE_TIMEOUT: unsigned, "BUN_CONFIG_HTTP_IDLE_TIMEOUT", { default: 300 }); new!(pub BUN_CRASH_REPORT_URL: string, "BUN_CRASH_REPORT_URL", {}); new!(pub BUN_DEBUG: string, "BUN_DEBUG", {}); @@ -77,10 +59,6 @@ new!(pub BUN_DEV_SERVER_TEST_RUNNER: string, "BUN_DEV_SERVER_TEST_RUNNER", {}); // renaming (`src/js_printer/renamer.zig`). Presence-checked, value ignored. new!(pub BUN_DUMP_SYMBOLS: string, "BUN_DUMP_SYMBOLS", {}); new!(pub BUN_ENABLE_CRASH_REPORTING: boolean, "BUN_ENABLE_CRASH_REPORTING", {}); -// Opt-in: when truthy, Bun watches its original parent pid and exits as soon -// as that process dies (even if the parent was SIGKILLed and couldn't forward -// a signal), and on its own clean exit recursively SIGKILLs every descendant -// so nothing it spawned outlives it. See `src/ParentDeathWatchdog.zig`. new!(pub BUN_FEATURE_FLAG_NO_ORPHANS: boolean, "BUN_FEATURE_FLAG_NO_ORPHANS", { default: false }); new!(pub BUN_FEATURE_FLAG_DUMP_CODE: string, "BUN_FEATURE_FLAG_DUMP_CODE", {}); // TODO(markovejnovic): It's unclear why the default here is 100_000, but this was legacy behavior @@ -92,10 +70,6 @@ new!(pub BUN_INSPECT_PRELOAD: string, "BUN_INSPECT_PRELOAD", {}); new!(pub BUN_INSTALL: string, "BUN_INSTALL", {}); new!(pub BUN_INSTALL_BIN: string, "BUN_INSTALL_BIN", {}); new!(pub BUN_INSTALL_GLOBAL_DIR: string, "BUN_INSTALL_GLOBAL_DIR", {}); -// Minimum response `Content-Length` (in bytes) for `bun install` to -// stream a tarball directly into libarchive instead of buffering the -// whole body first. Smaller tarballs stay on the buffered path where -// the fixed overhead of the resumable state machine isn't worth it. new!(pub BUN_INSTALL_STREAMING_MIN_SIZE: unsigned, "BUN_INSTALL_STREAMING_MIN_SIZE", { default: 2 * 1024 * 1024 }); new!(pub BUN_NEEDS_PROC_SELF_WORKAROUND: boolean, "BUN_NEEDS_PROC_SELF_WORKAROUND", { default: false }); new!(pub BUN_OPTIONS: string, "BUN_OPTIONS", {}); @@ -168,10 +142,6 @@ new!(pub TMUX: string, "TMUX", {}); new!(pub TODIUM: string, "TODIUM", {}); platform_specific_new!(pub USER: string, posix = "USER", windows = "USERNAME", {}); new!(pub WANTS_LOUD: boolean, "WANTS_LOUD", { default: false }); -// The same as system_root. -// Note: Do not use this variable directly -- use os.zig's implementation instead. -// TODO(markovejnovic): Perhaps we could add support for aliases in the library, so you could -// specify both WINDIR and SYSTEMROOT and the loader would check both? platform_specific_new!(pub WINDIR: string, posix = None, windows = "WINDIR", {}); // XDG Base Directory Specification variables. // For some reason, legacy usage respected these even on Windows. To avoid compatibility issues, @@ -226,10 +196,6 @@ pub mod feature_flag { // server selects it. Off by default while the client implementation // matures. `--experimental-http2-fetch` is the CLI equivalent. new_feature_flag!(pub BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP2_CLIENT, "BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP2_CLIENT", {}); - // Honor `Alt-Svc: h3` from fetch() responses: subsequent requests to the - // same origin go over QUIC/HTTP-3 instead of TCP. Off by default while - // the client implementation matures. `--experimental-http3-fetch` is the - // CLI equivalent. new_feature_flag!(pub BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP3_CLIENT, "BUN_FEATURE_FLAG_EXPERIMENTAL_HTTP3_CLIENT", {}); new_feature_flag!(pub BUN_FEATURE_FLAG_FORCE_IO_POOL, "BUN_FEATURE_FLAG_FORCE_IO_POOL", {}); new_feature_flag!(pub BUN_FEATURE_FLAG_FORCE_WINDOWS_JUNCTIONS, "BUN_FEATURE_FLAG_FORCE_WINDOWS_JUNCTIONS", {}); @@ -266,20 +232,6 @@ pub(crate) struct CacheConfiguration { pub opts: O, } -/// Structure which encodes the different types of environment variables supported. -/// -/// This requires the following static members: -/// -/// - `ValueType`: The underlying environment variable type if one is set. For -/// example, a string `$PATH` ought return a `[]const u8` when set. -/// - `Cache`: A struct implementing the following methods: -/// - `get_cached() -> CacheOutput`: Retrieve the cached value of the -/// environment variable, if any. -/// - `deser_and_invalidate(raw_env: Option<&[u8]>) -> Option` -/// - `CtorOptions`: A struct containing the options passed to the constructor of the environment -/// variable definition. -/// -/// This type will communicate with the common logic via the `CacheOutput` type. pub(crate) mod kind { use super::*; @@ -337,11 +289,6 @@ pub(crate) mod kind { &self, raw_env: Option<&'static [u8]>, ) -> Option { - // The implementation is racy and allows two threads to both set the value at - // the same time, as long as the value they are setting is the same. This is - // difficult to write an assertion for since it requires the DEV path take a - // .swap() path rather than a plain .store(). - if let Some(ev) = raw_env { self.ptr_value .store(ev.as_ptr().cast_mut(), Ordering::Relaxed); @@ -457,23 +404,12 @@ pub(crate) mod kind { } } - /// Control how deserializing and deserialization errors are handled. - /// - /// Note that deserialization errors cannot panic. If you need more robust means of - /// handling inputs, consider not using environment variables. #[derive(Clone, Copy, PartialEq, Eq)] pub(crate) enum ErrorHandling { /// debug_warn on deserialization errors. DebugWarn, /// Ignore deserialization errors and treat the variable as not set. NotSet, - /// Formatting errors are treated as truthy values. - /// - /// If this library fails to parse the value as an integer and truthy cast is - /// enabled, truthy values will be set to 1 or 0. - /// - /// Note: Most values are considered truthy, except for "", "0", "false", "no", - /// and "off". TruthyCast, } @@ -615,15 +551,6 @@ pub(crate) mod kind { } } -/// Create a new environment variable definition. -/// -/// The resulting type has methods for interacting with the environment variable. -/// -/// Technically, none of the operations here are thread-safe, so writing to environment variables -/// does not guarantee that other threads will see the changes. You should avoid writing to -/// environment variables. -// Zig: `fn New(comptime VariantType: type, comptime key: [:0]const u8, comptime opts) type` -// → `PlatformSpecificNew(VariantType, key, key, opts)` #[macro_export] #[doc(hidden)] macro_rules! new { @@ -635,20 +562,9 @@ macro_rules! new { } pub(crate) use new; -/// Identical to new, except it allows you to specify different keys for POSIX and Windows. -/// -/// If the current platform does not have a key specified, all methods that attempt to read the -/// environment variable will fail at compile time, except for `platform_get` and `platform_key`, -/// which will return None instead. -// Zig: `fn PlatformSpecificNew(comptime VariantType, comptime posix_key: ?[:0]const u8, -// comptime windows_key: ?[:0]const u8, comptime opts) type` #[macro_export] #[doc(hidden)] macro_rules! platform_specific_new { - // TODO(port): this macro is a draft of the Zig comptime type-generator. It expands to a - // `pub mod $name { pub fn get() / key() / platform_get() / ... }` so call sites read - // `env_var::HOME::get()` like Zig's `env_var.HOME.get()`. The opts-parsing arms below cover - // exactly the option shapes used in this file; harden / generalize if new shapes appear. ( $vis:vis $name:ident : $kind:ident, posix = $posix:tt, windows = $windows:tt, @@ -669,10 +585,6 @@ macro_rules! platform_specific_new { $kind, $crate::env_var::__first_key!($posix, $windows), { $($opts)* } ); - // Zig computed `DefaultType`/`ReturnType` at comptime from whether `opts.default` is - // set. We expose the default + a const HAS_DEFAULT and always return Option; - // a thin `pub fn get() -> ValueType` wrapper that `.unwrap()`s is added when a default - // exists. TODO(port): restore the non-nullable `get()` return type for defaulted vars. pub(crate) const DEFAULT: Option = $crate::env_var::__default_opt!($kind, { $($opts)* }); @@ -782,13 +694,6 @@ macro_rules! platform_specific_new { None } - /// Fetch the default value of this environment variable, if any. - /// - /// It is safe to compare the result of .get() to default to test if the variable is set to - /// its default value. - // Zig: `pub const default: DefaultType = if (opts.default) |d| d else {};` - // Exposed above as `DEFAULT: Option`. - /// Unit value so call sites read `env_var::FOO.get()` (matching Zig /// `bun.env_var.FOO.get()`). The module-path form `FOO::get()` also works. pub struct Accessor; @@ -802,10 +707,6 @@ macro_rules! platform_specific_new { } fn assert_platform_supported() { - // Zig: `@compileError` when the current platform's key is null. - // TODO(port): Rust cannot `compile_error!` from inside a const-evaluated `if cfg!` - // without separate macro arms per (posix=None / windows=None) combination. Could - // split the macro so e.g. `posix = None` emits `#[cfg(unix)] compile_error!`. debug_assert!( platform_key().is_some(), concat!( diff --git a/src/bun_core/external_shared.rs b/src/bun_core/external_shared.rs index 076cbf637d5..4ba8cd06ea4 100644 --- a/src/bun_core/external_shared.rs +++ b/src/bun_core/external_shared.rs @@ -15,10 +15,6 @@ pub unsafe trait ExternalSharedDescriptor { unsafe fn ext_deref(this: *mut Self); } -/// A shared pointer whose reference count is managed externally; e.g., by extern functions. -/// -/// `T` must implement [`ExternalSharedDescriptor`] (the Rust equivalent of Zig's -/// `T.external_shared_descriptor` struct with `ref(*T)` / `deref(*T)`). #[repr(transparent)] pub struct ExternalShared { // Zig: `#impl: *T` (private, non-null) @@ -187,12 +183,6 @@ impl Drop for ExternalSharedOptional { } } -// ────────────────────────────────────────────────────────────────────────── -// `WTF::StringImpl` descriptor — lives here (not `bun_string`) because the -// struct is defined in `bun_alloc` and the trait here; orphan rule requires -// one of them to be local. `bun_ptr` already depends on `bun_alloc`. -// ────────────────────────────────────────────────────────────────────────── - // SAFETY: ref/deref delegate to JSC's WTF::StringImpl atomic refcount via FFI; // the pointee remains valid while count > 0 (JSC contract). unsafe impl ExternalSharedDescriptor for bun_alloc::WTFStringImplStruct { diff --git a/src/bun_core/feature_flags.rs b/src/bun_core/feature_flags.rs index e95397b987b..c6063d6dec1 100644 --- a/src/bun_core/feature_flags.rs +++ b/src/bun_core/feature_flags.rs @@ -30,10 +30,6 @@ pub const HTTP_BUFFER_POOLING: bool = true; pub const DISABLE_LOLHTML: bool = false; -/// There is, what I think is, a bug in getaddrinfo() -/// on macOS that specifically impacts localhost and not -/// other ipv4 hosts. This is a workaround for that. -/// "localhost" fails to connect. pub const HARDCODE_LOCALHOST_TO_127_0_0_1: bool = false; /// React will issue warnings in development if there are multiple children @@ -49,42 +45,6 @@ pub const SAME_TARGET_BECOMES_DESTRUCTURING: bool = true; pub const HELP_CATCH_MEMORY_ISSUES: bool = env::ENABLE_ASAN || env::IS_DEBUG; -/// This performs similar transforms as https://github.com/rollup/plugins/tree/master/packages/commonjs -/// -/// Though, not exactly the same. -/// -/// There are two scenarios where this kicks in: -/// -/// 1) You import a CommonJS module using ESM. -/// -/// Semantically, CommonJS expects us to wrap everything in a closure. That -/// bloats the code. We want to make the generated code as small as we can. -/// -/// To avoid that, we attempt to unwrap the CommonJS module into ESM. -/// -/// But, we can't always do that. When you have cyclical require() or directly -/// mutate exported bindings, we can't unwrap it. -/// -/// However, in the simple case, where you do something like -/// -/// exports.foo = 123; -/// exports.bar = 456; -/// -/// We can unwrap it into -/// -/// export const foo = 123; -/// export const bar = 456; -/// -/// 2) You import a CommonJS module using CommonJS. -/// -/// This is a bit more complicated. We want to avoid the closure wrapper, but -/// it's really difficult to track down all the places where you mutate the -/// exports object. `require.cache` makes it even more complicated. -/// So, we just wrap the entire module in a closure. -/// -/// But what if we previously unwrapped it? -/// -/// In that case, we wrap it again in the printer. pub const UNWRAP_COMMONJS_TO_ESM: bool = true; /// https://sentry.engineering/blog/the-case-for-debug-ids @@ -99,12 +59,6 @@ pub const DISABLE_AUTO_JS_TO_TS_IN_NODE_MODULES: bool = true; pub const RUNTIME_TRANSPILER_CACHE: bool = true; -/// On Windows, node_modules/.bin uses pairs of '.exe' + '.bunx' files. The -/// fast path is to load the .bunx file within `bun.exe` instead of -/// `bun_shim_impl.exe` by using `bun_shim_impl.tryStartupFromBunJS` -/// -/// When debugging weird script runner issues, it may be worth disabling this in -/// order to isolate your bug. pub const WINDOWS_BUNX_FAST_PATH: bool = true; // TODO: fix Windows-only test failures in fetch-preconnect.test.ts diff --git a/src/bun_core/fmt.rs b/src/bun_core/fmt.rs index 4d5390bd891..3aa08a40f37 100644 --- a/src/bun_core/fmt.rs +++ b/src/bun_core/fmt.rs @@ -12,13 +12,6 @@ use crate::strings; /// SHA-512 digest length in bytes. Local constant to avoid bun_sha (T2) dependency. const SHA512_DIGEST: usize = 64; -// ════════════════════════════════════════════════════════════════════════════ -// js_lexer / js_printer minimal subsets. -// Only the free functions fmt.rs/output.rs actually call. The full modules -// (codepoint tables, JSON printer) stay in bun_str / bun_js_parser which add -// `pub use bun_core::strings::*` and extend with the heavy bits. -// ════════════════════════════════════════════════════════════════════════════ - pub mod js_lexer { /// Zig: js_lexer.isIdentifierStart — ASCII fast path; bun_js_parser extends /// with the full Unicode ID_Start table. @@ -55,10 +48,6 @@ pub mod js_printer { _allow_backtick: bool, enc: Encoding, ) -> fmt::Result { - // TODO(port): full impl in bun_js_printer; this tier only needs the - // "already quoted" passthrough for fmt.rs JS-string display. - // Zig writePreQuotedString writes the escaped body WITHOUT surrounding - // quotes — delegate to the canonical chars-only escaper. let _ = quote; match enc { Encoding::Latin1 => super::encode_json_string_chars_latin1(f, input), @@ -317,11 +306,6 @@ pub fn redacted_source(str: &[u8]) -> RedactedSourceFormatter<'_> { RedactedSourceFormatter { text: str } } -// ─────────────────────────────────────────────────────────────────────────── -// DependencyUrlFormatter -// https://github.com/npm/cli/blob/63d6a732c3c0e9c19fd4d147eaa5cc27c29b168d/node_modules/npm-package-arg/lib/npa.js#L163 -// ─────────────────────────────────────────────────────────────────────────── - pub struct DependencyUrlFormatter<'a> { pub url: &'a [u8], } @@ -465,12 +449,6 @@ thread_local! { const { SharedTempBufferSlot(Cell::new(None)) }; } -/// RAII borrow of the thread-local shared temp buffer. -/// -/// On construction: takes (or allocates) the buffer and nulls the thread-local -/// cell so any recursive borrow allocates a fresh one instead of aliasing this -/// one. On drop: restores the buffer to the cell, or frees it if recursion has -/// already restored a different buffer (mirrors fmt.zig's `defer` block). struct SharedTempBufferBorrow { ptr: NonNull, } @@ -726,16 +704,6 @@ pub fn fmt_path(path: &[u8], options: PathFormatOptions) -> FormatUTF8<'_> { fmt_path_u8(path, options) } -/// Non-validating `Display` adapter for a `&[u8]` known to be valid UTF-8. -/// -/// Port of Zig's `{s}` format specifier on a `[]const u8`: Zig writes the bytes -/// straight through with no codepoint check. `bstr::BStr`'s `Display` impl walks -/// the input via `Utf8Chunks` to substitute U+FFFD on invalid sequences, which -/// shows up in install-hot-path profiles (registry hosts, package names, semver -/// pre/build tags — all pre-validated ASCII). Use this where the bytes are -/// already known-good and you just want `f.write_str` semantics. -/// -/// Prefer the [`s`] alias at call sites — it reads like Zig's `{s}`. #[derive(Copy, Clone)] #[repr(transparent)] pub struct Raw<'a>(pub &'a [u8]); @@ -753,10 +721,6 @@ pub const fn raw(bytes: &[u8]) -> Raw<'_> { Raw(bytes) } -// Canonical `SliceCursor` / `buf_print` / `buf_print_len` live in T0 -// `bun_alloc` so that crate can use them too; re-exported here for the -// `bun_core::fmt::` callers and extended with an `io::Write` face so the same -// struct also serves as Zig's `std.io.fixedBufferStream` for write-only sites. pub use bun_alloc::{SliceCursor, buf_print, buf_print_len}; impl crate::io::Write for SliceCursor<'_> { @@ -817,15 +781,6 @@ pub fn buf_print_z_infallible<'a>( // VecWriter — `core::fmt::Write` over `&mut Vec` // ════════════════════════════════════════════════════════════════════════════ -/// `core::fmt::Write` adapter for `Vec`. -/// -/// Rust's `Vec` only implements `std::io::Write` (banned in lower crates -/// per PORTING.md), not `core::fmt::Write`. This is the port of Zig's -/// `std.ArrayList(u8).writer()` — infallible (Vec growth aborts on OOM). -/// -/// Both the tuple constructor and `::new()` are public so call sites can pick -/// whichever reads better: `write!(VecWriter(&mut buf), ...)` or -/// `VecWriter::new(&mut buf)`. pub struct VecWriter<'a>(pub &'a mut Vec); impl<'a> VecWriter<'a> { @@ -842,17 +797,6 @@ impl core::fmt::Write for VecWriter<'_> { } } -// ════════════════════════════════════════════════════════════════════════════ -// std.fmt.parseInt / parseUnsigned — canonical &[u8] integer parsers. -// -// Zig has exactly one impl (`std.fmt.parseIntWithSign`, vendor/zig/lib/std/ -// fmt.zig:409) plus thin no-sign wrapper `std.fmt.parseUnsigned` (:488). Every -// Zig caller invoked those directly on `[]const u8`; the Rust port spawned ~15 -// local digit loops solely to dodge `core::str::from_utf8`. These restore 1:1 -// parity. bun_string re-exports `parse_int` so existing `strings::parse_int` -// callers keep working. Re-exported via bun_core::lib.rs. -// ════════════════════════════════════════════════════════════════════════════ - /// Error from [`parse_int`] / [`parse_unsigned`] (`std.fmt.ParseIntError` port). #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ParseIntError { @@ -872,10 +816,6 @@ impl ParseIntError { } } -/// Shared digit loop behind [`parse_int`] / [`parse_unsigned`]. `digits` has -/// any sign already stripped; `radix` is post-auto-detect (2..=36). Mirrors -/// `std.fmt.parseIntWithSign` body: skips embedded `_` separators, rejects -/// leading/trailing `_`, accumulates in `u128` with checked overflow. #[inline] fn parse_with_sign(digits: &[u8], radix: u8) -> Result { debug_assert!((2..=36).contains(&radix)); @@ -923,15 +863,6 @@ fn auto_radix(digits: &[u8], radix: u8) -> (&[u8], u8) { (digits, 10) } -/// `std.fmt.parseInt(T, buf, radix)` — parse an integer of type `T` from `buf`. -/// -/// `radix` ∈ 2..=36, or `0` to auto-detect from a `0x`/`0o`/`0b` prefix -/// (defaulting to 10). Accepts an optional leading `+`/`-`. Embedded `_` -/// separators are skipped; leading/trailing `_` are rejected. Port keeps Zig's -/// error set: `Overflow` on range error, `InvalidCharacter` otherwise. -/// -/// Works directly on `&[u8]` so callers never need an intermediate -/// `core::str::from_utf8` round-trip. pub fn parse_int(buf: &[u8], radix: u8) -> Result where T: TryFrom + TryFrom, @@ -960,10 +891,6 @@ where } } -/// `std.fmt.parseUnsigned(T, buf, radix)` — [`parse_int`] without sign -/// handling: a leading `+`/`-` is `InvalidCharacter`. Use when the grammar -/// being parsed forbids signs (semver components, HTTP status codes, -/// content-length, etc.). #[inline] pub fn parse_unsigned(buf: &[u8], radix: u8) -> Result where @@ -974,10 +901,6 @@ where T::try_from(acc).map_err(|_| ParseIntError::Overflow) } -/// `std.fmt.parseInt(T, s, 10) catch null` — decimal convenience wrapper over -/// [`parse_int`]. Replaces the ~12 file-local -/// `fn parse_T(s:&[u8])->Option{ parse_int(s,10).ok() }` thin wrappers the -/// port spawned (Zig calls `std.fmt.parseInt` inline at every site). #[inline] pub fn parse_decimal(s: &[u8]) -> Option where @@ -986,23 +909,6 @@ where parse_int::(s, 10).ok() } -// ────────────────────────────────────────────────────────────────────────── -// parse_double — `WTF.parseDouble` (src/jsc/WTF.zig:20 / bun.zig:1150) -// -// Partial-match JS-semantics double parse over Latin-1 bytes. Unlike -// [`parse_f64`] this accepts a numeric *prefix* (`b"1.5x"` → `Ok(1.5)`) and -// does NOT special-case `inf`/`nan` — it is exactly WebKit's lexer behaviour. -// -// Lives in tier-0 `bun_core` (not `bun_jsc`) so `bun_interchange` (yaml/toml), -// `bun_js_parser::lexer`, and `bun_install` can call it without taking a -// `bun_jsc` edge. `bun_core::wtf`, `bun_jsc::wtf`, and `bun::` re-export it -// to preserve the Zig namespace shape. -// -// TODO(port): Zig `bun.parseDouble` falls back to `std.fmt.parseFloat` under -// `comptime Environment.isWasm` (no WebKit link). Restore when wasm target is -// brought up. -// ────────────────────────────────────────────────────────────────────────── - /// Error from [`parse_double`] — Zig `error{InvalidCharacter}`. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct InvalidCharacter; @@ -1037,22 +943,10 @@ pub fn parse_double(buf: &[u8]) -> Result { Ok(res) } -// `WTF__parseDouble` — WebKit's JS-semantics double parser (Latin-1 input, -// reports prefix length). Declared here (not via `bun_string`) so tier-0 -// callers can parse floats with no UTF-8 validation. Link-time symbol -// provided by `src/jsc/bindings/wtf-bindings.cpp`. unsafe extern "C" { fn WTF__parseDouble(bytes: *const u8, length: usize, counted: *mut usize) -> f64; } -/// `std.fmt.parseFloat(f64, buf)` — full-match parse of `s` as an `f64`. -/// Returns `None` on empty input, trailing garbage (`b"1.5x"`), or non-numeric -/// input. Backed by `WTF__parseDouble` (no `&str` round-trip — digits are -/// ASCII, validation is wasted work). -/// -/// `WTF::parseDouble` rejects `inf`/`nan` (JS-number semantics); those are -/// special-cased here so callers ported from `std.fmt.parseFloat` keep the -/// same surface. pub fn parse_f64(s: &[u8]) -> Option { if s.is_empty() { return None; @@ -1091,10 +985,6 @@ pub fn parse_f32(s: &[u8]) -> Option { parse_f64(s).map(|v| v as f32) } -/// Parse `s` as `T` for grammars whose alphabet is pure ASCII (IP addresses, -/// booleans). Any non-ASCII byte short-circuits to `None`, so the `&str` view -/// is always valid without a UTF-8 walk. **Do not** use for integers/floats — -/// use [`parse_int`] / [`parse_f64`]. #[inline] pub fn parse_ascii(s: &[u8]) -> Option { if !s.is_ascii() { @@ -1327,10 +1217,6 @@ impl Display for FormatValidIdentifier<'_> { // GitHub Actions formatting // ─────────────────────────────────────────────────────────────────────────── -/// Formats a string to be safe to output in a Github action. -/// - Encodes "\n" as "%0A" to support multi-line strings. -/// https://github.com/actions/toolkit/issues/193#issuecomment-605394935 -/// - Strips ANSI output as it will appear malformed. pub fn github_action_writer(writer: &mut impl fmt::Write, self_: &[u8]) -> fmt::Result { let mut offset: usize = 0; let end = self_.len() as u32; @@ -1388,11 +1274,6 @@ pub fn github_action(self_: &[u8]) -> GithubActionFormatter<'_> { GithubActionFormatter { text: self_ } } -/// Formats a string to be safe to use as a Github Actions workflow-command -/// *property* value (e.g. the `title=` in `::error title=...::`). Unlike -/// [`github_action`] (which only escapes the message-class metacharacters), this -/// escapes the property-class metacharacters per the actions/toolkit spec: -/// `%`->`%25`, `\r`->`%0D`, `\n`->`%0A`, `:`->`%3A`, `,`->`%2C`. pub fn github_action_property_writer(writer: &mut impl fmt::Write, self_: &[u8]) -> fmt::Result { let mut start: usize = 0; for (i, &byte) in self_.iter().enumerate() { @@ -2285,11 +2166,6 @@ impl Display for QuickAndDirtyJavaScriptSyntaxHighlighter<'_> { } prev_keyword = None; - // Zig `while (cond) { i += 1 } else { i = 1; break :jsx; }` — Zig's - // while-else runs the else branch whenever the condition becomes false - // (i.e. on normal loop exit, since the body has no `break`). So the - // else ALWAYS fires here and the code below is dead in Zig too. - // TODO(port): Zig while-else always fires here — likely upstream bug, worth verifying. while i < text.len() && js_lexer::is_identifier_continue(text[i] as i32) { i += 1; @@ -2396,10 +2272,6 @@ pub fn format_ip<'a>( write!(cursor, "{}", address).map_err(|_| crate::err!("NoSpaceLeft"))?; let written = cursor.position() as usize; - // PORT NOTE: reshaped for borrowck — compute (start, end) offsets against - // `into` instead of iteratively reborrowing a `result` slice, so the final - // returned `&mut into[start..end]` carries the caller's `'a` lifetime - // cleanly. Semantics match Zig's `result = result[a..b]` chain exactly. let mut start = 0usize; let mut end = written; @@ -2419,13 +2291,6 @@ pub fn format_ip<'a>( // count (std.fmt.count) // ─────────────────────────────────────────────────────────────────────────── -// ───────────────────────── CountingWriter / Null ───────────────────────── -// One type subsumes Zig's `std.Io.Writer.Discarding` (null sink) and the -// removed `std.io.countingWriter(inner)` (forwarding wrapper). Implements -// `core::fmt::Write` so it can replace the per-crate private `CountingWriter` -// reinventions (clap). The byte-level `bun_io::Write` counting sink stays in -// `bun_io::DiscardingWriter` (different trait, sits above bun_core). - /// Zero-sized `fmt::Write` no-op — default type param for [`CountingWriter`]. pub struct Null; impl fmt::Write for Null { @@ -2482,12 +2347,6 @@ impl fmt::Write for CountingWriter<'_, W> { } } -/// Port of `std.fmt.count`: number of bytes the formatted args would produce. -/// -/// Zig drives a `Writer.Discarding` (64-byte scratch buffer that drops writes -/// and tallies length); Rust's `fmt::Arguments` plugs into the same shape via -/// a `fmt::Write` impl that only sums `s.len()`. No allocation, no UTF-8 -/// validation beyond what the formatter already did. #[inline] pub fn count(args: fmt::Arguments<'_>) -> usize { // Implementation sunk to T0 so `bun_alloc` (which sits below `bun_core`) @@ -2496,24 +2355,6 @@ pub fn count(args: fmt::Arguments<'_>) -> usize { bun_alloc::fmt_count(args) } -// ─────────────────────────────────────────────────────────────────────────── -// digit_count — unified decimal-width helper -// -// Replaces the former two-impl split: -// • fast_digit_count(u64)->u64 — Lemire 32-entry table; PANICKED on x ≥ 2³² -// (table OOB) despite its u64 signature. -// • count_int(i64)->usize — /=10 loop, full i64 incl. MIN, +1 for '-'. -// Zig has the same split (bun.fmt.fastDigitCount vs std.fmt.count("{d}",..)); -// unifying here improves on the original. -// ─────────────────────────────────────────────────────────────────────────── - -/// Decimal digit count of an unsigned 64-bit integer — i.e. the byte length -/// of its default `{}` rendering (`0 → 1`). -/// -/// Values < 2³² use Lemire's branchless table -/// (); -/// the rare ≥ 2³² tail falls back to a `/= 10` loop so the full `u64` range is -/// covered (the old `fast_digit_count` panicked on table OOB there). #[inline] pub fn digit_count_u64(x: u64) -> usize { if x == 0 { @@ -2573,11 +2414,6 @@ pub fn digit_count_i64(n: i64) -> usize { (n < 0) as usize + digit_count_u64(n.unsigned_abs()) } -/// Polymorphic decimal-width helper — `digit_count(n)` returns the byte -/// length of `n`'s default `{}` rendering for any primitive integer. -/// -/// Dispatches via [`DigitCount`] to [`digit_count_u64`] / [`digit_count_i64`]. -/// Callers needing the old `u64` return type cast: `digit_count(x) as u64`. #[inline] pub fn digit_count(n: T) -> usize { n.digit_count() @@ -2809,13 +2645,6 @@ pub const HEX_DECODE_TABLE: [u8; 256] = { t }; -/// Decode a single ASCII hex digit (`0-9`, `a-f`, `A-F`) to its nibble value `0..=15`. -/// -/// Returns `None` for any other byte. Callers needing a wider int cast with `as u16/u32` -/// or `.map(u32::from)`; callers needing `Result` use `.ok_or(..)`; callers with a -/// pre-validated byte use `.unwrap()`. -/// -/// Zig precedent: `std.fmt.charToDigit(c, 16)` / `bun.strings.toASCIIHexValue`. #[inline] pub const fn hex_digit_value(b: u8) -> Option { match b { @@ -2826,11 +2655,6 @@ pub const fn hex_digit_value(b: u8) -> Option { } } -/// Widened wrapper over [`hex_digit_value`] for callers holding a decoded code -/// point (`u32`, or `i32` bit-cast via `as u32`). Any value outside `0..=0xFF` -/// — including a `-1i32 as u32` EOF sentinel — falls through to `None`. -/// -/// Returns the nibble `0..=15`; callers cast to their accumulator width. #[inline] pub const fn hex_digit_value_u32(c: u32) -> Option { if c <= 0xFF { @@ -2840,13 +2664,6 @@ pub const fn hex_digit_value_u32(c: u32) -> Option { } } -/// Decode two ASCII hex digits into a single byte: `(hi << 4) | lo`. -/// -/// Returns `None` if either byte is not `[0-9a-fA-F]`. Callers adapt the -/// error channel exactly as for [`hex_digit_value`]: `.ok_or(..)?` for -/// `Result`, `.unwrap()` when pre-validated, `?` in `Option` context. -/// -/// Zig precedent: inner loop of `std.fmt.hexToBytes`. #[inline] pub const fn hex_pair_value(hi: u8, lo: u8) -> Option { match (hex_digit_value(hi), hex_digit_value(lo)) { @@ -2855,17 +2672,6 @@ pub const fn hex_pair_value(hi: u8, lo: u8) -> Option { } } -/// Parse exactly 4 ASCII hex digits from `input[..4]` into a `u16`. -/// -/// Returns `None` if `input.len() < 4` or any of the first 4 bytes is not -/// `[0-9A-Fa-f]`. Non-consuming — caller advances its cursor by 4 on `Some`. -/// -/// This is the `\uHHHH` primitive for JSON/JS string-escape parsing. Surrogate -/// handling (WTF-8 pass-through vs U+FFFD replace, consume-both vs leave-trail -/// on a non-trail second unit) is intentionally **not** baked in: callers -/// compose this with [`crate::strings::u16_is_lead`] / -/// [`crate::strings::decode_surrogate_pair`] and apply their own policy, per -/// the `strings` module note on caller-specific surrogate policy. #[inline] pub const fn parse_hex4(input: &[u8]) -> Option { if input.len() < 4 { @@ -2880,20 +2686,6 @@ pub const fn parse_hex4(input: &[u8]) -> Option { } } -/// Consume a run of ASCII hex digits from the front of `input`, accumulating -/// into a `u32` and stopping at the first non-hex byte, end of slice, or after -/// `max_digits` (whichever comes first). -/// -/// Returns `(value, digits_consumed)`. With `max_digits <= 8` the accumulator -/// cannot overflow `u32`; callers passing larger caps are responsible for -/// validating the digit count themselves. -/// -/// This is the *prefix* primitive — it never fails. Callers needing exact-N -/// semantics (e.g. `\uHHHH`) check `digits_consumed == N` afterward; callers -/// needing a narrower result cast (`value as u8`, `value as i32`). -/// -/// Zig precedent: none (each module hand-rolls); analogous to a bounded -/// `std.fmt.parseInt(u32, prefix, 16)` that also reports how much it ate. #[inline] pub fn parse_hex_prefix(input: &[u8], max_digits: usize) -> (u32, usize) { let mut value: u32 = 0; @@ -2910,15 +2702,6 @@ pub fn parse_hex_prefix(input: &[u8], max_digits: usize) -> (u32, usize) { (value, n) } -/// Decode a `2 * size_of::()`-char ASCII hex slice into `T` via native-endian -/// byte reinterpretation. Mirrors Zig's `parseHexToInt` (DevServer.zig:961): -/// `std.fmt.hexToBytes` into `[@sizeOf(T)]u8` then `@bitCast` — i.e. pairwise -/// hex-decode then `from_ne_bytes`, **not** a big-endian numeric accumulator. -/// `"0100000000000000"` → `1u64` on little-endian. -/// -/// Returns `None` if `slice.len() != 2 * size_of::()` or any byte is not -/// `[0-9a-fA-F]`. `T` is capped at 16 bytes (u128) since stable Rust can't size -/// a stack array by a generic without `generic_const_exprs`. #[inline] pub fn parse_hex_to_int(slice: &[u8]) -> Option { let n = core::mem::size_of::(); @@ -2947,10 +2730,6 @@ pub const fn hex_char_upper(n: u8) -> u8 { UPPER_HEX_TABLE[(n & 0x0F) as usize] } -/// Encode a single byte as two lowercase ASCII hex digits `[hi, lo]`. -/// Port of the open-coded `CHARSET[(b>>4)] / CHARSET[(b&0xF)]` pair found -/// throughout the Zig sources. For contiguous full-slice output prefer -/// [`bytes_to_hex_lower`]. #[inline] pub const fn hex_byte_lower(b: u8) -> [u8; 2] { [ @@ -3017,10 +2796,6 @@ pub const fn hex_u16(v: u16) -> [u8; 4] { ] } -// TODO(port): Zig parameterizes on `comptime Int: type` and computes -// `BufType = [@bitSizeOf(Int) / 4]u8`. Rust const generics can't derive an array -// length from a type's bit-width. Represent as a generic over u64 with explicit -// nibble count; add per-width helpers if this shows up on a hot path. pub struct HexIntFormatter { pub value: u64, } @@ -3064,19 +2839,11 @@ pub fn hex_int_upper(value: u64) -> HexIntFormatter(v: u64) -> [u8; N] { HexIntFormatter::::get_out_buf(v) } -/// Format a 6-byte MAC address as `xx:xx:xx:xx:xx:xx` (lowercase hex, -/// colon-separated). Returns a fixed 17-byte ASCII buffer; borrow as `&[u8]` -/// for `ZigString::init`. Port of the inline `std.fmt.bufPrint(.., "{x:0>2}:..")` -/// pattern duplicated at `node_os.zig:686` and `:800`. #[inline] pub fn mac_address_lower(mac: [u8; 6]) -> [u8; 17] { let mut out = [b':'; 17]; @@ -3089,10 +2856,6 @@ pub fn mac_address_lower(mac: [u8; 6]) -> [u8; 17] { out } -/// `{:0N}` — zero-padded fixed-width decimal of a `u64` into `[u8; N]`. -/// Decimal sibling of [`u64_hex_fixed`] / [`hex_byte_upper`]. Port of Zig -/// `std.fmt.printInt(.., .{.width=N, .fill='0'})`. Caller guarantees -/// `val < 10^N`; excess high digits are silently dropped (debug-asserted). #[inline(always)] pub fn itoa_padded(mut val: u64) -> [u8; N] { debug_assert!(N == 0 || val < 10u64.saturating_pow(N as u32)); @@ -3140,10 +2903,6 @@ impl Display for TrimmedPrecisionFormatter { write!(f, "{}", whole)?; let rem = self.num - whole; if rem != 0.0 { - // buf size = "0." + PRECISION digits - // PORT NOTE: Zig used `[2 + precision]u8` stack array; Rust const-generic array - // length arithmetic is unstable, so use a small fixed upper bound and - // const-assert it suffices (matches Zig's compile-time sizing guarantee). const { assert!( PRECISION + 3 <= 32, @@ -3272,11 +3031,6 @@ pub fn fmt_duration_one_decimal(ns: u64) -> DurationOneDecimal { }) } -// ─────────────────────────────────────────────────────────────────────────── -// SQL connection-timeout error message — pure formatting, cycle-free: bun_core -// is already a dep of bun_sql_jsc AND bun_runtime. -// ─────────────────────────────────────────────────────────────────────────── - /// Which connection-level timeout fired. Drives the message template in /// [`fmt_conn_timeout`]; shared by the Postgres and MySQL backends. #[derive(Clone, Copy)] @@ -3286,13 +3040,6 @@ pub enum ConnTimeoutKind { MaxLifetime, } -/// Render the canonical SQL connection-timeout error message. -/// -/// `ms` is converted to nanoseconds with a saturating multiply and rendered -/// through [`fmt_duration_one_decimal`] (matching the Zig backends' inline -/// `bun.fmt.fmtDurationOneDecimal(ms * std.time.ns_per_ms)`). `suffix` is -/// appended verbatim — used for the per-status `(sent startup message…)` / -/// `(during authentication)` tails. pub fn fmt_conn_timeout(kind: ConnTimeoutKind, ms: u32, suffix: &str) -> impl Display + '_ { struct F<'a>(ConnTimeoutKind, u32, &'a str); impl Display for F<'_> { @@ -3355,10 +3102,6 @@ pub struct FormatDouble { // TODO(port): move to _sys unsafe extern "C" { - // `&mut [u8; 124]` is ABI-identical to the C `char *` argument (thin - // non-null pointer to 124 writable bytes); the type encodes WTF__dtoa's - // only precondition (≥124-byte writable buffer), so `safe fn` discharges - // the link-time proof and callers need no `unsafe` block. safe fn WTF__dtoa(buf: &mut [u8; 124], number: f64) -> usize; } @@ -3390,14 +3133,6 @@ impl Display for FormatDouble { /// convention rather than the `Format*` struct convention used here). pub type DoubleFormatter = FormatDouble; -// ─── Integer → ASCII ─────────────────────────────────────────────────────── -// One path for every base-10 integer write. Backed by the `itoa` crate (LUT -// 2-digits-at-a-time — same code serde_json/cssparser ship), already in the -// workspace link graph. Replaces the three competing impls the port grew: -// `core::fmt`-via-SliceCursor (slow + silent-truncate footgun), the hand- -// rolled `itoa_u64` reverse-fill, and tcc_sys's private `itoa::Buffer` use. -// Zig has exactly one path (`std.fmt.printInt`); this restores that parity. - /// Stack scratch for [`itoa`]. 40 bytes — fits `i128::MIN`. `::new()` is a /// const no-op (uninit array), so declare it inline at the call site. pub use ::itoa::Buffer as ItoaBuf; @@ -3409,12 +3144,6 @@ pub fn itoa(buf: &mut ItoaBuf, n: T) -> &[u8] { buf.format(n).as_bytes() } -/// If `val` is exactly `10^e` for `e` in `4..=9`, return `Some(e)`; else `None`. -/// -/// Used by `js_printer`'s non-negative-integer fast path to emit the -/// minified-JS forms `1e4`..`1e9` (which are shorter than the full digit -/// expansion). `e ≤ 3` is not shorter; `e ≥ 10` exceeds `u32::MAX` and is -/// handled by the printer's f64 path. #[inline] pub const fn pow10_exp_1e4_to_1e9(val: u64) -> Option { match val { @@ -3428,11 +3157,6 @@ pub const fn pow10_exp_1e4_to_1e9(val: u64) -> Option { } } -/// Port of `std.fmt.printInt(buf, value, 10, .lower, .{})`: format `value` -/// into `buf` as base-10 ASCII and return the number of bytes written. -/// Panics if `buf` is too small — callers size the buffer by the type's max -/// digit count. Use [`itoa`] directly when you can own a fresh [`ItoaBuf`]; -/// this exists for offset-writes into a larger caller buffer. #[inline] pub fn print_int(buf: &mut [u8], value: T) -> usize { let mut tmp = ItoaBuf::new(); @@ -3441,23 +3165,12 @@ pub fn print_int(buf: &mut [u8], value: T) -> usize { s.len() } -/// [`print_int`] returning the written sub-slice of `buf` — the moral -/// equivalent of Zig's `std.fmt.bufPrint(&buf, "{d}", .{v}) catch unreachable`. -/// Use this when the caller wants the bytes; use [`print_int`] directly when -/// writing at an offset and only the byte-count is needed. #[inline] pub fn int_as_bytes(buf: &mut [u8], value: T) -> &[u8] { let n = print_int(buf, value); &buf[..n] } -/// NUL-terminated decimal `u64` → ASCII into a caller-owned scratch buffer, -/// returning a [`CStr`] borrowing the head of `buf`. -/// -/// This is the Rust analogue of Zig's `std.fmt.bufPrintZ(&buf, "{d}", .{n})` -/// — used when handing an integer to a C API that wants a `*const c_char` -/// service/port string (e.g. `getaddrinfo`, `ares_getaddrinfo`). 21 bytes -/// covers `u64::MAX` (20 digits) + NUL; `u16`/`u32` callers widen via `as u64`. #[inline] pub fn itoa_z(buf: &mut [u8; 21], n: u64) -> &core::ffi::CStr { let mut tmp = ItoaBuf::new(); @@ -3540,10 +3253,6 @@ fn escape_powershell_impl(str: &[u8], writer: &mut impl fmt::Write) -> fmt::Resu // OutOfRangeFormatter — Equivalent to ERR_OUT_OF_RANGE // ─────────────────────────────────────────────────────────────────────────── -// TODO(port): Zig `NewOutOfRangeFormatter(comptime T: type)` branches on `@typeName(T)` -// and `std.meta.hasFn(T, "format")` for the "Received" tail. The `@typeName(T)` fallback -// path is debug-only (Zig panics if field_name unset in debug). Represent as a trait so -// each `T` controls how it prints "Received ". pub trait OutOfRangeValue { fn write_received(&self, f: &mut Formatter<'_>) -> fmt::Result; fn type_name() -> &'static str; @@ -3676,13 +3385,6 @@ pub fn out_of_range( // truncatedHash32 // ─────────────────────────────────────────────────────────────────────────── -/// esbuild has an 8 character truncation of a base32 encoded bytes. this -/// is not exactly that, but it will appear as such. the character list -/// chosen omits similar characters in the unlikely case someone is -/// trying to memorize a hash. -/// -/// this hash is used primarily for the hashes in bundler chunk file names. the -/// output is all lowercase to avoid issues with case-insensitive filesystems. pub struct TruncatedHash32(pub u64); pub fn truncated_hash32(int: u64) -> TruncatedHash32 { @@ -3699,10 +3401,6 @@ fn truncated_hash32_impl(int: u64, writer: &mut impl fmt::Write) -> fmt::Result write_bytes(writer, &truncated_hash32_bytes(int)) } -/// Const-fn core of [`truncated_hash32`] / [`TruncatedHash32`]: the 8-byte -/// base32-ish encoding (native-endian, matches Zig `@bitCast([8]u8, int)`). -/// Exposed so const contexts (e.g. `js_parser::generated_symbol_name!`) can -/// share the single alphabet table instead of copy-pasting it. pub const fn truncated_hash32_bytes(int: u64) -> [u8; 8] { const CHARS: &[u8; 32] = b"0123456789abcdefghjkmnpqrstvwxyz"; let b = int.to_ne_bytes(); @@ -3744,22 +3442,6 @@ fn splat_byte_all(w: &mut impl fmt::Write, byte: u8, count: usize) -> fmt::Resul Ok(()) } -// ════════════════════════════════════════════════════════════════════════════ -// std.json.encodeJsonString — single canonical port. -// Zig: vendor/zig/lib/std/json/Stringify.zig:670 (encodeJsonString → -// encodeJsonStringChars → outputSpecialEscape). Every Rust copy that was -// hand-ported from a Zig `std.json.fmt(...)` call funnels through here. -// ════════════════════════════════════════════════════════════════════════════ - -/// Port of Zig stdlib `std.json.encodeJsonStringChars` with default options -/// (`escape_unicode = false`): writes the escaped body of a JSON string -/// **without** surrounding quotes. -/// -/// Escape set (matches `outputSpecialEscape` exactly): -/// - `\"` `\\` `\b` `\f` `\n` `\r` `\t` -/// - other `0x00..=0x1F` → `\u00XX` (lowercase hex) -/// - `0x20..=0xFF` → emitted verbatim in run-batched `write_str` calls -/// (input is treated as UTF-8/Latin-1 bytes; no transcoding). pub fn encode_json_string_chars(w: &mut impl fmt::Write, s: &[u8]) -> fmt::Result { let mut run = 0; for (i, &b) in s.iter().enumerate() { @@ -3795,10 +3477,6 @@ pub fn encode_json_string_chars(w: &mut impl fmt::Write, s: &[u8]) -> fmt::Resul Ok(()) } -/// Latin-1 sibling of [`encode_json_string_chars`]: same escape table, but -/// non-escaped bytes are widened (`b as char`) so 0x80..=0xFF are emitted as -/// their U+0080..U+00FF UTF-8 encodings rather than passed through as raw -/// (invalid) single bytes. ASCII runs are still batched via `write_bytes`. pub fn encode_json_string_chars_latin1(w: &mut impl fmt::Write, s: &[u8]) -> fmt::Result { let mut run = 0; for (i, &b) in s.iter().enumerate() { diff --git a/src/bun_core/heap.rs b/src/bun_core/heap.rs index 304bd155a79..38ea3bf7c5e 100644 --- a/src/bun_core/heap.rs +++ b/src/bun_core/heap.rs @@ -35,39 +35,11 @@ pub fn alloc(value: T) -> *mut T { Box::into_raw(Box::new(value)) } -/// Hand off an existing `Box` as its raw pointer. Type-preserving — works -/// for `Box<[T]>`, `Box`, etc. Pair with [`take`] or [`destroy`]. -/// -/// NOT a leak — this is `Box::into_raw`. Named `into_raw` (not `leak`) so the -/// pairing with `take`/`destroy` (= `from_raw`) reads correctly at call sites. #[inline(always)] pub fn into_raw(boxed: Box) -> *mut T { Box::into_raw(boxed) } -/// Give up our owning `Box` and return a `&mut T` whose lifetime the caller -/// picks (annotate it `&'static mut T` at the call site if the owner is -/// process-lifetime). The backing allocation's lifetime is now managed by -/// **something other than this scope**: -/// -/// - an intrusive refcount on the payload (the trailing `deref()` / `unref()` -/// reclaims via `Box::from_raw` once the count hits zero), -/// - a JSC `ExternalStringImpl` / `MarkedArrayBuffer` that owns the bytes and -/// frees them on GC, -/// - a `WeakPtr` table that may have outstanding aliases, -/// - an enqueued work-pool task that reclaims in its `destroy()` / `run()`. -/// -/// This is **`Box::leak` by another name** — the machine code is identical — but -/// the call site reads as "ownership handed off to ", not "leaked". -/// Use this (with a comment naming the owner) instead of a bare `Box::leak` -/// whenever the allocation *is* reclaimed, just not here. A bare `Box::leak` -/// should be reserved for genuine process-lifetime statics that are never freed. -/// -/// Prefer a paired typed helper that owns *both* halves of the round-trip when -/// one applies (`bun_threading::WorkPool::schedule_owned`, -/// `bun_libuv_sys::UvHandle::set_owned_data`, `#[js_class]` `to_js_boxed`, …); -/// `release` is for the residual cases (intrusive-refcount finalizers, FFI -/// ownership protocols) where no such helper exists. #[inline(always)] pub fn release<'a, T: ?Sized + 'a>(boxed: Box) -> &'a mut T { Box::leak(boxed) diff --git a/src/bun_core/hint.rs b/src/bun_core/hint.rs index c299887c813..726f2bf4d41 100644 --- a/src/bun_core/hint.rs +++ b/src/bun_core/hint.rs @@ -1,21 +1,5 @@ //! Branch-prediction hints — Rust port of Zig's `@branchHint`. -/// Mark the surrounding branch as cold/unlikely. Port of Zig -/// `@branchHint(.unlikely)` / `@branchHint(.cold)` (docs/PORTING.md:211). -/// -/// Calling a `#[cold]` callee makes LLVM treat the *call site's* basic block -/// as cold and lay it out off the hot path. `#[inline(never)]` is required: -/// if the empty body is inlined the call instruction — and with it the hint — -/// disappears. Do NOT mark this `#[inline]` / `#[inline(always)]`. -/// -/// ```ignore -/// if rare { -/// bun_core::hint::cold(); -/// return Err(e); -/// } -/// ``` -// TODO: replace with `core::hint::cold_path()` once rust-lang/rust#117174 -// stabilizes (then drop `#[inline(never)]`). #[cold] #[inline(never)] pub const fn cold() {} diff --git a/src/bun_core/lib.rs b/src/bun_core/lib.rs index 4744b5bd653..babae86a98e 100644 --- a/src/bun_core/lib.rs +++ b/src/bun_core/lib.rs @@ -2,10 +2,6 @@ #![feature(adt_const_params)] #![feature(thread_local)] // bare `__thread` slot for `thread_id::current()` cache #![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)] -// bun_core is the T0 foundation crate that bun_threading, bun_sys, and -// bun_collections depend on; importing any of them to satisfy the disallowed-* -// lints would create a dependency cycle. `output`/`Progress`/`Global` here ARE -// the std-backed implementations the lints route everyone else through. #![allow( clippy::disallowed_types, clippy::disallowed_methods, @@ -22,10 +18,6 @@ pub mod tty; pub mod util; pub use atomic_cell::{Atom, AtomicCell, ThreadCell}; -/// Shared state-machine tag for the streaming (de)compressors in -/// `bun_brotli` / `bun_zlib` / `bun_zstd`. Mirrors the identical -/// `pub const State = enum { Uninitialized, Inflating, End, Error }` -/// nested in each Zig reader/compressor struct. pub mod compress { #[derive(Clone, Copy, PartialEq, Eq)] pub enum State { @@ -43,17 +35,8 @@ pub mod env; pub mod windows_sys; pub mod wtf; -// ────────────────────────────────────────────────────────────────────────── -// `string` — the former `bun_string` crate, merged in to break the -// `bun_core ↔ bun_string` dep cycle. The `bun_string` crate is now a -// one-line re-export shim over this module. -// ────────────────────────────────────────────────────────────────────────── pub mod string; pub use ::bstr::{BStr, BString, ByteSlice}; -/// `bun.strings` (the full SIMD-backed `immutable` module). Distinct from the -/// scalar-fallback `crate::strings` shim below — several names -/// (`index_of_char`, `CodepointIterator`, `Encoding`) differ in signature. -/// Callers that previously wrote `bun_core::strings::X` import this. pub use string::immutable; pub use string::string_joiner::StringJoiner; pub use string::{ @@ -68,11 +51,6 @@ pub use string::{ }; pub use string::{StringPointer, Tag, slice_to_nul, slice_to_nul_mut}; -// ────────────────────────────────────────────────────────────────────────── -// Low-tier homes for types the merged `string` module needs that previously -// lived in `bun_ptr` / `bun_collections` (both depend on `bun_core`, so the -// merge would otherwise cycle). The original crates re-export these. -// ────────────────────────────────────────────────────────────────────────── pub mod external_shared; pub use external_shared::{ ExternalShared, ExternalSharedDescriptor, ExternalSharedOptional, WTFString, @@ -110,14 +88,6 @@ pub const unsafe fn cast_fn_ptr(f: F) -> G { unsafe { (&raw const f).cast::().read() } } -/// Non-owning borrowed slice whose backing storage outlives the holder. -/// -/// Runtime sibling of `bun_ast::StoreSlice` for `*const [T]` struct -/// fields. Same contract as `bun_ptr::BackRef`: the slice memory is owned -/// elsewhere (parent struct, leaked `Box`, interned string) and remains valid -/// for the holder's full lifetime. Stores a fat raw pointer (`*const [T]`, -/// `usize` len) so it is a byte-for-byte drop-in for the raw `*const [T]` -/// fields it replaces. #[repr(transparent)] pub struct RawSlice(*const [T]); @@ -215,19 +185,10 @@ unsafe impl Send for RawSlice {} // SAFETY: same reasoning as the `Send` impl above — `&[T]: Sync ⇔ T: Sync`. unsafe impl Sync for RawSlice {} -/// Port of Zig's `std.os.environ` global (`[][*:0]u8`). On Windows the -/// startup path `bun_sys::windows::env::convert_env_to_wtf8` overwrites this -/// with a WTF-8-encoded envp slice; `getenvZ` and `bun_main` then read it via -/// `os_environ_ptr()`. POSIX builds leave it empty and use libc's `environ`. #[cfg(windows)] pub mod os { use core::ffi::c_char; - // Stored as raw (ptr, len) — NOT `&'static mut [_]` — so `environ()` (which - // hands out a shared `&[_]`) never aliases a live `&mut`. Zig's - // `std.os.environ` is a plain slice global with no exclusivity guarantee; - // mirroring that with `&'static mut` would be UB the moment a reader - // borrows while a writer holds the swapped-out `&mut`. static mut ENVIRON: (*mut *mut c_char, usize) = (core::ptr::null_mut(), 0); /// Swap in a new envp slice; returns the previous (ptr, len) pair (Zig: @@ -328,13 +289,6 @@ pub mod path_sep { } } - /// Host-OS-native absolute-path predicate (Zig: `std.fs.path.isAbsolute`). - /// POSIX: leading `/`. Windows: leading `/` or `\`, or 3-byte `X:/`|`X:\` - /// — faithful to Zig std: **no** alphabetic gate on the drive byte, and a - /// bare `X:` with no trailing separator is **not** absolute. - /// - /// Sunk from `bun_paths::is_absolute` so tier-0 (`util::which`) and - /// tier-2+ share a single impl. #[inline] pub const fn is_absolute_native(p: &[u8]) -> bool { #[cfg(not(windows))] @@ -354,11 +308,6 @@ pub mod path_sep { } } -// ─── libm shims ─────────────────────────────────────────────────────────────── -// Canonical extern for libm's `powf`/`pow` (Zig: `bun.zig` `pub extern "c" fn -// powf`). Hot CSS color-space conversion paths (gam_srgb, lab, prophoto) call -// the safe wrapper below; keep `#[inline]` so cross-crate use stays a direct -// libm call. unsafe extern "C" { // safe: all args by-value; libm `powf` is defined for all f32 inputs. #[link_name = "powf"] @@ -370,22 +319,7 @@ pub fn powf(x: f32, y: f32) -> f32 { libm_powf(x, y) } -/// Safe `Vec` growth helpers — consolidate the -/// `reserve(n); spare_capacity_mut(); MaybeUninit::write…; unsafe set_len(n)` -/// pattern (S025) so the single `unsafe { set_len }` lives here behind a -/// locally-proven invariant instead of being open-coded at every fill site. pub mod vec { - /// Extend `v` by `n` elements, each produced by `f(i)` for `i in 0..n`. - /// - /// Equivalent to `for i in 0..n { v.push(f(i)) }` but reserves once and - /// writes through `spare_capacity_mut()` so no per-element capacity check - /// or length bump occurs in the hot loop. Replaces the Zig-ported - /// `reserve; ptr::write…; set_len` blocks where the fill is a pure - /// per-index function (constant, default, or `i`-derived). - /// - /// Panic-safety: if `f` panics at index `k`, `v.len()` is left at its - /// original value plus `k` — every exposed element is initialized, and the - /// partially-written tail stays in spare capacity (never dropped). #[inline] pub fn extend_from_fn(v: &mut Vec, n: usize, mut f: impl FnMut(usize) -> T) { v.reserve(n); @@ -409,13 +343,6 @@ pub mod vec { unsafe { v.set_len(prev + n) }; } - /// Append `n` copies of `value` to `v`. Zig: `std.ArrayList.appendNTimes` — - /// `ensureUnusedCapacity(n); @memset(unusedCapacitySlice()[0..n], value); len += n`. - /// - /// Unlike `v.extend(repeat_n(value, n))` or a `for _ { v.push(value) }` loop, - /// this reserves once and fills via `[MaybeUninit]::fill` (lowers to - /// `memset` for byte-sized `T`, vectorized stores for wider `Copy` types) — - /// no per-element `RawVec` capacity branch in the hot loop. #[inline] pub fn push_n(v: &mut Vec, value: T, n: usize) { v.reserve(n); @@ -427,13 +354,6 @@ pub mod vec { unsafe { v.set_len(prev + n) }; } - /// Extend `v` by `n` `T::default()` elements and return a mutable slice - /// of the newly-appended tail (`&mut v[prev_len .. prev_len + n]`). - /// - /// Replaces the Zig-ported `reserve(n); set_len(len+n); &mut v[len..]` - /// pattern (S022) where the tail is immediately overwritten by a clone/ - /// fill loop — the default-fill keeps every exposed `T` valid even if the - /// caller bails partway through writing. #[inline] pub fn grow_default(v: &mut Vec, n: usize) -> &mut [T] { let prev = v.len(); @@ -477,15 +397,6 @@ pub mod vec { &mut v[prev..] } - /// Drop the first `n` elements of `v` in place via overlapping memmove - /// (`copy_within(n.., 0)`) + `truncate`, retaining capacity. Equivalent - /// to `v.drain(..n)` for `T: Copy` but without the iterator machinery. - /// - /// `n == 0` is a no-op; `n >= len` degenerates to `clear()` (capacity - /// retained). All current callers are `Vec` ring/line buffers - /// shifting a consumed prefix down after a partial write/parse — the Zig - /// port open-coded `std.mem.copyForwards` + `items.len -= n` at every - /// site. #[inline] pub fn drain_front(v: &mut Vec, n: usize) { if n == 0 { @@ -500,13 +411,6 @@ pub mod vec { v.truncate(len - n); } - // ── Zig `ArrayList(u8).unusedCapacitySlice()` family ────────────────── - // The Zig stdlib has ONE helper and ~30 call sites; the Rust port had - // grown 11 hand-rolled `spare_capacity_mut().as_mut_ptr().cast::()` - // + `set_len(len+n)` copies because `spare_capacity_mut` returns - // `MaybeUninit` and every C-ABI fill site (read/recv/pread, simdutf, - // zlib, zstd, libdeflate, base64) needs `*mut u8` / `&mut [u8]`. - /// View `v[len..capacity]` as a write-only `&mut [u8]` for an FFI / /// syscall producer to fill. Pair with [`commit_spare`] (or use /// [`fill_spare`] which does both). @@ -656,39 +560,23 @@ impl ErrnoNames { /// so `$crate::pretty_fmt!` resolves from the wrapper macros in `output.rs`. pub use bun_core_macros::{EnumTag, pretty_fmt}; -/// Stand-in for Zig's `@import("build_options")`. Values are written at -/// configure time by `scripts/build/buildOptionsRs.ts` from the resolved -/// `Config` and `include!()`'d here; `build.rs` exports `BUN_CODEGEN_DIR` -/// and fingerprints the file so a sha/version change recompiles this crate. pub mod build_options { include!(concat!(env!("BUN_CODEGEN_DIR"), "/build_options.rs")); } // ── re-exports (the tier-0 surface downstream crates need) ──────────────── +pub use Global::*; pub use bun_alloc::oom_from_alloc; pub use bun_alloc::{ Alignment, AllocError, Allocator, is_slice_in_buffer, is_slice_in_buffer_t, out_of_memory, page_size, range_of_slice_in_buffer, }; -// FFI ABI-safety primitives — `bun_opaque` is the zero-dep `#![no_std]` crate -// that hosts both the opaque-handle macro and the layout-assert macro, so all -// "FFI shape invariant" tooling lives in one file. Re-exported here so callers -// can write `bun_core::assert_ffi_layout!(...)` without naming `bun_opaque`. -pub use Global::*; pub use bun_opaque::{FfiLayout, assert_ffi_discr, assert_ffi_layout}; pub use ffi::{Zeroable, boxed_zeroed, boxed_zeroed_unchecked}; pub use result::*; pub use tty::Winsize; pub use util::*; -// ── intrusive-container parent recovery ─────────────────────────────────── -// -// Port of Zig's parent-from-field intrinsic. Intrusive data structures (task -// queues, timer heaps, linked lists) hand callbacks a `*mut Field` and expect -// the callee to walk back to the owning `*mut Parent`. Earlier ports open-coded -// this at ~150 sites as `ptr.cast::().sub(offset_of!(P, f)).cast::

()`; the -// helpers below are the single canonical spelling. Re-exported from `bun_ptr`. - /// Recover `*mut P` from a pointer to one of its fields. /// /// Accepts `*const F` so both `*mut` and `*const` field pointers coerce in; @@ -749,12 +637,6 @@ pub unsafe fn callback_ctx<'a, T>(ctx: *mut core::ffi::c_void) -> &'a mut T { unsafe { &mut *ctx.cast::() } } -/// `from_field_ptr!(Parent, field, ptr)` → `*mut Parent`. -/// -/// Type-checked wrapper over [`container_of`]: expands to -/// `container_of::(ptr, offset_of!(Parent, field))`. The call is -/// `unsafe` (caller asserts `ptr` points at `Parent.field` with whole-`Parent` -/// provenance) and must appear inside an `unsafe` block. #[macro_export] macro_rules! from_field_ptr { ($Parent:ty, $field:ident, $ptr:expr $(,)?) => { @@ -868,17 +750,6 @@ macro_rules! impl_field_parent { // ─── IntrusiveField ────────────────────────────────────────────────────── -/// Declares that `Self` embeds exactly one intrusive `F` field at byte -/// [`OFFSET`](IntrusiveField::OFFSET). This is the single Rust analogue of -/// Zig's `@fieldParentPtr` builtin: every per-module `const X_OFFSET: usize` -/// trait the port grew (`TASK_OFFSET`, `MIXIN_OFFSET`, -/// `CHANNEL_OFFSET`, `LazyBool<_, const OFFSET>`, `from_task`, …) is the same -/// `(Parent, Field, OFFSET)` triple plus [`container_of`] arithmetic — this -/// trait is exactly that triple, with both directions provided. -/// -/// Implement via [`intrusive_field!`]; the trait is `unsafe` because -/// [`from_field_ptr`](IntrusiveField::from_field_ptr) trusts the offset to -/// recover a `*mut Self` from a `*mut F` without any runtime check. pub unsafe trait IntrusiveField: Sized { /// `offset_of!(Self, )`. const OFFSET: usize; @@ -916,12 +787,6 @@ pub unsafe trait IntrusiveField: Sized { } } -/// Stamp `unsafe impl IntrusiveField<$F> for $T { const OFFSET = offset_of!($T, $field); }`. -/// -/// ```ignore -/// bun_core::intrusive_field!(ShellCpTask, task: ShellTask); -/// bun_core::intrusive_field!([T: Send] Wrapper, inner: Mixin>); -/// ``` #[macro_export] macro_rules! intrusive_field { // Bracketed-generics arm MUST come first: the bare `$T:ty` arm below would @@ -942,14 +807,6 @@ macro_rules! intrusive_field { /// `bun_core::OOM` per PORTING.md type map (`OOM!T` → `Result`). pub type OOM = AllocError; -/// `bun.JSError` — the canonical JS error union (`error{JSError, OutOfMemory, JSTerminated}` -/// in Zig). Tier-0 so every layer of the runtime can name it directly; `bun_jsc` re-exports -/// it as `bun_jsc::JsError` and `bun_event_loop` re-exports it as `ErasedJsError` for -/// historical call sites. -/// -/// `#[repr(u8)]` with explicit discriminants: `AnyTask` stores -/// `fn(*mut c_void) -> Result<(), JsError>` and the dispatcher relies on the 1-byte layout -/// surviving the type-erased round-trip. #[repr(u8)] #[derive(Debug, Copy, Clone, Eq, PartialEq)] pub enum JsError { @@ -965,19 +822,11 @@ bun_alloc::oom_from_alloc!(JsError); impl From for JsError { fn from(_: crate::Error) -> Self { - // PORT NOTE: Zig coerces arbitrary `anyerror` into the JS error union by - // throwing a generic Error; the throw happens at the call site. Mapping - // to `Thrown` here lets `?` propagate while the actual throw is handled - // by the host-fn wrapper. JsError::Thrown } } impl From for crate::Error { - /// Widen a `bun.JSError` value back into the `anyerror` newtype. Preserves - /// the exact Zig tag (`@errorName`) so call sites that round-trip through - /// `bun_core::Error` (e.g. the `bun_bundler::dispatch::DevServerVTable` - /// boundary) keep `error.OutOfMemory` distinguishable from `error.JSError`. #[inline] fn from(e: JsError) -> Self { match e { @@ -1023,28 +872,6 @@ pub fn concat<'b>(buf: &'b mut [u8], parts: &[&[u8]]) -> &'b [u8] { concat_into(buf, parts) } -/// Zig `union(enum)` field projection — `data.file`, `chunk.content.javascript`. -/// -/// In safety-checked Zig builds, reading a tagged-union field on the wrong -/// active variant panics at runtime. The Rust port hand-wrote ~20 identical -/// `match self { Self::V(x) => x, _ => unreachable!() }` accessors across -/// `jsc` / `bundler` / `ini` / `resolver` / `ast` / `install`; this macro is -/// the 1:1 analogue. Invoke it *inside* an `impl Enum { ... }` block: -/// -/// ```ignore -/// impl Data { -/// bun_core::enum_unwrap!(pub Data, File => fn as_file / as_file_mut -> File); -/// bun_core::enum_unwrap!(pub Data, Bytes => fn as_bytes / as_bytes_mut -> Bytes); -/// } -/// impl<'b> PrepareResult<'b> { -/// bun_core::enum_unwrap!(PrepareResult, Value => into fn into_value -> Expr); -/// } -/// ``` -/// -/// The `&`/`&mut` arm returns `&$Out` / `&mut $Out`, so when the variant -/// payload is itself a reference (e.g. `Entries(&'static mut DirEntry)`), -/// auto-deref/reborrow coerces `&&mut T` → `&T` and `&mut &mut T` → `&mut T` -/// to satisfy the declared return type. #[macro_export] macro_rules! enum_unwrap { ($vis:vis $Enum:ident, $Variant:ident => fn $get:ident / $get_mut:ident -> $Out:ty) => { @@ -1089,12 +916,6 @@ macro_rules! enum_unwrap { }; } -/// Zig: `bun.handleOom(expr)` — unwrap a `Result`, calling `outOfMemory()` on -/// `Err`. The full multi-arm version (which narrows mixed error sets) lives in -/// `bun_crash_handler::handle_oom`; that crate sits *above* `bun_core` in the -/// dep graph, so this tier-0 alias is the OOM-only arm — sufficient for the -/// `Result` / `Result` callers in `js_parser`, -/// `bake/DevServer`, etc. that spell it `bun_core::handle_oom`. #[inline] #[track_caller] pub fn handle_oom(r: core::result::Result) -> T { @@ -1104,17 +925,6 @@ pub fn handle_oom(r: core::result::Result) -> T { } } -/// Extension-method form of [`handle_oom`]: `.unwrap_or_oom()` on any -/// `Result`. Zig: `expr catch bun.outOfMemory()` — the *loose* idiom -/// that panics on **any** `Err`, not just OOM-only error sets. For the -/// Zig-faithful narrowing version (`bun.handleOom` with comptime error-set -/// reflection) see `bun_crash_handler::HandleOom`. -/// -/// PORT NOTE: this is intentionally a blanket `impl` — it matches the -/// existing `bun_core::handle_oom` free fn and the two pre-existing local -/// blanket impls in `run_command.rs` / `valkey.rs`. Callers that want a strict -/// `error{OutOfMemory}`-only whitelist should use `bun_crash_handler::HandleOom` -/// instead. pub trait UnwrapOrOom { type Output; fn unwrap_or_oom(self) -> Self::Output; @@ -1128,23 +938,12 @@ impl UnwrapOrOom for core::result::Result { } } -/// Zig: `bun.handleErrorReturnTrace(err, @errorReturnTrace())` — captures the -/// Zig error-return trace for crash reporting. Rust has no `@errorReturnTrace()` -/// builtin (panics already carry a backtrace), so this tier-0 shim is a no-op -/// that keeps call-site shape; the real reporter lives above in -/// `bun_crash_handler::handle_error_return_trace`. #[inline(always)] pub fn handle_error_return_trace(_err: E) {} // Real `declare_scope!`/`scoped_log!`/`pretty*!`/`warn!`/`note!` are // `#[macro_export]`ed from output.rs. -/// Zig: `bun.todoPanic(@src(), fmt, args)`. Intentional *runtime* "feature not -/// yet implemented" path that the Zig source ships with — distinct from a -/// porting placeholder. Captures file/line via `file!()`/`line!()` (the -/// `@src()` equivalent) and routes through `Output::panic`. -// TODO(port): wire `bun_analytics::Features::todo_panic` once the analytics -// crate is reachable from bun_core without a dep cycle. #[macro_export] macro_rules! todo_panic { ($($arg:tt)*) => {{ @@ -1157,20 +956,6 @@ macro_rules! todo_panic { }}; } -// `err!(Name)` / `err!("Name")` — Zig `error.Name` literal. -// -// Expands to a per-site `AtomicU16` slot that interns the stringified name on -// first hit, then hands back the cached `NonZeroU16` forever after. Two -// `err!(Foo)` at different sites resolve to the *same* code (the table is -// process-global), so `e == err!(Foo)` is a plain u16 compare — the property -// h2 `error_code_for`, install retry loops, etc. were blocked on. -// -// Layout: `AtomicU16::new(0)` is 2 bytes of all-zeros (vs `OnceLock` at -// 8+), so the ~1.3k call-site statics shrink and land in `.bss` for free. On -// ELF targets they're additionally clustered into a dedicated `.bun_err` -// section so the whole set occupies one page. The cold miss path is a single -// non-generic `#[cold]` function (`intern_cached`) — no per-closure -// `get_or_init` monomorphization, one `.text` body instead of thousands. #[macro_export] macro_rules! err { ($name:ident) => { $crate::err!(@__cached ::core::stringify!($name)) }; @@ -1205,10 +990,6 @@ pub fn set_start_time(ns: i128) { let _ = START_TIME.set(ns); } -/// `bun.Timer` / `std.time.Timer` — minimal monotonic stopwatch. Mirrors Zig's -/// `std.time.Timer.{start,read}` so callers ported verbatim (e.g. -/// `Lockfile::clean_with_logger`, `LifecycleScriptSubprocess`) compile against -/// the tier-0 surface without pulling in `bun_perf`. pub mod time { // `std.time.*` — defined in `util::time`; re-exported so `bun_core::time::*` resolves uniformly. pub use crate::util::time::{ @@ -1234,10 +1015,6 @@ pub mod time { } } -/// `bun.schema` — `src/options_types/schema.zig`. The full generated API -/// types live in `bun_api` (tier-2); tier-0 only needs the namespace to -/// exist so `bun_core::schema::api::StringPointer` etc. resolve as re-exports -/// once that crate un-gates. For now expose the one type tier-0 itself owns. pub mod schema { pub mod api { pub use crate::util::StringPointer; @@ -1251,16 +1028,6 @@ pub use fmt::{ InvalidCharacter, ParseIntError, js_lexer, js_printer, parse_decimal, parse_int, parse_unsigned, }; -// ────────────────────────────────────────────────────────────────────────── -// Flattened top-level string/fmt API. -// -// `string_immutable` is the full ported `bun.strings` namespace (was the -// `bun_core::immutable` module before the crate merge). The former -// `pub mod strings { … }` cycle-breaker shim is now an internal -// `strings_impl` module whose items are glob-re-exported at crate root, and -// `pub mod strings { pub use super::*; }` keeps `bun_core::strings::X` -// resolving for callers that haven't been rewritten yet. -// ────────────────────────────────────────────────────────────────────────── pub use crate::string::immutable as string_immutable; pub use crate::string::immutable::{ @@ -1298,22 +1065,7 @@ pub use crate::fmt::{ truncated_hash32, truncated_hash32_bytes, utf16, }; -/// Surrogate/transcode primitives + scalar-fallback string helpers that -/// predate the `string::immutable` merge. Glob-re-exported at crate root so -/// `crate::strings::X` (via the alias module below) and `bun_core::X` both -/// resolve. Do NOT add a `pub use string::immutable::*` glob here — several -/// names (`first_non_ascii`, `index_of_char`, `Encoding`, `CodepointIterator`) -/// have intentionally-different signatures in the two layers. pub(crate) mod strings_impl { - // ─── UTF-16 surrogate-pair encoding (ICU U16_LEAD / U16_TRAIL) ───────────── - // Zig parity: src/string/immutable/unicode.zig:1480 `u16Lead`/`u16Trail`, - // re-exported as `strings.u16Lead`/`strings.u16Trail`. Defined here in - // bun_core (not bun_string) so the WTF-8 fallback transcoder below and any - // other tier-0 caller can use it without a dep cycle. - // - // Precondition: `supplementary` is in U+10000..=U+10FFFF. Out-of-range input - // is not checked in release (matches the ICU C macros' truncating cast). - /// ICU `U16_LEAD`: high surrogate for a supplementary code point. #[inline] pub const fn u16_lead(supplementary: u32) -> u16 { @@ -1434,11 +1186,6 @@ pub(crate) mod strings_impl { out.extend_from_slice(&input[i..]); out } - /// Zig: `strings.eqlCaseInsensitiveASCII` (src/string/immutable.zig). - /// Spec-faithful port: defers to libc `strncasecmp`/`_strnicmp` for the - /// hot path (CSS parser, HTTP header matching). Unlike Zig's NUL-terminated - /// literals, Rust slices have no terminator, so a `b` shorter than `a` is - /// rejected instead of read past. #[inline] pub fn eql_case_insensitive_ascii(a: &[u8], b: &[u8], check_len: bool) -> bool { if check_len { @@ -1473,10 +1220,6 @@ pub(crate) mod strings_impl { _strnicmp(a.as_ptr().cast(), b.as_ptr().cast(), a.len()) == 0 } } - /// Zig: `strings.containsCaseInsensitiveASCII` — naive O(n·m) windowed - /// case-insensitive ASCII substring search (matches the Zig scalar impl; - /// callers are cold path-lookup on macOS/Windows where the FS is - /// case-insensitive). #[inline] pub fn contains_case_insensitive_ascii(haystack: &[u8], needle: &[u8]) -> bool { if needle.len() > haystack.len() { @@ -1491,10 +1234,6 @@ pub(crate) mod strings_impl { } false } - /// `bun.strings.isWindowsAbsolutePathMissingDriveLetter` (immutable/paths.zig) - /// — true for `\foo`-style absolute paths that lack a `C:` / `\\?\` / - /// `\\server\` prefix and therefore need the cwd's drive prepended. - /// Generic over `u8`/`u16` to mirror the Zig comptime `T: type` param. pub fn is_windows_absolute_path_missing_drive_letter( chars: &[T], ) -> bool { @@ -1528,34 +1267,14 @@ pub(crate) mod strings_impl { } } - // Zig: `bun.path.windowsFilesystemRootT(T, chars).len == 1`. With - // `chars[0]` already known to be a separator, that fn returns len > 1 - // only via its UNC/device branch (`len >= 5 && sep[0] && sep[1] && - // !sep[2]`); every other separator-led path resolves to a single-char - // root. Inlined here because `bun_paths` would be a tier-0 cycle. - // - // '\\Server\Share' -> false (UNC) - // '\\Server\\Share' -> true (extra separator — not UNC) - // '\Server\Share' -> true (posix-style) !(chars.len() >= 5 && sep(chars[1]) && !sep(chars[2])) } - /// `strings.eqlComptimeIgnoreLen` — caller has already checked `a.len() == - /// b.len()` (the "ignore len" means "don't re-check"). PERF(port): the Zig - /// version generates length-specialized SWAR loads at comptime; this scalar - /// fallback is fine for the only T0/T1 caller (ComptimeStringMap, where - /// `b` is a small static). #[inline] pub fn eql_comptime_ignore_len(a: &[u8], b: &'static [u8]) -> bool { debug_assert_eq!(a.len(), b.len()); a == b } - /// `const fn` byte-slice equality — slice `==` is not `const` on stable, so - /// const-context callers (clap param-name lookup, MultiArrayList field-name - /// reflection, host-fn error-set parsing) need the manual len-check + while - /// loop. Zig precedent: a single `std.mem.eql(u8, a, b)`; the per-crate - /// duplication was a Rust-port artifact, not a design choice. Runtime - /// callers should prefer plain `==` (lowers to `memcmp`). #[inline] pub const fn const_bytes_eq(a: &[u8], b: &[u8]) -> bool { if a.len() != b.len() { @@ -1577,20 +1296,10 @@ pub(crate) mod strings_impl { const_bytes_eq(a.as_bytes(), b.as_bytes()) } - // ────────────────────────────────────────────────────────────────────── - // Transcoding (from src/string/immutable/unicode.zig). Lives in T0 so - // collections::Vec can call it without depending on bun_string. - // Allocator params dropped per PORTING.md §Allocators. - // ────────────────────────────────────────────────────────────────────── use bun_simdutf_sys::simdutf; #[inline] pub fn is_all_ascii(slice: &[u8]) -> bool { - // Short-string fast path: for ≤32 bytes the scalar loop wins. Without - // cross-LTO the FFI path is Rust → `simdutf__validate_ascii` shim - // (push/mov/pop/jmp) → `simdutf::validate_ascii` (runtime CPU-dispatch - // vtable load + indirect call) → impl; on the `path.dirname` micro the - // 2-hop dispatch was ~60% of the SIMD work for 14-byte inputs. if slice.len() <= 32 { return slice.iter().all(|&b| b < 0x80); } @@ -1641,17 +1350,6 @@ pub(crate) mod strings_impl { } } - // ── UTF-16 surrogate primitives (ICU `utf16.h` macros) ──────────────────── - // Canonical home is bun_core (not bun_string) because bun_core::strings itself - // needs these for the simdutf scalar-fallback paths (append_wtf8_from_utf16, - // copy_utf16_into_utf8, util::getcwd on Windows) and bun_string already - // depends on bun_core. bun_string re-exports the full set via - // `pub use bun_core::strings::{u16_is_lead, ...}`. - // - // DO NOT add a one-size `Utf16CodepointIter` here: unpaired-surrogate policy - // is caller-specific and load-bearing (WTF-8 pass-through vs U+FFFD replace - // vs js_printer's unchecked-trail combine). Callers compose the primitives. - /// ICU `U16_SURROGATE_OFFSET` = `(0xD800 << 10) + 0xDC00 - 0x10000`. pub const U16_SURROGATE_OFFSET: u32 = (0xD800u32 << 10) + 0xDC00 - 0x10000; @@ -1710,10 +1408,6 @@ pub(crate) mod strings_impl { } } - /// Decode one code point from `input[0..]` with **WTF-16 pass-through**: - /// well-formed pairs are combined; *unpaired* surrogates are returned - /// verbatim (so the caller can re-encode them as 3-byte WTF-8). - /// Returns `(code_point, units_consumed ∈ {1,2})`. `input` must be non-empty. #[inline] pub fn decode_wtf16_raw(input: &[u16]) -> (u32, u8) { let c0 = input[0]; @@ -1834,21 +1528,6 @@ pub(crate) mod strings_impl { convert_utf16_to_utf8(Vec::new(), utf16) } - /// Transcode raw UTF-16-LE *bytes* (no alignment requirement) to a fresh - /// UTF-8 `Vec`. - /// - /// `to_utf8_alloc` takes `&[u16]`, but constructing a `&[u16]` from a - /// `&[u8]` whose pointer is not 2-byte-aligned is immediate language-level - /// UB (`core::slice::from_raw_parts` requires `data` be aligned for `T`), - /// regardless of how the consumer reads the memory. Callers that hold a - /// `Vec` / `&[u8]` of LE bytes (e.g. BOM-stripping a file buffer) MUST - /// route through this helper instead of casting. - /// - /// The bytes are first copied into a freshly-allocated, properly-aligned - /// `Vec` via a raw byte `memcpy` (no per-element decode — simdutf - /// interprets the buffer as little-endian and Bun targets only LE hosts), - /// then handed to `to_utf8_alloc`. An odd trailing byte is dropped, which - /// matches the prior `len() / 2` truncation. pub fn to_utf8_alloc_from_le_bytes(le_bytes: &[u8]) -> Vec { let n_u16 = le_bytes.len() / 2; if n_u16 == 0 { @@ -2053,21 +1732,11 @@ pub(crate) mod strings_impl { crate::ZBox::from_vec_with_nul(to_utf8_alloc(utf16)) } - /// Port of `firstNonASCII16`: index of the first u16 codeunit `>= 0x80`, or - /// `None` if all-ASCII. Single SIMD-upgrade target — Zig uses `@Vector(8,u16)` - /// max-reduce + `@ctz` bitmask (immutable.zig:1720); simdutf exposes no - /// u16-ASCII-index fn and WTF's `charactersAreAllASCII` is bool-only, - /// so scalar until portable_simd lands. #[inline] pub fn first_non_ascii16(utf16: &[u16]) -> Option { utf16.iter().position(|&u| u >= 0x80) } - /// Narrow ASCII-only `src` into `dst`. Returns `Some(&mut dst[..src.len()])` - /// iff every unit is `< 0x80` and `dst.len() >= src.len()`; otherwise `None` - /// (partial writes to `dst` are not rolled back). Composes `firstNonASCII16` - /// + `copyU16IntoU8` — Zig has no single helper and open-codes this per site - /// (e.g. string.zig `inMapCaseInsensitive`). #[inline] pub fn narrow_ascii_u16<'a>(src: &[u16], dst: &'a mut [u8]) -> Option<&'a mut [u8]> { let dst = dst.get_mut(..src.len())?; @@ -2089,13 +1758,6 @@ pub(crate) mod strings_impl { s.iter().position(|c| chars.contains(c)) } - // Bound relaxed Eq → PartialEq to match core::slice::<[T]>::starts_with / - // ends_with exactly. Bodies are semantically identical to the stdlib - // methods; kept as named free fns so Zig-port call sites that read - // `strings::has_prefix_t(a, b)` stay 1:1 with `bun.strings.hasPrefixComptime` - // / `std.mem.startsWith`. Rust already lowers slice `==` on integer T to - // memcmp, so the `eql_long`/`reinterpret_to_u8` perf path from - // immutable.rs is unnecessary. #[inline] pub fn has_prefix_t(s: &[T], prefix: &[T]) -> bool { s.len() >= prefix.len() && s[..prefix.len()] == *prefix @@ -2129,11 +1791,6 @@ pub(crate) mod strings_impl { eql_case_insensitive_ascii(a, b, true) } - /// Zig: open-coded `or`-chains over `eqlCaseInsensitiveASCII` at every site - /// (custom.zig:1526, ident.zig:278, WebSocketUpgradeClient.zig:1426 — the - /// `css.todo_stuff.match_ignore_ascii_case` markers). Haystacks are 6-12 - /// const literals; `#[inline]` lets LLVM unroll back to the original - /// short-circuit chain. For key→value dispatch use `in_map_case_insensitive`. #[inline] pub fn eql_any_case_insensitive_ascii(needle: &[u8], haystack: &[&[u8]]) -> bool { haystack @@ -2141,27 +1798,11 @@ pub(crate) mod strings_impl { .any(|h| eql_case_insensitive_ascii(needle, h, true)) } - // ────────────────────────────────────────────────────────────────────── - // Scanners / sniffers used by fmt.rs (URL redaction, path quoting, etc.). - // Formerly a duplicate `mod strings` in fmt.rs; merged here so the crate - // has a single `bun_core::strings` and fmt.rs picks up the simdutf-backed - // `first_non_ascii`/`is_all_ascii` instead of scalar shims. - // ────────────────────────────────────────────────────────────────────── - #[inline] pub fn index_of_any(s: &[u8], chars: &[u8]) -> Option { s.iter().position(|b| chars.contains(b)) } - // ────────────────────────────────────────────────────────────────────── - // IP-literal predicates. Spec (immutable.zig:1984-2004) calls - // `bun.c_ares.ares_inet_pton`, the vendored c-ares implementation. - // Do NOT call the system `inet_pton` here: on Windows that resolves into - // ws2_32.dll and fails with WSANOTINITIALISED whenever it runs before - // `WSAStartup()`, which URL/host parsing can. c-ares' impl is pure C, no - // preconditions. bun_core sits below bun_cares_sys in the dep graph, so we - // re-declare the extern locally (zero new deps; `libc` is already here). - // ────────────────────────────────────────────────────────────────────── unsafe extern "C" { pub fn ares_inet_pton( af: core::ffi::c_int, @@ -2169,19 +1810,11 @@ pub(crate) mod strings_impl { dst: *mut core::ffi::c_void, ) -> core::ffi::c_int; } - // dep-graph: bun_core < bun_sys, so cannot import the canonical - // `bun_sys::posix::AF`. Keep a thin libc/ws2def passthrough instead. The - // previous hand-rolled cfg ladder hardcoded `10` for the BSD fallback, - // which is wrong (FreeBSD AF_INET6 == 28); routing through `libc` fixes that. #[cfg(not(windows))] const AF_INET6: core::ffi::c_int = libc::AF_INET6 as core::ffi::c_int; #[cfg(windows)] const AF_INET6: core::ffi::c_int = 23; // ws2def.h - /// Zig: `bun.strings.isIPV6Address` — `ares_inet_pton(AF_INET6, …) > 0`. - /// Must be a strict parse, not a `contains(':')` heuristic: on Windows a - /// unix-socket path like `C:/Windows/Temp/…` contains a colon and the old - /// heuristic mis-bracketed it as `unix://[C:/…]`, which fails URL parsing. pub fn is_ipv6_address(input: &[u8]) -> bool { let mut buf = [0u8; 512]; if input.len() >= buf.len() { @@ -2476,18 +2109,6 @@ pub(crate) mod strings_impl { } } - /// `strings.convertUTF16ToUTF8InBuffer` — write UTF-8 into `out`, return - /// the written sub-slice. Infallible: Zig's `![]const u8` has an empty - /// inferred error set, so every `catch` at call sites is dead code. The - /// caller is responsible for sizing `out` for the worst case (≤ 3× input - /// code units). - /// - /// PORT NOTE: Zig passes `out` straight to simdutf with no bounds check - /// (UB if undersized). We assert in release too — one extra SIMD length - /// scan is cheap, and a panic beats heap corruption if a future caller - /// gets the sizing wrong. All current callers (~10, Windows wide-path - /// code) size `out` at `3 * utf16.len()` or `MAX_PATH * 3`, so this never - /// fires in practice. pub fn convert_utf16_to_utf8_in_buffer<'a>(out: &'a mut [u8], utf16: &[u16]) -> &'a mut [u8] { if utf16.is_empty() { return &mut out[..0]; @@ -2501,10 +2122,6 @@ pub(crate) mod strings_impl { let result = simdutf::convert::utf16::to::utf8::le(utf16, out); &mut out[..result] } - // ─── path basename (std.fs.path.basename{Posix,Windows}) ────────────────── - // Minimal code-unit trait so the generic basename impls can live at T0 - // without pulling `bun_paths::PathChar` (T1) down. `PathChar` and - // `PathUnit` both add `: PathByte` as a supertrait and inherit `from_u8`. pub trait PathByte: Copy + Eq + 'static { fn from_u8(b: u8) -> Self; } @@ -2607,24 +2224,7 @@ pub(crate) mod strings_impl { pub use crate::string::immutable::convert_utf8_to_utf16_in_buffer; pub use strings_impl::*; -/// Back-compat alias: `bun_core::strings::X` → `bun_core::X`. The full -/// `bun.strings` namespace is `bun_core::immutable` (formerly -/// `bun_core::strings`); this alias keeps the ~200 existing -/// `bun_core::strings::` / `crate::strings::` call sites compiling. -/// -/// NOTE: a handful of names (`index_of_char`, `eql_long`, `first_non_ascii`, -/// `Encoding`, `CodepointIterator`) have a different signature here than in -/// `bun_core::immutable`. Callers that need the Zig-spec -/// `bun.strings.*` form import `bun_core::immutable as strings` instead. pub mod strings { - // `bun_core::strings` is the union of the crate-root surface (`super::*`, - // which carries the scalar-fallback `strings_impl::*` glob plus every - // `bun_core::Foo`) and the full Zig-spec `bun.strings.*` namespace - // (`string::immutable::*`). Names that exist in BOTH layers — same - // identifier, different signature — are explicitly disambiguated below - // in favour of `immutable` (matches every former `bun_core::strings::X` - // caller). Internal `bun_core` code that needs the scalar form spells - // `crate::strings_impl::X` directly. pub use super::*; pub use crate::string::immutable::*; pub use crate::string::immutable::{ @@ -2668,18 +2268,9 @@ pub mod feature_flag { BUN_FEATURE_FLAG_EXPERIMENTAL_BAKE ); } -/// Port of `bun.linuxKernelVersion()` (src/bun.zig) → `analytics.GeneratePlatform.kernelVersion()`. -/// Lives in T1 because `bun_sys` calls it from feature probes (copy_file_range, -/// ioctl_ficlone, RWF_NONBLOCK) and cannot depend on `bun_analytics`. Parses -/// `uname(2).release` major.minor.patch directly; the full Semver parse with -/// pre/build tags stays in `bun_analytics`. #[cfg(any(target_os = "linux", target_os = "android"))] pub fn linux_kernel_version() -> Version { use core::sync::atomic::{AtomicU32, Ordering}; - // Packed u32: u32::MAX = uninit, otherwise (major<<20)|(minor<<10)|patch. - // (Using MAX, not 0, as the sentinel so a parse that yields {0,0,0} caches - // as 0 and round-trips to {0,0,0} on every call — the previous 0-sentinel - // stored 1 in that case, returning {0,0,1} on subsequent calls.) static CACHE: AtomicU32 = AtomicU32::new(u32::MAX); let packed = CACHE.load(Ordering::Relaxed); if packed != u32::MAX { @@ -2707,21 +2298,7 @@ pub fn linux_kernel_version() -> Version { } } -/// FFI helpers shared by `#[uws_callback]` thunks and raw C-string call sites. -/// -/// The former `catch_unwind_ffi` / `abort_on_panic` panic barrier was removed: -/// the workspace builds with `panic = "abort"`, so Rust panics terminate inside -/// `bun_crash_handler`'s `std::panic` hook before any unwind starts — -/// `catch_unwind` always returns `Ok` and the wrapper was dead weight. JSC does -/// not throw C++ exceptions across its public API, so there is no foreign -/// unwind to catch either. Macro-generated `extern "C"` thunks now call the -/// user body directly (same end state as Zig `@panic` → `bun.crash_handler`). pub mod ffi { - // `core`-only primitives shared with the freestanding `bun_shim_impl` PE - // (which cannot link `bun_core`'s `#[no_mangle]` C-ABI surface). Single - // audited copy lives in `bun_opaque::ffi`; re-exported here so existing - // `bun_core::ffi::{wcslen,wstr_units,slice,slice_mut}` call paths are - // unchanged. pub use bun_opaque::ffi::{slice, slice_mut, wcslen, wstr_units}; /// Borrow a NUL-terminated C string from an FFI pointer. @@ -2795,10 +2372,6 @@ pub mod ffi { #[cfg(unix)] #[inline] pub fn uname() -> libc::utsname { - // `&mut libc::utsname` is ABI-identical to libc's `struct utsname *` - // (thin non-null pointer to a `#[repr(C)]` struct); the type encodes - // the only pointer-validity precondition, so `safe fn` discharges the - // link-time proof and the call needs no `unsafe` block. unsafe extern "C" { #[link_name = "uname"] safe fn libc_uname(buf: &mut libc::utsname) -> core::ffi::c_int; @@ -2819,19 +2392,6 @@ pub mod ffi { &b[..b.iter().position(|&c| c == 0).unwrap_or(b.len())] } - /// All-bits-zero value of `T` for `#[repr(C)]` FFI structs. - /// - /// Single audited wrapper over `core::mem::zeroed()` so libc/uv/c-ares - /// out-param init sites (`let mut x: libc::sigaction = zeroed();`) don't - /// each open-code an `unsafe` block. This is the Rust spelling of Zig's - /// `std.mem.zeroes(T)` / `= .{}` for `extern struct`. - /// - /// The `T: Zeroable` bound discharges the `mem::zeroed` safety obligation - /// once per type (at the `unsafe impl`), so callers need no `unsafe` - /// block. Prefer `T::default()` when `T` implements (or can derive) - /// `Default` — reserve this for foreign POD where the orphan rule blocks a - /// `Default` impl (libc, bindgen output) or where `Default` would be wrong - /// but zero-init matches the C API contract. #[inline(always)] pub const fn zeroed() -> T { // SAFETY: `T: Zeroable` is exactly the assertion that the all-zero bit @@ -3064,27 +2624,8 @@ pub mod ffi { unsafe { core::mem::zeroed() } } - /// Pointer to the calling thread's libc `errno` (Zig: `std.c._errno()`). - /// - /// Single audited cfg-ladder over the per-libc TLS accessor symbol so the - /// tree has ONE place that knows glibc/musl spell it `__errno_location()`, - /// bionic spells it `__errno()`, Darwin/BSD spell it `__error()`, and the - /// Windows CRT spells it `_errno()`. Every higher-tier crate routes through - /// this — `bun_errno::posix::errno`, `bun_sys::last_errno`, - /// `bun_sys::c::errno_location`, `bun_platform::linux` — instead of each - /// re-deriving the same target_os→symbol mapping. - /// - /// Obtaining the pointer has no preconditions (the per-libc TLS accessor - /// takes no args and never returns null); the deref obligation lives at - /// the call site. The returned pointer is valid for the calling thread's - /// lifetime — `*mut c_int` is `!Send`, so the cross-thread hazard is - /// already type-enforced. #[inline(always)] pub fn errno_ptr() -> *mut core::ffi::c_int { - // Per-libc TLS errno accessor: no args, never null, no preconditions. - // `safe fn` discharges the link-time proof so the body is a plain - // call; only the per-platform symbol *name* varies, expressed via - // `#[cfg_attr(.., link_name = ..)]` on a single declaration. unsafe extern "C" { #[cfg_attr( any( @@ -3126,26 +2667,10 @@ pub mod ffi { } pub mod asan { - //! Low-tier mirror of `src/safety/asan.zig`. `bun_safety` depends on - //! `bun_core`, so the implementation lives here and `bun_safety::asan` - //! re-uses the same `cfg(bun_asan)` gate. Callers in `bun_jsc`, - //! `bun_runtime`, and `bun_collections` reach the real LSAN/ASAN runtime - //! through this module — it must NOT be a no-op stub or LSAN root-region - //! registration (`VirtualMachine::rare_data`, `Listener.group`) silently - //! does nothing and every malloc-backed `us_socket_t` reachable only via a - //! mimalloc page is reported as a leak. use core::ffi::c_void; #[cfg(bun_asan)] unsafe extern "C" { - // The ASAN/LSAN runtime never dereferences `ptr` — it indexes shadow - // memory by address value (poison/unpoison/is_poisoned/describe) or - // records the range in an internal table (LSAN root regions). Misuse - // produces a controlled abort, not UB, so `safe fn` discharges the - // link-time proof and callers need no `unsafe` block. The *logical* - // "you own this region" precondition is advisory only — violating it - // trips an ASAN report (controlled abort), never language-level UB — - // so the public wrappers below are likewise safe `fn`s. safe fn __asan_poison_memory_region(ptr: *const c_void, size: usize); safe fn __asan_unpoison_memory_region(ptr: *const c_void, size: usize); safe fn __asan_address_is_poisoned(ptr: *const c_void) -> bool; @@ -3186,10 +2711,6 @@ pub mod asan { #[cfg(not(bun_asan))] let _ = ptr; } - /// Tell LSAN to scan `[ptr, ptr+size)` for live pointers during leak - /// checking. Needed when a malloc-backed object is reachable only through - /// a pointer that itself lives inside a mimalloc page (which LSAN does not - /// scan). #[inline] pub fn register_root_region(ptr: *const c_void, size: usize) { #[cfg(bun_asan)] @@ -3207,10 +2728,6 @@ pub mod asan { } } -// ──────────────────────────────────────────────────────────────────────────── -// glibc-compat / link wraps. Zig: src/workaround_missing_symbols.zig. -// build.ninja links with `-Wl,--wrap=gettid` so libc/std references land here. -// ──────────────────────────────────────────────────────────────────────────── #[cfg(target_os = "linux")] #[unsafe(no_mangle)] pub(crate) extern "C" fn __wrap_gettid() -> libc::pid_t { @@ -3218,11 +2735,6 @@ pub(crate) extern "C" fn __wrap_gettid() -> libc::pid_t { unsafe { libc::syscall(libc::SYS_gettid) as libc::pid_t } } -/// `bun.getTotalMemorySize()` (bun.zig:3498) — process-wide RAM budget, -/// cgroup/jetsam-aware. Backed by the linked C++ `Bun__ramSize()` -/// (src/jsc/bindings/c-bindings.cpp). Lives in `bun_core` so both -/// `bun_runtime` (node:fs preallocation guard) and the binary root can -/// call it without re-declaring the C ABI. pub fn get_total_memory_size() -> usize { unsafe extern "C" { // Pure FFI into Bun's C++ bindings; no arguments, no invariants. @@ -3239,11 +2751,6 @@ pub fn capture_stack_trace(begin: usize, addrs: &mut [usize]) -> usize { debug::capture_current(first, addrs) } -/// Zig `@returnAddress()`: a PC inside the caller's caller. `#[inline(always)]` -/// so this has no frame of its own — `frame_address()` reads the caller's fp, -/// and `[fp + PC_OFFSET]` is the caller's saved return address. Used as the -/// `first_address` trim point for `capture_current` (which falls back to the -/// full trace if it doesn't match). #[inline(always)] pub fn return_address() -> usize { #[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))] diff --git a/src/bun_core/output.rs b/src/bun_core/output.rs index dcf9a7c3c05..d1dda800f1f 100644 --- a/src/bun_core/output.rs +++ b/src/bun_core/output.rs @@ -29,15 +29,6 @@ use crate::Mutex; // MOVE_DOWN: io::Writer → bun_core (move-in pass) — re-exported as crate::io::Writer. use crate::io; -// ────────────────────────────────────────────────────────────────────────── -// Output sink (CYCLEBREAK §Debug-hook / §Dispatch cold path) -// -// `bun_sys::{File, QuietWrite, file::QuietWriter, make_path, create_file, -// deprecated::BufferedReader}` are higher-tier I/O primitives that `bun_core` -// cannot name. The `bun_dispatch::link_interface!` `OutputSink[Sys]` (declared -// at crate root) provides the seam; `bun_sys` supplies the `Sys` arm. -// ────────────────────────────────────────────────────────────────────────── - pub use crate::OutputSink; #[inline] @@ -171,14 +162,6 @@ pub fn argv() -> impl Iterator { crate::util::argv().into_iter() } -/// `bun.Output.debugWarn` — yellow `debug warn:` prefix to stderr in debug -/// builds, through the Bun output sink (so colour/redirect logic applies), -/// followed by an explicit flush. Zig output.zig:1189-1194. -/// -/// Function form takes a single [`PrettyFmtInput`] payload — callers pass -/// `format_args!("template {}", x)` (the dominant convention across the -/// codebase) or a bare `&str`. The payload is rendered first, then -/// ``-rewritten. For the comptime-literal fast path use the macro form. #[inline] pub fn debug_warn(payload: impl PrettyFmtInput) { if cfg!(debug_assertions) { @@ -214,10 +197,6 @@ pub fn debug(payload: impl PrettyFmtInput) { } } -/// `Output.prettyErrorln` — function form. Performs `` → ANSI rewrite on -/// the rendered payload (using stderr's colour state), writes to stderr, and -/// appends `\n` if the rendered output does not already end in one. Macro -/// form: `crate::pretty_errorln!`. #[inline] pub fn pretty_errorln(payload: impl PrettyFmtInput) { let buf = payload.into_pretty_buf(enable_ansi_colors_stderr()); @@ -249,10 +228,6 @@ pub fn init_test() { ENABLE_ANSI_COLORS_STDERR.store(false, Ordering::Relaxed); } -/// `bun.Output.Source.Stdio.restore` — restore terminal to cooked mode on exit. -/// Thin alias over [`crate::output::stdio::restore`] (the real impl, also in -/// this crate); the indirection exists only because Zig spells the path both -/// `Output.Source.Stdio.restore` and `Output.Stdio.restore`. pub mod source { pub mod stdio { #[inline] @@ -280,11 +255,6 @@ static STDERR_STREAM: crate::RacyCell = crate::RacyCell::new(StreamT static STDOUT_STREAM: crate::RacyCell = crate::RacyCell::new(StreamType::ZEROED); static STDOUT_STREAM_SET: AtomicBool = AtomicBool::new(false); -// Track which stdio descriptors are TTYs (0=stdin, 1=stdout, 2=stderr). -// `[AtomicI32; 3]` has identical layout to `[i32; 3]` (`AtomicI32` is -// `#[repr(C, align(4))]`), so the `#[no_mangle]` symbol is bit-compatible with -// the C declaration `int32_t bun_stdio_tty[3]`. Using atomics instead of -// `RacyCell` makes Rust-side reads/writes fully safe (cell-get reduction). #[unsafe(no_mangle)] pub(crate) static bun_stdio_tty: [AtomicI32; 3] = [AtomicI32::new(0), AtomicI32::new(0), AtomicI32::new(0)]; @@ -426,17 +396,6 @@ impl Source { Self::configure_thread(); } - /// Like [`configure_thread`] but **skips** the JSC `StackCheck` FFI call - /// (`Bun__StackCheck__initialize` → `WTF::StackBounds::currentThreadStackBoundsInternal`). - /// - /// Use on pure-Rust worker threads (ThreadPool workers, HTTP client, - /// watchers) that never execute JavaScript. On the `bun install` cold path - /// the WTF/JSC `.text` pages backing those C++ symbols sit ~6 pages across - /// three otherwise-untouched 64 KB blocks; faulting them in from every - /// worker is pure overhead when no JS will ever run on that thread. - /// - /// Threads that *may* run JS (web workers, debugger, the main VM thread) - /// must keep using [`configure_thread`] / [`configure_named_thread`]. pub fn configure_thread_no_js() { if SOURCE_SET.get() { return; @@ -462,11 +421,6 @@ impl Source { pub fn get_force_color_depth() -> Option { let force_color = env_var::FORCE_COLOR.get()?; - // Supported by Node.js, if set will ignore NO_COLOR. - // - "0" to indicate no color support - // - "1", "true", or "" to indicate 16-color support - // - "2" to indicate 256-color support - // - "3" to indicate 16 million-color support Some(match force_color { 0 => ColorDepth::None, 1 => ColorDepth::C16, @@ -482,10 +436,6 @@ impl Source { pub fn is_color_terminal() -> bool { #[cfg(windows)] { - // https://github.com/chalk/supports-color/blob/d4f413efaf8da045c5ab440ed418ef02dbb28bf1/index.js#L100C11-L112 - // Windows 10 build 10586 is the first Windows release that supports 256 colors. - // Windows 10 build 14931 is the first release that supports 16m/TrueColor. - // Every other version supports 16 colors. return true; } #[cfg(not(windows))] @@ -549,25 +499,12 @@ pub mod windows_stdio { use crate::windows_sys as w; use crate::windows_sys::kernel32 as c; - // `HANDLE` is an opaque kernel handle (kernel32 validates and returns 0 on - // a non-console handle); `&mut DWORD` is ABI-identical to `LPDWORD` (thin - // non-null pointer). The reference type encodes the only pointer-validity - // precondition, so `safe fn` discharges the link-time proof. (`c::Get/Set - // ConsoleMode` from `bun_windows_sys` still take `*mut DWORD`; redeclared - // locally so the startup/restore paths below are plain calls.) #[link(name = "kernel32")] unsafe extern "system" { safe fn GetConsoleMode(hConsoleHandle: w::HANDLE, lpMode: &mut w::DWORD) -> w::BOOL; safe fn SetConsoleMode(hConsoleHandle: w::HANDLE, dwMode: w::DWORD) -> w::BOOL; } - /// At program start, we snapshot the console modes of standard in, out, and err - /// so that we can restore them at program exit if they change. Restoration is - /// best-effort, and may not be applied if the process is killed abruptly. - /// - /// Write-once at startup → `Once`, not `RacyCell`: `init()` builds the - /// snapshot locally and `.set()`s it; `restore()` reads via `.get()`. Both - /// sides are fully safe (cell-get reduction). pub(crate) static CONSOLE_MODE: crate::Once<[Option; 3]> = crate::Once::new(); pub(crate) static CONSOLE_CODEPAGE: core::sync::atomic::AtomicU32 = core::sync::atomic::AtomicU32::new(0); @@ -656,11 +593,6 @@ pub mod windows_stdio { if GetConsoleMode(stdin, &mut mode) != 0 { console_mode[0] = Some(mode); bun_stdio_tty[0].store(1, Ordering::Relaxed); - // There are no flags to set on standard in, but just in case something - // later modifies the mode, we can still reset it at the end of program run - // - // In the past, Bun would set ENABLE_VIRTUAL_TERMINAL_INPUT, which was not - // intentionally set for any purpose, and instead only caused problems. } if GetConsoleMode(stdout, &mut mode) != 0 { @@ -697,12 +629,6 @@ pub mod stdio { // TODO(port): move to bun_core_sys unsafe extern "C" { - // Written once by C at process startup before threads; Rust only reads. - // `[AtomicI32; 3]` has identical layout to C's `int32_t[3]` (`AtomicI32` - // is `#[repr(C, align(4))]`) and, unlike a plain non-`mut` extern static, - // does not assert immutability to the optimizer. `safe static` (Rust - // 2024 `unsafe extern`) discharges the link-time existence proof here so - // readers need no `unsafe` (cell-get reduction). pub(crate) safe static bun_is_stdio_null: [AtomicI32; 3]; /// No preconditions; one-shot stdio fixup at process startup. pub(crate) safe fn bun_initialize_process(); @@ -1006,10 +932,6 @@ pub fn disable_buffering() { } } -/// RAII: `disable_buffering()` now, `enable_buffering()` on drop. Covers the -/// Zig `Output.disableBuffering(); defer Output.enableBuffering();` pair used -/// around child-process exec where the child writes directly to inherited -/// stdio and Bun's buffer must not interleave. #[must_use = "dropping immediately re-enables buffering; bind to `let _scope = ...`"] pub struct DisableBufferingScope(()); @@ -1032,12 +954,6 @@ pub fn disable_buffering_scope() -> DisableBufferingScope { #[cold] pub fn panic(args: fmt::Arguments<'_>) -> ! { - // PORT NOTE: Zig branched on enable_ansi_colors_stderr to pick the comptime-colored - // vs stripped format string. In Rust callers use `panic!(pretty_fmt!(...))` directly; - // this fn is kept for non-macro callers and writes the (already-formatted) args. - // TODO(port): branch on ENABLE_ANSI_COLORS_STDERR once the colored vs - // stripped paths actually differ; callers should wrap fmt in - // `pretty_fmt!(.., enable_ansi_colors_stderr)`. core::panic!("{args}"); } @@ -1046,15 +962,6 @@ pub fn raw_error_writer() -> StreamType { SOURCE.with_borrow(|s| s.raw_error_stream) } -// TODO: investigate migrating this to the buffered one. -// -// TODO(port): these accessors hand out a `&'static mut` to a thread-local -// `Source` field. The Zig original returned `*Writer` and callers used it -// briefly. Returning `&'static mut` is *unsound* if two are alive at once, but -// matches the Zig contract until callers migrate to `with_error_writer(|w| ..)`. -// -// `source_writer_escape` centralises the escape: one `unsafe` for all five -// public `*_writer*()` accessors (nonnull-asref reduction: 5 sites → 1). #[inline] fn source_writer_escape(project: fn(&mut Source) -> &mut io::Writer) -> &'static mut io::Writer { debug_assert!(SOURCE_SET.get()); @@ -1142,11 +1049,6 @@ pub fn flush() { } } -/// RAII guard that calls [`flush`] on `Drop`. -/// -/// This is the Rust spelling of Zig's `defer Output.flush();` — hold one of -/// these across a scope with early returns to guarantee buffered stdout/stderr -/// is drained on every exit path. #[must_use = "FlushGuard flushes on Drop; binding to `_` drops it immediately"] pub struct FlushGuard(()); @@ -1215,21 +1117,6 @@ impl fmt::Display for ElapsedFormatter { } } -// PORT NOTE: Zig's `printElapsedToWithCtx` passed the raw `[...]` -// template to a `(comptime fmt, args)` printer (`prettyError`/`pretty`), which -// then routed through `prettyTo` to branch on `enable_ansi_colors_{stdout,stderr}`. -// In Rust the `pretty_fmt!` rewrite must happen at the macro call site, so the -// public entry points (`print_elapsed`/`print_elapsed_stdout` below) inline the -// match and call `pretty_error!`/`pretty!` directly — those macros emit both the -// colored and stripped variants and let `pretty_to` pick at runtime. The -// intermediate helper had no way to defer the color/no-color choice without -// leaking raw `\x1b[` escapes when colors are disabled, so it was removed. - -// `print_elapsed_to` intentionally removed — see PORT NOTE above. The colored -// template can't be deferred through an `fmt::Arguments`-taking printer without -// either leaking raw escapes or stripping color, so callers must use -// `print_elapsed` / `print_elapsed_stdout` (or `ElapsedFormatter` directly). - pub fn print_elapsed(elapsed: f64) { match elapsed.round() as i64 { 0..=1500 => pretty_error!("[{:>.2}ms]", elapsed), @@ -1276,33 +1163,6 @@ pub fn print_timer(timer: &mut impl ReadTimer) { print_elapsed(elapsed as f64); } -// ────────────────────────────────────────────────────────────────────────── -// Print routing -// -// `print` / `printError` / `pretty*` are called from thousands of sites with -// distinct comptime format strings. They used to be `noinline`, which forced -// one ~400-byte function body per (fmt, args-type) pair — about 2,650 of them, -// or ~1 MB of .text in release builds. The bodies were nearly identical: pick -// buffered-vs-unbuffered writer, then `Writer.print(fmt, args)`. -// -// Now the public entry points are `inline` and bottom out in two non-generic -// helpers (`destWriter`, `writeBytes`). The only per-call-site code is either -// a `writeBytes` call (when `args` is empty — ~40% of sites) or one -// `Writer.print` call. The buffering branch lives in `destWriter` so each -// site formats once instead of twice. -// ────────────────────────────────────────────────────────────────────────── - -/// Pick the active writer for `dest`, honoring `enable_buffering`. Non-generic -/// so the buffering branch isn't duplicated into every call site. -/// -/// Hands `f` a **raw** `*mut io::Writer`, not a `&mut`. `f` may call into -/// `write_fmt`, which evaluates user `Display` impls that can re-enter this -/// module (e.g. `debug_warn` → `print_to`, or `flush()`). If we materialized a -/// `&mut io::Writer` here, the re-entrant call would produce a second `&mut` -/// aliasing the first while it is still live — UB. Zig's `destWriter()` -/// (output.zig:731-737) returns a raw `*std.Io.Writer` with no exclusivity -/// contract, so callers must do the same and route writes through the vtable -/// fn pointers directly (see `write_fmt_raw`). #[inline(never)] fn with_dest_writer(dest: Destination, f: impl FnOnce(*mut io::Writer) -> R) -> R { debug_assert!(SOURCE_SET.get()); @@ -1454,15 +1314,6 @@ pub fn println(args: fmt::Arguments<'_>) { // Scoped debug logging // ────────────────────────────────────────────────────────────────────────── -/// Debug-only logs which should not appear in release mode. -/// -/// To enable a specific log at runtime, set the environment variable -/// `BUN_DEBUG_${TAG}` to 1. -/// -/// For example, to enable the "foo" log, set the environment variable -/// BUN_DEBUG_foo=1 -/// To enable all logs, set the environment variable -/// BUN_DEBUG_ALL=1 pub type LogFunction = fn(fmt::Arguments<'_>); #[derive(Copy, Clone, PartialEq, Eq)] @@ -1553,13 +1404,6 @@ impl ScopedLogger { !self.really_disable.load(Ordering::Relaxed) } - /// Debug-only logs which should not appear in release mode - /// To enable a specific log at runtime, set the environment variable - /// BUN_DEBUG_${TAG} to 1 - /// For example, to enable the "foo" log, set the environment variable - /// BUN_DEBUG_foo=1 - /// To enable all logs, set the environment variable - /// BUN_DEBUG_ALL=1 pub fn log(&self, args: fmt::Arguments<'_>) { if !Environment::ENABLE_LOGS { return; @@ -1603,12 +1447,6 @@ impl ScopedLogger { } } -/// Declare a scoped logger. Expands to a `static SCOPE: ScopedLogger`. -/// -/// ```ignore -/// declare_scope!(EventLoop, hidden); -/// scoped_log!(EventLoop, "tick {}", n); -/// ``` #[macro_export] macro_rules! declare_scope { ($name:ident, hidden) => { @@ -1641,10 +1479,6 @@ macro_rules! scoped_log { // there is no `debug_logs` feature and §Forbidden bans silent no-ops. if cfg!(debug_assertions) && $scope.is_visible() { const __NL: &str = $crate::output::_needs_nl($crate::pretty_fmt!($fmt, false)); - // Branch on ANSI *before* `format_args!` so each `$arg` evaluates - // exactly once (Zig builds the args tuple once — output.zig:922-933). - // Prefix `[tag]` is built at runtime from `$scope.tagname` lowercased - // (Zig: output.zig:826-835 lowercases via `std.ascii.toLower`). if $crate::output::_scoped_use_ansi() { $scope.log(::core::format_args!( concat!( @@ -1668,22 +1502,6 @@ macro_rules! scoped_log { }; } -/// Declare a scoped logger static **and** a local forwarding macro in one shot. -/// Replaces the per-file `declare_scope!(X, vis); macro_rules! log { … }` boilerplate. -/// -/// ```ignore -/// bun_core::define_scoped_log!(log, EventLoop, hidden); -/// log!("tick {}", n); -/// ``` -/// -/// The two-arg form skips `declare_scope!` and just forwards to an existing -/// `ScopedLogger` static (by path) — for hand-declared statics (keyword tagnames) -/// or scopes that live in another module. -// -// Nested-macro `$` escaping uses the "`$` token smuggle" so caller crates do -// NOT need `#![feature(macro_metavar_expr)]`: the public arms forward a -// literal `$` token to a `(@inner …, $d:tt)` arm, which then writes `$d` -// where the inner macro wants `$`. #[macro_export] macro_rules! define_scoped_log { ($mac:ident, $scope:ident, $vis:tt) => { @@ -1701,10 +1519,6 @@ macro_rules! define_scoped_log { }; } -/// `Display` adapter that lowercases an ASCII tag on the fly. Used by -/// `scoped_log!` so the printed `[tag]` prefix matches Zig's -/// `std.ascii.toLower`-folded `tagname` (output.zig:826-835) without needing a -/// compile-time lowercasing proc-macro. #[doc(hidden)] pub struct _LowerTag(pub &'static str); impl fmt::Display for _LowerTag { @@ -1727,26 +1541,6 @@ pub fn clear_to_end() { // prettyFmt — compile-time `` → ANSI substitution // ────────────────────────────────────────────────────────────────────────── -// Valid "colors": -// -// -// -// -// -// -// -// -// -// -// - bold -// - dim -// - reset -// - reset - -/// Lowercase lookup wrapper (Zig: `Output.color_map.get(name)`). The table -/// itself lives in `bun_output_tags` (shared with the `pretty_fmt!` proc-macro -/// so there is exactly one copy); this fn-module mirrors the Zig -/// `ComptimeStringMap` `.get()` surface. pub mod color_map { #[inline] pub fn get(name: &[u8]) -> Option<&'static str> { @@ -1757,27 +1551,12 @@ pub mod color_map { pub use ansi::{BOLD, DIM, RESET}; pub use bun_output_tags::{ansi, ansi_b}; -/// `bun.Output.pretty(fmt, args)` — write to stdout with `` color expansion. -/// Function form: performs the `` → ANSI rewrite at runtime on the rendered -/// payload (using stdout's colour state). Prefer the `pretty!` macro for literal -/// templates so the rewrite stays comptime. -/// -/// `inline(always)`: with plain `#[inline]` the `` -/// monomorphization materializes once in `bun_runtime` and every other crate's -/// single startup-banner call jumps to that island, dragging a cold .text page -/// into the install/no-op fault-around set. Forcing the body into each caller -/// keeps it next to the call site (and the body is two calls — cheap). #[inline(always)] pub fn pretty(payload: impl PrettyFmtInput) { let buf = payload.into_pretty_buf(enable_ansi_colors_stdout()); write_bytes(Destination::Stdout, &buf); } -/// `bun.Output.prettyln(fmt, args)` — `pretty()` with a trailing newline. -/// Function form: performs the `` → ANSI rewrite at runtime on the rendered -/// payload and appends `\n` if the result does not already end in one (matches -/// Zig output.zig:1090-1093). Prefer the `prettyln!` macro for literal templates. -/// `inline(always)` for the same .text-layout reason as [`pretty`]. #[inline(always)] pub fn prettyln(payload: impl PrettyFmtInput) { let buf = payload.into_pretty_buf(enable_ansi_colors_stdout()); @@ -1787,23 +1566,8 @@ pub fn prettyln(payload: impl PrettyFmtInput) { } } -/// Compile-time `` → ANSI escape rewriter. -/// -/// In Zig this was a `comptime` function building a `[:0]const u8`. In Rust the -/// equivalent must be a proc-macro because it consumes a string literal and emits -/// a new string literal usable as a `format_args!` template. -/// -/// `pretty_fmt!("{s}", true)` → `"\x1b[31m{}\x1b[0m"` -/// `pretty_fmt!("{s}", false)` → `"{}"` -/// -/// The reference algorithm is `pretty_fmt_runtime` below (kept 1:1 with the Zig -/// body so the proc-macro can be tested against it). pub use bun_core_macros::pretty_fmt; -/// Input accepted by [`pretty_fmt`]: either a `&str`/`&[u8]` template or a -/// pre-formatted `&fmt::Arguments<'_>` (which is first rendered to a string -/// then ``-rewritten — used by `Custom Inspect`-style call sites that -/// build the template via `format_args!`). pub trait PrettyFmtInput { fn into_pretty_buf(self, is_enabled: bool) -> PrettyBuf; } @@ -1834,12 +1598,6 @@ impl PrettyFmtInput for fmt::Arguments<'_> { } } -/// `Output.prettyFmt` — runtime `` → ANSI rewrite. Const-generic -/// `ENABLE_ANSI_COLORS` mirrors the Zig `comptime is_enabled: bool` parameter -/// so callers can do `Output::pretty_fmt::("…")`. -/// -/// For a runtime bool (e.g. from `enable_ansi_colors_stderr()`), see -/// [`pretty_fmt_rt`]. #[inline] pub fn pretty_fmt(input: impl PrettyFmtInput) -> PrettyBuf { input.into_pretty_buf(ENABLE_ANSI_COLORS) @@ -1886,12 +1644,6 @@ impl fmt::Display for PrettyBuf { } } -// ── FmtTuple ────────────────────────────────────────────────────────────── -// Zig `fn print(comptime fmt: []const u8, args: anytype)` takes a tuple of -// arguments and substitutes positionals. The Rust port models `args: anytype` -// as `impl FmtTuple` so call sites can pass `()`, `(a,)`, `(a, b)`, … or a -// pre-built `fmt::Arguments<'_>` (treated as a single positional). - /// Positional-argument bundle for runtime template substitution. pub trait FmtTuple { /// Write the `idx`-th positional into `f`. Returns `false` if `idx` is out @@ -2030,12 +1782,6 @@ impl fmt::Display for TemplateDisplay<'_, A> { } } -/// Runtime `` → ANSI rewrite *with* a positional-argument tuple -/// substituted at each `{}` / `{s}` / `{d}` placeholder. Returns a `Display` -/// impl so callers can `write!(w, "{}", pretty_fmt_args(fmt, true, (a, b)))`. -/// -/// Port of `Output.prettyFmt` + `print` fused for the dynamic-template case -/// (crash_handler builds the template at runtime). pub fn pretty_fmt_args( fmt: &str, is_enabled: bool, @@ -2047,13 +1793,6 @@ pub fn pretty_fmt_args( } } -/// Runtime mirror of Zig `prettyFmt` for testing the proc-macro and for the rare -/// dynamic case. Produces the same byte sequence the Zig comptime version would. -/// -/// Colour table lives in `bun_output_tags`; the state machine is kept duplicated -/// vs `bun_core_macros::rewrite` because the two intentionally diverge in the -/// `{` arm (proc-macro rewrites Zig specs `{s}`→`{}`; this side copies braces -/// verbatim) and on unknown tags (proc-macro errors; this side emits `""`). pub fn pretty_fmt_runtime(fmt: &[u8], is_enabled: bool) -> Vec { let mut out = Vec::with_capacity(fmt.len() * 4); let mut i = 0usize; @@ -2133,10 +1872,6 @@ pub fn enable_color_for(dest: Destination) -> bool { } } -/// Returns `"\n"` if `fmt` does not already end in a newline, else `""`. -/// Mirrors Zig's `if (fmt.len == 0 or fmt[fmt.len - 1] != '\n')` guard so the -/// `*ln!` macros never emit a double newline when the caller's template already -/// ends in one. #[doc(hidden)] #[inline] pub const fn _needs_nl(fmt: &str) -> &'static str { @@ -2161,17 +1896,6 @@ pub fn _scoped_use_ansi() -> bool { } } -/// Internal: bind each `$arg` exactly once into a `match` tuple, then dispatch -/// on the per-destination color flag and call `print_to` with the appropriate -/// `pretty_fmt!`-expanded template. Mirrors Zig's `prettyTo` which receives the -/// args tuple already evaluated and only branches on the color flag -/// (output.zig:1066-1074). -/// -/// The recursive `@go` arm zips each user arg with a name from `pool` so the -/// emitted `match (&a, &b, ..)` pattern can rebind them as plain idents — both -/// `format_args!` branches then borrow the *same* evaluated values, so a -/// side-effecting `$arg` runs exactly once and the expression text is not -/// duplicated into each branch. #[doc(hidden)] #[macro_export] macro_rules! __pretty_dispatch { @@ -2271,10 +1995,6 @@ macro_rules! pretty { #[macro_export] macro_rules! prettyln { ($fmt:expr $(, $arg:expr)* $(,)?) => {{ - // Only append `\n` when the *processed* template doesn't already end in - // one — `pretty_fmt!` flattens `concat!`/`stringify!` so wrapper macros - // (`note!`, `warn!`, …) that prefix the user template still see the - // user's trailing newline and don't emit a second one. const __NL: &str = $crate::output::_needs_nl($crate::pretty_fmt!($fmt, false)); $crate::__pretty_dispatch_start!( $crate::output::Destination::Stdout, $fmt, [__NL]; [$($arg,)*] @@ -2316,15 +2036,6 @@ macro_rules! pretty_errorln { }}; } -/// `write!` an `Output.prettyFmt`-style template to an arbitrary `fmt::Write` -/// sink, branching on a (possibly const-generic) color flag so each branch gets -/// the proc-macro-expanded literal template. -/// -/// Port of Zig `writer.print(comptime Output.prettyFmt(FMT, enable_ansi_colors), .{args})` -/// for `writeFormat`-style impls that take `comptime enable_ansi_colors: bool`. -/// The `pretty_fmt!` proc-macro requires a `true`/`false` *literal*, so the -/// const-generic is dispatched here; only one arm executes at runtime, so each -/// `$arg` evaluates exactly once. #[macro_export] macro_rules! write_pretty { ($w:expr, $colors:expr, $fmt:expr $(, $arg:expr)* $(,)?) => { @@ -2398,14 +2109,6 @@ pub fn print_error(args: impl core::fmt::Display) { print_to(Destination::Stderr, format_args!("{args}")); } -/// `Output.printErrorln` — function form (the `print_errorln!` macro at crate -/// root is the comptime-string variant). Takes anything `Display` so both -/// `format_args!(..)` and bare `&str` call sites compile; appends `\n`. -/// -/// NOTE: unlike the macro (and Zig output.zig:1095-1098), this fn form cannot -/// inspect the comptime template and therefore *always* appends `\n`. Callers -/// must NOT pass a template that already ends in `\n` or output will contain a -/// doubled newline. Prefer the `print_errorln!` macro where possible. #[inline] pub fn print_errorln(args: impl core::fmt::Display) { print_to(Destination::Stderr, format_args!("{args}\n")); @@ -2492,19 +2195,6 @@ macro_rules! debug_warn { }; } -/// Print a red error message. The first argument takes an `error_name` value, which can be either -/// be a Zig error, or a string or enum. The error name is converted to a string and displayed -/// in place of "error:", making it useful to print things like "EACCES: Couldn't open package.json" -/// -/// The Zig original switched on `@typeInfo` of `error_name`. The Rust port accepts anything -/// implementing `ErrName` (impl'd for `&[u8]`, `&str`, `bun_core::Error`, `bun_sys::Error`, -/// and any `#[derive(strum::IntoStaticStr)]` enum). -// TODO(port): the comptime-literal fast path (is_comptime_name) is dropped — -// could be recovered with a proc-macro overload that detects string literals. -// -// By-value `error_name` is intentional: callers pass `"EACCES"` / `b"tag"` / -// `bun_core::Error` (Copy) and `bun_sys::Error` (non-Copy, consumed). Taking -// `&impl ErrName` would force `&"literal"` at every site. #[allow(clippy::needless_pass_by_value)] // by-value for call-site ergonomics; see above pub fn err(error_name: impl ErrName, fmt: &str, args: impl FmtTuple) { // Zig concatenates `fmt` into the prettyErrorln template, whose trailing-\n @@ -2540,10 +2230,6 @@ pub fn err(error_name: impl ErrName, fmt: &str, args: impl FmtTuple) { ); } -/// `Output.err(.TAG, fmt, args)` with a bare string tag — e.g. -/// `Output::err_tag("EACCES", format_args!(...))`. Thin sugar over `err` for -/// call sites that already hold a fully-formatted body (not a positional -/// `{}` template). #[inline] pub fn err_tag(tag: &str, body: core::fmt::Arguments<'_>) { pretty_errorln!("{}: {}", tag, body); @@ -2569,11 +2255,6 @@ pub struct SysErrInfo { pub syscall: &'static str, } -/// Trait abstracting the `@typeInfo` switch in Zig `err()`. -/// -/// `as_sys_err_info()` replaces the former `as_sys_error() -> Option<&bun_sys::Error>`: -/// bun_core (T0) can't name `bun_sys::Error` (T1). bun_sys impls `ErrName` for -/// its own error type (move-in pass) and returns the projected info here. pub trait ErrName { fn name(&self) -> &[u8]; fn as_sys_err_info(&self) -> Option { @@ -2689,11 +2370,6 @@ pub(crate) fn init_scoped_debug_writer_at_startup() { } fn scoped_writer() -> QuietWriter { - // Zig used `@compileError` here (output.zig:1320-1325) which is lazy — it - // only fires if `scopedWriter()` is referenced, and in release Zig the - // `Scoped()` no-op struct never references it. Rust's `compile_error!` is - // eager and would break every release build, so assert at runtime instead. - // All callers are already gated on `Environment::ENABLE_LOGS`. #[cfg(debug_assertions)] if !Environment::ENABLE_LOGS { unreachable!("scopedWriter() should only be called in debug mode"); @@ -2718,15 +2394,6 @@ pub fn err_fmt(formatter: impl fmt::Display) { err_generic!("{}", formatter); } -// ────────────────────────────────────────────────────────────────────────── -// Stdin readers (CYCLEBREAK §Dispatch cold path) -// -// Zig's `bun.Output.buffered_stdin` is a `bun.deprecated.BufferedReader(4096, -// File.Reader)`. The concrete `File.Reader` lives in bun_sys; bun_core routes -// the underlying `read(2)` through the [`OutputSinkVTable::read`] slot so the -// `prompt`/`init`/`publish` callers can read stdin without naming bun_sys. -// ────────────────────────────────────────────────────────────────────────── - pub(crate) static BUFFERED_STDIN: crate::RacyCell = crate::RacyCell::new(BufferedStdin { fd: { @@ -2763,11 +2430,6 @@ impl BufferedStdin { self } - /// Zig `BufferedReader.read` — fill `dest` from the buffer, refilling from - /// the underlying fd until `dest` is full or EOF. Returns `Ok(0)` on EOF. - /// - /// PORT NOTE: matches std `BufferedReader.read` fill-to-completion semantics - /// (loops on the underlying fd), not POSIX partial-read. pub fn read(&mut self, dest: &mut [u8]) -> Result { let mut written: usize = 0; loop { @@ -2936,17 +2598,9 @@ impl Synchronized { #[cfg(test)] mod output_macro_tests { - //! Compile-shape regression tests for the `pretty*!`/`note!`/`warn!`/ - //! `debug!` wrapper macros. These don't drive a live `Source` (no I/O); - //! they assert that the macros *expand* for the shapes the Zig originals - //! accept and that the `*ln!` newline guard const-evaluates correctly. use super::_needs_nl; use bun_core_macros::pretty_fmt; - /// `note!`/`warn!`/`debug!` must accept a `concat!(..)` template — Zig's - /// `note(comptime fmt, args)` is routinely called with `"a" ++ "b"`. The - /// `:literal` matcher rejected this; `:expr` + proc-macro `concat!` - /// flattening makes it compile. #[test] fn prefix_wrappers_accept_concat() { if false { @@ -2961,10 +2615,6 @@ mod output_macro_tests { } } - /// `pretty!`/`pretty_error!`/`prettyln!`/`pretty_errorln!` bind each arg - /// once into a `match` tuple; a side-effecting block arg must therefore be - /// usable, and a moved non-`Copy` value must remain usable afterwards - /// (the macro takes `&($arg)`, never moves). #[test] fn pretty_binds_args_once() { if false { @@ -2985,10 +2635,6 @@ mod output_macro_tests { } } - /// `*ln!` macros must not append a second `\n` when the template already - /// ends in one. The guard runs on the *processed* template (`pretty_fmt!` - /// flattens `concat!`), so a wrapper-prefixed template that ends in `\n` - /// still suppresses the extra newline. #[test] fn ln_macros_suppress_double_newline() { // Direct templates. diff --git a/src/bun_core/result.rs b/src/bun_core/result.rs index d9d9db3dc14..9f7011e017c 100644 --- a/src/bun_core/result.rs +++ b/src/bun_core/result.rs @@ -23,12 +23,6 @@ use core::num::NonZeroU16; #[derive(Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] pub struct Error(NonZeroU16); -// ── intern table ────────────────────────────────────────────────────────── -// -// Codes `1..=SEED.len()` index `SEED`; codes above that index the dynamic -// `EXTRA` vec at `code - SEED.len() - 1`. SEED is frozen so the handful of -// `pub const` Errors below have stable values without touching the lock. - /// Pre-seeded names. **Append only** — existing indices are load-bearing for /// the `pub const` Errors below. (The errno→name map lives in bun_errno via the /// `ErrnoNames` hook; entries here are only fast-path intern hits.) @@ -116,12 +110,6 @@ fn intern_slow(name: &'static str) -> NonZeroU16 { unsafe { NonZeroU16::new_unchecked(code as u16) } } -/// Cold half of the `err!()` macro: intern `name` and publish the code into -/// `slot`. Non-generic (`&AtomicU16` + `&'static str`) so every `err!` call -/// site shares ONE `.text` body — the previous `OnceLock::get_or_init(|| …)` -/// monomorphized a fresh closure type per site (~1.9k copies). `Relaxed` is -/// sufficient: the slot only caches an idempotent u16; a racing reader that -/// observes `0` simply re-interns to the same value. #[cold] #[inline(never)] pub fn intern_cached(slot: &core::sync::atomic::AtomicU16, name: &'static str) -> Error { @@ -142,13 +130,6 @@ impl Error { /// Aliases `Unexpected` so it round-trips through `name()` sensibly. pub const TODO: Self = Self::UNEXPECTED; - /// Intern `name`, returning its process-unique code. Idempotent: the same - /// string (by value) always yields the same `Error`. This is the runtime - /// half of Zig's link-time `anyerror` assignment. - /// - /// `#[cold]`: only reached on a per-site cache miss (or `err!(from e)`); - /// keeps the SEED scan + RwLock probe out of `.text.hot` so - /// `--sort-section=name` groups it with the other unlikely paths. #[cold] pub fn intern(name: &'static str) -> Self { // Fast path: SEED hit (covers all errno + common names) without locking. @@ -206,10 +187,6 @@ impl Error { /// Port of `bun.errnoToZigErr`: map a raw OS errno to its named error. /// Unknown errnos collapse to `Unexpected` (matching the Zig `@memset`). pub fn from_errno(errno: i32) -> Self { - // Zig builds `errno_map: [max+1]anyerror` at comptime (bun.zig:2841); - // we build the equivalent once at first use by interning every - // platform `SystemErrno` tag name. After init, lookup is a plain - // bounds-checked array index — same cost as the Zig version. static ERRNO_MAP: crate::Once> = crate::Once::new(); let map = ERRNO_MAP.get_or_init(|| { // Index 0 ("SUCCESS") is the no-error hole → Unexpected, @@ -262,16 +239,6 @@ impl From for Error { // `SystemErrno`-domain integer — feed it straight through. #[cfg(not(windows))] Some(code) => Self::from_errno(code), - // Windows: `raw_os_error()` returns the raw Win32 `GetLastError()` - // code (ERROR_ACCESS_DENIED=5, ERROR_SHARING_VIOLATION=32, …), - // NOT a `SystemErrno`. Routing it through `ErrnoNames::SYS.name()` would - // alias garbage (5→EIO, 32→EPIPE). The Zig pipeline first runs - // `Win32Error.toSystemErrno()` (windows_errno.zig:290) before any - // `errno_map` lookup; that table lives in `bun_errno`, which is - // tier-above `bun_core` (dep cycle), so we can't call it here. - // Fall back to `Unexpected` rather than return a wrong name. - // TODO(port): plumb a Win32→SystemErrno hook (or duplicate the - // table) so `?`-propagated `io::Error`s name correctly on Windows. #[cfg(windows)] Some(_code) => Self::UNEXPECTED, None => Self::UNEXPECTED, @@ -283,22 +250,12 @@ impl From for Error { Self::OUT_OF_MEMORY } } -/// Zig's `std.Io.Writer` error set surfaces as `error.WriteFailed` when -/// propagated through `try writer.print(…)`; the Rust port routes formatted -/// output through `core::fmt::Write`, whose only error value is the unit -/// `fmt::Error`. Map it to the same tag so `?`-propagation matches the spec. impl From for Error { fn from(_: core::fmt::Error) -> Self { Self::WRITE_FAILED } } -/// Extension for `?`-propagating non-`fmt::Error` write failures (e.g. -/// `std::io::Error` from `write!(&mut Vec, …)` / `Cursor` / `BufWriter`) -/// as the spec's `error.WriteFailed` tag. Bare `?` on those would route through -/// [`From`] → errno/`Unexpected`, which diverges from the Zig -/// `try writer.print(…)` contract. Replaces the open-coded -/// `.map_err(|_| err!("WriteFailed"))` pattern at ~20 call sites. pub trait OrWriteFailed { fn or_write_failed(self) -> core::result::Result; } @@ -318,15 +275,6 @@ impl OrWriteFailed for Result { } } -/// Stamp out `impl From<$t> for bun_core::Error` for one or more -/// `strum::IntoStaticStr`-deriving error enums, routing each variant through -/// [`Error::from_name`]. Expansion is byte-identical to the hand-written -/// 5-line impl this replaces, so codegen is unchanged. -/// -/// A blanket `impl> From for Error` is intentionally -/// NOT provided: it would over-match (`&'static str` itself) and risk future -/// coherence overlap with the bespoke `From` / `From` / -/// `From` impls above. #[macro_export] macro_rules! named_error_set { ($($t:ty),+ $(,)?) => { @@ -341,13 +289,6 @@ macro_rules! named_error_set { }; } -/// Stamp out `impl Display + impl Error` for one or more -/// `strum::IntoStaticStr`-deriving error enums whose user-facing string is -/// exactly the variant tag (Zig `@errorName(e)` semantics). Replaces the -/// hand-rolled 5-line `f.write_str(<&'static str>::from(self))` boilerplate. -/// -/// Kept separate from [`named_error_set!`] because not every named error set -/// wants the tag-as-Display behavior (some have bespoke `Display` impls). #[macro_export] macro_rules! impl_tag_error { ($($t:ty),+ $(,)?) => {$( @@ -361,21 +302,6 @@ macro_rules! impl_tag_error { )+}; } -// ─── coreutils_error_map ───────────────────────────────────────────────── -// Zig builds a comptime `EnumMap` with a per-OS -// `switch (Environment.os)` body (src/sys/coreutils_error_map.zig). The full -// EnumMap lives in `bun_sys::coreutils_error_map`; that crate is tier-above -// `bun_core`, so for `output.rs`'s integer-errno hot path we keep a parallel -// table here, keyed by `SystemErrno` *name* and resolved through the per-OS -// `ErrnoNames` hook — i.e. the same `errno → SystemErrno → message` -// composition the Zig does, just without the cross-crate enum. -// -// Layout: one shared BASE table (the glibc/coreutils strings — used as-is on -// linux/android/windows/wasm) plus a small per-OS DELTA on macOS/FreeBSD that -// overrides divergent texts and adds OS-only errnos. Because lookup is gated -// by the per-OS `SystemErrno` name space, BASE rows for Linux-only errnos are -// unreachable on macOS/FreeBSD and harmless to keep — so the three full per-OS -// `phf_map!`s collapse to BASE + two ~40-row deltas with identical behavior. pub mod coreutils_error_map { /// Returns the GNU-coreutils-style short label for an errno, if known. #[inline] diff --git a/src/bun_core/string/HashedString.rs b/src/bun_core/string/HashedString.rs index 47a8f70ec88..bced35f94a1 100644 --- a/src/bun_core/string/HashedString.rs +++ b/src/bun_core/string/HashedString.rs @@ -33,10 +33,6 @@ impl HashedString { } } - // TODO(port): Zig `eql` took `other: anytype` and switched on `@TypeOf(other)`: - // - HashedString / *HashedString / *const HashedString → compare hash/ptr/len - // - else (slice-like with .len and indexing) → rehash bytes and compare - // Rust has no type-switch; split into `eql` (HashedString) and `eql_bytes` (&[u8]). pub fn eql(&self, other: &HashedString) -> bool { ((self.hash.max(other.hash) > 0 && self.hash == other.hash) || (self.ptr == other.ptr)) && self.len == other.len diff --git a/src/bun_core/string/MutableString.rs b/src/bun_core/string/MutableString.rs index 3a7201e35a5..8f1719fdb31 100644 --- a/src/bun_core/string/MutableString.rs +++ b/src/bun_core/string/MutableString.rs @@ -1,22 +1,12 @@ use crate::string::{ZStr, strings}; use bun_alloc::AllocError; -/// VTable surface for `bun.ast.E.String` (CYCLEBREAK b0: GENUINE upward dep on -/// `bun_ast::E::String`). Low tier defines the interface; high tier -/// (`bun_js_parser`) provides `impl EStringRef for E::String`. -/// PERF(port): was inline concrete type — cold path (formatter/writer). pub trait EStringRef { fn is_utf8(&self) -> bool; fn slice(&mut self) -> &[u8]; fn slice16(&mut self) -> &[u16]; } -/// Layout-identical to Zig's `std.posix.iovec_const` -/// (`extern struct { base: [*]const u8, len: usize }`), which is defined -/// unconditionally for every target — it does NOT alias `uv_buf_t`/`WSABUF` -/// on Windows (those have reversed field order and a `u32` len). The Zig -/// spec `MutableString.toSocketBuffers` returns this shape on all platforms, -/// so there is no `cfg(windows)` split. #[repr(C)] #[derive(Clone, Copy)] pub struct SocketBuffer { @@ -33,11 +23,6 @@ pub struct MutableString { pub list: Vec, } -// Zig: `Npm.Registry.BodyPool = ObjectPool(MutableString, MutableString.init2048, true, 8)` -// (src/install/npm.zig). The `bun_collections::pool::ObjectPoolType` impl -// lives in `bun_collections` (trait owner) to avoid a `bun_core → -// bun_collections` dep cycle now that `MutableString` is in `bun_core`. - impl MutableString { pub fn init2048() -> Result { MutableString::init(2048) @@ -65,13 +50,6 @@ impl MutableString { // Zig `deinit` only freed `list`; `Vec` drops automatically — no `Drop` impl needed. - /// Zig: `self.list.expandToCapacity()` — set `len = capacity` so callers - /// can index into the spare region (e.g. `read()` into `&mut list[n..]`). - /// - /// Matches Zig semantics: the new tail is left **uninitialized** — callers - /// must treat `list[old_len..]` as write-only until overwritten (typically - /// by `read()`). The previous port zero-filled here, which memset the - /// entire pooled scratch buffer before every `package.json` read. #[inline] pub fn expand_to_capacity(&mut self) { // Zero only the spare region so the exposed tail is defined (Zig's @@ -156,10 +134,6 @@ impl MutableString { /// identifier, you're going to potentially cause trouble with non-BMP code /// points in target environments that don't support bracketed Unicode escapes. pub fn ensure_valid_identifier(str: &[u8]) -> Result, AllocError> { - // TODO(port): Zig returned `[]const u8` which could be either the input - // borrow or a fresh allocation. Rust cannot express that without a - // lifetime + Cow; for now we always return owned `Box<[u8]>` and copy - // on the borrow paths. Consider `Cow<'a, [u8]>`. if str.is_empty() { return Ok(Box::<[u8]>::from(b"_".as_slice())); } @@ -308,10 +282,6 @@ impl MutableString { } pub fn inflate(&mut self, amount: usize) -> Result<(), AllocError> { - // Zig MutableString.inflate: `list.resize(amount)` leaves new bytes - // uninitialized. Callers always overwrite the inflated region, so the - // zero-fill here is technically redundant — but it lowers to a single - // memset and avoids `clippy::uninit_vec` / a `set_len` over uninit bytes. self.list.resize(amount, 0); Ok(()) } @@ -543,13 +513,6 @@ impl<'a> BufferedWriter<'a> { if pending.len() >= Self::MAX { self.flush()?; - // PORT NOTE: Zig wrote into `this.remain()[0..bytes.len*2]` here, - // which after `flush()` is `this.buffer[0..bytes.len*2]` — but - // `bytes.len*2 > MAX`, so that indexes past the stack buffer. This - // looks like a latent bug in the Zig (should write into - // `context.list`). Porting the apparent intent: write into the - // freshly-reserved context.list tail. - // TODO(port): confirm and fix upstream. let old = self.context.list.len(); // SAFETY: copy_utf16_into_utf8 writes <= bytes.len*2; trimmed below. let tail = diff --git a/src/bun_core/string/PathString.rs b/src/bun_core/string/PathString.rs index 6191c79429f..cee3cdc9050 100644 --- a/src/bun_core/string/PathString.rs +++ b/src/bun_core/string/PathString.rs @@ -17,13 +17,6 @@ const PATH_INT_LEN_BITS: u32 = { const USE_SMALL_PATH_STRING_: bool = (usize::BITS - PATH_INT_LEN_BITS) >= 53; -// const PathStringBackingIntType = if (use_small_path_string_) u64 else u128; -// Zig picks the backing integer at comptime: u64 if 53 ptr bits + len bits fit -// (MAX_PATH_BYTES ≤ 2048 → ≤ 11 len bits); u128 otherwise (Linux/Android -// MAX_PATH_BYTES=4096 → 13 len bits → 64-13=51 < 53; Windows → way more). -// Stable Rust cannot select a type from a const bool, so cfg by OS — this list -// MUST track `MAX_PATH_BYTES` in `bun_core/util.rs`. The const-assert below -// verifies they agree. #[cfg(any( target_os = "linux", target_os = "android", @@ -108,24 +101,11 @@ impl PathString { Self(ptr | len) } - /// Take ownership of `bytes`, store its raw pointer/len, and forget the - /// allocation. The returned PathString must be paired with - /// [`deinit_owned`] (typically by the containing struct's `Drop`) to avoid - /// a leak — this mirrors Zig, where `Bytes.deinit` runs - /// `default_allocator.free(stored_name.slice())`. - /// - /// PathString itself stays `Copy` (it is a packed pointer), so ownership - /// is a contract on the *container*, not enforced by the type. #[inline] pub fn init_owned(bytes: Vec) -> Self { if bytes.is_empty() { return Self::EMPTY; } - // Shed any unused capacity so the (ptr,len) pair fully describes the - // allocation and `deinit_owned` can reconstruct it without tracking - // capacity separately. `heap::alloc` (not `leak`) is the explicit - // ownership-transfer-to-raw API; the matching `heap::take` lives - // in `deinit_owned`. let raw: *mut [u8] = crate::heap::into_raw(bytes.into_boxed_slice()); // SAFETY: `raw` is a fresh non-null allocation; reborrow only to pack // ptr+len into the backing int. diff --git a/src/bun_core/string/SmolStr.rs b/src/bun_core/string/SmolStr.rs index dade67f31d9..b66489182d9 100644 --- a/src/bun_core/string/SmolStr.rs +++ b/src/bun_core/string/SmolStr.rs @@ -7,12 +7,6 @@ const _: () = assert!(cfg!(target_endian = "little")); // NOTE: the packed layout assumes 64-bit pointers (`__ptr` occupies the upper 64 bits of the u128). const _: () = assert!(mem::size_of::() == 8); -/// This is a string type that stores up to 15 bytes inline on the stack, and heap allocates if it is longer. -/// -/// Zig layout (`packed struct(u128)`, little-endian bit order): -/// bits 0..32 = `__len: u32` -/// bits 32..64 = `cap: u32` -/// bits 64..128 = `__ptr: [*]u8` (bit 127 is the inlined tag) #[repr(transparent)] pub struct SmolStr(u128); @@ -254,10 +248,6 @@ pub trait JsonWriter { // --------------------------------------------------------------------------- -/// Zig layout (`packed struct(u128)`, little-endian bit order): -/// bits 0..120 = `data: u120` (15 inline bytes) -/// bits 120..127 = `__len: u7` -/// bit 127 = `_tag: u1` #[repr(transparent)] #[derive(Copy, Clone, PartialEq, Eq, Debug)] pub struct Inlined(u128); diff --git a/src/bun_core/string/StringBuilder.rs b/src/bun_core/string/StringBuilder.rs index 38b0f14b48b..3ff53c2384d 100644 --- a/src/bun_core/string/StringBuilder.rs +++ b/src/bun_core/string/StringBuilder.rs @@ -5,14 +5,6 @@ use core::slice; use crate::string::{String as BunString, StringPointer, ZStr}; use bun_simdutf_sys::simdutf; -/// Two-phase string builder: callers first `count()` every slice they will -/// append, then `allocate()` once, then `append()` each slice. Returned slices -/// point into the single backing buffer. -/// -// TODO(port): the `append*` methods return `&[u8]` borrowing `self.ptr` while -// also taking `&mut self`. Zig hands out aliasing slices freely; in Rust this -// needs either an explicit `'a` on the builder, interior mutability (`Cell` -// for len), or callers must use `StringPointer` offsets instead. #[derive(Default)] pub struct StringBuilder { pub len: usize, @@ -48,10 +40,6 @@ impl StringBuilder { } pub fn count16_z(&mut self, slice: &[u16]) { - // PORT NOTE: WStr has no len method on its DST slice yet; callers pass &[u16]. - // Zig's `elementLengthUTF16IntoUTF8` is the same simdutf length call when input - // is valid; for WTF-16 with lone surrogates the slow path overestimates by 0-1 - // bytes which is fine for a capacity reservation. self.cap += simdutf::length::utf8::from::utf16::le(slice) + 1; } @@ -77,12 +65,6 @@ impl StringBuilder { // SAFETY: buf_ptr[count] == 0 written above. Some(unsafe { ZStr::from_raw_mut(buf_ptr, count) }) } else { - // Fallback: WTF-16 → WTF-8 via the slow path that handles lone surrogates. - // Zig allocated from `fallback_allocator` and handed ownership to the - // caller; the Rust signature returns a borrow into `self`, so we copy - // the WTF-8 bytes into the builder's reserved buffer (count16_z reserved - // enough — simdutf's length estimate is an upper bound for WTF-16) and - // drop the temporary Vec normally. No `mem::forget`. let out = crate::string::strings::to_utf8_alloc(slice); let len = out.len(); let avail = self.cap - self.len; @@ -306,17 +288,7 @@ impl StringBuilder { unsafe { slice::from_raw_parts_mut(ptr.as_ptr().add(self.len), self.cap - self.len) } } - /// Transfer ownership of the underlying memory to a slice. - /// - /// After calling this, you are responsible for freeing the underlying memory. - /// This StringBuilder should not be used after calling this function. pub fn move_to_slice(&mut self) -> Box<[u8]> { - // TODO(port): Zig wrote into `*[]u8` out-param and reset self. Here we - // reconstruct the Box (allocated in init_capacity/allocate) and hand it back. - // - // `take()` first: `*self = Self::default()` drops the old value, and - // `Drop` frees the buffer when `ptr` is still `Some` — leaving it set - // here would free the allocation we're about to hand back. let Some(ptr) = self.ptr.take() else { *self = Self::default(); return Box::default(); diff --git a/src/bun_core/string/StringJoiner.rs b/src/bun_core/string/StringJoiner.rs index f70c32b7a9d..99c5bc0eaae 100644 --- a/src/bun_core/string/StringJoiner.rs +++ b/src/bun_core/string/StringJoiner.rs @@ -76,10 +76,6 @@ impl<'a> StringJoiner<'a> { self.push_owned(Box::from(data)); } - // PORT NOTE: Zig signature was `push(data: []const u8, ?Allocator param)`. - // The optional allocator only encoded ownership of `data`, which has no Rust - // analogue for a borrowed `&[u8]`; callers wanting owned semantics use - // `push_owned`/`push_cloned` instead. pub fn push(&mut self, data: &'a [u8]) { if data.is_empty() { return; @@ -143,10 +139,6 @@ impl<'a> StringJoiner<'a> { let len = self.len; self.len = 0; - // Zig: `allocator.alloc(u8, this.len)` — allocates uninitialized. - // `Vec::with_capacity` + `extend_from_slice` is also zero-fill-free - // (each push is a `memcpy` into spare capacity), and since the final - // `len == capacity` the `into_boxed_slice` is a no-realloc move. let mut out = Vec::::with_capacity(len); for node in self.nodes.drain(..) { out.extend_from_slice(node.slice()); diff --git a/src/bun_core/string/identifier.rs b/src/bun_core/string/identifier.rs index 5819dfabc25..e732df8bfa2 100644 --- a/src/bun_core/string/identifier.rs +++ b/src/bun_core/string/identifier.rs @@ -20,14 +20,6 @@ pub fn is_identifier_part(codepoint: i32) -> bool { use crate::string::strings::{CodePoint, CodepointIterator, Cursor}; -/// Whole-string ES identifier check over WTF-8 bytes. -/// -/// Port of `js_lexer.isIdentifier` (src/js_parser/lexer.zig:3058). Zig has -/// exactly one impl; the Rust port had triplicated it across `bun_string`, -/// `bun_ast`, and `bun_js_parser` during the move-down layering pass. This is -/// the canonical home: it sits next to the per-codepoint predicates and the -/// two-stage Unicode tables it bottoms out in, and `CodepointIterator` lives -/// in this crate. pub fn is_identifier(text: &[u8]) -> bool { if text.is_empty() { return false; @@ -45,15 +37,6 @@ pub fn is_identifier(text: &[u8]) -> bool { true } -/// Whole-string ES identifier check over WTF-16. Port of -/// `src/js_parser/lexer.zig:isIdentifierUTF16`. -/// -/// Surrogate decoding is open-coded on purpose: an unpaired high surrogate -/// (0xD800..=0xDBFF not followed by a low surrogate) advances ONE unit and is -/// fed raw to `is_identifier_start/part` — exactly matching Zig -/// `lexer.zig:3091-3096`. `crate::string::strings::utf16_codepoint` would advance -/// TWO units in that case (see immutable.rs:1644 PORT NOTE), so do NOT swap it -/// in here. pub fn is_identifier_utf16(text: &[u16]) -> bool { let n = text.len(); if n == 0 { @@ -76,12 +59,6 @@ pub fn is_identifier_utf16(text: &[u16]) -> bool { true } -// ────────────────────────────────────────────────────────────────────────── -// The remainder of this file is auto-generated. Do not edit. -// TODO(port): re-run the identifier-table generator with .rs output instead -// of post-processing the .zig; tables below were mechanically transcribed. -// ────────────────────────────────────────────────────────────────────────── - // PORT NOTE: Zig `u21` codepoint type → `u32`. Callers must pass cp <= 0x10FFFF // (stage1 tables are sized for that range); out-of-range indexes panic. diff --git a/src/bun_core/string/immutable.rs b/src/bun_core/string/immutable.rs index 4c3ec1dad72..0c4c3474a3d 100644 --- a/src/bun_core/string/immutable.rs +++ b/src/bun_core/string/immutable.rs @@ -129,12 +129,6 @@ pub mod unicode { } impl<'a> NewCodePointIterator<'a> { - /// Zig-style cursor advance. Returns `false` at end. - // PERF(port): `#[inline]` alone is hint-only; LLVM declined to inline - // this cross-crate into `bun_js_printer::print_identifier_ascii_only` - // (the multibyte slow path makes the body look heavy). Called per-byte - // of every printed identifier under `ASCII_ONLY=true`. Zig's `iter.next` - // lives in the same TU and inlines. Force it. #[inline(always)] pub fn next(&self, cursor: &mut Cursor) -> bool { let bytes = self.bytes; @@ -185,12 +179,6 @@ pub mod unicode { false } - /// Zig: `unicode.zig:containsNonBmpCodePointOrIsInvalidIdentifier` — fused - /// "must I quote this import/export alias?" predicate for `js_printer`. - /// - /// Returns `true` if `text` is empty, OR any codepoint is non-BMP (>U+FFFF, - /// even if a valid identifier char), OR the codepoint sequence is not a - /// valid ECMAScript IdentifierName. pub fn contains_non_bmp_code_point_or_is_invalid_identifier(text: &[u8]) -> bool { let iter = CodepointIterator::init(text); let mut curs = Cursor::default(); @@ -208,13 +196,6 @@ pub mod unicode { false } - /// `toUTF16Literal` — port of `unicode.zig:toUTF16Literal` → - /// `std.unicode.utf8ToUtf16LeStringLiteral`. Zig evaluated this at - /// `comptime` into a `Holder.value` const yielding `[:0]const u16`; the - /// Rust runtime port returns an owned `Box<[u16]>` (no `Box::leak` per - /// PORTING.md §Forbidden). Prefer the const `crate::string::w!("…")` macro at call - /// sites with literal inputs — this fn exists for the residual runtime - /// callers that thread `&[u8]` through. pub fn to_utf16_literal(s: &[u8]) -> Box<[u16]> { if s.is_empty() { return Box::new([]); @@ -233,16 +214,6 @@ pub mod unicode { } } -/// Peek `n` WTF-8 codepoints from `bytes[at..]` and return the spanning slice -/// `bytes[at..end]`. Codepoint width is `wtf8_byte_sequence_length_with_invalid` -/// (invalid lead byte → 1). Stops early at EOF or a truncated trailing sequence, -/// returning the slice up to the last complete codepoint boundary. -/// -/// Shared body of `js_parser::Lexer::peek` / `toml::Lexer::peek` (Zig: -/// `js_lexer.zig:267`, `toml/lexer.zig:128`). Unlike the upstream Zig copies — -/// whose `*const Self` stepper never advances and re-reads the first byte `n` -/// times — this helper actually advances; the sole live caller passes ASCII so -/// the fix is unobservable. #[inline] pub fn peek_n_codepoints_wtf8(bytes: &[u8], at: usize, n: usize) -> &[u8] { let mut end = at; @@ -259,18 +230,6 @@ pub fn peek_n_codepoints_wtf8(bytes: &[u8], at: usize, n: usize) -> &[u8] { &bytes[at..end] } -/// WTF-8 codepoint stepper shared by the JS / JSON / TOML lexers. -/// -/// Zig: `js_parser/lexer.zig` `nextCodepointSlice` / `nextCodepoint` (and the -/// byte-identical copy in `parsers/toml/lexer.zig`). The Rust port grew a -/// third copy when `json_lexer.rs` was carved out of `js_parser` to break the -/// `bun_js_parser ↔ bun_interchange` crate cycle; all three call the same -/// `wtf8_byte_sequence_length_with_invalid` / `decode_wtf8_rune_t_multibyte` -/// pair defined alongside this module, so the stepper belongs here. -/// -/// NOT the same algorithm as [`CodepointIterator::next_codepoint`] — that one -/// uses `utf8ByteSequenceLength` + `next_width` lookahead, has no `end` -/// cursor, and does not advance-by-1 on U+FFFD. pub mod lexer_step { use super::{ CodePoint, UNICODE_REPLACEMENT, decode_wtf8_rune_t_multibyte, @@ -292,13 +251,6 @@ pub mod lexer_step { } } - /// `nextCodepoint` — decode the codepoint at `*current`, advance - /// `*current`, and write the pre-advance offset to `*end`. Returns `-1` on - /// EOF or a truncated trailing multibyte sequence. - /// - /// Split into an `#[inline(always)]` ASCII/EOF fast path plus an outlined - /// multibyte tail so the hot per-byte loop folds into every `step()` site - /// (matches Zig's per-byte `ptr[current]` increment). #[inline(always)] pub fn next_codepoint(contents: &[u8], current: &mut usize, end: &mut usize) -> CodePoint { let len = contents.len(); @@ -316,15 +268,6 @@ pub mod lexer_step { next_codepoint_multibyte(contents, current, first) } - /// Non-ASCII tail of [`next_codepoint`]. Kept out-of-line so the hot - /// ASCII path stays small enough to inline into every `step()` site. - /// - /// `#[cold]` is required: with fat LTO + `codegen-units = 1`, LLVM's - /// single-caller heuristic merges an `#[inline(never)]`-only callee back - /// into its sole caller, which then makes `next_codepoint` too large to - /// inline into `next()` (perf showed it as a separate ~2.6% symbol with - /// the multibyte decode folded in). `cold` parks this in `.text.unlikely` - /// and survives LTO's IPO inliner. #[cold] #[inline(never)] pub fn next_codepoint_multibyte(contents: &[u8], current: &mut usize, first: u8) -> CodePoint { @@ -332,12 +275,6 @@ pub mod lexer_step { let cp_len = wtf8_byte_sequence_length_with_invalid(first) as usize; let avail = len - *current; - // Zig spec (lexer.zig nextCodepoint): `switch (slice.len) { 0 => -1, 1 => slice[0], else => decode }` - // where `slice` is empty when `cp_len + current > len` and `cp_len` bytes otherwise. - // The ASCII fast path above handled `first < 0x80`; here `first >= 0x80` but `cp_len` - // may still be 1 for invalid lead bytes (0x80-0xBF, 0xF8-0xFF) — those must yield the - // raw byte, NOT the EOF sentinel, so the main lex loop falls through to its syntax-error - // arm instead of silently emitting TEndOfFile mid-stream. let code_point: CodePoint = if cp_len == 1 { first as CodePoint } else if avail < cp_len { @@ -477,10 +414,6 @@ pub fn contains_char_t>(self_: &[T], char: u #[inline] pub fn contains(self_: &[u8], str: &[u8]) -> bool { - // Zig: containsT(u8) → indexOfT(u8) → indexOf, which routes through - // std.mem.indexOf and returns None for empty needle. The generic - // index_of_t below returns Some(0) for empty, so dispatch to the - // u8-specific index_of (which matches Zig/std.mem semantics). index_of(self_, str).is_some() } @@ -549,11 +482,6 @@ pub fn contains_comptime(self_: &[u8], str: &'static [u8]) -> bool { pub use contains as includes; -/// Lowercase `probe` (ASCII fold only) into a 256-byte stack buffer and hand -/// the lowered slice to `f`. Returns `None` when `probe.len() > 256` — every -/// caller's key set is shorter, so an oversize probe is a guaranteed miss. -/// Bytes ≥ 0x80 pass through `to_ascii_lowercase` unchanged; all callers' keys -/// are pure lowercase ASCII, so such probes miss regardless. #[inline] pub fn with_ascii_lowercase(probe: &[u8], f: impl FnOnce(&[u8]) -> R) -> Option { let (buf, len) = crate::strings::ascii_lowercase_buf::<256>(probe)?; @@ -581,13 +509,6 @@ pub fn contains_any(in_: &[&[u8]], target: &[u8]) -> bool { false } -/// https://docs.npmjs.com/cli/v8/configuring-npm/package-json -/// - The name must be less than or equal to 214 characters. This includes the scope for scoped packages. -/// - The names of scoped packages can begin with a dot or an underscore. This is not permitted without a scope. -/// - New packages must not have uppercase letters in the name. -/// - The name ends up being part of a URL, an argument on the command line, and -/// a folder name. Therefore, the name can't contain any non-URL-safe -/// characters. pub fn is_npm_package_name(target: &[u8]) -> bool { if target.len() > 214 { return false; @@ -636,10 +557,6 @@ pub fn is_npm_package_name_ignore_length(target: &[u8]) -> bool { !scoped || (slash_index > 0 && slash_index + 1 < target.len()) } -// Secret-redaction scanners are canonical in crate::strings (only callers -// live in bun_core/fmt.rs). Re-exported here to preserve the bun.strings.* path. -// NOTE: starts_with_npm_secret now returns usize (was u8 in the Zig-literal port); -// no external callers depended on the narrow type. pub use crate::strings::{ find_url_password, is_uuid, starts_with_npm_secret, starts_with_secret, starts_with_uuid, }; @@ -756,10 +673,6 @@ pub fn index_of(self_: &[u8], str: &[u8]) -> Option { let self_len = self_.len(); let str_len = str.len(); - // > Both old and new libc's have the bug that if needle is empty, - // > haystack-1 (instead of haystack) is returned. And glibc 2.0 makes it - // > worse, returning a pointer to the last byte of haystack. This is fixed - // > in glibc 2.1. if self_len == 0 || str_len == 0 || self_len < str_len { return None; } @@ -839,14 +752,6 @@ pub fn cat(first: &[u8], second: &[u8]) -> Result, AllocError> { Ok(out.into_boxed_slice()) } -// 31 character string or a slice -// -// PERF NOTE: `remainder_buf` is `MaybeUninit` because `init`/`init_lower_case` -// only write `[0..len]` (or `[0..16]` for the slice case) and `slice()` only -// reads `[0..remainder_len]`. Zig leaves the tail `undefined`; the original -// Rust port zeroed `[0; 31]` on every call which showed up as ~0.45% of cycles -// in the next-lint profile (~6M calls × ~24B avg waste). Tail bytes have no -// validity requirement, so we leave them uninit to match Zig. #[repr(C)] #[derive(Copy, Clone)] pub struct StringOrTinyString { @@ -908,12 +813,6 @@ impl StringOrTinyString { // PORT NOTE: Zig deinit was a no-op (commented-out free). No Drop impl. - // PORT NOTE: plain `#[inline]` (not `#[inline(always)]`). These are tiny - // generic delegators: a length check plus a tail call into the non-generic - // `init`/`init_lower_case` or the `Appender` method. `#[inline]` lets the - // small fast path fold into callers (and lets duplicate `A` instantiations - // be ICF'd at link time / clustered by the symbol-ordering file) without - // forcing the cold `append*` arm into every call site. #[inline] pub fn init_append_if_needed( stringy: &[u8], @@ -1090,23 +989,10 @@ pub fn starts_with(self_: &[u8], str: &[u8]) -> bool { /// Transliterated from: /// https://github.com/rust-lang/rust/blob/91376f416222a238227c84a848d168835ede2cc3/library/core/src/str/mod.rs#L188 pub fn is_on_char_boundary(self_: &[u8], idx: usize) -> bool { - // 0 is always ok. - // Test for 0 explicitly so that it can optimize out the check - // easily and skip reading string data for that case. - // Note that optimizing `self.get(..idx)` relies on this. if idx == 0 { return true; } - // For `idx >= self.len` we have two options: - // - // - idx == self.len - // Empty strings are valid, so return true - // - idx > self.len - // In this case return false - // - // The check is placed exactly here, because it improves generated - // code on higher opt-levels. See PR #84751 for more details. if idx >= self_.len() { return idx == self_.len(); } @@ -1314,10 +1200,6 @@ fn eql_comptime_check_len_with_known_type( a: &[T], b: &[T], @@ -1341,12 +1223,6 @@ pub fn eql_case_insensitive_asciii_check_length(a: &[u8], b: &[u8]) -> bool { eql_case_insensitive_ascii(a, b, true) } -// PORT NOTE: Zig's `comptime check_len: bool` was first ported as a const -// generic, but the dominant call shape across the tree passes it as a runtime -// 3rd arg (`eql_case_insensitive_ascii(a, b, true)`). Accept it at runtime — -// the branch is trivially predicted/inlined; callers wanting the -// length-agnostic forms still have the `_check_length` / `_ignore_length` -// wrappers above. #[inline] pub fn eql_case_insensitive_ascii(a: &[u8], b: &[u8], check_len: bool) -> bool { // NOTE: must call `strings_impl` directly — `crate::strings::eql_case_insensitive_ascii` @@ -1554,10 +1430,6 @@ macro_rules! w { }}; } -/// Index of first non-ASCII byte. Thin `u32` view over the canonical -/// `crate::strings_impl::first_non_ascii` (Zig spec `firstNonASCII -> ?u32`). -/// NOTE: must call `strings_impl` directly — `crate::strings::first_non_ascii` -/// re-exports *this* function (177f671a9046), so routing through it recurses. #[inline] pub fn first_non_ascii(slice: &[u8]) -> Option { crate::strings_impl::first_non_ascii(slice).map(|i| i as u32) @@ -1571,13 +1443,6 @@ pub fn is_valid_utf8(slice: &[u8]) -> bool { simdutf::validate::utf8(slice) } -/// SIMD-validated `&str` view of `bytes`; `None` if not valid UTF-8. -/// -/// This is the codebase-wide replacement for `core::str::from_utf8` — every -/// runtime UTF-8 validation goes through simdutf (~3-10× faster than std's -/// scalar DFA on AVX2/NEON). NOT `const`: the one allowed exception is -/// `crate::env::const_str_slice` (compile-time git-SHA slicing), which is -/// the only place `core::str::from_utf8` may appear. #[inline] pub fn str_utf8(bytes: &[u8]) -> Option<&str> { if simdutf::validate::utf8(bytes) { @@ -1590,14 +1455,6 @@ pub fn str_utf8(bytes: &[u8]) -> Option<&str> { pub use index_of_newline_or_non_ascii as index_of_newline_or_non_ascii_or_ansi; -/// Checks if slice[offset..] has any < 0x20 or > 127 characters -// PERF: `#[inline]` — this is the predicate of the source-map column-tracking -// fast path (`Chunk.rs::update_generated_line_and_column`) and the per-rune -// fast-forward inside its slow loop; it's also the LineOffsetTable scan step. -// Without the hint LLVM emits a cross-crate `call` (the body is a couple of -// branches plus a tail-call into the SIMD `highway` routine), so the -// `is_none()` fast path doesn't fold into the caller. Same rationale as -// `str_utf8` above. #[inline] pub fn index_of_newline_or_non_ascii(slice_: &[u8], offset: u32) -> Option { index_of_newline_or_non_ascii_check_start::(slice_, offset) @@ -1648,10 +1505,6 @@ pub fn index_of_newline_or_non_ascii_check_start( pub use highway::contains_newline_or_non_ascii_or_quote; -/// Supports: -/// - `"` -/// - `'` -/// - "`" pub fn index_of_needs_escape_for_java_script_string(slice: &[u8], quote_char: u8) -> Option { if slice.is_empty() { return None; @@ -1776,10 +1629,6 @@ pub enum DecodeHexError { InvalidByteSequence, } -/// Source character types accepted by the hex decoder: `u8` (Latin-1) and -/// `u16` (UTF-16). The associated function routes full pairs through the -/// matching Highway kernel while `_decode_hex_to_bytes` keeps the generic -/// scalar path for short inputs. pub trait HexChar: Copy + Into { /// Decode up to `min(src.len() / 2, dst.len())` hex pairs with SIMD, /// stopping at the first pair containing a non-hex character. @@ -1820,10 +1669,6 @@ fn _decode_hex_to_bytes( destination: &mut [u8], source: &[Char], ) -> Result { - // Highway fast path: decode whole pairs in bulk, stopping at the first - // invalid pair — the same semantics as the scalar loop below. Short inputs - // stay scalar; the dynamically-dispatched FFI call isn't worth it for a - // handful of pairs. const HIGHWAY_MIN_PAIRS: usize = 16; let pairs = destination.len().min(source.len() / 2); if pairs >= HIGHWAY_MIN_PAIRS { @@ -1904,10 +1749,6 @@ pub fn encode_bytes_to_hex(destination: &mut [u8], source: &[u8]) -> usize { crate::fmt::bytes_to_hex_lower(&source[..to_read], &mut destination[..to_write]) } -/// Leave a single leading char -/// ``` -/// trim_subsequent_leading_chars("foo\n\n\n\n", '\n') -> "foo\n" -/// ``` pub fn trim_subsequent_leading_chars(slice: &[u8], char: u8) -> &[u8] { if slice.is_empty() { return slice; @@ -1928,10 +1769,6 @@ pub fn trim_leading_char(slice: &[u8], char: u8) -> &[u8] { b"" } -/// Trim leading pattern of 2 bytes -/// -/// e.g. -/// `trim_leading_pattern2("abcdef", 'a', 'b') == "cdef"` pub fn trim_leading_pattern2(slice_: &[u8], byte1: u8, byte2: u8) -> &[u8] { let mut slice = slice_; while slice.len() >= 2 { @@ -2196,20 +2033,6 @@ pub fn join(slices: &[&[u8]], delimiter: &[u8]) -> Result, AllocError> Ok(out.into_boxed_slice()) } -// ── Lexicographic slice ordering ────────────────────────────────────────── -// Canonical home for what Zig calls `std.mem.order`. The Zig tree had three -// hand-rolled copies (bun.strings.order, md.entity.orderStrings, -// ast.e.stringCompareForJavaScript); the Rust port keeps exactly one of each -// shape here. - -/// Lexicographic byte-slice ordering (memcmp fast path). -/// Semantically identical to `<[u8] as Ord>::cmp` / Zig `std.mem.order(u8, a, b)`. -/// -/// Delegates to `<[u8] as Ord>::cmp` rather than an extern `libc::memcmp` call: -/// the std specialisation lowers to the `memcmp` LLVM builtin, so LLVM can -/// inline the short-string fast path and skip the PLT trampoline — matching -/// what Zig gets for `bun.c.memcmp` after LTO. `#[inline(always)]` because this -/// sits inside `sort_unstable_by` comparators on the install hot path. #[inline(always)] pub fn order(a: &[u8], b: &[u8]) -> Ordering { a.cmp(b) @@ -2320,12 +2143,6 @@ impl &[u8]> GlobLengthSorter { } } -/// Reflection adapter for [`move_all_slices`]. Zig's `moveAllSlices` used -/// `std.meta.fields(Type)` to enumerate every `[]const u8` field at comptime; -/// Rust has no field reflection, so each container type hand-implements this -/// trait (or, once landed, `#[derive(MoveSlices)]`) to yield the same set of -/// fields as `&mut &'a [u8]` so they can be re-pointed into a new backing -/// buffer of lifetime `'a` without any unsafe. pub trait MoveSlices<'a> { /// Invoke `f` once per byte-slice field of `self`. fn for_each_byte_slice_field(&mut self, f: &mut dyn FnMut(&mut &'a [u8])); @@ -2379,10 +2196,6 @@ pub const UNICODE_REPLACEMENT: u32 = 0xFFFD; // UTF-8 encoding of U+FFFD pub const UNICODE_REPLACEMENT_STR: [u8; 3] = [0xEF, 0xBF, 0xBD]; -// Spec (immutable.zig:1990, 2003) calls `bun.c_ares.ares_inet_pton`, the vendored -// c-ares implementation. Do NOT call the system `inet_pton` here: on Windows that -// resolves into ws2_32.dll and fails with WSANOTINITIALISED whenever it runs before -// `WSAStartup()`, which URL/host parsing can. c-ares' impl is pure C, no preconditions. unsafe extern "C" { pub fn ares_inet_pton( af: c_int, @@ -2390,10 +2203,6 @@ unsafe extern "C" { dst: *mut core::ffi::c_void, ) -> c_int; } -// dep-graph: bun_string < bun_sys, so cannot import the canonical -// `bun_sys::posix::AF`. Keep a thin libc/ws2def passthrough instead. The -// previous hand-rolled cfg ladder hardcoded `10` for the BSD fallback, which -// is wrong (FreeBSD AF_INET6 == 28); routing through `libc` fixes that. const AF_INET: c_int = 2; #[cfg(not(windows))] const AF_INET6: c_int = libc::AF_INET6 as c_int; @@ -2436,15 +2245,6 @@ pub fn left_has_any_in_right(to_check: &[&[u8]], against: &[&[u8]]) -> bool { false } -/// Returns true if the input has the prefix and the next character is not an identifier character -/// Also returns true if the input ends with the prefix (i.e. EOF) -/// -/// Example: -/// ```text -/// has_prefix_with_word_boundary("console.log", "console") // true -/// has_prefix_with_word_boundary("console.log", "log") // false -/// has_prefix_with_word_boundary("console.log", "console.log") // true -/// ``` pub fn has_prefix_with_word_boundary(input: &[u8], prefix: &'static [u8]) -> bool { if has_prefix_comptime(input, prefix) { if input.len() == prefix.len() { @@ -2770,14 +2570,6 @@ pub fn is_hex_code_point>(cp: T) -> bool { cp.try_into().is_ok_and(|b: u8| b.is_ascii_hexdigit()) } -/// Unicode `Zs` (Space_Separator) general category — the exact 17-codepoint -/// set, stable since Unicode 4.0. Shared core of: -/// - ECMAScript `WhiteSpace` (js_parser::lexer) -/// - the JSON5/JS-flavoured JSON lexer (parsers::json_lexer) -/// - CommonMark §2.1 "Unicode whitespace" (md::helpers) -/// Callers compose with their own ASCII / U+FEFF / line-terminator extras — -/// those differ per spec and MUST NOT be folded in here (FEFF is Cf, not Zs, -/// and is ECMAScript-only; 2028/2029 are Zl/Zp, json_lexer-only). #[inline] pub const fn is_unicode_space_separator(cp: u32) -> bool { matches!( @@ -2830,11 +2622,6 @@ impl ANSIIterator { // TODO(port): move to _sys unsafe extern "C" { - // `&mut ANSIIterator` is ABI-identical to the C++ `ANSIIterator*` (thin - // non-null pointer to a `#[repr(C)]` POD struct); C++ reads `input`/ - // `input_len`/`cursor` and writes `cursor`/`slice_ptr`/`slice_len`. The - // `&mut` encodes the only pointer-validity precondition, so `safe fn` - // discharges the link-time proof and callers need no `unsafe`. safe fn Bun__ANSI__next(it: &mut ANSIIterator) -> bool; } @@ -2852,11 +2639,6 @@ pub fn to_utf8_alloc_with_type(utf16: &[u16]) -> Vec { crate::strings::to_utf8_alloc(utf16) } -// ───────────── minimal real impls of submodule fns ───────────── -// These mirror the same-named fns in `unicode_draft` so dependents can link -// against `bun_core::strings::*` directly. Each is a thin wrapper over simdutf -// or the scalar logic from the .zig source. - pub use crate::strings_impl::utf8_byte_sequence_length; /// `std.mem.trimLeft(u8, str, chars)` — strip leading chars in `values_to_strip`. @@ -2929,22 +2711,11 @@ pub fn without_prefix<'a>(input: &'a [u8], prefix: &[u8]) -> &'a [u8] { } } -// Zig: `pub const withoutTrailingSlash = paths_.withoutTrailingSlash;` -// (immutable.zig:2380). The full `paths` submodule now lives in -// `bun_paths::string_paths` (it depends upward on `bun_paths` resolve/pool -// helpers and would cycle here). Callers reach the Windows path-shape -// helpers (`to_nt_path` / `to_kernel32_path` / `from_w_path` / …) via -// `bun_paths::strings::*`; this module keeps only the re-export of the -// scalar `without_trailing_slash` already defined in `crate::strings`. pub use crate::strings_impl::{remove_leading_dot_slash, without_trailing_slash}; // Zig: `pub const convertUTF16ToUTF8InBuffer = unicode.convertUTF16ToUTF8InBuffer;` // (immutable.zig). Re-export the bun_core implementation so callers can spell // `strings::convert_utf16_to_utf8_in_buffer` without reaching into `unicode`. pub use crate::strings::convert_utf16_to_utf8_in_buffer; -// Zig: `pub const convertUTF8toUTF16InBufferZ = unicode.convertUTF8toUTF16InBufferZ;` -// — re-export the NUL-terminated variant so callers can spell -// `strings::convert_utf8_to_utf16_in_buffer_z` (used by the Windows profilers -// to widen output paths for `File::write_file_os_path`). pub use unicode_draft::convert_utf8_to_utf16_in_buffer_z; /// `strings.startsWithWindowsDriveLetterT` — true for `[A-Za-z]:` prefix @@ -2958,11 +2729,6 @@ pub fn starts_with_windows_drive_letter_t>(s: &[T]) -> bool } } -/// `strings.convertUTF8toUTF16InBuffer` — UTF-8 → UTF-16LE into a caller-supplied -/// buffer (capacity ≥ `input.len()` u16). SIMD fast path via simdutf; on invalid -/// UTF-8 falls back to a scalar WTF-8 decoder that emits U+FFFD for malformed -/// bytes and passes unpaired surrogates through (so non-empty input never yields -/// an empty slice — fixes #8197 / the TODO at unicode.zig:1537). pub fn convert_utf8_to_utf16_in_buffer<'a>(buf: &'a mut [u16], input: &[u8]) -> &'a mut [u16] { if input.is_empty() { return &mut buf[..0]; @@ -3034,34 +2800,16 @@ fn decode_wtf8_one(s: &[u8]) -> (u32, usize) { ) } -/// `strings.toUTF8ListWithType` — append UTF-8 transcoding of `utf16` onto -/// `list` and return the (possibly-reallocated) list. Port of -/// `unicode.zig:toUTF8ListWithType` (always uses simdutf path; Bun is built -/// with `FeatureFlags.use_simdutf = true`). pub fn to_utf8_list_with_type(mut list: Vec, utf16: &[u16]) -> Result, AllocError> { if utf16.is_empty() { return Ok(list); } - // Zig: `list.ensureTotalCapacityPrecise(length + 16)` then `convertUTF16ToUTF8`. - // `convert_utf16_to_utf8_append` writes directly into `spare_capacity_mut()` and - // requires the caller to pre-reserve (its doc says so explicitly); without this - // reserve a fresh `Vec::new()` has a dangling `0x1` spare pointer and simdutf - // segfaults writing to it. The +16 padding mirrors Zig's SIMD over-read slack. let length = simdutf::length::utf8::from::utf16::le(utf16); list.try_reserve(length + 16).map_err(|_| AllocError)?; - // PORT NOTE: Zig's path validates UTF-16 first then falls back to a manual - // loop on failure (`toUTF8ListWithTypeBun`). Here we route through - // `crate::strings::convert_utf16_to_utf8_append`, which already replaces - // unpaired surrogates with U+FFFD — semantically equivalent. crate::strings::convert_utf16_to_utf8_append(&mut list, utf16); Ok(list) } -/// Errors from `to_utf16_alloc` when `fail_if_invalid = true`. -/// -/// Re-exported from `unicode_draft` so that `to_utf16_alloc_maybe_buffered` -/// (defined there) and `to_utf16_alloc` (defined here) share a single error -/// type — callers like `TextDecoder` match on `strings::ToUTF16Error` for both. pub use unicode_draft::ToUTF16Error; impl From for crate::Error { fn from(e: ToUTF16Error) -> Self { @@ -3072,12 +2820,6 @@ impl From for crate::Error { } } -/// `strings.toUTF16Alloc` — convert UTF-8 → UTF-16LE **iff** `bytes` contains -/// any non-ASCII byte; pure-ASCII inputs return `Ok(None)` (caller keeps the -/// 8-bit form). When `fail_if_invalid` is set, invalid UTF-8 yields -/// `Err(InvalidByteSequence)`; otherwise invalid sequences are replaced with -/// U+FFFD (per `unicode.zig:toUTF16Alloc`). When `sentinel` is set the result -/// includes a trailing 0 u16. pub fn to_utf16_alloc( bytes: &[u8], fail_if_invalid: bool, @@ -3089,11 +2831,6 @@ pub fn to_utf16_alloc( let out_length = simdutf::length::utf16::from::utf8(bytes); let cap = out_length + if sentinel { 1 } else { 0 }; - // Hot path: allocate uninitialised and let simdutf write directly into the - // spare capacity — avoids the redundant zero-fill of `vec![0u16; cap]`, - // which for large source files (build/create-next benches) is a measurable - // memset. `.max(1)` keeps the buffer pointer non-dangling so simdutf never - // sees `Vec::with_capacity(0)`'s `0x2` sentinel. let mut out: Vec = Vec::with_capacity(cap.max(1)); // SAFETY: `out` has ≥ `out_length` u16 of capacity (just reserved). simdutf // never reads from the output buffer and writes at most `out_length` code @@ -3141,10 +2878,6 @@ pub fn to_utf16_alloc( Ok(Some(out)) } -/// `PATTERN_KEY_COMPARE` from the Node.js ESM resolution spec — the comparator -/// behind `NewGlobLengthSorter`. Returns an [`Ordering`] suitable for -/// `slice.sort_by(|a, b| glob_length_compare(a, b))` to sort in **descending -/// order of specificity** (matches Zig `lessThan` returning `true` ⇒ `Less`). pub fn glob_length_compare(key_a: &[u8], key_b: &[u8]) -> Ordering { let star_a = index_of_char(key_a, b'*'); let star_b = index_of_char(key_b, b'*'); @@ -3173,12 +2906,6 @@ pub fn glob_length_compare(key_a: &[u8], key_b: &[u8]) -> Ordering { #[cfg(test)] mod tests { - // Regression guard for 3e7f1dabc079: `crate::strings::{first_non_ascii, - // eql_case_insensitive_ascii}` are explicit re-exports of the wrappers in - // *this* module (lib.rs `pub mod strings`), so the wrappers must call - // `crate::strings_impl::*` directly. Routing through `crate::strings::*` - // tail-recurses; rustc's `unconditional_recursion` lint does NOT fire - // across `pub use` re-export chains, so assert termination here instead. #[test] fn strings_reexport_wrappers_terminate() { assert_eq!(super::first_non_ascii(b"abc"), None); diff --git a/src/bun_core/string/immutable/exact_size_matcher.rs b/src/bun_core/string/immutable/exact_size_matcher.rs index b1f31b44e25..1cf0622fd0b 100644 --- a/src/bun_core/string/immutable/exact_size_matcher.rs +++ b/src/bun_core/string/immutable/exact_size_matcher.rs @@ -90,11 +90,6 @@ where } } - /// Zig: `pub fn case(comptime str: []const u8) T` - /// - /// Runtime variant. For const-position (Zig's comptime use in `switch` - /// arms), use the [`exact_case!`] macro below — `const fn` cannot call - /// the non-const trait method `read_le` on stable. #[inline(always)] pub fn case(str: &'static [u8]) -> >::T { // if (str.len < max_bytes) { zero-pad } else if (== max_bytes) { read } else { @compileError } @@ -114,11 +109,6 @@ where } } -/// Const-position equivalent of `ExactSizeMatcher::::case(b"..")` for the -/// common N (1/2/4/8/16). Expands to a `::from_le_bytes` of a zero-padded -/// literal, which IS const-evaluable. -/// -/// Usage: `match ExactSizeMatcher::<4>::r#match(s) { exact_case!(4, b"foo") => .., _ => .. }` #[macro_export] macro_rules! exact_case { (1, $s:expr) => {{ diff --git a/src/bun_core/string/immutable/unicode.rs b/src/bun_core/string/immutable/unicode.rs index fd58c265281..e7aa9e2c6e7 100644 --- a/src/bun_core/string/immutable/unicode.rs +++ b/src/bun_core/string/immutable/unicode.rs @@ -20,10 +20,6 @@ use bun_simdutf_sys::simdutf; crate::declare_scope!(strings, hidden); -/// Widen-append `src` ASCII bytes onto `dst` as `u16` code units. Reserves, -/// commits the new length, then scalar-widens via `copy_u8_into_u16`. Folds -/// the `reserve + set_len + copy_u8_into_u16` triple repeated across the -/// UTF-8→UTF-16 paths into one audited block. #[inline] fn append_u8_as_u16(dst: &mut Vec, src: &[u8]) { if src.is_empty() { @@ -44,22 +40,6 @@ fn append_u16_as_u8(dst: &mut Vec, src: &[u16]) { copy_u16_into_u8(unsafe { crate::vec::writable_slice(dst, src.len()) }, src); } -// ───── canonical WTF-8 single-rune decode (Zig: unicode.zig decodeWTF8RuneT) ───── -// Lives in `bun_core::string::immutable::unicode_draft` (this file), re-exported -// through the inline `pub mod unicode` in immutable.rs and onward as -// `bun_core::strings::{decode_wtf8_rune_t, decode_wtf8_rune_t_multibyte, codepoint_size}`. -// -// Visibility promoted pub(super) → pub so the inline shim mod can re-export -// instead of carrying a second body, and so md/glob/parsers can call directly. - -/// Integer types usable as the codepoint result of [`decode_wtf8_rune_t`]. -/// Sealed to the two instantiations Zig uses (`i32` aka `CodePoint`, and `u32`); -/// every caller in-tree is one of these. `ZERO_VALUE` is the per-type sentinel -/// Zig threaded as a `comptime_int` (`-1` for i32, `0` for u32). -/// -/// PORT NOTE: bounds widened from `From` to include the bit-ops needed by -/// `decode_wtf8_rune_t_multibyte` plus a `from_u32` constructor (folds in the -/// previously separate `FromU32`/`FromU32Const` helper traits). pub trait CodePointZero: Copy + Eq @@ -115,23 +95,6 @@ pub fn decode_wtf8_rune_t(p: [u8; 4], len: U3Fast, zero: T) -> pub use crate::strings::codepoint_size; -// ───────────────────────────── UTF16 → UTF8 ───────────────────────────── -// -// The transcoding suite (`convert_utf16_to_utf8{,_append}`, `to_utf8_alloc{,_z}`, -// `to_utf8_alloc_with_type`, `to_utf8_list_with_type`, `to_utf8_append_to_list`, -// `to_utf8_from_latin1{,_z}`, `allocate_latin1_into_utf8_with_list`) lives -// canonically in `crate::strings` (T0) and is re-exported by -// `crate::string::immutable`. The `pub(super)` copies that previously lived here were -// shadowed dead code (the parent module re-exports the bun_core versions, not -// these) and have been deleted in D056. -// -// Kept below: the two variants bun_core does NOT provide — -// * `convert_utf16_to_utf8_without_invalid_surrogate_pairs` (hard-error on -// unpaired surrogates instead of U+FFFD replacement), and -// * `to_utf8_list_with_type_bun::` (returns the -// dangling lead surrogate instead of replacing it — streaming TextEncoder -// contract). - /// Returns `Some(u16)` (the trailing lead surrogate) when `SKIP_TRAILING_REPLACEMENT` and a /// dangling lead surrogate is at the end; otherwise `None`. When `SKIP_TRAILING_REPLACEMENT` is /// false the Zig version returned the list by value — in Rust the caller already owns `list`. @@ -170,10 +133,6 @@ pub fn to_utf8_list_with_type_bun( } } - // Encode into a stack scratch then append the `count` (≤ 4) bytes. - // Capacity for `count` is reserved above, so `extend_from_slice` is a - // bounds-check + memcpy with no realloc — same write traffic as the - // previous in-place `*[4]u8` cast without the raw-pointer view. let mut four = [0u8; 4]; let _ = crate::strings::encode_wtf8_rune(&mut four, replacement.code_point); list.extend_from_slice(&four[..count]); @@ -195,10 +154,6 @@ pub fn to_utf8_list_with_type_bun( use crate::strings::EncodeIntoResult; -/// Thin wrapper over the canonical T0 `crate::strings::allocate_latin1_into_utf8_with_list`. -/// Kept `Result`-typed for source-compat with existing callers (TextEncoder / -/// encoding.rs / ConsoleObject) — the bun_core impl is infallible per -/// PORTING.md §Allocators (panic-on-OOM), so this is always `Ok`. pub fn allocate_latin1_into_utf8(latin1_: &[u8]) -> Result, AllocError> { Ok(crate::strings::allocate_latin1_into_utf8_with_list( Vec::with_capacity(latin1_.len()), @@ -207,15 +162,6 @@ pub fn allocate_latin1_into_utf8(latin1_: &[u8]) -> Result, AllocError> )) } -// ─── CANONICAL: UTF-16 codepoint decode ───────────────────────────────────── -// Faithful port of unicode.zig:569 / :1321 / :1325 / :1361. Re-exported from -// immutable.rs as `strings::{utf16_codepoint, utf16_codepoint_with_fffd, -// UTF16Replacement}`; the parallel Utf16CodepointLen + duplicate struct/fns -// that lived in immutable.rs are deleted in favour of this single source of -// truth. All callers (visible.rs ×1; unicode.rs ×4; shell_parser/parse.rs ×1; -// TextEncoderStreamEncoder.rs ×1) resolve through the -// `pub use unicode_draft::{…}` re-export with no source change. - /// `strings.UTF16Replacement` (unicode.zig:569) — decoded UTF-16 codepoint /// with surrogate metadata. #[derive(Clone, Copy)] @@ -427,10 +373,6 @@ pub(super) fn convert_utf8_bytes_into_utf16(bytes: &[u8]) -> UTF16Replacement { convert_utf8_bytes_into_utf16_with_length(sequence, sequence_length, bytes.len()) } -// SWAR body moved down into `crate::strings` (T0) so the canonical -// `copy_latin1_into_utf8` is the spec-faithful fast path. Re-export here so -// `pub use unicode_draft::copy_latin1_into_utf8_stop_on_non_ascii` in -// `immutable.rs` keeps resolving. pub use crate::strings::copy_latin1_into_utf8_stop_on_non_ascii; pub fn replace_latin1_with_utf8(buf_: &mut [u8]) { @@ -558,11 +500,6 @@ pub fn copy_latin1_into_ascii(dest: &mut [u8], src: &[u8]) { } } -/// It is common on Windows to find files that are not encoded in UTF8. Most of these include -/// a 'byte-order mark' codepoint at the start of the file. The layout of this codepoint can -/// determine the encoding. -/// -/// https://en.wikipedia.org/wiki/Byte_order_mark #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum BOM { Utf8, @@ -637,10 +574,6 @@ impl BOM { bytes } BOM::Utf16Le => { - // `trimmed_bytes` is `&[u8]` at offset 2 of a `Vec`; alignment is - // not guaranteed ≥ 2, so casting to `&[u16]` (the Zig `@alignCast` - // port) is UB. Route through the byte-level helper which copies into - // an aligned `Vec` first. let trimmed_bytes = &bytes[Self::UTF16_LE_BYTES.len()..]; let out = crate::strings::to_utf8_alloc_from_le_bytes(trimmed_bytes); drop(bytes); @@ -654,10 +587,6 @@ impl BOM { } } - /// This is required for fs.zig's `use_shared_buffer` flag. we cannot free that pointer. - /// The returned slice will always point to the base of the input. - /// - /// Requires an arraylist in case it must be grown. pub fn remove_and_convert_to_utf8_without_dealloc<'a>(self, list: &'a mut Vec) -> &'a [u8] { match self { BOM::Utf8 => { @@ -1000,11 +929,6 @@ pub(super) fn cp1252_to_codepoint_bytes_assume_not_ascii16(char: u32) -> u16 { CP1252_TO_UTF16_CONVERSION_TABLE[(char as u8) as usize] } -// `copy_utf16_into_utf8` (the non-generic wrapper) lives canonically in -// `crate::strings`; re-exported at `crate::string::immutable` (line ~391). The -// `_impl` variant below is what hot paths -// (encoding.rs, Sink.rs, websocket_client.rs) call directly. - /// See comment on `copy_utf16_into_utf8_with_buffer_impl` on what `allow_truncated_utf8_sequence` should do pub fn copy_utf16_into_utf8_impl( buf: &mut [u8], @@ -1039,21 +963,6 @@ pub fn copy_utf16_into_utf8_impl( copy_utf16_into_utf8_with_buffer_impl::(buf, utf16, utf16.len()) } -/// Q: What does the `allow_truncated_utf8_sequence` parameter do? -/// A: If the output buffer can't fit everything, this function will write -/// incomplete utf-8 byte sequences if `allow_truncated_utf8_sequence` is -/// enabled. -/// -/// Q: Doesn't that mean this function would output invalid utf-8? Why would you -/// ever want to do that? -/// A: Yes. This is needed for writing a UTF-16 string to a node Buffer that -/// doesn't have enough space for all the bytes: -/// -/// ```js -/// let buffer = Buffer.allocUnsafe(1); -/// buffer.fill("Ȣ"); -/// expect(buffer[0]).toBe(0xc8); -/// ``` pub(super) fn copy_utf16_into_utf8_with_buffer_impl( buf: &mut [u8], utf16: &[u16], @@ -1158,10 +1067,6 @@ pub(super) fn copy_utf16_into_utf8_with_buffer_impl [u8; 4] { } } -/// Convert potentially ill-formed UTF-8 or UTF-16 bytes to a Unicode Codepoint. -/// Invalid codepoints are replaced with `zero` parameter -/// This is a clone of esbuild's decodeWTF8Rune -/// which was a clone of golang's "utf8.DecodeRune" that was modified to decode using WTF-8 instead. -/// Asserts a multi-byte codepoint #[inline] pub fn decode_wtf8_rune_t_multibyte(p: [u8; 4], len: U3Fast, zero: T) -> T { debug_assert!(len > 1); diff --git a/src/bun_core/string/immutable/visible.rs b/src/bun_core/string/immutable/visible.rs index d9ef8f90d33..3c79af03d65 100644 --- a/src/bun_core/string/immutable/visible.rs +++ b/src/bun_core/string/immutable/visible.rs @@ -51,10 +51,6 @@ pub mod visible { } } - /// Byte index of the longest prefix of `input` whose visible - /// width is <= `max_width`. ANSI escapes count as zero-width - /// and are always included in the prefix. Never splits a - /// multi-byte UTF-8 codepoint. pub fn utf8_index_at_width(input: &[u8], max_width: usize) -> usize { // SAFETY: `input` is a live slice for the duration of the call. unsafe { diff --git a/src/bun_core/string/mod.rs b/src/bun_core/string/mod.rs index 048590ab038..8e6410a80e7 100644 --- a/src/bun_core/string/mod.rs +++ b/src/bun_core/string/mod.rs @@ -39,26 +39,12 @@ pub use write::Write; #[path = "immutable.rs"] pub mod immutable; -// Unicode ID-Start/ID-Continue two-stage tables (`js_lexer/identifier_data.zig`). -// Pure data with no upward deps; hosted here so [`lexer`], [`mutable_string`], -// and [`immutable::unicode`] get full Unicode coverage without depending on -// `bun_js_parser`. `bun_js_parser::lexer::identifier` re-exports this module. #[path = "identifier.rs"] pub mod identifier; use core::sync::atomic::{AtomicUsize, Ordering}; pub use wtf::{WTFStringImpl, WTFStringImplExt, WTFStringImplStruct}; -// ────────────────────────────────────────────────────────────────────────── -// `bun.String` — 5-variant tagged WTFString-or-ZigString. extern layout -// must match Zig `extern struct { tag: Tag, value: StringImpl }` (= C++ -// `BunString` in BunString.cpp), 24 bytes on 64-bit. -// ────────────────────────────────────────────────────────────────────────── -// Canonical layout lives in `bun_alloc` (T0 TYPE_ONLY landing for -// `bun.String`); re-exported so existing `bun_core::{Tag, StringImpl}` paths -// keep working. `String` is a `#[repr(transparent)]` newtype over -// `bun_alloc::String` so the FFI layout has ONE source of truth while this -// crate retains its inherent impl block (toJS/toUTF8/WTF refcounting). pub use bun_alloc::{StringImpl, Tag}; #[repr(transparent)] @@ -78,11 +64,6 @@ unsafe extern "C" { fn BunString__fromUTF16ToLatin1(bytes: *const u16, len: usize) -> String; safe fn BunString__fromLatin1Unitialized(len: usize) -> String; safe fn BunString__fromUTF16Unitialized(len: usize) -> String; - // `&mut String` / `&String` are ABI-identical to the C++ `BunString*` - // (thin non-null pointer to a `#[repr(C)]` struct, asserted by - // `assert_ffi_layout!` above). C++ reads/writes only the `tag`/`value` - // fields in place; the type encodes the sole pointer-validity precondition, - // so `safe fn` discharges the link-time proof here. safe fn BunString__toWTFString(this: &mut String); safe fn BunString__toThreadSafe(this: &mut String); fn BunString__createAtom(bytes: *const u8, len: usize) -> String; @@ -100,13 +81,6 @@ unsafe extern "C" { fn BunString__createExternalGloballyAllocatedUTF16(bytes: *mut u16, len: usize) -> String; } -/// `ctx` is the pointer passed into `create_external`; `buffer` is the -/// `[*]u8`/`[*]u16` storage; `len` is the character count. -/// -/// C++ signature (`BunString.cpp` `BunString__createExternal`): -/// `void (*)(void*, void*, size_t)` — the third arg is `size_t`, **not** -/// `unsigned`. A `u32` here would truncate on 64-bit and (worse) shift the -/// stack/register layout for the callee on Win64 where `size_t` ≠ `unsigned`. pub(crate) type ExternalStringImplFreeFunction = extern "C" fn(ctx: Ctx, buffer: *mut core::ffi::c_void, len: usize); @@ -174,10 +148,6 @@ impl String { unsafe { self.0.value.wtf_string_impl } } - /// `bun.String.init(anytype)` — polymorphic borrow constructor - /// (string.zig:331). Mirrors the Zig `switch (@TypeOf(value))` table via - /// `Into` impls below: `String` is identity, `ZigString` is wrapped, - /// byte/str slices go through `ZigString::from_bytes`. #[inline] pub fn init>(value: T) -> Self { value.into() @@ -198,10 +168,6 @@ impl String { Self::init(ZigString::init(s)) } - /// `bun.String.static` — `'static` slice; converted to JS via - /// `WTF::ExternalStringImpl` without copying. Generic over `str`/`[u8]` - /// so call sites may pass either `"lit"` or `b"lit"` (Zig's `[:0]const u8` - /// literal maps to both in ported code). #[inline] pub fn static_>(s: &'static S) -> Self { // Zig: ZigString.init(input) — no UTF-8 mark on the static path. @@ -278,15 +244,6 @@ impl String { } Self::clone_utf8(bytes) } - /// `bun.String.createExternal` — wraps `bytes` in a `WTF::ExternalStringImpl` - /// that calls `callback(ctx, buffer, len)` when the impl is destroyed. - /// - /// External strings are WTF strings whose bytes live elsewhere; `bytes` is - /// borrowed (not copied). If `bytes.len() >= max_length()`, `callback` is - /// invoked immediately and a `dead` string is returned (string.zig:404). - /// - /// `Ctx` must be a pointer-sized type (raw pointer or `&T`); enforced by - /// the const-assert below to keep the C-ABI cast sound. pub fn create_external( bytes: &[u8], is_latin1: bool, @@ -311,11 +268,6 @@ impl String { callback(ctx, bytes.as_ptr().cast_mut().cast::(), bytes.len()); return Self::DEAD; } - // PORT NOTE: Zig asserted `@typeInfo(Ctx) == .pointer` (raw pointer, no - // destructor). The Rust const-assert only checks size, so an owning - // pointer-sized `Ctx` (e.g. `Box`) would otherwise be dropped here - // and later double-freed by the WTF finalizer. Ownership transfers to - // the external string; suppress the local drop. let ctx = core::mem::ManuallyDrop::new(ctx); // SAFETY: Ctx is pointer-sized and pointer-aligned (const-asserted // above); read the bits as `*mut c_void`. @@ -408,10 +360,6 @@ impl String { (s, buf) } - /// `bun.String.createExternalGloballyAllocated(.latin1, bytes)` — takes - /// ownership of a globally-allocated (mimalloc-backed) Latin-1 buffer and - /// wraps it in a WTF::ExternalStringImpl. On allocation failure, frees the - /// bytes and returns `String::DEAD`. pub fn create_external_globally_allocated_latin1(bytes: Vec) -> Self { if bytes.is_empty() { return Self::EMPTY; @@ -419,11 +367,6 @@ impl String { if bytes.len() >= Self::max_length() { return Self::DEAD; } - // Do NOT call `into_boxed_slice()` — when `len < capacity` it issues a - // shrink_to_fit realloc. mimalloc's `mi_free` only needs the original - // base pointer (capacity is recovered from the heap), so leaking the - // spare capacity to the ExternalStringImpl finalizer is correct and - // matches Zig's `allocator.alloc(u8, n)` → `to[0..wrote]` pattern. let mut bytes = core::mem::ManuallyDrop::new(bytes); let (ptr, len) = (bytes.as_mut_ptr(), bytes.len()); // SAFETY: ownership transferred to WTF::ExternalStringImpl, which frees @@ -512,12 +455,6 @@ impl String { true } } - /// Debug-only guard for the `Send`/`Sync` contract: panics if this - /// `String` wraps a non-thread-safe `WTF::StringImpl`. Intended for the - /// hand-off point where a `String` is stored into a value that will cross - /// threads (worker task payloads, channel sends, `Arc`-shared state) — - /// the Rust spelling of Zig's `bun.assert(str.isThreadSafe())` before a - /// thread-pool dispatch. #[inline(always)] #[track_caller] pub fn debug_assert_thread_safe(&self) { @@ -617,11 +554,6 @@ impl String { } } - /// Narrow this string into `dst` iff it is non-empty, fits, and every code - /// unit is ASCII (`< 0x80`). UTF-16 narrows via - /// [`strings::narrow_ascii_u16`]; 8-bit copies after rejecting any high - /// Latin-1 byte. Returns `Some(&mut dst[..len])` on success. Zig open-codes - /// this per call site (e.g. `inMapCaseInsensitive`, `Encoding.from`). pub fn ascii_into<'a>(&self, dst: &'a mut [u8]) -> Option<&'a mut [u8]> { let len = self.length(); if len == 0 || len > dst.len() { @@ -640,10 +572,6 @@ impl String { } } - /// `bun.String.inMapCaseInsensitive` (string.zig) — case-insensitive ASCII - /// lookup against a phf map whose keys are lowercase ASCII. UTF-16 inputs - /// are narrowed (non-ASCII code unit ⇒ miss); 8-bit inputs delegate - /// straight to [`strings::in_map_case_insensitive`]. pub fn in_map_case_insensitive(&self, map: &phf::Map<&'static [u8], V>) -> Option { if self.is_utf16() { let mut buf = [0u8; 256]; @@ -653,10 +581,6 @@ impl String { } } - /// `bun.String.trunc` (string.zig:317) — clamp to `len` code units. The - /// returned `String` borrows the same storage; for `WTFStringImpl` this - /// downgrades to a `ZigString` view (no ref taken), so the original must - /// outlive the result. pub fn trunc(&self, len: usize) -> String { if self.length() <= len { // PORT NOTE: Zig returns `this` by value with no refcount bump; @@ -667,10 +591,6 @@ impl String { String::init(self.to_zig_string().trunc(len)) } - /// `bun.String.substring` (string.zig:669) — borrowed slice from `start_index` - /// to end. The returned `String` borrows the same underlying storage; for - /// `WTFStringImpl` this downgrades to a `ZigString` view (no ref taken), so - /// the original must outlive the result. pub fn substring(&self, start_index: usize) -> String { let len = self.length(); self.substring_with_len(start_index.min(len), len) @@ -696,10 +616,6 @@ impl String { } } - /// `String.toUTF8` — borrowed-or-owned UTF-8 byte slice. - /// - `WTFStringImpl`: refs the impl (Latin-1, all-ASCII) or transcodes (Latin-1/UTF-16 → owned). - /// - `ZigString`: borrows (UTF-8) or transcodes (UTF-16/non-ASCII Latin-1). - /// - `StaticZigString`: borrows always. #[inline] pub fn to_utf8(&self) -> ZigStringSlice { match self.0.tag { @@ -717,18 +633,6 @@ impl String { _ => ZigStringSlice::EMPTY, } } - /// Like [`to_utf8`] but, for the 8-bit all-ASCII `WTFStringImpl` fast path, - /// returns a non-owning [`ZigStringSlice::WtfBorrowed`] view instead of - /// [`to_utf8`]'s ref-holding [`ZigStringSlice::WTF`] — skipping the - /// `WTF::StringImpl::ref` (and the matching `deref` on drop) entirely. - /// - /// The returned slice borrows the impl's buffer, so it is only safe to pair - /// with a value whose `underlying` keeps the impl alive (e.g. the - /// [`SliceWithUnderlyingString`] returned by [`to_slice`]). All other tags - /// behave exactly like [`to_utf8`] / [`to_utf8_without_ref`]. - /// - /// [`to_utf8`]: Self::to_utf8 - /// [`to_slice`]: Self::to_slice #[inline] pub fn to_utf8_borrowed(&self) -> ZigStringSlice { match self.0.tag { @@ -771,29 +675,17 @@ impl String { } pub fn eql_utf8(&self, other: &[u8]) -> bool { - // PORT NOTE: no `as_utf8()` fast-path here — for a 16-bit ZigString, - // `as_utf8()` would call `slice()` (which debug-asserts !is_16bit) and - // `is_all_ascii` on the wrong byte view. Match Zig's `eqlUTF8` and go - // straight through encoding-aware `to_utf8_without_ref`. self.to_utf8_without_ref().slice() == other } pub fn eql_comptime>(&self, lit: &S) -> bool { self.eql_utf8(lit.as_ref()) } - /// Port of `bun.String.githubAction` (string.zig). Returns a `Display` - /// formatter that escapes the string for GitHub Actions annotation output - /// (`%0A` for newlines, ANSI stripped). Encoding-aware: materialises a - /// UTF-8 view inside `fmt` so 16-bit / WTF-backed strings are handled. #[inline] pub fn github_action(&self) -> StringGithubActionFormatter<'_> { StringGithubActionFormatter { text: self } } - /// Port of `bun.String.hasPrefixComptime` (string.zig). ASCII-only prefix - /// check that avoids materialising the whole UTF-8 view when the - /// underlying encoding is 8-bit; falls back to `to_utf8_without_ref` for - /// 16-bit / WTF-backed strings. pub fn has_prefix_comptime(&self, prefix: &'static [u8]) -> bool { if let Some(bytes) = self.as_utf8() { return strings::has_prefix_comptime(bytes, prefix); @@ -891,12 +783,6 @@ impl String { self.to_zig_string().to_owned_slice_z() } - // `bun.String.encodeInto` / `bun.String.encode` — moved UP to - // `bun_runtime::webcore::encoding::BunStringEncode` (extension trait). - // The encoder bodies (`jsc.WebCore.encoding.{encodeIntoFrom8,16, - // constructFromU8,U16}`) live in `bun_runtime`; defining the methods here - // would invert the crate graph. See PORTING.md §Dep-cycle. - /// `bun.String.visibleWidthExcludeANSIColors` — terminal column width of /// `self`, treating ANSI escape sequences as zero-width (string.zig). /// Dispatches on encoding to [`strings::visible::width::exclude_ansi_colors`]. @@ -1027,13 +913,6 @@ impl String { /// [`SliceWithUnderlyingString`], leaving `self` as [`EMPTY`]. #[inline] pub fn to_slice(&mut self) -> SliceWithUnderlyingString { - // Move our ref into `underlying` first, then derive the UTF-8 view as a - // *borrow* of that already-pinned impl: the ref-holding `to_utf8()` / - // `ZigStringSlice::WTF` variant would be a redundant second handle on - // the same `StringImpl` (one extra atomic `ref` here + one extra - // `deref` on `deinit`). `WtfBorrowed` keeps the impl pointer so - // `SliceWithUnderlyingString::to_thread_safe` can still re-derive after - // a thread-safe migration. let underlying = core::mem::replace(self, Self::EMPTY); let utf8 = underlying.to_utf8_borrowed(); SliceWithUnderlyingString { @@ -1060,11 +939,6 @@ impl String { did_report_extra_memory_debug: false, }; } - // Thread-safe impl. If `slice` is borrowed (all-ASCII Latin-1), - // re-use the impl's storage: take a single ref for `underlying` and - // hand back a non-owning `WtfBorrowed` view pinned by it. (Zig took - // two refs plus a WTF allocator; the second ref was redundant since - // `underlying` already keeps the impl alive.) if let ZigStringSlice::Static(ptr, len) = slice { self.ref_(); let string_impl = self.wtf_ptr(); @@ -1144,11 +1018,6 @@ impl String { Tag::WTFStringImpl => self.as_wtf().byte_length(), } } - - // `to_js` / `transfer_to_js` / `create_utf8_for_js` are tier-6 (jsc) — the - // *_jsc alias pattern: deleted here per PORTING.md, defined as inherent - // free fns / extension trait in `bun_jsc::string` (would otherwise create - // a `bun_string ↔ bun_jsc` dependency cycle). } // `bun.String.init(anytype)` dispatch table (string.zig:331) — Rust side is // expressed as `From` impls feeding `String::init>`. The @@ -1251,19 +1120,6 @@ unsafe impl Send for String {} // `is_thread_safe()`; non-WTF tags are inert and trivially `Sync`. unsafe impl Sync for String {} -// ────────────────────────────────────────────────────────────────────────── -// `OwnedString` — RAII `defer s.deref()`. -// -// `String` is intentionally `#[derive(Copy)]` so it stays bit-identical to the -// C++ `BunString` for FFI by-value passing (matching Zig's value-type -// `bun.String`). That precludes `impl Drop for String`. Instead, sites that -// receive a +1 ref (any `clone*`/`create*`/`to_bun_string` constructor) wrap -// it in `OwnedString` to get scope-exit `deref()` — the Rust spelling of Zig's -// pervasive `defer s.deref()`. -// -// Prefer this over ad-hoc `scopeguard::guard(s, |s| s.deref())` so the -// pattern is greppable and `?`-early-returns can't skip the release. -// ────────────────────────────────────────────────────────────────────────── #[repr(transparent)] pub struct OwnedString(String); @@ -1284,12 +1140,6 @@ impl OwnedString { pub fn get(&self) -> String { self.0 } - /// View `&[OwnedString]` as `&[String]` for FFI sites that take a raw - /// `*const BunString` array (e.g. `BunString__createArray`). Sound because - /// `OwnedString` is `#[repr(transparent)]` over `String`; the borrow keeps - /// every element alive for the call, and `Drop` still runs on the owning - /// slice afterwards — the Rust spelling of Zig's - /// `defer { for (items) |s| s.deref(); }` around `toJSArray`. #[inline] pub fn as_raw_slice(owned: &[OwnedString]) -> &[String] { // `#[repr(transparent)]` over `String` ⇒ bytemuck's safe slice peel. @@ -1330,10 +1180,6 @@ impl Default for OwnedString { } } impl Clone for OwnedString { - /// Bumps the WTF refcount (or copies a `ZigString` into a fresh - /// WTF::StringImpl) and wraps the resulting +1 in a new `OwnedString`. - /// Mirrors Zig's `s.clone()` followed by an implicit `defer deref` on the - /// new value. #[inline] fn clone(&self) -> Self { Self(self.0.clone()) @@ -1394,14 +1240,6 @@ impl core::fmt::Display for ZigString { } } -// ────────────────────────────────────────────────────────────────────────── -// `ZigString` — `#[repr(transparent)]` newtype over the canonical T0 -// `bun_alloc::ZigString` (`{ ptr: *const u8, len: usize }` with flag bits in -// the POINTER's high byte). The pointer-tag accessors / `slice` / -// `utf16_slice_aligned` are reached via `Deref`; this crate adds the -// encoding-aware + allocating methods (`to_slice`, `to_owned_slice`, `eql`, -// `Display`, …) that depend on `bun_core::strings`. -// ────────────────────────────────────────────────────────────────────────── #[repr(transparent)] #[derive(Clone, Copy)] pub struct ZigString(pub bun_alloc::ZigString); @@ -1458,16 +1296,8 @@ impl ZigString { pub fn from_utf8(s: &[u8]) -> Self { Self::init_utf8(s) } - /// `ZigString.dupeForJS` — duplicates `utf8` into a globally-allocated - /// buffer suitable for handing to JSC. Widens to UTF-16 if `utf8` contains - /// any non-ASCII byte; otherwise leaves as 8-bit. Marks the result global - /// so JSC frees it via mimalloc. pub fn dupe_for_js(utf8: &[u8]) -> Result { if let Some(utf16) = strings::to_utf16_alloc(utf8, false, false)? { - // Ownership transferred to JSC: `mark_global()` tags the buffer so - // `Zig::toString*` adopts it into a WTF string and `mi_free`s it on - // string death. `heap::release` is the hand-off-to-foreign-owner - // spelling (Zig `ZigString.dupeForJS` never frees `utf16` locally). let leaked: &'static mut [u16] = crate::heap::release(utf16.into_boxed_slice()); let mut out = ZigString::init_utf16(leaked); out.mark_global(); @@ -1530,10 +1360,6 @@ impl ZigString { } } - /// `ZigString.static` — wraps a `'static` ASCII literal. Zig returned a - /// `*const ZigString` to a comptime-interned holder; Rust callers consume - /// the value directly (ZigString is `Copy`), so we return by value. - /// Generic over `str`/`[u8]` so either `"lit"` or `b"lit"` is accepted. #[inline] pub fn static_>(slice: &'static S) -> Self { Self(bun_alloc::ZigString::init(slice.as_ref())) @@ -1616,10 +1442,6 @@ impl ZigString { crate::ZBox::from_vec_with_nul(list) } - /// `ZigString.indexOfAny` (ZigString.zig:89) — first index whose code unit - /// matches any byte in `chars`. The 16-bit branch narrows each unit to the - /// Latin-1 range before comparing (mirrors Zig's comptime widening of the - /// `[]const u8` needle to `u16` inside `strings.indexOfAny16`). pub fn index_of_any(self, chars: &'static [u8]) -> Option { if self.is_16bit() { self.utf16_slice() @@ -1642,12 +1464,6 @@ impl ZigString { } } - /// `ZigString.eqlComptime` — encoding-aware equality against a `'static` - /// ASCII literal (ZigString.zig:272). UTF-16 inputs go through the - /// per-unit `eql_comptime_utf16` path; 8-bit inputs compare bytes - /// directly. The Zig version `@compileError`s on non-ASCII `other`; in - /// Rust we cannot enforce that at compile time, so it falls through to - /// the byte compare (caller is expected to pass ASCII). pub fn eql_comptime>(self, other: &S) -> bool { let other = other.as_ref(); if self.is_16bit() { @@ -1720,10 +1536,6 @@ impl ZigString { self.len * 2 } - /// `ZigString.detectEncoding` — if the (currently-untagged) bytes contain - /// any non-ASCII, mark the pointer as UTF-16. Mirrors ZigString.zig's - /// `detectEncoding` (which assumes the bytes were sourced from a - /// JS-produced 8-bit string and need re-widening on non-ASCII). #[inline] pub fn detect_encoding(&mut self) { if !strings::is_all_ascii(self.slice()) { @@ -1904,11 +1716,6 @@ impl ZigString { pub fn trunc(self, len: usize) -> ZigString { Self::from_tagged_ptr(self.tagged_ptr(), self.len.min(len)) } - /// `ZigString.toSlice` — borrowed-or-owned UTF-8. - /// - /// `#[inline]` so the 32-byte `ZigStringSlice` enum return is constructed - /// directly in the caller's slot (NRVO-ish) instead of being assembled in a - /// local and AVX-memcpy'd out — measurable in `path.join` per-arg loops. #[inline] pub fn to_slice(&self) -> ZigStringSlice { if self.len == 0 { @@ -1928,13 +1735,6 @@ impl ZigString { ZigStringSlice::Static(Self::untagged(self.tagged_ptr()), self.len) } - /// `ZigString.toOwnedSlice` — allocate a fresh UTF-8 `Vec` regardless - /// of the source encoding (ZigString.zig:239). UTF-16 → transcode; UTF-8 → - /// copy; Latin-1 → transcode (or copy if all-ASCII). - /// - /// The returned buffer is NUL-terminated one byte past `len()` (the - /// terminator is *not* included in `len()`), matching ZigString.zig:243-245 - /// so `sliceZBuf` / C-string consumers can read `as_ptr()` directly. pub fn to_owned_slice(&self) -> Vec { // Write a NUL sentinel at `v[len]` without bumping `len` (mirrors // ZigString.zig:243-245 / `dupeZ`). @@ -1962,12 +1762,6 @@ impl ZigString { with_sentinel(crate::strings::to_utf8_from_latin1(bytes).unwrap_or_else(|| bytes.to_vec())) } - /// `ZigString.toSliceClone` — the returned slice is *always* heap-owned - /// (ZigString.zig:693). Unlike `to_slice`, this never borrows the source - /// bytes, so the result outlives a GC'd `JSString` that produced `self`. - /// - /// PORT NOTE: Zig returned `OOM!Slice`; with mimalloc as the global - /// allocator OOM aborts the process, so this is infallible. pub fn to_slice_clone(&self) -> ZigStringSlice { if self.len == 0 { return ZigStringSlice::EMPTY; @@ -1975,15 +1769,6 @@ impl ZigString { ZigStringSlice::Owned(self.to_owned_slice()) } - /// `ZigString.toSliceZ` — heap-owned UTF-8 with a NUL sentinel one past - /// the end (`slice().as_ptr()` is a valid C string of length `slice().len()`). - /// `slice()` itself does *not* include the terminator. - /// - /// PORT NOTE: the Zig method this targets was never instantiated (lazy - /// compilation); JSString/JSValue callers reached for it but no `.zig` - /// caller forced codegen. Semantics here match `toOwnedSliceZ` wrapped in - /// a `Slice` so `JSValue::to_slice_z` / `JSString::to_slice_z` get the - /// `[:0]` guarantee they document. pub fn to_slice_z(&self) -> ZigStringSlice { if self.len == 0 { // Static "" already points at a NUL byte. @@ -2006,27 +1791,11 @@ pub enum ZigStringSlice { Static(*const u8, usize), /// Heap-owned; Drop frees via global mimalloc. Owned(Vec), - /// Backed by a WTFStringImpl ref; Drop derefs it. Stored as raw ptr to - /// avoid wtf-module cycle; `wtf::to_latin1_slice` constructs this. - /// `*const` because we only ever hand it back to `Bun__WTFStringImpl__deref` - /// (which takes `*const`); refcount mutation happens on the C++ side. WTF { string_impl: *const wtf::WTFStringImplStruct, ptr: *const u8, len: usize, }, - /// Borrowed view of a `WTFStringImpl`'s all-ASCII Latin-1 buffer that does - /// **not** hold its own refcount: the enclosing - /// [`SliceWithUnderlyingString::underlying`] (or some equivalent owner) - /// pins the impl for as long as the view is used. `string_impl` is recorded - /// only so [`SliceWithUnderlyingString::to_thread_safe`] can tell the view - /// must be re-derived after a thread-safe migration, and so [`clone_ref`] - /// can promote it to an owning [`Self::WTF`]. Drop is a no-op. Produced by - /// [`String::to_utf8_borrowed`] — it avoids the redundant `ref`/`deref` - /// pair that the ref-holding `WTF` variant would cost on the - /// `String::to_slice` hot path. - /// - /// [`clone_ref`]: ZigStringSlice::clone_ref WtfBorrowed { string_impl: *const wtf::WTFStringImplStruct, ptr: *const u8, @@ -2050,10 +1819,6 @@ impl ZigStringSlice { pub fn init_dupe(input: &[u8]) -> Result { Ok(Self::Owned(input.to_vec())) } - /// `ZigString.Slice.cloneIfBorrowed` — if this slice borrows external - /// storage (`Static`/`WTF`), allocate an owned copy; otherwise return - /// `self` unchanged. The result is always safe to outlive the original - /// backing. pub fn clone_if_borrowed(self) -> Self { match &self { Self::Owned(_) => self, @@ -2080,12 +1845,6 @@ impl ZigStringSlice { /// Consume into an owned `Vec` — moves out the buffer if `Owned`, /// allocates a copy otherwise. WTF-backed slices deref the impl. pub fn into_vec(mut self) -> Vec { - // For `Owned`, move the buffer out (leaving an empty Vec to drop - // harmlessly). For `Static`/`WTF`/`WtfBorrowed`, allocate a copy of the - // borrowed bytes; the subsequent `Drop` of `self` releases the `WTF` - // ref (paired with the ref taken in `to_latin1_slice`) — `WtfBorrowed` - // / `Static` drop as no-ops. Equivalent to the prior `ManuallyDrop` + - // per-variant raw-read dance without any unsafe. if let Self::Owned(v) = &mut self { return core::mem::take(v); } @@ -2101,10 +1860,6 @@ impl Drop for ZigStringSlice { } } -/// `bun.SliceWithUnderlyingString` (string.zig:1035) — a UTF-8 byte view paired -/// with the `bun.String` that may back it. `utf8` is either a borrowed/owned -/// byte slice or empty; `underlying` is the `String` whose ref keeps `utf8` -/// alive when WTF-backed. pub struct SliceWithUnderlyingString { pub utf8: ZigStringSlice, pub underlying: String, @@ -2161,10 +1916,6 @@ impl SliceWithUnderlyingString { self.utf8.slice() } - /// `deinit` — release `utf8`'s allocation (if any) and deref `underlying`. - /// Explicit for parity with Zig call sites; `Drop` is intentionally not - /// implemented because `underlying: String` is `Copy` (matches Zig manual - /// `defer .deinit()` pattern). pub fn deinit(self) { // `utf8` drops via ZigStringSlice::Drop. self.underlying.deref(); @@ -2180,11 +1931,6 @@ impl SliceWithUnderlyingString { let new = self.underlying.wtf_ptr(); if new != orig { if self.utf8.is_wtf_allocated() { - // Drop the stale view first: a `WtfBorrowed` into the - // just-released `orig` drops as a no-op, and a ref-holding - // `WTF` keeps `orig` alive until this line runs — so this is - // safe either way. Re-derive against the new (live) impl as - // a borrow pinned by `underlying`; no extra ref needed. self.utf8 = ZigStringSlice::EMPTY; self.utf8 = self.underlying.to_utf8_borrowed(); } @@ -2213,34 +1959,16 @@ impl ZigStringSlice { } } - /// True iff this slice owns a heap allocation that would be freed on - /// `Drop`. Replaces the Zig `slice.allocator.get().is_some()` idiom: in - /// Rust the allocator is implicit in the variant. `WtfBorrowed` is *not* - /// allocated — it borrows storage pinned by someone else. #[inline] pub fn is_allocated(&self) -> bool { matches!(self, Self::Owned(_) | Self::WTF { .. }) } - /// True iff this slice is a view into a `WTF::StringImpl` (the Zig - /// `String.isWTFAllocator(slice.allocator)` check) — whether it holds its - /// own ref (`WTF`) or is pinned by an `underlying` (`WtfBorrowed`). Used by - /// [`SliceWithUnderlyingString::to_thread_safe`] to decide whether the view - /// must be re-derived after a thread-safe migration. #[inline] pub fn is_wtf_allocated(&self) -> bool { matches!(self, Self::WTF { .. } | Self::WtfBorrowed { .. }) } - /// `ZigString.Slice.cloneRef` — produce an independently-droppable copy - /// of this slice that views the *same bytes*: `Static` is bitwise-copied, - /// `WTF`/`WtfBorrowed` bump the StringImpl refcount (the clone has no - /// `underlying` to pin it, so it must become an owning `WTF`), `Owned` - /// deep-copies the buffer. - /// - /// Used by `PathLike::clone()` so a cloned path returns identical bytes - /// from `slice()` (unlike `SliceWithUnderlyingString::dupe_ref`, which - /// drops the utf8 view). pub fn clone_ref(&self) -> Self { match self { Self::Static(p, l) => Self::Static(*p, *l), @@ -2301,10 +2029,6 @@ pub mod zig_string { } } -/// `bun.schema.api.StringPointer` — canonical definition lives in `bun_core` -/// (lowest tier); re-exported here so existing `bun_core::StringPointer` -/// callers (FFI sigs in `bun_jsc::FetchHeaders`, lockfile, sourcemap) keep -/// resolving. pub use crate::StringPointer; pub use hashed_string::HashedString; @@ -2342,10 +2066,6 @@ pub use encoding::Encoding as NodeEncoding; // `strings` is the canonical Zig namespace name; alias to the real module. pub use immutable as strings; -// ────────────────────────────────────────────────────────────────────────── -// `lexer` — identifier predicates. Thin `u32`-taking wrapper over the -// [`identifier`] two-stage Unicode tables (moved down from `bun_js_parser`). -// ────────────────────────────────────────────────────────────────────────── pub mod lexer { #[inline] pub fn is_identifier_start(c: u32) -> bool { @@ -2363,11 +2083,6 @@ pub mod lexer { } pub mod lexer_tables { - /// The 9 strict-mode reserved words (ES2015 §11.6.2.2). Single source of - /// truth — [`strict_mode_reserved_word_remap`] and - /// [`is_strict_mode_reserved_word`] are derived from the same key set. - /// Plain array (not `phf::Set`): callers only need `.len()` / `.iter()`, - /// and membership goes through the length-gated `match` below. pub const STRICT_MODE_RESERVED_WORDS: [&[u8]; 9] = [ b"implements", b"interface", @@ -2380,12 +2095,6 @@ pub mod lexer_tables { b"yield", ]; - /// Hot-path strict-mode reserved-word check. Length-bucketed fixed-array - /// compare to avoid the SipHash inside `phf::Set::contains`. All entries - /// are 3..=10 ASCII bytes with ≤2 per length bucket — a `match` on - /// `len()` then exact bytes rejects the overwhelming miss case on a single - /// `usize` compare. See clap::find_param (12577e958d71) for the reference - /// length-gated pattern. #[inline] pub fn is_strict_mode_reserved_word(s: &[u8]) -> bool { match s.len() { @@ -2399,11 +2108,6 @@ pub mod lexer_tables { } } - /// Same key set as [`is_strict_mode_reserved_word`], mapped to an - /// underscore-prefixed replacement. Used by - /// `MutableString::ensure_valid_identifier` to mangle a name that is - /// already a syntactically valid identifier but would collide with a - /// strict-mode reserved word. #[inline] pub fn strict_mode_reserved_word_remap(s: &[u8]) -> Option<&'static [u8]> { match s.len() { @@ -2430,23 +2134,9 @@ pub mod lexer_tables { } } -/// `jsc::VirtualMachine::string_allocation_limit` (VirtualMachine.zig:14) — -/// process-wide WTF::StringImpl character-count cap, exported for C++ as -/// `Bun__stringSyntheticAllocationLimit`. The value lives here (not `bun_jsc`) -/// because [`String::max_length`] / `create_external*` need it without an -/// upward dep; `bun_jsc::VirtualMachine` writes it during init / via the -/// `setSyntheticAllocationLimitForTesting` hook. #[unsafe(export_name = "Bun__stringSyntheticAllocationLimit")] pub static STRING_ALLOCATION_LIMIT: AtomicUsize = AtomicUsize::new(u32::MAX as usize); -// ────────────────────────────────────────────────────────────────────────── -// move-in: printer (MOVE_DOWN ← src/js_printer/js_printer.zig) -// -// Self-contained string-quoting helpers used by `strings::format_escapes`, -// `bun_sourcemap::Chunk` (JSON serialization), and `bun_ast::Expr`. -// Breaking the `bun_js_printer → bun_sourcemap` cycle by hosting the -// pure-string `quoteForJSON` here. -// ────────────────────────────────────────────────────────────────────────── pub mod printer { use crate::string::immutable::{self as strings, Encoding as StrEncoding}; use crate::string::mutable_string::MutableString; @@ -2667,19 +2357,8 @@ pub use printer::quote_for_json; // Top-level free helpers (move-ins from misc Zig namespaces). // ────────────────────────────────────────────────────────────────────────── -/// `bun.sliceTo(buf, 0)` — slice up to (not including) the first NUL byte, -/// or the whole buffer if none. Port of `std.mem.sliceTo` for `u8`/`0`. -/// Sunk to `crate::ffi` so tier-1 crates (cares_sys, sys) can share it; -/// re-exported here for the existing `bun_core::slice_to_nul` callers. pub use crate::ffi::{slice_to_nul, slice_to_nul_mut}; -/// move-in: `cheap_prefix_normalizer` (MOVE_DOWN ← `bundle_v2.zig`). -/// -/// Pure path-string helper used by the bundler chunk writer and `css::printer`. -/// Returns `[prefix', suffix']` such that concatenating them produces a -/// reasonably-normalized path (collapses `./` leading and avoids `//`). -/// Matches the .zig spec `[2]string` return shape so bundler call-sites can -/// index it directly. pub fn cheap_prefix_normalizer<'a>(prefix: &'a [u8], suffix: &'a [u8]) -> [&'a [u8]; 2] { if prefix.is_empty() { let suffix_no_slash = strings::remove_leading_dot_slash(suffix); @@ -2712,17 +2391,6 @@ pub fn cheap_prefix_normalizer<'a>(prefix: &'a [u8], suffix: &'a [u8]) -> [&'a [ // Re-export `wtf::parse_double` at crate root (callers spell it `bun_core::parse_double`). pub use wtf::parse_double; -/// [`Cell`]-shaped interior-mutable owned `BunString` slot. Layout-identical -/// to `Cell` (`#[repr(transparent)]`) so it's a drop-in field -/// replacement in `#[repr(C)]` FFI structs (`Blob.name`, `Request.url`). -/// -/// Unlike `Cell`, [`set`] derefs the previous value and [`replace`] -/// returns an [`OwnedString`] — so the only way to leak a refcount is to -/// `mem::forget` the cell or its `replace` result. The R-2 `&self` migrations -/// introduced `Cell::set(..)` calls that silently leaked the old +1. -/// -/// [`get`] returns a bitwise `String` copy with **borrow** semantics (no ref -/// bump). Do NOT `.deref()` the returned value. #[repr(transparent)] #[derive(Default)] pub struct OwnedStringCell(core::cell::Cell); diff --git a/src/bun_core/string/wtf.rs b/src/bun_core/string/wtf.rs index af41a549646..b693e2ddf29 100644 --- a/src/bun_core/string/wtf.rs +++ b/src/bun_core/string/wtf.rs @@ -2,16 +2,8 @@ use crate::string::strings; // TODO(port): ZigString.Slice is a nested type in Zig; in Rust it lives alongside ZigString. use crate::string::ZigStringSlice; -// Canonical layout lives in `bun_alloc` (lowest-tier crate) so the -// `is_wtf_allocator` vtable-identity check is a local pointer compare with no -// upward dependency. Re-exported here for back-compat with existing -// `bun_core::wtf::*` / `bun_core::WTFStringImpl*` import paths. pub use bun_alloc::{WTFStringImpl, WTFStringImplPtr, WTFStringImplStruct}; -/// Behaves like `WTF::Ref`. The -/// [`crate::external_shared::ExternalSharedDescriptor`] impl lives alongside -/// the trait in `bun_core::external_shared` (orphan rule: trait owner gets -/// the impl since the type is foreign — defined in `bun_alloc`). pub use crate::external_shared::WTFString; /// `WTF::RefPtr` — a nullable owning reference into an externally-refcounted @@ -23,12 +15,6 @@ pub type RefPtr = crate::external_shared::ExternalShared; /// spell `wtf::StringImpl` (used by `wtf::RefPtr`). pub type StringImpl = WTFStringImplStruct; -/// Extension methods on [`WTFStringImplStruct`] that depend on -/// `bun_string` types ([`ZigStringSlice`], `crate::ZBox`) or -/// `crate::string::strings::*` transcoding. Kept as a trait because the struct is -/// defined in `bun_alloc` and an inherent `impl` here would violate the orphan -/// rule. Glob-imported via `bun_core::WTFStringImplExt` so method-call syntax -/// keeps working at every existing callsite. pub trait WTFStringImplExt { fn to_latin1_slice(&self) -> ZigStringSlice; fn to_utf8(&self) -> ZigStringSlice; @@ -85,14 +71,6 @@ impl WTFStringImplExt for WTFStringImplStruct { ZigStringSlice::init_owned(strings::to_utf8_alloc(self.utf16_slice())) } - /// Like [`to_utf8`] but the 8-bit all-ASCII fast path returns a non-owning - /// [`ZigStringSlice::WtfBorrowed`] view (no `r#ref`/`deref` pair) instead of - /// the ref-holding [`ZigStringSlice::WTF`]. The caller MUST keep this impl - /// alive for the lifetime of the returned slice — `bun.String::to_slice` - /// does so via `SliceWithUnderlyingString.underlying`. `WtfBorrowed` still - /// records `self` so a later thread-safe migration can re-derive the view. - /// - /// [`to_utf8`]: WTFStringImplExt::to_utf8 #[inline] fn to_utf8_borrowed(&self) -> ZigStringSlice { if self.is_8bit() { @@ -149,10 +127,6 @@ impl WTFStringImplExt for WTFStringImplStruct { if self.is_8bit() { let input = self.latin1_slice(); if !input.is_empty() { - // Port: latin1→utf8 length is just elementLengthLatin1IntoUTF8 - // (each high byte becomes 2 utf8 bytes). The Zig went through - // jsc.WebCore.encoding.byteLengthU8 but for Utf8 target that - // reduces to the same arithmetic. strings::element_length_latin1_into_utf8(input) } else { 0 @@ -177,11 +151,6 @@ impl WTFStringImplExt for WTFStringImplStruct { } } -// PORT NOTE: Zig's `StringImplAllocator` was a `std.mem.Allocator` vtable trick -// (alloc() bumped ref, free() dropped it) so a `ZigString.Slice` would deref the -// WTFStringImpl when freed. Replaced by `ZigStringSlice::WTF { .. }` explicit -// ownership variant — see `to_latin1_slice` above. No allocator trait needed. - // `WTF.parseDouble` canonical now lives in bun_core::fmt (tier-0) so // `bun_interchange` (yaml/toml) and `bun_js_parser::lexer` can call it without // any string/jsc dep. Re-exported here to keep the Zig namespace shape. diff --git a/src/bun_core/thread_id.rs b/src/bun_core/thread_id.rs index 9a1195f6059..8449510632c 100644 --- a/src/bun_core/thread_id.rs +++ b/src/bun_core/thread_id.rs @@ -15,11 +15,6 @@ //! needs a plain integer it can store in an atomic and compare against a //! sentinel — exactly Zig's semantics. -// ── ThreadId width (mirrors Zig `std.Thread.Id` switch) ─────────────────── -// linux / *bsd / haiku / wasi / serenity → u32 -// macOS / iOS / watchOS / tvOS / visionOS → u64 -// Windows → DWORD (u32) -// else → usize #[cfg(any( target_os = "linux", target_os = "android", @@ -107,33 +102,9 @@ pub type AtomicThreadId = core::sync::atomic::AtomicUsize; // Zig: `pub const invalid = std.math.maxInt(std.Thread.Id);` pub const INVALID: ThreadId = ThreadId::MAX; -/// Per-thread cache of [`current()`]. Zig's `LinuxThreadImpl.getCurrentId()` -/// reads a `threadlocal var tls_thread_id` set once at thread start -/// (vendor/zig/lib/std/Thread.zig:841,885); the Rust port called the syscall -/// (`gettid`/`pthread_threadid_np`/`GetCurrentThreadId`) on every call. The -/// bundler's `Worker::get(ctx)` calls `current()` once per scheduled task — -/// parse, line-offset table, quoted source contents, compile-result -/// generation, link step 5 — so a 19 K-module build paid ~109 K `gettid` -/// syscalls (~36 % of total syscall time on the rolldown `apps/10000` bench). -/// -/// `0` is the unset sentinel: kernel TIDs / `pthread_threadid_np` IDs / -/// Win32 thread IDs are all nonzero. A bare `#[thread_local]` slot (not the -/// `thread_local!` macro) so this is a single TLS load with no `LocalKey` -/// initialization-state branch or destructor registration — same as Zig's -/// `threadlocal var`. #[thread_local] static TLS_THREAD_ID: core::cell::Cell = core::cell::Cell::new(0); -/// Returns the platform's notion of the calling thread's ID. -/// -/// Port of Zig `std.Thread.getCurrentId()` (`PosixThreadImpl` / `WindowsThreadImpl` / -/// `LinuxThreadImpl`). Attempts to use OS-specific primitives so the value matches what -/// debuggers/tracers report; falls back to `pthread_self()` as a `usize` on unknown targets. -/// -/// Cached per-thread after the first call (see [`TLS_THREAD_ID`]); subsequent -/// calls are a single TLS read with no syscall, matching Zig's -/// `tls_thread_id` slot. Lazy rather than set-at-spawn so threads not started -/// by Bun's pool (FFI callbacks, the main thread) still get a valid ID. #[inline] pub fn current() -> ThreadId { let cached = TLS_THREAD_ID.get(); diff --git a/src/bun_core/util.rs b/src/bun_core/util.rs index 9a5cb2e640c..0882faed497 100644 --- a/src/bun_core/util.rs +++ b/src/bun_core/util.rs @@ -25,14 +25,6 @@ pub unsafe fn bytes_as_slice_mut(bytes: &mut [u8]) -> &mut [T] { unsafe { core::slice::from_raw_parts_mut(bytes.as_mut_ptr().cast::(), len) } } -// ─── Unaligned ───────────────────────────────────────────────────────────── -/// Port of Zig's `align(1) T` element type. Rust references and slices require -/// natural alignment for `T`; producing a `&[u16]` from an odd address is -/// instant UB even if never dereferenced. `#[repr(packed)]` on this wrapper -/// drops the alignment requirement to 1, so `&[Unaligned]` is the sound -/// translation of `[]align(1) T`. Reads/writes go through `ptr::read_unaligned` -/// / `ptr::write_unaligned` (the compiler emits byte-wise or unaligned-load -/// instructions as appropriate for the target). #[repr(C, packed)] #[derive(Copy, Clone)] pub struct Unaligned(T); @@ -85,18 +77,6 @@ impl Unaligned { } } -// ════════════════════════════════════════════════════════════════════════════ -// Low-tier primitives hoisted into bun_core. -// Forward-referenced as `crate::X` by Global.rs / output.rs / fmt.rs / env.rs. -// Source bodies extracted from the corresponding .zig (ground truth). -// ════════════════════════════════════════════════════════════════════════════ - -// ─── ZStr / WStr / zstr! (from bun_str) ─────────────────────────────────── -// Zig: `[:0]const u8` / `[:0]const u16` — slice with sentinel. Rust models the -// borrowed forms as DSTs over the byte/u16 slice (NUL not counted in len). -// TYPE_ONLY move-down; full impls (from_raw, as_cstr, …) live in bun_str which -// re-exports these via `pub use bun_core::{ZStr, WStr}`. - /// Borrowed `[:0]const u8` — bytes are valid UTF-8-ish, len excludes the NUL. #[repr(transparent)] pub struct ZStr([u8]); @@ -133,11 +113,6 @@ impl ZStr { // SAFETY: caller-supplied literal ends in NUL; lifetime is 'static. unsafe { Self::from_raw(s.as_ptr(), s.len() - 1) } } - /// Borrow `buf[..len]` as a `&ZStr`, where `buf[len] == 0`. This is the - /// safe-surface form of [`from_raw`] for the dominant call shape in the - /// install pipeline: a stack `PathBuffer` filled to `len` with a NUL - /// written at `buf[len]`. The slice bound proves `buf[..=len]` is in the - /// same allocation; the NUL is debug-asserted. #[inline] pub fn from_buf(buf: &[u8], len: usize) -> &ZStr { debug_assert!(len < buf.len(), "ZStr::from_buf: NUL must lie within buf"); @@ -147,12 +122,6 @@ impl ZStr { // precondition, same contract as Zig `[:0]const u8` slicing). unsafe { Self::from_raw(buf.as_ptr(), len) } } - /// Borrow `buf[..buf.len()-1]` as a `&ZStr`, where the last byte of `buf` - /// is the NUL terminator. This is [`from_buf`] specialized for the second - /// most common call shape: a slice that already includes its trailing NUL - /// (e.g. a `Vec` with `0` pushed, or `CStr::to_bytes_with_nul`). - /// Debug-asserts the trailing NUL; release relies on the documented - /// precondition (same contract as Zig `[:0]const u8` slicing). #[inline] pub fn from_slice_with_nul(buf: &[u8]) -> &ZStr { debug_assert!(!buf.is_empty(), "ZStr::from_slice_with_nul: empty slice"); @@ -195,12 +164,6 @@ impl ZStr { // SAFETY: invariant — byte at `len` is NUL and owned by the same allocation. unsafe { core::slice::from_raw_parts(self.0.as_ptr(), self.0.len() + 1) } } - /// View as `&CStr`. Safe-surface bridge for FFI sites that need a - /// `*const c_char` via `CStr` — funnels the ~dozen open-coded - /// `CStr::from_bytes_with_nul_unchecked` call sites through one audited - /// `unsafe`. Debug-asserts no interior NUL (CStr's extra invariant over - /// ZStr); release relies on the construction-site contract (path/host - /// bytes never embed NUL — same assumption Zig `[:0]const u8` → C makes). #[inline] pub fn as_cstr(&self) -> &core::ffi::CStr { debug_assert!( @@ -236,13 +199,6 @@ impl ZStr { // SAFETY: `p[len] == 0` (strlen postcondition) and `p[..len]` readable. unsafe { Self::from_raw(p.cast::(), len) } } - // NOTE: prefer `ZBox` for owned NUL-terminated strings. `Box` is - // supported only as a transitional shim for ported fields that were typed - // `Box` before `ZBox` existed (e.g. `PackageManager.cache_directory_path`). - // The slice metadata of the returned `Box` covers `bytes.len() + 1` - // (i.e. INCLUDES the trailing NUL) so `Drop` deallocates the full - // allocation; `as_bytes()` will therefore include the trailing NUL. - // TODO(port): retire once all `Box` fields are migrated to `ZBox`. pub fn boxed(bytes: &[u8]) -> Box { let mut v = Vec::with_capacity(bytes.len() + 1); v.extend_from_slice(bytes); @@ -344,11 +300,6 @@ impl core::ops::Deref for ZStr { } } -/// `bun.getenvZ` — read an environment variable. Returns the value as borrowed -/// process-static bytes (env block lives for the process). On POSIX wraps -/// `libc::getenv`; on Windows scans `environ` case-insensitively. -/// -/// Port of `bun.zig:getenvZ` / `getenvZAnyCase`. pub fn getenv_z(key: &ZStr) -> Option<&'static [u8]> { #[cfg(not(any(unix, windows)))] { @@ -369,26 +320,13 @@ pub fn getenv_z(key: &ZStr) -> Option<&'static [u8]> { } #[cfg(windows)] { - // Windows env names are case-insensitive (Zig spec: `getenvZ` on - // Windows delegates to `getenvZAnyCase`). Walk the WTF-8 env block - // populated at startup by `bun_sys::windows::env::convert_env_to_wtf8` - // (main.zig:47). The block is `Box::leak`'d for process lifetime so - // `'static` borrows here are sound. getenv_z_any_case(key) } } -/// Read the C `environ` global (`*const *const c_char`, NUL-terminated array of -/// NUL-terminated `KEY=VALUE` entries). Single decl for all POSIX call sites. -/// `#[inline]` and allocation-free so it stays async-signal-safe for the -/// post-fork crash-handler child path. #[cfg(unix)] #[inline] pub fn c_environ() -> *const *const core::ffi::c_char { - // `AtomicPtr` is `#[repr(C)]` over `*mut T`, so this has the same - // layout as libc's `char **environ`; a Relaxed word load is sound under - // concurrent `setenv` and compiles to the same single load as a plain - // `static` read. unsafe extern "C" { // `safe static` (Rust 2024 `unsafe extern`) discharges the link-time // existence proof; `AtomicPtr::load` itself is already safe. @@ -472,12 +410,6 @@ impl WStr { &*(std::ptr::from_ref::<[u16]>(core::slice::from_raw_parts(ptr, len)) as *const WStr) } } - /// Borrow `buf[..len]` as a `&WStr`, where `buf[len] == 0`. Safe-surface - /// form of [`from_raw`] for the dominant call shape: a stack `WPathBuffer` - /// filled to `len` with a NUL written at `buf[len]`. The slice bound proves - /// `buf[..=len]` lies in one allocation; the NUL is debug-asserted (release - /// relies on the documented `buf[len] == 0` precondition — same contract as - /// Zig `[:0]const u16` slicing). Mirrors [`ZStr::from_buf`]. #[inline] pub fn from_buf(buf: &[u16], len: usize) -> &WStr { debug_assert!(len < buf.len(), "WStr::from_buf: NUL must lie within buf"); @@ -566,12 +498,6 @@ impl AsRef<[u16]> for WStr { } } -/// `wstr!("lit")` → `&'static [u16; N+1]` (NUL-terminated). Compile-time -/// ASCII→UTF-16LE widening for Windows path / API literals; mirrors Zig -/// `bun.strings.w("lit")` / `std.unicode.utf8ToUtf16LeStringLiteral`. -/// -/// Restricted to ASCII (`debug_assert` in the const evaluator) — every call -/// site is a hard-coded path component (`"node_modules"`, `".git"`, etc.). #[macro_export] macro_rules! wstr { ($lit:literal) => {{ @@ -601,40 +527,11 @@ macro_rules! zstr { }}; } -/// Nomicon-style opaque FFI handle. Expands to a zero-sized `#[repr(C)]` -/// struct whose address is the only thing Rust ever observes. -/// -/// `_p: UnsafeCell<[u8; 0]>` makes the type `!Freeze`, so a `&T` does **not** -/// carry `readonly`/`noalias` at the ABI boundary — the C side mutates through -/// these handles regardless of whether Rust holds `&` or `&mut`, and a -/// `readonly` attribute would license LLVM to cache loads across the FFI call. -/// `PhantomData<(*mut u8, PhantomPinned)>` makes the type `!Send + !Sync + -/// !Unpin`, matching the conservative defaults for foreign-owned state. -/// -/// Thin re-export of [`bun_opaque::opaque_ffi!`] under the legacy name. The -/// canonical macro lives in the zero-dep `bun_opaque` crate so tier-0 `*_sys` -/// leaves (`mimalloc_sys`, `brotli_sys`, …) can reach it without pulling -/// `bun_core` into their build graph; this alias just keeps existing -/// `bun_core::opaque_extern!(...)` callers compiling. #[macro_export] macro_rules! opaque_extern { ($($t:tt)*) => { ::bun_opaque::opaque_ffi!($($t)*); }; } -// ─── Mutex / RwLock (poison-free std::sync wrappers) ────────────────────── -// -// LAYERING: `bun_core` sits *below* `bun_threading` in the crate graph, so it -// cannot use the futex-backed `Guarded` / `RwLock` defined there. The -// handful of low-tier call sites (this crate, `bun_ptr`, `bun_alloc`) instead -// get thin newtype wrappers around `std::sync` that strip the poisoning API — -// Bun aborts on panic, so a poisoned lock is unreachable in practice and the -// `LockResult` ceremony is pure noise. Higher-tier crates should use -// `bun_threading::Guarded` / `bun_threading::RwLock` directly. -// -// API parity with the previous `parking_lot` aliases: `const fn new(T)`, -// `.lock()` → guard (no `Result`), `.try_lock()` → `Option`, `.get_mut()`, -// `Default`. - /// Poison-free `std::sync::Mutex` wrapper. See module note above for why /// this is not `bun_threading::Guarded`. pub struct Mutex(std::sync::Mutex); @@ -760,33 +657,10 @@ pub type OSPathSliceZ = ZStr; pub use bun_alloc::SEP; -/// Zig: `[MAX_PATH_BYTES]u8` stack buffer (`var buf: bun.PathBuffer = undefined`). -/// -/// Canonical definition; `bun_paths::PathBuffer` re-exports this so the two -/// crates share ONE nominal type and callers can pass a `bun_paths` buffer to -/// `bun_core::getcwd`/`which` without a pointer cast. -/// -/// NOTE on alignment: `os_path_kernel32` (Windows) reinterprets a -/// `&mut PathBuffer` as `&mut [u16]` via [`bytes_as_slice_mut`]. The language -/// only guarantees align=1 for `[u8; N]`, so that reinterpret is guarded by a -/// hard `assert!` (mirroring Zig `@alignCast`). We do *not* bump this struct -/// to `#[repr(align(2))]` because several call sites reinterpret an arbitrary -/// `&mut [u8]` *as* `PathBuffer`, and raising the nominal alignment would -/// make *those* casts unsound instead. In practice every `PathBuffer` fed to -/// the `[u16]` view is a fresh stack local or a pooled heap allocation, both -/// of which are ≥8-byte aligned on every supported target. #[repr(transparent)] pub struct PathBuffer(pub [u8; MAX_PATH_BYTES]); impl PathBuffer { pub const ZEROED: Self = Self([0; MAX_PATH_BYTES]); - /// Zig `= undefined`. The bytes are immediately overwritten by the syscall - /// that fills it, so the initial contents are never observed. - /// - /// On Windows `MAX_PATH_BYTES` is 98 302 (vs 4 096 Linux / 1 024 macOS), so - /// the previous `Self::ZEROED` body here was a ~100 KB `memset` at every - /// one of the ~400 call sites — turning hot loops (glob scan, module load, - /// stack-trace formatting) into multi-GB zero-fill workloads and timing out - /// the leak/stress tests. Match the Zig spec and leave the bytes uninit. #[inline] #[allow(invalid_value, clippy::uninit_assumed_init)] pub fn uninit() -> Self { @@ -831,10 +705,6 @@ impl core::ops::DerefMut for PathBuffer { pub struct WPathBuffer(pub [u16; PATH_MAX_WIDE]); impl WPathBuffer { pub const ZEROED: Self = Self([0; PATH_MAX_WIDE]); - /// Zig `= undefined`. See [`PathBuffer::uninit`] — `PATH_MAX_WIDE` is - /// 32 767 `u16`s (~64 KB), and these are allocated per Windows syscall - /// for UTF-8→UTF-16 path conversion, so zero-initialising dominated the - /// hot path on Windows. #[inline] #[allow(invalid_value, clippy::uninit_assumed_init)] pub fn uninit() -> Self { @@ -906,30 +776,15 @@ pub fn dirname(path: &[u8]) -> Option<&[u8]> { while i > root_end { i -= 1; if is_sep(path[i]) { - // Zig `std.fs.path.dirnamePosix/Windows` returns up to (excluding) - // the separator found — it does NOT collapse a preceding run of - // separators, so `/foo//bar` → `/foo/`. Preserve that contract for - // re-export parity with `bun_paths::dirname`. return Some(&path[..i]); } } - // No separator AFTER root, but content past it (e.g. "/foo", "C:\foo"): - // Zig returns the root prefix iff the root itself ends in a separator - // (`"/foo"` → `"/"`, `"C:\\foo"` → `"C:\\"`). A bare drive prefix with no - // separator (`"C:foo"`, root_end==2) falls through to `None`, matching - // `std.fs.path.dirnameWindows`. Root-only inputs ("/", "C:\") have - // `end == root_end` and also fall through. if root_end > 0 && end > root_end && is_sep(path[root_end - 1]) { return Some(&path[..root_end]); } None } -// ─── Fd + fd module (from bun_sys::fd) ──────────────────────────────────── -// TYPE_ONLY: bun_core needs only the handle wrapper + stdin/out/err/cwd ctors. -// Full method set (close, makeLibUVOwned, …) stays in bun_sys which re-exports -// `pub use bun_core::Fd as FD;` and adds inherent impls there. - // Zig backing_int (fd.zig:1): c_int on posix, u64 on Windows. #[cfg(not(windows))] type FdBacking = i32; @@ -987,10 +842,6 @@ impl Fd { debug_assert!((h as u64) <= FD_VALUE_MASK); Fd((h as u64) & FD_VALUE_MASK) } - /// Native OS file descriptor (`fd_t`). On POSIX this is just the backing - /// `c_int`. On Windows, when `kind == Uv`, calls `uv_get_osfhandle` to - /// obtain the underlying HANDLE — so the returned value may not be safely - /// closed via libc; use `FdExt::close()` instead. #[cfg(not(windows))] #[inline] pub const fn native(self) -> FdNative { @@ -1004,23 +855,10 @@ impl Fd { DecodeWindows::Uv(file_number) => fd::uv_get_osfhandle(file_number), } } - /// Borrow this `Fd` as a [`std::os::fd::BorrowedFd`] for handing to APIs - /// (e.g. `rustix`) that speak the std I/O-safety types. - /// - /// The returned borrow is tied to `&self`'s lifetime. `Fd` is a plain - /// `Copy` integer wrapper, so this does *not* by itself prevent the - /// underlying descriptor from being closed elsewhere — it encodes the - /// same "caller keeps the fd open for the duration of the call" contract - /// every `bun_sys` syscall wrapper already relies on when accepting `Fd` - /// by value. #[cfg(unix)] #[inline] pub fn as_borrowed_fd(&self) -> std::os::fd::BorrowedFd<'_> { let raw = self.native(); - // `BorrowedFd`'s niche is `-1`; constructing one with that value is - // immediate UB regardless of later use. `Fd::INVALID` (i32::MIN) and - // `Fd::cwd()` (AT_FDCWD, -100) are both ≠ -1, so the only way to hit - // this is a caller explicitly wrapping a raw `-1`. assert!(raw != -1, "Fd::as_borrowed_fd on raw fd -1"); // SAFETY: `raw != -1` (asserted above, satisfying `BorrowedFd`'s // niche). The "remains open for the borrow's lifetime" invariant is @@ -1029,12 +867,6 @@ impl Fd { // borrow cannot outlive `&self`. unsafe { std::os::fd::BorrowedFd::borrow_raw(raw) } } - /// libuv c_int file number. On POSIX this equals `native()`. On Windows, - /// when kind=uv this extracts the stored uv_file; when kind=system this - /// maps stdio handles to 0/1/2 (checking both the cached statics and the - /// live `GetStdHandle` result) and **panics** otherwise — converting an - /// arbitrary HANDLE to a uv fd makes closing impossible. The supplier - /// should call `make_lib_uv_owned()` near where `open()` was called. #[cfg(not(windows))] #[inline] pub const fn uv(self) -> i32 { @@ -1045,12 +877,6 @@ impl Fd { match self.decode_windows() { DecodeWindows::Uv(v) => v, DecodeWindows::Windows(handle) => { - // `.stdin()`/`.stdout()`/`.stderr()` hand out the cached - // `WINDOWS_CACHED_STD{IN,OUT,ERR}` (snapshotted at startup), - // so round-trip against those first. Comparing only against - // the live `GetStdHandle` result panics if the process std - // handle was swapped after startup via `SetStdHandle`, - // `AllocConsole`, `AttachConsole`, etc. if Some(self) == fd::WINDOWS_CACHED_STDIN.get().copied() { return 0; } @@ -1188,14 +1014,6 @@ impl Fd { } } - /// Zig `FD.makeLibUVOwned` (`fd.zig`): on Windows, convert a system-kind - /// `Fd` (raw `HANDLE`) into a libuv-kind `Fd` (CRT `_open_osfhandle`-backed - /// `int`) so libuv `uv_fs_*` APIs can consume it. uv-kind passes through. - /// On POSIX this is the identity (libuv fd == posix fd). - /// - /// Returns `Err(())` (= Zig's `error.SystemFdQuotaExceeded`) when - /// `uv_open_osfhandle` returns `-1`; the caller decides whether to close - /// the original handle (see `make_libuv_owned_for_syscall`). #[inline] pub fn make_libuv_owned(self) -> Result { debug_assert!(self.is_valid()); @@ -1475,10 +1293,6 @@ pub unsafe fn fd_path_raw_w(fd: Fd, buf: *mut u16, cap: usize) -> isize { // VOLUME_NAME_DOS (0) — matches `bun_sys::windows::GetFinalPathNameByHandle` default. // SAFETY: buf has `cap` u16 units; handle from Fd::native(). let n = unsafe { GetFinalPathNameByHandleW(fd.native(), buf, cap as u32, 0) } as usize; - // Zig `bun.windows.GetFinalPathNameByHandle`: `if (return_length >= - // out_buffer.len) return error.NameTooLong;` — `>=` because a return - // value equal to `cap` is the buffer-too-small sentinel (required size - // including NUL), not a successful write of `cap` chars. if n == 0 || n >= cap { return -1; } @@ -1576,29 +1390,13 @@ pub mod fd { #[cfg(windows)] unsafe extern "C" { - /// libuv: convert C-runtime fd → OS HANDLE. By-value `c_int` in, opaque - /// HANDLE out — wraps `_get_osfhandle`, which validates the fd and - /// returns `INVALID_HANDLE_VALUE` on a bad index. No memory-safety - /// preconditions. pub safe fn uv_get_osfhandle(fd: c_int) -> *mut c_void; - /// libuv: `_open_osfhandle(os_fd, 0)` — wraps a HANDLE in a CRT fd so - /// libuv `uv_fs_*` (which speak `uv_file == int`) can use it. Returns - /// `-1` on `EMFILE` (CRT fd table full) or invalid handle. The `*mut - /// c_void` is an opaque kernel HANDLE, never dereferenced; no - /// memory-safety preconditions. pub safe fn uv_open_osfhandle(os_fd: *mut c_void) -> c_int; } #[cfg(windows)] pub use crate::windows_sys::{STD_ERROR_HANDLE, STD_INPUT_HANDLE, STD_OUTPUT_HANDLE}; #[cfg(windows)] pub fn is_stdio_handle(id: u32, handle: *mut c_void) -> bool { - // Zig: `const h = std.os.windows.GetStdHandle(id) catch return false; - // return handle == h;` — the Zig wrapper maps both NULL and - // INVALID_HANDLE_VALUE to an error, so use the Option-returning - // wrapper here. Without the INVALID_HANDLE_VALUE filter, a detached - // console (GetStdHandle → INVALID_HANDLE_VALUE) compared against - // `Fd::INVALID.native()` (= INVALID_HANDLE_VALUE) would spuriously - // report a stdio match. match crate::windows_sys::GetStdHandle(id) { Some(h) => handle == h, None => false, @@ -1653,16 +1451,6 @@ pub mod fd { // Zig: src/sys/sys.zig — pure S_IFMT arithmetic, no syscalls (libarchive_sys req). pub type Mode = u32; // std.posix.mode_t -/// `stat` mode-flag constants and predicates (Zig: `std.posix.S`). -/// -/// Values are POSIX-standard octal — frozen since 1988 and identical across -/// linux/darwin/freebsd. Typed against the cross-platform `Mode` (= `u32`) -/// rather than each platform's native `mode_t`, so `Mode`-typed expressions -/// like `S::IRUSR | S::IWUSR` and `(st_mode as u32) & S::IFMT` compile -/// uniformly; the libc-boundary cast to native `mode_t` happens in `bun_sys`. -/// -/// Canonical home for the per-OS `bun_errno::posix::S` re-exports (errno -/// depends on bun_core, not vice-versa). #[allow(non_snake_case)] pub mod S { use super::Mode; @@ -1812,20 +1600,8 @@ pub mod io { pub pos: usize, } - // ════════════════════════════════════════════════════════════════════════ - // trait Write — canonical byte-level write sink (port of Zig - // `std.Io.Writer`). Lives in `bun_core` (not `bun_io`) so leaf crates - // below `bun_io` in the dep graph — `bun_string`, `bun_collections`, - // `bun_url` — can implement it without an upward dep. `bun_io` re-exports - // this verbatim as `bun_io::Write`. - // ════════════════════════════════════════════════════════════════════════ use core::fmt; - /// Byte-level write sink — port of Zig `std.Io.Writer`. - /// - /// Only [`write_all`](Write::write_all) is required; every other method has - /// a default in terms of it. Object-safe: `&mut dyn Write` works. Generic - /// helpers that would break object safety carry `where Self: Sized`. pub trait Write { /// Write the entire buffer. Zig: `writeAll`. fn write_all(&mut self, buf: &[u8]) -> Result<(), crate::Error>; @@ -1837,10 +1613,6 @@ pub mod io { Ok(()) } - /// Total bytes written to this sink so far. - /// - /// Only implemented for in-memory / counting sinks; the default panics, - /// matching the Zig `@panic("css: got bad writer type")` fallthrough. #[inline] fn written_len(&self) -> usize { panic!("io::Write::written_len: writer does not track bytes written"); @@ -2046,12 +1818,6 @@ impl Version { patch: 0, }; - /// Parse leading `"MAJOR.MINOR.PATCH"` from a byte slice. Per field: - /// accumulate ASCII digits (wrapping on overflow), stop at the first - /// non-digit, then advance past a single `'.'` to the next field; missing - /// or empty fields default to 0. Tolerates trailing junk (e.g. uname's - /// `"5.10.16-microsoft-standard"` → {5,10,16}). `const fn` so it can - /// populate `static`/`const` initializers. pub const fn parse_dotted(bytes: &[u8]) -> Self { let mut nums = [0u32; 3]; let mut idx = 0usize; @@ -2082,21 +1848,6 @@ impl Version { } } -// ─── RacyCell ───────────────────────────────────────────────────────────── -/// Stable equivalent of `core::cell::SyncUnsafeCell` (nightly-only as of -/// 1.79). A `static`-safe interior-mutability cell with **no** synchronization. -/// -/// This exists to replace `static mut` (banned per docs/PORTING.md §Global -/// mutable state). Unlike `static mut`, taking `&RACY` does not assert -/// uniqueness; callers stay in raw-ptr land via `.get()` and only deref for -/// the duration of a single statement. -/// -/// **Invariant the caller upholds:** all access is either single-threaded -/// (e.g. HTTP-thread-only buffers, main-thread-only CLI state) or externally -/// synchronized. For anything actually shared across threads, use -/// `Atomic*` / `OnceLock` / `Mutex` instead — `RacyCell` is the last resort -/// for scratch buffers and FFI-shaped globals where the Zig already proved -/// thread-affinity. #[repr(transparent)] pub struct RacyCell(core::cell::Cell); // SAFETY: by construction, callers promise external synchronization or @@ -2152,14 +1903,6 @@ impl Default for RacyCell { } } -// ─── ThreadLock (from bun_safety) ───────────────────────────────────────── -// Debug-only re-entrancy guard. Release builds compile to a ZST. -// -// `locked_at` is `Cell` so `lock()`/`lock_or_assert()` can take `&self` -// (callers like `RefCount::assert_single_threaded` only have `&self`). The -// whole point of ThreadLock is asserting single-threaded access, so the -// unsynchronized write to `locked_at` is exactly the Zig semantics — if two -// threads race here, the `owning_thread.swap` panic fires first. pub struct ThreadLock { #[cfg(debug_assertions)] owning_thread: core::sync::atomic::AtomicU64, @@ -2194,11 +1937,6 @@ impl ThreadLock { pub fn init_locked_if_non_comptime() -> Self { Self::init_locked() } - /// RAII spelling of `lock()` + `defer unlock()`. Returns a guard that - /// `unlock()`s on `Drop`. The guard stores a raw pointer (not a borrow) - /// so the caller's surrounding `&mut self` on the owning struct stays - /// usable for the rest of the scope — `ThreadLock` is a debug-only - /// ownership assertion, not a real mutex. #[inline] pub fn guard(&self) -> ThreadLockGuard { self.lock(); @@ -2277,12 +2015,6 @@ impl ThreadLock { } } -/// RAII guard returned by [`ThreadLock::guard`]. Calls `unlock()` on drop. -/// -/// Stores a raw `*const ThreadLock` (not a borrow) so holding the guard does -/// not freeze the borrow of the struct that owns the lock — every call site is -/// `self.field.guard()` inside a `&mut self` method that needs the rest of -/// `self` mutably for the scope. #[must_use = "dropping immediately unlocks the ThreadLock"] pub struct ThreadLockGuard(*const ThreadLock); @@ -2323,11 +2055,6 @@ unsafe extern "C" { safe fn Bun__StackCheck__initialize(); /// No preconditions; returns the cached stack-bound pointer for this thread. safe fn Bun__StackCheck__getMaxStack() -> *mut core::ffi::c_void; - /// `&mut libc::timespec` is ABI-identical to libc's `struct timespec *` - /// (thin non-null pointer to a `#[repr(C)]` struct); the type encodes the - /// only pointer-validity precondition, so `safe fn` discharges the - /// link-time proof and `nano_timestamp`/`Timespec::now_real` call it - /// directly. #[cfg(unix)] safe fn clock_gettime(clk_id: libc::clockid_t, tp: &mut libc::timespec) -> core::ffi::c_int; } @@ -2372,11 +2099,6 @@ impl StackCheck { remaining > threshold } - /// Like [`is_safe_to_recurse`] but reserves `extra` bytes of additional - /// headroom on top of the platform threshold. Use when the code after the - /// check makes a deep call (e.g. into the transpiler) before reaching the - /// next check — on Windows a single stack `PathBuffer` is ~96 KB, so a - /// chain of two or three exceeds the default 256 KB headroom. #[inline] pub fn is_safe_to_recurse_with_extra(self, extra: usize) -> bool { let remaining = Self::frame_address().saturating_sub(self.cached_stack_end); @@ -2388,17 +2110,6 @@ impl StackCheck { remaining > threshold.saturating_add(extra) } - /// Approximate the current stack position. Reads the stack-pointer - /// register so the result is on the real machine stack — taking the - /// address of a local lands on ASAN's heap-backed fake stack when - /// use-after-return detection is on, which makes the comparison against - /// `cached_stack_end` useless. - /// - /// Zig uses `@frameAddress()` (rbp/x29), but Zig builds always keep frame - /// pointers. Rust release builds omit them, leaving rbp/x29 as a - /// general-purpose register with arbitrary contents — reading it there - /// makes `is_safe_to_recurse()` spuriously fail at depth 0. The stack - /// pointer is always valid and is what we actually want to measure. #[inline(always)] fn frame_address() -> usize { #[cfg(target_arch = "x86_64")] @@ -2435,11 +2146,6 @@ impl StackCheck { /// invalidate stale cache entries. pub type Generation = u16; -// ── Ordinal ─────────────────────────────────────────────────────────────── -// Port of `OrdinalT(c_int)` (bun.zig:3421). ABI-equivalent of WTF::OrdinalNumber: -// a zero-based index where -1 means "invalid". Represented as a transparent -// newtype rather than a Rust enum so the full `c_int` range round-trips across -// FFI without UB. #[repr(transparent)] #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] pub struct Ordinal(pub core::ffi::c_int); @@ -2488,26 +2194,6 @@ impl Default for Ordinal { } } -// ── Once ────────────────────────────────────────────────────────────────── -// Port of `bun.Once(f)` (bun.zig:3637). Zig parameterizes over a comptime fn -// and stores the payload; Rust callers use two shapes: -// * `Once` — fn supplied at `.call(f)` / `.get_or_init(f)` time -// * `Once T>` — fn supplied at construction (PackageManagerDirectories.rs) -// -// Open-coded double-checked-init (AtomicU8 + UnsafeCell>) rather -// than `std::sync::OnceLock`. The previous `OnceLock` backing produced 157 -// `OnceLock::initialize` + 30 `LazyLock` monomorphizations (~36.7 KB) whose -// shared callee `std::sys::sync::once::futex::Once::call` lives in libstd's -// own CGU — every hot-path `get_or_init` paid a cross-CGU call + futex-aware -// state machine even when the value was already initialised. The Zig original -// is a plain `bool` flag + payload; this matches it: post-init reads are one -// Acquire load + cmp inlined into the caller. Pattern proven at -// `bun_alloc/lib.rs::bss_heap_init`'s accessor macro. -// -// Contention: startup is single-threaded for every current call site; the -// rare cross-thread race spins on `yield_now()` (no futex). No poisoning — -// a panic mid-init resets to UNINIT so the next call retries (Zig has no -// poisoning either). const ONCE_UNINIT: u8 = 0; const ONCE_BUSY: u8 = 1; const ONCE_DONE: u8 = 2; @@ -2532,11 +2218,6 @@ impl core::panic:: { } -/// Cold contended path shared by every `Once` instantiation. Taking -/// `&AtomicU8` (not `&self`) keeps this **non-generic** so exactly one copy -/// lands in `bun_core`'s CGU regardless of how many `T`s the crate uses. -/// Returns `true` if the caller won the claim and must initialise + publish; -/// `false` if another thread finished first (cell is now DONE). #[cold] #[inline(never)] fn once_claim_slow(state: &core::sync::atomic::AtomicU8) -> bool { @@ -2582,12 +2263,6 @@ impl Once { self.init_slow(f) } - // `#[inline(never)]`, not `#[cold]`: the very first call to every `Once` - // *always* lands here during single-threaded startup, so this is not a - // rare branch — `#[cold]` would only relocate every monomorphisation into - // `.text.unlikely`, scattering init code away from the startup.order - // cluster. We only want it outlined so the DONE fast path in - // `get_or_init` stays a load+branch. #[inline(never)] fn init_slow(&self, f: impl FnOnce() -> T) -> &T { if once_claim_slow(&self.state) { @@ -2615,10 +2290,6 @@ impl Once { unsafe { (*self.cell.get()).assume_init_ref() } } - /// `OnceLock::set` equivalent: store `value` if uninitialised, else hand it - /// back. Never blocks — if another thread is mid-init (BUSY) this returns - /// `Err(value)` rather than waiting, which is fine for the write-once - /// startup statics that use it (`START_TIME`, `STD*_DESCRIPTOR_TYPE`, …). #[inline] pub fn set(&self, value: T) -> Result<(), T> { use core::sync::atomic::Ordering::{Acquire, Release}; @@ -2687,14 +2358,6 @@ impl Default for Once { } } -/// Void-result sibling of [`Once`]: declares a hidden `static std::sync::Once` -/// and runs `$body` exactly once for the process lifetime. Replaces the -/// hand-rolled `static AtomicBool; if X.swap(true){return}` one-shot guards -/// (D006). Acquire/Release per `std::sync::Once`; poisons on panic — second -/// call after a mid-init panic will panic instead of silently returning. -/// -/// Do **not** use when the guard must be reset on failure (e.g. retry-on-error) -/// or when both first/subsequent arms run real code — keep the `AtomicBool`. #[macro_export] macro_rules! run_once { ($body:block) => {{ @@ -2703,10 +2366,6 @@ macro_rules! run_once { }}; } -// ── Pollable / is_readable / is_writable ────────────────────────────────── -// Port of `bun.PollFlag` + `bun.isReadable` / `bun.isWritable` (bun.zig:637). -// Named `Pollable` to match the original draft callers (io/PipeReader.rs). -// D050 dedup: this is the single canonical copy; `bun::`/`bun_sys::` re-export. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum Pollable { Ready, @@ -2838,13 +2497,6 @@ pub fn is_writable(fd: Fd) -> Pollable { } } -// ── csprng ──────────────────────────────────────────────────────────────── -// Zig calls `BoringSSL.c.RAND_bytes` (bun.zig:621). bun_core sits below -// boringssl_sys in the crate graph, so we go to the OS CSPRNG directly: -// getrandom(2) on Linux, SecRandomCopyBytes/getentropy on Darwin, -// RtlGenRandom on Windows. All are the same entropy source BoringSSL seeds -// from. PERF(port): if a hot path needs the BoringSSL DRBG, install a -// vtable hook from bun_runtime at startup. pub fn csprng(bytes: &mut [u8]) { #[cfg(any(target_os = "linux", target_os = "android"))] { @@ -2903,17 +2555,6 @@ pub fn self_exe_path() -> Result<&'static ZStr, crate::Error> { static CELL: Once> = Once::new(); let r = CELL.get_or_init(|| { let path = std::env::current_exe().map_err(crate::Error::from)?; - // PORT NOTE: Zig's `std.fs.selfExePath` resolves symlinks. Rust's - // `current_exe()` already does on Linux (`readlink /proc/self/exe`), - // but on Darwin it returns the raw `_NSGetExecutablePath` result and on - // Windows it returns the raw `GetModuleFileNameW` result — neither - // realpaths, so a symlinked argv0 (Darwin) or an un-normalized - // `C:\a\.\b\bun.exe` load path (Windows) leaks through to - // `process.execPath` / `process.argv[0]`. Zig's Windows impl calls - // `realpathW` (GetFinalPathNameByHandle); match that here so parent and - // child agree on argv[0] regardless of how the binary was invoked - // (test/js/node/process/process-args.test.js, - // test/js/node/test/parallel/test-process-execpath.js). #[cfg(any(target_vendor = "apple", windows))] let path = path.canonicalize().unwrap_or(path); #[cfg(unix)] @@ -2930,10 +2571,6 @@ pub fn self_exe_path() -> Result<&'static ZStr, crate::Error> { .into_os_string() .into_string() .unwrap_or_else(|os| os.to_string_lossy().into_owned()); - // `canonicalize()` on Windows returns a verbatim `\\?\` path; Zig's - // `realpathW` strips that back to a plain DOS path before WTF-8 - // encoding, so do the same (Node's `process.execPath` is never - // verbatim-prefixed). if let Some(rest) = s.strip_prefix(r"\\?\UNC\") { s = format!(r"\\{}", rest); } else if let Some(rest) = s.strip_prefix(r"\\?\") { @@ -2968,10 +2605,6 @@ pub fn get_thread_count() -> u16 { } } } - // Windows: `getenv_z` is currently a no-op (no narrow C - // environ to borrow from); honour the override via - // `std::env::var` so behaviour matches Zig `bun.getThreadCount` - // on all platforms. POSIX keeps the borrow path above. #[cfg(windows)] if let Ok(s) = std::env::var( // SAFETY: keys above are ASCII literals. @@ -2987,16 +2620,6 @@ pub fn get_thread_count() -> u16 { None }; let raw = from_env().unwrap_or_else(|| { - // Zig (bun.zig:3621) calls `jsc.wtf.numberOfProcessorCores()` — - // `WTF::numberOfProcessorCores()` → sysconf(_SC_NPROCESSORS_ONLN) - // on POSIX / GetSystemInfo on Windows. **Not** the same as - // `std::thread::available_parallelism()`, which on Linux also - // consults sched_getaffinity + cgroup cpu.max quota; on - // cgroup-limited CI runners or P/E-core machines the two diverge, - // changing bundler `max_threads` (and per-thread mimalloc arena - // RSS) vs the Zig binary. Declare the C symbol locally — `jsc` - // is above `bun_core` in the crate DAG so we can't `use` it, but - // the symbol is always linked (wtf-bindings.cpp). unsafe extern "C" { safe fn WTF__numberOfProcessorCores() -> core::ffi::c_int; } @@ -3006,23 +2629,12 @@ pub fn get_thread_count() -> u16 { }) } -// ── errno_to_zig_err ────────────────────────────────────────────────────── -// Port of `bun.errnoToZigErr` (bun.zig:2854). Zig indexes into a comptime -// `errno_map: [N]anyerror`; the Rust intern table reproduces that mapping in -// `Error::from_errno` (errno → tag name → interned code). #[inline] pub fn errno_to_zig_err(errno: i32) -> crate::Error { debug_assert!(errno != 0); crate::Error::from_errno(errno) } -// ── time ────────────────────────────────────────────────────────────────── -// Port of `std.time` (vendor/zig/lib/std/time.zig:80-107) — the full -// `comptime_int` constant ladder plus `{nano,milli,}timestamp()`. Zig's -// `comptime_int` coerces to any numeric type; Rust callers `as`-cast at the -// use-site (`NS_PER_S as i128`, `MS_PER_S as f64`, …). Every value fits in -// `u64` (and the ≤per-second constants in `i32`), so all such casts — -// including to `f64` — are lossless. pub mod time { // ns pub const NS_PER_US: u64 = 1_000; @@ -3103,15 +2715,6 @@ pub mod time { } } -// ── runtime_embed_file ──────────────────────────────────────────────────── -// Port of `bun.runtimeEmbedFile` (bun.zig:2938). The Zig version comptime- -// captures `sub_path` to manufacture a per-call-site `static once` cache; Rust -// can't do that from a plain fn without leaking, so the canonical port is the -// `runtime_embed_file!` macro below (per-site `OnceLock` — sanctioned -// by PORTING.md §Forbidden, "true process-lifetime singleton"). The fn form is -// kept so existing draft callers type-check; it's only reachable when the -// `codegen_embed` feature is off (debug fast-iteration), where panicking with a -// migration hint is the same UX as the Zig `Output.panic` on read failure. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub enum EmbedKind { Codegen, @@ -3152,23 +2755,6 @@ pub fn __runtime_embed_load(kind: EmbedKind, sub: &'static str) -> String { }) } -/// Per-call-site embedded file. `($root, $sub_path)` mirrors the Zig -/// signature; `$root` must be one of the bare idents `Codegen` / -/// `CodegenEager` / `Src` / `SrcEager` and `$sub_path` a string literal. -/// -/// The `cfg(bun_codegen_embed)` split lives **inside** the macro so call -/// sites never repeat the `#[cfg]`/`#[cfg(not)]` pair (which is error-prone -/// — a missed pair leaves a release binary that panics with "Failed to load -/// '/…'"). Under the cfg, the file is `include_str!`-ed -/// at compile time; otherwise it's read once at runtime into a per-site -/// `OnceLock` for fast iteration. -/// -/// `Src` paths are relative to `/src/`; `Codegen` paths are relative -/// to `BUN_CODEGEN_DIR`. The embed arm resolves both via the *call-site -/// crate's* `CARGO_MANIFEST_DIR` / `BUN_CODEGEN_DIR` (every workspace crate -/// lives at `src//`, so `../../src/` is the repo `src/`; -/// `BUN_CODEGEN_DIR` is exported to every rustc by `scripts/build/rust.ts` -/// whenever `bun_codegen_embed` is set). #[macro_export] macro_rules! runtime_embed_file { (Codegen, $sub:literal) => { $crate::__runtime_embed_impl!(@codegen $sub) }; @@ -3214,27 +2800,6 @@ macro_rules! __runtime_embed_impl { }}; } -// ── StringBuilder ───────────────────────────────────────────────────────── -// Port of src/string/StringBuilder.zig. Count-then-allocate-then-append arena -// for building a single contiguous buffer. Allocator param dropped per -// PORTING.md §Allocators (always `bun.default_allocator`). -// -// PORT NOTE: returned sub-slices borrow `*self`, but in Zig they alias the -// final `allocated_slice()` and outlive the builder. To keep that pattern -// without self-referential lifetimes, callers stash `(offset, len)` via -// `StringPointer` (see install/hosted_git_info.rs). -// -// Canonical `StringBuilder` lives in `bun_core::StringBuilder` -// (src/string/StringBuilder.rs). Cannot re-export here (`bun_string` depends -// on `bun_core` → cycle); callers import `bun_core::StringBuilder` directly. -// `StringPointer` stays here as the layered #[repr(C)] ABI type re-exported by -// `bun_string` et al. - -/// `bun.schema.api.StringPointer` — `(offset, length)` span into an external -/// buffer. Canonical definition; re-exported by `bun_string`, `bun_http_types`, -/// and `bun_url` (formerly each had a structurally-identical copy). Layout MUST -/// match `extern struct { offset: u32, length: u32 }` — C++ (`WebCore::FetchHeaders`) -/// and on-disk formats (lockfile, npm manifest cache) read it directly. #[repr(C)] #[derive(Clone, Copy, Default, Debug, PartialEq, Eq)] pub struct StringPointer { @@ -3278,10 +2843,6 @@ impl PartialEq<&[u8; N]> for ZStr { } } -// ── Hasher trait (Zig "anytype with .update([]const u8)") ───────────────── -// Used by `bun_core::write_any_to_hasher` and bundler/css hashing. Mirrors -// the minimal Zig hasher protocol — *not* `core::hash::Hasher` because Bun's -// hashers (Wyhash, XxHash64, sha1) expose `.update(&[u8])` + `.final()`. pub trait Hasher { fn update(&mut self, bytes: &[u8]); } @@ -3298,95 +2859,31 @@ impl Hasher for H { /// direct `bytemuck` dep. pub use bytemuck::NoUninit; -/// Port of Zig `std.mem.asBytes(&v)`: reinterpret a value's storage as a -/// borrowed byte slice. -/// -/// Safe: the [`bytemuck::NoUninit`] bound statically guarantees `T` is `Copy`, -/// `'static`, and contains no uninitialized (padding) bytes, so every byte of -/// the returned slice is initialized and reading it is defined behaviour. #[inline] pub fn bytes_of(v: &T) -> &[u8] { bytemuck::bytes_of(v) } -/// Mutable counterpart of [`bytes_of`]: reinterpret `&mut T` as `&mut [u8]`. -/// -/// Safe: the [`bytemuck::Pod`] bound guarantees `T` has no padding bytes and -/// every bit pattern is a valid `T`, so writing arbitrary bytes through the -/// returned slice cannot produce an invalid value. #[inline] pub fn bytes_of_mut(v: &mut T) -> &mut [u8] { bytemuck::bytes_of_mut(v) } -// ─── Slice reinterpretation (canonical) ─────────────────────────────────────── -// Port of Zig `bun.reinterpretSlice` / `std.mem.bytesAsSlice` / `sliceAsBytes`. -// Zig has ONE polymorphic `reinterpretSlice(comptime T, slice: anytype)` that -// handles const+mut via comptime; Rust splits by mutability and offers two -// safety surfaces: -// - `cast_slice` / `cast_slice_mut` → SAFE, bytemuck-bounded, panics on -// misalign or `len % size_of::() != 0`. Use for Pod↔Pod (u8↔u16 etc.). -// - `bytes_as_slice_mut` → UNSAFE escape hatch, unbounded `T`, -// TRUNCATES trailing bytes (Zig `@divTrunc`). Use only when `T` is not -// `AnyBitPattern` or the input length is intentionally not a multiple. -// Every current caller targets `u16` over an even-length buffer, so the safe -// path is the default. - -/// Port of Zig `std.mem.sliceAsBytes` / `bun.reinterpretSlice` for the -/// read-only `&[A]` → `&[B]` direction. Safe: the [`bytemuck::NoUninit`] bound -/// on `A` guarantees every source byte is initialized, and -/// [`bytemuck::AnyBitPattern`] on `B` guarantees every byte pattern is a valid -/// `B`. Panics if size/alignment don't divide evenly (same as `bytemuck`). #[inline] pub fn cast_slice(a: &[A]) -> &[B] { bytemuck::cast_slice(a) } -/// Mutable counterpart of [`cast_slice`]: reinterpret `&mut [A]` as `&mut [B]`. -/// Safe: both [`bytemuck::Pod`] bounds guarantee every byte pattern is valid in -/// both directions and there are no uninitialized bytes. Panics on misalignment -/// or if `a.len() * size_of::() % size_of::() != 0` (same as `bytemuck`). #[inline] pub fn cast_slice_mut(a: &mut [A]) -> &mut [B] { bytemuck::cast_slice_mut(a) } -/// Port of Zig `std.mem.sliceAsBytes`: reinterpret `&[T]` as `&[u8]`. -/// -/// This is [`cast_slice`] with the output type fixed to `u8`, so callers never -/// need a `::<_, u8>` turbofish. Safe: [`bytemuck::NoUninit`] guarantees every -/// byte of `T` is initialized; `align_of::() == 1` and -/// `size_of::() % 1 == 0` mean the bytemuck size/align checks are trivially -/// satisfied and this never panics. #[inline] pub fn slice_as_bytes(s: &[T]) -> &[u8] { bytemuck::cast_slice(s) } -// ─── extern_union_accessors! ────────────────────────────────────────────────── -// Zig accesses bare-union fields inline (`this.value.npm`) with no ceremony; the -// Rust port wraps each read in a tag-asserted `unsafe` accessor so call sites -// stay safe. Four crates hand-rolled the same accessor shape (Resolution, Bin, -// DependencyVersion, PackageManager Task) — this macro is the single definition. -// -// Emits, per arm, `pub fn $field(&self) -> &$Ty` and optionally -// `pub fn $field_mut(&mut self) -> &mut $Ty`, each guarded by -// `debug_assert!(self.$tag_field == $TagTy::$Variant)`. -// -// Projection uses `addr_of!`/`addr_of_mut!` so no intermediate `&Union` is -// formed (defensive against partially-initialized padding). The trailing -// `as *const $Ty` cast is identity for plain fields and unwraps -// `ManuallyDrop<$Ty>` (`#[repr(transparent)]`) for the `Task::Request`/`Data` -// case without needing a separate macro arm. -// -// Syntax: -// extern_union_accessors! { -// tag: as , value: ; -// Variant => accessor: Ty; // ro, accessor==union field -// Variant => accessor: Ty, mut accessor_mut; // ro+rw -// Variant => accessor @ union_field: Ty; // ro, accessor≠union field -// Variant => accessor @ union_field: Ty, mut accessor_mut; -// } #[macro_export] macro_rules! extern_union_accessors { ( @@ -3452,10 +2949,6 @@ macro_rules! extern_union_accessors { }; } -/// Port of `bun.writeAnyToHasher`. Zig fed `std.mem.asBytes(&thing)`; Rust -/// can't take a generic by-value-as-bytes safely without `bytemuck`, so this -/// accepts anything that is itself viewable as bytes (covers the actual call -/// sites: `u8` tags, `usize` lengths, `Index` newtypes). #[inline] pub fn write_any_to_hasher(hasher: &mut H, thing: T) { hasher.update(thing.as_bytes_for_hash()); @@ -3485,15 +2978,6 @@ impl AsBytes for &T { } } -// ── GenericIndex ────────────────────────────────────────────────────────── -// Port of `bun.GenericIndex(backing_int, uid)` (bun.zig:3513). Zig used a -// distinct enum-per-uid for nominal typing; Rust gets that via a phantom -// marker. `MAX` is reserved as the "none" sentinel for `Optional`. -// -// NOTE on const-ness: hand-rolled monomorphic sites used `const fn init/get`. -// The generic impl cannot be `const fn` on stable (trait-bound `I::NULL_VALUE` -// comparison is not const-evaluable). Audited: zero call sites use `init`/`get` -// in const context, so dropping `const` is a no-op. #[repr(transparent)] pub struct GenericIndex(I, core::marker::PhantomData); @@ -3663,11 +3147,6 @@ macro_rules! generic_index_int { ($($t:ty),*) => { $( )* } } generic_index_int!(u8, u16, u32, u64, usize, i32, i64); -/// Generic-integer bound replacing Zig's `comptime T: type` + `@typeInfo(T).Int` -/// in `validateIntegerRange` / `validateBigIntRange` / `getInteger` -/// (src/jsc/JSGlobalObject.zig). Provides the small surface those callers need: -/// signedness, range as `i128`, and lossy/wrapping casts from the JSC numeric -/// carriers (i32 / f64 / i64 / u64). pub trait Integer: Copy + Default { const SIGNED: bool; const MIN_I128: i128; @@ -3699,13 +3178,6 @@ impl_integer!( u8: false, u16: false, u32: false, u64: false, usize: false, ); -/// Primitive integers transcodable as native-endian raw bytes. -/// -/// Replaces Zig's `comptime T: type` + `std.mem.readIntSliceNative` / -/// `std.mem.asBytes` / `@bitCast` reflection pattern with an explicit trait -/// bound. Shared by the peechy wire codec (`bun_analytics::SchemaInt`) and the -/// MySQL protocol reader (`bun_sql::ReadableInt`), which re-export this under -/// their local names. pub trait NativeEndianInt: Copy + 'static { const SIZE: usize; /// Reinterpret `b[..SIZE]` as `Self` (native endian). @@ -3836,10 +3308,6 @@ pub mod hash { } } -// ── base64 ──────────────────────────────────────────────────────────────── -// Thin simdutf-backed encoders + scalar decoder. Port of the subset of -// `src/base64/base64.zig` that tier-0/1 callers need (npm auth, sourcemaps, -// ansi_renderer). Full URL-safe / streaming variants stay in bun_base64. pub mod base64 { use bun_simdutf_sys::simdutf; @@ -3936,16 +3404,6 @@ pub mod base64 { } } -// ── dupe_z / free_sensitive ─────────────────────────────────────────────── -/// `bun.default_allocator.dupeZ(u8, bytes)` → heap-allocated NUL-terminated -/// copy. Returns a raw `*const c_char` because the SSLConfig FFI surface -/// stores C-strings. Caller frees via [`free_sensitive`]. -/// -/// Allocated via the default allocator (`bun_alloc::default_alloc` — -/// mimalloc, or `std::alloc::System` under `cfg(bun_asan)`), so the -/// allocation is visible to ASAN's interceptor and LeakSanitizer like every -/// other heap allocation. Pairs with [`free_sensitive`], which frees through -/// the same `default_alloc::free`. pub fn dupe_z(bytes: &[u8]) -> *const core::ffi::c_char { let p = bun_alloc::default_alloc::malloc(bytes.len() + 1).cast::(); if p.is_null() { @@ -3966,30 +3424,11 @@ pub use bun_alloc::free_sensitive_cstr as free_sensitive; /// Port of `std.crypto.secureZero` — re-exported from `bun_alloc`. pub use bun_alloc::secure_zero; -// ── argv ────────────────────────────────────────────────────────────────── -// `bun.argv` — process argv as a slice of NUL-terminated byte strings. -// Zig: `pub var argv: [][:0]const u8`. The owned `ZBox` backing for the -// initial OS argv lives in `ARGV_STORAGE`; `ARGV` is the mutable *view* -// slice that call sites read (and that `set_argv` swaps for the -// `--compile` exec-argv splicing path in `cli.zig`). Exposed via a tiny -// `Argv` wrapper so call sites can use it both as a slice (`.get(0)`, -// `.iter()`, `.len()`, `.as_slice()`) and as an `IntoIterator` -// for `for arg in argv()`. static ARGV_STORAGE: Once> = Once::new(); static ARGV_VIEW: Once> = Once::new(); static ARGV: RacyCell<&'static [&'static ZStr]> = RacyCell::new(&[]); static ARGV_INIT: std::sync::Once = std::sync::Once::new(); -/// Raw `(argc, argv)` as passed to `main` by the C runtime. Captured by -/// [`init_argv`] before any other code runs. On glibc / macOS / Windows, -/// libstd captures argv independently via a `.init_array` constructor / -/// `_NSGetArgv` / `GetCommandLineW`, so `std::env::args_os()` works without -/// this; on **musl-static** the `.init_array` constructor is invoked with no -/// arguments (musl's `__libc_start_main` does not forward `(argc,argv,envp)` -/// to constructors), so `std::env::args_os()` returns empty and we must read -/// the kernel-provided block ourselves. Zig's `_start` writes `std.os.argv` -/// directly from the stack — this is the equivalent for a clang-linked -/// `extern "C" fn main`. static OS_ARGC: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0); static OS_ARGV: core::sync::atomic::AtomicPtr<*const core::ffi::c_char> = core::sync::atomic::AtomicPtr::new(core::ptr::null_mut()); @@ -4027,13 +3466,6 @@ fn raw_os_argv() -> Option<&'static [*const core::ffi::c_char]> { fn argv_storage() -> &'static [ZBox] { ARGV_STORAGE.get_or_init(|| { - // Windows: the CRT-provided `char** argv` captured by `init_argv` is - // ANSI-encoded (CP_ACP) — `WideCharToMultiByte` lossy-converts the - // UTF-16 command line, replacing unrepresentable code points with `?`. - // Zig's `initArgv` (bun.zig) goes straight to `GetCommandLineW` + - // `CommandLineToArgvW` and converts each UTF-16 arg to WTF-8 itself; - // do the same here so non-ASCII argv (e.g. `bun -e "🌊 测试"`) - // round-trips. See https://github.com/oven-sh/bun/issues/11610. #[cfg(windows)] { use bun_windows_sys::externs::{CommandLineToArgvW, GetCommandLineW}; @@ -4073,10 +3505,6 @@ fn argv_storage() -> &'static [ZBox] { }) .collect(); } - // Fallback for entry points that don't go through `extern "C" fn main` - // (e.g. `cargo test` harness, Rust `fn main()` via `lang_start`). On - // glibc/macOS/Windows this also works for the real binary — only - // musl-static needs the `raw_os_argv` path above. std::env::args_os() .map(|a| ZBox::from_vec_with_nul(a.into_encoded_bytes())) .collect() @@ -4175,14 +3603,6 @@ pub fn argv() -> Argv { /// variable. Set once during single-threaded startup (`init_argv`). static BUN_OPTIONS_ARGC: core::sync::atomic::AtomicUsize = core::sync::atomic::AtomicUsize::new(0); -/// Zig: `bun.bun_options_argc` — read accessor. -/// -/// Forces the lazy `argv_view()` init before reading: in Zig `initArgv()` -/// runs eagerly in `main()` so `bun.bun_options_argc` is always populated by -/// the time `cli.zig` reads it; here `argv()` is lazy, so a caller that reads -/// `bun_options_argc()` *before* `argv()` (e.g. the standalone-executable -/// path in `Command::start`) would otherwise see 0 and miscount the -/// BUN_OPTIONS-injected args when computing the passthrough offset. #[inline] pub fn bun_options_argc() -> usize { let _ = argv_view(); @@ -4375,12 +3795,6 @@ pub unsafe fn set_argv(v: &'static [&'static ZStr]) { unsafe { ARGV.write(v) }; } -/// Park an owned argv `Vec` in process-static storage and return the -/// now-`'static` slice. Used by the `--compile` exec-argv splice path -/// (`cli_body.rs`) which needs to extend argv beyond the original -/// OS-provided storage and then hand sub-slices to [`set_argv`]. Single-shot: -/// the slot is a `Once`, so a second call drops `v` and returns the -/// first-stored slice. pub fn intern_argv(v: Vec<&'static ZStr>) -> &'static [&'static ZStr] { static SLOT: Once> = Once::new(); SLOT.get_or_init(move || v.into_boxed_slice()) @@ -4446,19 +3860,6 @@ pub fn getcwd(buf: &mut PathBuffer) -> Result<&ZStr, crate::Error> { } } -// ── which ───────────────────────────────────────────────────────────────── -// `bun.which` lives in the `bun_which` crate (tier-2, full Windows PATHEXT -// support). bun_core cannot re-export it (bun_which → bun_core dep cycle), so -// callers import `bun_which::which` directly. See `src/bun.rs` for the -// `bun::which` re-export. -// -// A POSIX-only copy is kept here because `spawn_sync_inherit` (below) needs -// PATH resolution at tier-0 and cannot reach up to `bun_which`. This is a -// load-bearing duplicate; do NOT dedup against `src/which/lib.rs`. -/// Tier-0 POSIX `which`. Resolves `bin` against `cwd` and each `PATH` entry -/// for an executable named `bin`; returns the NUL-terminated match written -/// into `buf`. POSIX semantics; Windows `PATHEXT` handling stays in -/// `bun_which` (tier-2). pub fn which<'a>(buf: &'a mut PathBuffer, path: &[u8], cwd: &[u8], bin: &[u8]) -> Option<&'a ZStr> { if bin.is_empty() { return None; @@ -4528,10 +3929,6 @@ pub fn which<'a>(buf: &'a mut PathBuffer, path: &[u8], cwd: &[u8], bin: &[u8]) - None } -// ── auto_reload_on_crash / reload_process group ─────────────────────────── -// Port of `bun.zig:1527-1686`. Full body of `reloadProcess` depends on -// `bun.spawn` (tier-4); the crash-handler only needs the flag + the -// thread-coordination helpers + a best-effort POSIX `execve` path. use core::sync::atomic::{AtomicBool, Ordering as AOrdering}; static AUTO_RELOAD_ON_CRASH: AtomicBool = AtomicBool::new(false); static RELOAD_IN_PROGRESS: AtomicBool = AtomicBool::new(false); @@ -4559,10 +3956,6 @@ pub fn is_process_reload_in_progress_on_another_thread() -> bool { pub fn exit_thread() -> ! { #[cfg(unix)] { - // `retval` is stored opaquely for `pthread_join` and never - // dereferenced by libc itself; thread termination leaks but cannot - // violate memory safety (same rationale as `std::process::exit` - // being safe), so `safe fn` discharges the link-time proof. unsafe extern "C" { safe fn pthread_exit(retval: *mut core::ffi::c_void) -> !; } @@ -4577,14 +3970,6 @@ pub fn exit_thread() -> ! { } } -/// Zig: `bun.deleteAllPoolsForThreadExit()` — release thread-local pooled -/// buffers (PathBuffer pool, ObjectPool, …) before the thread terminates so -/// the backing storage is returned to mimalloc rather than leaked with the -/// TLS block. -/// -/// LAYERING: the actual pool registries live in higher-tier crates -/// (`bun_paths`, `bun_collections`). They register a destructor here at init -/// via [`register_thread_exit_pool_destructor`]; this fn just walks the list. static THREAD_EXIT_POOL_DESTRUCTORS: Mutex> = Mutex::new(Vec::new()); pub fn register_thread_exit_pool_destructor(f: fn()) { @@ -4615,10 +4000,6 @@ pub fn maybe_handle_panic_during_process_reload() { core::hint::spin_loop(); #[cfg(unix)] { - // `&libc::timespec` / `Option<&mut libc::timespec>` are - // ABI-identical to libc's `const struct timespec *` / nullable - // `struct timespec *`; the types encode the only pointer-validity - // preconditions, so `safe fn` discharges the link-time proof. unsafe extern "C" { #[link_name = "nanosleep"] safe fn libc_nanosleep( @@ -4637,12 +4018,6 @@ pub fn maybe_handle_panic_during_process_reload() { } } -/// Port of `bun.reloadProcess`. Allocator param dropped (uses libc malloc via -/// `dupe_z`). `may_return == true` → returns on failure; `false` → panics. -/// macOS posix_spawn path is deferred to bun_spawn (tier-4); tier-0 falls -/// back to plain `execve` on all POSIX which is correct on Linux/BSD and -/// best-effort on macOS (CLOEXEC handled by `on_before_reload_process_linux` -/// hook on Linux; Darwin gets the simpler path until tier-4 wires spawn). pub fn reload_process(clear_terminal: bool, may_return: bool) { RELOAD_IN_PROGRESS.store(true, AOrdering::Relaxed); RELOAD_IN_PROGRESS_ON_CURRENT_THREAD.with(|c| c.set(true)); @@ -4747,10 +4122,6 @@ pub fn reload_process(clear_terminal: bool, may_return: bool) { } } -// ── spawn_sync_inherit ──────────────────────────────────────────────────── -/// Minimal "spawn argv, inherit stdio, wait" used by crash_handler's -/// symbolizer. Port of the subset of `bun.spawnSync` needed at tier-0. -/// Full `bun.spawnSync` (with buffered stdio, env, cwd) is in bun_spawn. #[derive(Debug, Clone, Copy)] pub struct SpawnStatus { pub code: i32, @@ -4762,22 +4133,6 @@ impl SpawnStatus { } } -// ── posix_spawn_bun FFI (canonical #[repr(C)] mirror) ───────────────────── -// RULE: libc `posix_spawn`/`posix_spawnp` must NEVER be called directly on -// Linux/FreeBSD. Bun ships its own vfork-based spawner in -// `src/jsc/bindings/bun-spawn.cpp` (`posix_spawn_bun`) which is async-signal- -// safe, supports CLOEXEC sweeps, pdeathsig, PTY setup, and writes the exec -// errno back through a pipe. glibc's posix_spawn forks (not vfork) on some -// configurations and musl's has had signal-mask bugs; ours is the audited -// path. macOS keeps libc `posix_spawnp` for the non-PTY case because Apple's -// implementation is a true kernel fast-path (no fork at all), but the macOS -// PTY path also routes through `posix_spawn_bun` (setsid + TIOCSCTTY before -// exec), hence `cfg(unix)` here. -// -// This is the single source of truth for the request layout; `spawn_sys` -// re-exports these types rather than re-declaring them. The #[repr(C)] data -// mirrors are target-agnostic so the module is ungated; only the extern decl -// is `cfg(unix)` (Windows spawns go through libuv and never link this symbol). pub mod spawn_ffi { use core::ffi::{c_char, c_int}; @@ -4792,14 +4147,6 @@ pub mod spawn_ffi { Open = 3, } - /// Matches `bun_spawn_request_file_action_t`. - /// - /// ABI: this struct crosses FFI to `posix_spawn_bun` via `*const Action` - /// (see [`ActionsList`]) and must match spawn.zig's `extern struct` / - /// bun-spawn.cpp's C struct exactly. `path` is `?[*:0]const u8` on the - /// Zig/C side — an 8-byte thin nullable pointer — so it MUST be - /// `*const c_char` here, not `Option` (which is a 16-byte fat - /// pointer and would shift `fds`/`flags`/`mode`). #[repr(C)] pub struct Action { pub kind: FileActionType, @@ -4889,10 +4236,6 @@ pub fn spawn_sync_inherit(argv: &[impl AsRef<[u8]>]) -> Result]) -> Result]) -> Result Timespec { if matches!(mode, TimespecMockMode::AllowMockedTime) { @@ -5281,19 +4605,11 @@ pub enum TimespecMockMode { ForceRealTime, } -/// `bun_core::timespec::Mode` namespace shim — Zig nested it under the struct; -/// Rust can't do inherent associated types stably, so expose a module with the -/// same path. Callers write `bun_core::timespec_mode::AllowMockedTime` or use -/// the `Timespec::now_allow_mocked_time()` helper. pub mod timespec_mode { pub use super::TimespecMockMode::*; pub type Mode = super::TimespecMockMode; } -/// Mocked-time storage. The data lives at T0 so `Timespec::now` reads it -/// directly; the test-runner (`useFakeTimers`) writes via `set`/`clear` -/// from `bun_runtime::test_runner::timers::FakeTimers::CurrentTime`. -/// Sentinel `i64::MIN` ⇒ not mocked. pub mod mock_time { use core::sync::atomic::{AtomicI64, Ordering}; @@ -5318,11 +4634,6 @@ pub mod mock_time { } } -// ── f16 ─────────────────────────────────────────────────────────────────── -// Zig's native `f16` (IEEE-754 binary16). Rust's `f16` is still nightly-only, -// so model it as a transparent `u16` bit-container with `f64` widening for the -// one hot caller (ConsoleObject Float16Array printing). PERF(port): scalar -// soft-float decode; revisit once `core::f16` stabilizes. #[allow(non_camel_case_types)] #[repr(transparent)] #[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] @@ -5387,18 +4698,6 @@ impl core::fmt::Display for f16 { } } -// ── perf ────────────────────────────────────────────────────────────────── -// Port of `bun.perf` (src/perf/perf.zig). The Linux ftrace backend is -// libc-only, so it folds in directly and `bun_core::perf::trace("X")` is real -// instrumentation on Linux. macOS: the Zig backend wraps `Bun__signpost_emit` -// (c-bindings.cpp) which keys on the codegen `PerfEvent` int — that table -// lives in `bun_perf` (T2, owns generated_perf_trace_events), so T0 reports -// disabled on macOS. **No functional divergence today**: `bun_perf`'s Darwin -// arm currently routes through the `bun_sys::darwin::os_log::signpost::Interval` -// stub whose `end()` is a no-op, so neither tier emits signposts yet. When -// `Bun__signpost_emit` is wired, callers above T0 use `bun_perf::trace`; T0 -// callsites (audited r5) are bundler/parser hot paths where Linux ftrace is -// the profiling target. Windows/other platforms are no-ops in Zig too. pub mod perf { #[cfg(any(target_os = "linux", target_os = "android"))] use core::sync::atomic::AtomicBool; @@ -5433,10 +4732,6 @@ pub mod perf { } } - // Tri-state so the disabled fast path is a single Relaxed load (this sits - // on every `trace()` call across the bundler/parser hot paths). The flag - // is write-once-at-init so Relaxed is sufficient; a benign init race just - // re-runs the env probe. const UNSET: u8 = 0; const DISABLED: u8 = 1; const ENABLED: u8 = 2; @@ -5449,11 +4744,6 @@ pub mod perf { .get() .unwrap_or(false) && Linux::is_supported(); - // macOS: os_signpost requires `bun_sys::darwin::OSLog` (above T0). - // **`bun_perf` is the canonical entry point** (it drives both the - // ftrace and signpost backends via `PerfEvent`); `bun_core::perf` is - // the T0 subset for low-tier callers that cannot reach `bun_perf` and - // only need Linux ftrace. T0 therefore reports disabled on macOS. #[cfg(not(any(target_os = "linux", target_os = "android")))] let on = false; IS_ENABLED.store(if on { ENABLED } else { DISABLED }, Ordering::Relaxed); @@ -5537,10 +4827,6 @@ pub mod perf { } } - /// Single source of truth for the Linux ftrace FFI decls (defined in - /// `src/jsc/bindings/linux_perf_tracing.cpp`). Re-exported so `bun_perf` - /// (the canonical signpost/ftrace entry point) imports these instead of - /// re-declaring them — see src/perf/perf.zig:127-129 for the spec. #[cfg(any(target_os = "linux", target_os = "android"))] pub mod sys { unsafe extern "C" { @@ -5556,13 +4842,6 @@ pub mod perf { } } -// ── form_data ───────────────────────────────────────────────────────────── -// Port of `bun.FormData.{Encoding, AsyncFormData, getBoundary}` (src/runtime/ -// webcore/FormData.zig:16-95). The JSC-touching parts (`toJS`, the field map, -// multipart parser) stay in `bun_runtime::webcore::form_data`; T0 owns only -// the encoding-detection types so `Request`/`Response`/`Body` can name them -// without a runtime→core cycle. Per PORTING.md §JSC: `to_js` is an extension -// method that lives in the higher-tier crate. pub mod form_data { /// `FormData.Encoding` — `union(enum) { URLEncoded, Multipart: []const u8 }`. /// `Multipart` owns its boundary (Zig `AsyncFormData.init` duped it; here @@ -5587,15 +4866,6 @@ pub mod form_data { } } - /// `FormData.getBoundary` — borrow the `boundary=` value out of a - /// `Content-Type` header. Returns `None` on malformed quoting. - /// - /// Parameters are `;`-delimited per RFC 7231 and the parameter *name* must - /// be exactly `boundary`, so a different parameter (`xboundary=FAKE`) or a - /// `boundary=` substring inside another parameter's value is not picked up - /// by an unanchored substring search. A `;` inside a quoted parameter - /// value (RFC 7230 quoted-string, `\` escapes the next byte) does not - /// delimit parameters. pub fn get_boundary(content_type: &[u8]) -> Option<&[u8]> { let mut rest = content_type; loop { @@ -5638,10 +4908,6 @@ pub mod form_data { None } - /// `FormData.AsyncFormData` — heap-allocated, owns its `Encoding`. - /// PORT NOTE: Zig stored `std.mem.Allocator param`; deleted (non-AST - /// crate, global mimalloc per §Allocators). `deinit` becomes `Drop` on the - /// `Box`/`Box<[u8]>` fields — no explicit impl needed. #[derive(Debug)] pub struct AsyncFormData { pub encoding: Encoding, diff --git a/src/bun_core/windows_sys.rs b/src/bun_core/windows_sys.rs index 06b54e2a9c3..6a29f0f2ec7 100644 --- a/src/bun_core/windows_sys.rs +++ b/src/bun_core/windows_sys.rs @@ -35,13 +35,6 @@ pub fn GetStdHandle(std_handle: DWORD) -> Option { } } -// ────────────────────────────────────────────────────────────────────────── -// PEB access (`std.os.windows.peb()`). `bun_core::output::windows_stdio` -// reads `ProcessParameters.hStd{Input,Output,Error}` to snapshot the console -// handles before libuv touches them. Canonical structs/asm live in the tier-0 -// `bun_windows_sys` leaf and are re-exported here for the -// `crate::windows_sys::*` path used by callers. -// ────────────────────────────────────────────────────────────────────────── pub use bun_windows_sys::UNICODE_STRING as UnicodeString; pub use bun_windows_sys::{ CURDIR, Curdir, PEB, PebView, ProcessParameters, RTL_USER_PROCESS_PARAMETERS, TEB, peb, teb, diff --git a/src/bun_core_macros/lib.rs b/src/bun_core_macros/lib.rs index f4947f4288b..840a6166385 100644 --- a/src/bun_core_macros/lib.rs +++ b/src/bun_core_macros/lib.rs @@ -80,14 +80,6 @@ fn eval_literal(expr: &Expr, out: &mut String) -> Result<(), syn::Error> { use bun_output_tags::{RESET, color_for}; -/// 1:1 port of `prettyFmt` from output.zig, plus Zig→Rust format-spec rewrites -/// (`{s}`/`{d}` → `{}`, `{any}`/`{?}` → `{:?}`). -/// -/// Colour table lives in `bun_output_tags`; the state machine is kept duplicated -/// vs `bun_core::output::pretty_fmt_runtime` because the two intentionally -/// diverge in the `{` arm (this side rewrites Zig specs `{s}`→`{}` for the -/// emitted `format_args!` template; runtime copies braces verbatim) and on -/// unknown tags (this side `Err`→`compile_error!`; runtime emits `""`). fn rewrite(fmt: &str, is_enabled: bool) -> Result { let bytes = fmt.as_bytes(); let mut out = String::with_capacity(bytes.len() * 2); @@ -167,10 +159,6 @@ fn rewrite(fmt: &str, is_enabled: bool) -> Result { Ok(out) } -/// `pretty_fmt!("hi {s}", true)` → `"\u{1b}[31mhi {}\u{1b}[0m"` -/// `pretty_fmt!("hi {s}", false)` → `"hi {}"` -/// -/// Expands to a string literal — valid in `format_args!` / `concat!` position. #[proc_macro] pub fn pretty_fmt(input: TokenStream) -> TokenStream { let PrettyFmtInput { fmt, enabled } = parse_macro_input!(input as PrettyFmtInput); @@ -189,32 +177,6 @@ pub fn pretty_fmt(input: TokenStream) -> TokenStream { } } -// ────────────────────────────────────────────────────────────────────────── -// #[derive(CellRefCounted)] / #[derive(ThreadSafeRefCounted)] -// ────────────────────────────────────────────────────────────────────────── -// -// Replaces the former `impl_cell_ref_counted` declarative macro and -// the ~80 hand-written `ref_count: Cell` + `unsafe impl` pairs. The -// derive locates the intrusive refcount field and emits the trait impl, the -// `AnyRefCounted` bridge (so `RefPtr`/`ScopedRef` accept the type), and -// inherent `ref_()`/`deref()` forwarders so existing call sites keep working -// without importing the trait. -// -// Field selection (first match wins): -// 1. a field annotated `#[ref_count]` -// 2. a field literally named `ref_count` -// -// There is no type-based fallback. An earlier draft fell back on "the unique -// field whose type's last path segment is `Cell`", but that matched any -// `Cell<_>` (e.g. `Cell`), turning the helpful "no ref_count field -// found" diagnostic into a buried type-mismatch inside generated code. The -// Zig spec (`@FieldType(T, "ref_count")` in src/ptr/ref_count.zig) requires -// the literal name anyway, so rules 1+2 are sufficient and exhaustive. -// -// Custom destructor: `#[ref_count(destroy = Self::deinit)]` on the struct -// routes the trait's `destroy` to that path instead of the default -// `drop(Box::from_raw(this))`. - /// Locate the refcount field per the rules above. fn find_ref_count_field(fields: &Fields) -> Result<&syn::Ident, syn::Error> { let named = match fields { @@ -375,15 +337,6 @@ pub fn derive_cell_ref_counted(input: TokenStream) -> TokenStream { expanded.into() } -// ────────────────────────────────────────────────────────────────────────── -// #[derive(Anchored)] -// ────────────────────────────────────────────────────────────────────────── -// -// Locates the (unique) field of type `LiveMarker` / `bun_ptr::LiveMarker` / -// `bun_ptr::parent_ref::LiveMarker` (or one annotated `#[live_marker]`) and -// emits the trivial `Anchored` impl. Expands to `::bun_ptr::…` paths so the -// canonical spelling is `#[derive(bun_ptr::Anchored)]`. - fn find_live_marker_field(fields: &Fields) -> Result<&syn::Ident, syn::Error> { let named = match fields { Fields::Named(n) => &n.named, @@ -548,31 +501,6 @@ pub fn derive_thread_safe_ref_counted(input: TokenStream) -> TokenStream { expanded.into() } -// ────────────────────────────────────────────────────────────────────────── -// #[derive(RefCounted)] — intrusive single-thread `RefCount` mixin -// ────────────────────────────────────────────────────────────────────────── -// -// Third sibling of CellRefCounted / ThreadSafeRefCounted. Ports Zig's -// `bun.ptr.RefCount(@This(), "ref_count", destructor, .{ .debug_name = … })` -// comptime mixin (src/ptr/ref_count.zig:67) — the form taken by ~17 Rust -// hand-rolls that all spell out `type DestructorCtx = (); get_ref_count = -// &raw mut (*this).ref_count; destructor = drop(heap::take(this))`. -// -// Struct-level attribute: -// #[ref_count(destroy = )] — `unsafe fn(*mut Self)`; default is -// `drop(::bun_core::heap::take(this))` -// #[ref_count(debug_name = "Name")] — overrides `RefCounted::debug_name()` -// (Zig `.{ .debug_name = … }` option) -// -// Field selection follows the shared `find_ref_count_field` rules (a -// `#[ref_count]`-annotated field, else a field literally named `ref_count`). -// -// Unlike `CellRefCounted` this emits **no** inherent `ref_()`/`deref()` -// forwarders and **no** `AnyRefCounted` impl: `bun_ptr::ref_count` already -// provides a blanket `impl AnyRefCounted for T`, and several -// migrated structs keep their own bespoke `ref_`/`r#ref`/`deref` thin -// wrappers — emitting inherent fns here would collide. - /// Parse the struct-level `#[ref_count(destroy = …, debug_name = "…")]` /// attribute (both keys optional, either order). fn parse_ref_count_attrs( @@ -664,35 +592,6 @@ pub fn derive_ref_counted(input: TokenStream) -> TokenStream { .into() } -// ────────────────────────────────────────────────────────────────────────── -// #[derive(EnumTag)] -// ────────────────────────────────────────────────────────────────────────── -// -// Rust port of Zig's `union(Tag)` / `std.meta.Tag(T)` language built-in. -// Every Zig tagged-union ported to a Rust `enum` lost the implicit -// data→discriminant projection and grew a hand-written -// `fn tag(&self) -> Tag { match self { Self::A(..) => Tag::A, … } }` (14 -// copies, 160+ arms total — see ast/expr.rs, ast/stmt.rs, shell_parser, etc.; -// stmt.rs:466 literally comments "Zig got this for free from `union(Tag)`"). -// -// Two modes: -// -// • `#[enum_tag(existing = path::to::Tag)]` (PRIMARY — used by all 14 -// migrated sites). Emits ONLY the inherent `const fn tag(&self) -> Tag` -// and `From<&Data> for Tag`. The tag type is left untouched — it may be a -// real `enum`, a `#[repr(transparent)] struct Tag(u8)` with sparse -// associated `pub const Variant: Tag`, or anything else that exposes a -// `Tag::Variant` per data variant. No `From for &'static str`, no -// iterator — those belong on the existing tag type if needed. -// -// • bare `#[derive(EnumTag)]` — also generates a fresh -// `pub enum Tag { … }` mirror (one fieldless variant per data -// variant). Unused by the current dedup but kept for future ports that -// don't already have a hand-written tag enum to point at. -// -// The emitted `tag()` is `pub const fn` and matches every variant shape -// (unit / tuple / struct) by using `Self::V { .. }` arms. - /// `#[derive(EnumTag)]` — see module comment above. #[proc_macro_derive(EnumTag, attributes(enum_tag))] pub fn derive_enum_tag(input: TokenStream) -> TokenStream { diff --git a/src/bun_output_tags/lib.rs b/src/bun_output_tags/lib.rs index 3b5859c4d65..1a26cc6a7c4 100644 --- a/src/bun_output_tags/lib.rs +++ b/src/bun_output_tags/lib.rs @@ -9,11 +9,6 @@ #![no_std] -/// Named ANSI SGR escape sequences. One canonical literal per colour/attribute; -/// every other crate aliases this module rather than re-declaring the bytes. -/// -/// `WHITE` is SGR 37 (normal). printDiff.zig:177 uses SGR 97 — that is -/// [`BRIGHT_WHITE`], kept distinct so diff output stays byte-identical. pub mod ansi { pub const RESET: &str = "\x1b[0m"; pub const BOLD: &str = "\x1b[1m"; @@ -35,10 +30,6 @@ pub mod ansi { pub(crate) const BG_GREEN: &str = "\x1b[42m"; } -/// Byte-slice views of [`ansi`] for callers that write into `&[u8]` buffers -/// (md ANSI renderer, stack-frame colour codes). `str::as_bytes` is a `const -/// fn`, so each constant is the *same* static storage as its `&str` twin — -/// no second copy of the escape bytes is emitted. pub mod ansi_b { use super::ansi; pub const RESET: &[u8] = ansi::RESET.as_bytes(); diff --git a/src/bundler/AstBuilder.rs b/src/bundler/AstBuilder.rs index e728bea1731..54bbd454029 100644 --- a/src/bundler/AstBuilder.rs +++ b/src/bundler/AstBuilder.rs @@ -56,12 +56,6 @@ pub struct AstBuilder<'a, 'bump> { pub hmr_api_ref: Ref, } -// stub fields for ImportScanner duck typing -// -// Zig used `comptime` zero-sized fields (`options`, `import_items_for_namespace`) -// and a `parser_features` decl so `ImportScanner.scan` could duck-type over both -// the real parser and `AstBuilder`. In Rust this becomes a trait that both impl. -// TODO(port): define `ImportScannerHost` trait in `bun_js_parser` and impl it here. impl<'a, 'bump> AstBuilder<'a, 'bump> { // stub for ImportScanner duck typing — Zig: `comptime import_items_for_namespace: struct { fn get(_, _) ?Map { return null; } }` pub fn import_items_for_namespace_get( @@ -273,10 +267,6 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { Ok(ref_) } - // PORT NOTE: returns `BundledAst<'static>` (== `JSAst`) directly. The only - // `'arena`-carrying field, `url_for_css`, is always set to `b""` here, and - // every other field stores arena data via raw pointers / `StoreSlice`, so - // nothing borrows `&mut self` past this call. pub fn to_bundled_ast( &mut self, target: options::Target, @@ -286,13 +276,6 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { let module_scope = self.current_scope; let mut parts = bun_ast::PartList::with_capacity_in(2, self.bump); - // PORT NOTE: Zig grew len then wrote `parts.mut(i).* = ...`, which is a - // bitwise store on the SoA slot. In Rust `*parts.mut_(i) = ...` first - // *drops* the (uninitialized) prior `Part` — and `Part` carries Drop - // fields (`Vec`/`HashMap`), so that drop frees garbage and corrupts the - // heap (observed downstream as `printStmt` reading a junk `Stmt` - // discriminant from an arena allocation that was clobbered). Append - // into the reserved capacity instead so no drop runs. parts.push(Part::default()); parts.push(Part { // overwritten below with the arena-backed copy (`stmts_in_bump`) @@ -323,41 +306,12 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { let module_scope_ref = unsafe { &*module_scope }; let generated_len = module_scope_ref.generated.len(); top_level_symbols_to_parts.ensure_total_capacity(generated_len)?; - // PORT NOTE: reshaped — Zig grew `entries` then wrote keys/values columns - // in lockstep + `reIndex`. Rust `ArrayHashMap` keeps keys/values in private - // `Vec`s and rebuilds hashes on every `put_assume_capacity`, so a plain - // pre-reserved insert loop is equivalent (and `re_index` is a no-op here). - // Zig shallow-copied a single `Vec(u32){1}`; `Vec` is move-only - // in Rust, so allocate a fresh one per key. for &ref_ in module_scope_ref.generated.slice() { top_level_symbols_to_parts .put_assume_capacity(ref_, bun_alloc::AstAlloc::vec_from_slice(&[1])); } top_level_symbols_to_parts.re_index()?; - // For more details on this section, look at js_parser.toAST - // This is mimicking how it calls ImportScanner - // - // PORT NOTE: Zig duck-typed `ImportScanner.scan(AstBuilder, ...)` and - // `ConvertESMExportsForHmr.{convertStmt,finalize}` over `AstBuilder` - // via `anytype`. The Rust `ImportScanner` is currently monomorphized - // over the concrete `P<'_, TS, J, SCAN>` parser only (see - // ImportScanner.rs:30), so the equivalent transform is open-coded here - // for the stmt shapes `AstBuilder` callers actually emit (`S.Import`, - // `S.Local{is_export}`, `S.ExportDefault(expr)`). Without this, the - // generated server-component proxy keeps raw `export` keywords inside - // the HMR function wrapper and JSC rejects the chunk with - // `SyntaxError: Unexpected keyword 'export'`. - // TODO(port): replace with a `ParserLike` trait so the real - // `ImportScanner`/`ConvertESMExportsForHmr` can accept `AstBuilder`. - // - // PORT NOTE: Zig assigned `p.stmts.items` directly — its - // `ArrayListUnmanaged` storage is owned by `worker.allocator` and - // outlives the `AstBuilder` stack value. Rust's `Vec` would - // drop with `self`, leaving `parts[1].stmts` dangling once the - // builder goes out of scope (UAF in the printer). Copy the `Copy` - // `Stmt`s/`*mut Scope`s into the bump arena so the returned - // `BundledAst` owns them with parser-arena lifetime. if self.hot_reloading { // get a estimate on how many statements there are going to be let prealloc_count = self.stmts.len() + 2; @@ -396,11 +350,6 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { hmr_stmts.push(*stmt); } bun_ast::StmtData::SLocal(mut st) if st.is_export => { - // convertStmt: strip `export`, then visitBindingToExport - // for each decl. AstBuilder only emits `B.Identifier` - // bindings with `kind != .import` and - // `has_been_assigned_to == false`, so the simple - // `'abc,'` arm of visitRefToExport applies. st.is_export = false; for i in 0..st.decls.len_u32() as usize { let binding = st.decls.slice()[i].binding; @@ -514,14 +463,6 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { let stmts_in_bump: &mut [Stmt] = self.bump.alloc_slice_copy(hmr_stmts.as_slice()); parts[1].stmts = bun_ast::StoreSlice::new_mut(stmts_in_bump); } else { - // Non-HMR path: mirror `ImportScanner.scan(AstBuilder, p, stmts, - // false, false, {})` for the stmt shapes AstBuilder callers emit - // (`S.Import`, `S.Local{is_export}`, `S.ExportDefault`). The Zig - // duck-typed scanner is what populates `named_exports` / - // `named_imports` / `import_records_for_current_part`; without it - // the linker can't bind imports against this generated module - // (e.g. `import { ssrManifest } from "bun:bake/server"` → - // "No matching export"). See PORT NOTE above re: monomorphization. let in_stmts = core::mem::take(&mut self.stmts); for stmt in in_stmts.iter() { match stmt.data { @@ -593,10 +534,6 @@ impl<'a, 'bump> AstBuilder<'a, 'bump> { flags: Default::default(), target, top_level_await_keyword: Range::NONE, - // .nested_scope_slot_counts = if (p.options.features.minify_identifiers) - // renamer.assignNestedScopeSlots(p.arena, p.scopes.items[0], p.symbols.items) - // else - // js_ast.SlotCounts{}, nested_scope_slot_counts: Default::default(), hashbang: b"".into(), css: None, diff --git a/src/bundler/BundleThread.rs b/src/bundler/BundleThread.rs index 5e4c689e785..37e3ed8b266 100644 --- a/src/bundler/BundleThread.rs +++ b/src/bundler/BundleThread.rs @@ -12,18 +12,8 @@ use crate::{BundleV2, Transpiler}; #[cfg(windows)] pub(crate) extern "C" fn timer_callback(_: *mut bun_sys::windows::libuv::Timer) {} -/// Port of `std.Thread.ResetEvent` — single-shot manual-reset event used to -/// block `spawn()` until the bundle thread has initialized its `Waker`. -// PORT NOTE: re-export `bun_threading::ResetEvent` (futex-backed); the local -// `wait()`/`set()`/`Default` API is identical, and the futex impl preserves -// the "set-before-wait does not deadlock" property the parking_lot draft had. pub use bun_threading::ResetEvent; -/// Result of a `Bun.build` invocation handed back to the JS thread. -// PORT NOTE: mirrors `BundleV2.JSBundleCompletionTask.Result` (bundle_v2.zig). -// Defined here (not re-exported from `bundle_v2`) because the un-gated -// `bundle_v2` module keeps the draft body private; T6 (`bundler_jsc`) consumes -// this via the `CompletionStruct` trait. pub struct BuildResult { pub output_files: Vec, pub metafile: Option>, @@ -36,17 +26,6 @@ pub enum BundleV2Result { Value(BuildResult), } -/// Originally, bake.DevServer required a separate bundling thread, but that was -/// later removed. The bundling thread's scheduling logic is generalized over -/// the completion structure. -/// -/// CompletionStruct's interface: -/// -/// - `configureBundler` is used to configure `Bundler`. -/// - `completeOnBundleThread` is used to tell the task that it is done. -// PORT NOTE: trait bound lives on the `impl` (not the struct) so the -// `singleton` static can name `BundleThread` before T6 -// provides the `CompletionStruct` impl for the forward-decl. pub struct BundleThread { pub waker: Async::Waker, pub ready_event: ResetEvent, @@ -56,20 +35,7 @@ pub struct BundleThread { pub generation: bun_core::Generation, } -/// Trait capturing the interface the Zig `CompletionStruct: type` parameter -/// must satisfy. -/// -/// Zig used comptime duck typing and additionally asserted (via `@compileError`) -/// that the *only* valid instantiation is `BundleV2.JSBundleCompletionTask`. -/// The body of `generateInNewThread` directly touched JSBundleCompletionTask -/// fields (`.result`, `.log`, `.plugins`, `.config.files`, `.transpiler`); in -/// Rust those become trait accessors so the generic `BundleThread` stays -/// layout-agnostic. The concrete impl lives in T6 (`bun_bundler_jsc`). pub trait CompletionStruct: Node + Send + 'static { - /// Zig: `completion.configureBundler(transpiler, arena)` — `arena` - /// is the per-build mimalloc heap that backs `transpiler`, so the two - /// share lifetime `'a` (option fields like `optimize_imports: &'a StringSet` - /// borrow from `bump`). fn configure_bundler<'a>( &mut self, transpiler: &mut Transpiler<'a>, @@ -88,43 +54,14 @@ pub trait CompletionStruct: Node + Send + 'static { /// Zig: `if (completion.config.files.map.count() > 0) &completion.config.files else null` /// — folded into a single accessor so the opaque `FileMap` layout stays in T6. fn file_map(&mut self) -> Option>; - /// Zig: `switch (CompletionStruct) { BundleV2.JSBundleCompletionTask => completion, … }` - /// — the comptime type-switch collapses to a §Dispatch handle (erased - /// owner + `&'static` vtable) the impl provides, so the bundler can read - /// `result == .err` / `jsc_event_loop.enqueueTaskConcurrent` without - /// naming the concrete struct. fn as_js_bundle_completion_task(&mut self) -> dispatch::CompletionHandle; - /// Zig: `const transpiler = try arena.create(bun.Transpiler);` followed by - /// `try completion.configureBundler(transpiler, arena);` — Zig left the - /// `Transpiler` `undefined` and let `configureBundler` initialize it in place. - /// Rust's `Transpiler<'a>` has borrow-carrying fields (`arena: &'a Arena`, - /// `resolver: Resolver<'a>`) that cannot be zero-init'd, so the allocate + - /// configure pair is folded into one trait call returning the - /// arena-allocated, fully-configured transpiler. - // The returned `&'a mut Transpiler<'a>` is arena-allocated via `bump.alloc(...)` - // (bumpalo `Bump`), which hands out `&mut` from `&self` through interior - // mutability — the standard arena pattern `mut_from_ref` cannot see through. #[allow(clippy::mut_from_ref)] fn create_and_configure_transpiler<'a>( &mut self, bump: &'a Arena, ) -> Result<&'a mut Transpiler<'a>, bun_core::Error>; - /// Zig: `try BundleV2.init(transpiler, null, arena, jsc.AnyEventLoop.init(arena), - /// false, jsc.WorkPool.get(), heap)` → wire `this.{plugins,completion,file_map}` - /// from `self` → `completion.transpiler = this` → `this.runFromJSInNewThread(...)` - /// → on success `self.set_result(Value(..))` → `this.deinitWithoutFreeingArena()`; - /// on error drain `this.linker.source_maps.*_wait_group` then deinit. - /// - /// PORT NOTE: the `BundleV2` impl does not yet expose `init` / - /// `run_from_js_in_new_thread` (pending the linker pipeline). The Zig - /// `@compileError` already - /// proves this body is `JSBundleCompletionTask`-specific, so the - /// construction + run is delegated to the trait impl in T6, which has - /// access to the concrete event-loop / work-pool wiring. The shared - /// scaffolding (arena, AST arena push/pop, log copy, - /// `completeOnBundleThread`) stays in `generate_in_new_thread` below. fn init_and_run<'a>( &mut self, transpiler: &'a mut Transpiler<'a>, @@ -137,14 +74,6 @@ pub trait CompletionStruct: Node + Send + 'static { } impl BundleThread { - /// To initialize, put this somewhere in memory, and then call `spawn()` - // PORT NOTE: Zig `uninitialized` left `waker` as `undefined`. We can't use - // `mem::zeroed()` here — on macOS `Waker` holds a `Box<[u8]>` and on - // Windows a `&'static` loop, both of which have a NonNull validity - // invariant (zeroing them is *language-level* UB even if never read). - // `placeholder()` yields a fully-initialized inert value instead. - // `ready_event.wait()` in `spawn()` blocks until `thread_main` overwrites - // it via `ptr::write`, so the placeholder is never observed live. pub fn uninitialized() -> Self { Self { #[cfg(unix)] @@ -225,12 +154,6 @@ impl BundleThread { // SAFETY: raw-ptr field projection; spawning thread is blocked in `ready_event.wait()`. unsafe { (*instance).ready_event.set() }; - // PORT NOTE: libuv Timer lives on stack for the lifetime of this never-returning fn. - // It MUST be declared at function scope (not inside the `#[cfg(windows)] { ... }` - // block below) because `timer.init()`/`timer.start()` register `&timer`'s address - // into the uv loop's intrusive handle queue / timer min-heap, and `waker.wait()` - // (→ `uv_run`) in the `loop {}` below dereferences that address. Matches Zig spec - // (BundleThread.zig:77) which hoists `var timer` to `threadMain` scope. #[cfg(windows)] let mut timer: bun_sys::windows::libuv::Timer = bun_core::ffi::zeroed(); #[cfg(windows)] @@ -307,11 +230,6 @@ impl BundleThread { transpiler.resolver.generation = generation; - // Zig: `const this = try BundleV2.init(transpiler, null, arena, - // jsc.AnyEventLoop.init(arena), false, jsc.WorkPool.get(), heap);` - // followed by field wiring + `runFromJSInNewThread`. Delegated — see - // `init_and_run` doc. Reborrow `transpiler` through a raw ptr so - // `completion` can be borrowed again below (Zig stored `*Transpiler`). let transpiler_ptr: *mut Transpiler<'_> = transpiler; let run = completion.init_and_run( // SAFETY: `transpiler` lives in `bump` for the duration of `heap`. @@ -322,13 +240,6 @@ impl BundleThread { std::ptr::from_ref(bun_threading::work_pool::WorkPool::get()).cast_mut(), ); - // PORT NOTE: Zig's overlapping `defer { ast_memory_store.pop(); - // this.deinitWithoutFreeingArena(); }` + `errdefer { wait_groups; copy log }` - // captured ≥2 disjoint &mut borrows. Restructured as straight-line: log copy - // runs on both paths; `completeOnBundleThread` only on success (the error - // path's `set_result(Err)` + complete happens in `thread_main`). The - // `deinitWithoutFreeingArena` + wait-group drain live inside `init_and_run` - // (it owns `this`). let mut out_log = bun_ast::Log::init(); // SAFETY: `transpiler.log` is the arena-allocated `*mut Log` set up by // `configure_bundler`; valid for the lifetime of `heap`. Raw deref so the @@ -373,23 +284,9 @@ impl BundleThread { } } -/// Lazily-initialized singleton. This is used for `Bun.build` since the -/// bundle thread may not be needed. -// PORT NOTE: Zig had a per-monomorphization `singleton` struct with -// `static var instance`. Rust forbids generic statics, so the storage is -// type-erased (`*mut ()`) and the accessor functions are generic over `C`. -// The Zig source already `@compileError`s for any `CompletionStruct` other than -// `JSBundleCompletionTask`, so in practice exactly one `C` is ever used and the -// erased static is sound. T6 (`bun_bundler_jsc`) calls these with its concrete -// completion-task type. pub mod singleton { use super::*; - /// `Send + Sync` newtype around the leaked `BundleThread` allocation so it - /// can sit inside a `OnceLock`. Type-erased because Rust forbids generic - /// statics; see module comment. Stored as a raw pointer (not `&'static`) - /// because the bundle thread mutates `*self` concurrently — callers must - /// only ever project fields via raw-pointer access. struct Instance(NonNull<()>); // SAFETY: the allocation is a leaked `Box>` valid for // `'static`; cross-thread access is mediated entirely through diff --git a/src/bundler/Chunk.rs b/src/bundler/Chunk.rs index b5e92040cbe..01282d073c1 100644 --- a/src/bundler/Chunk.rs +++ b/src/bundler/Chunk.rs @@ -4,16 +4,11 @@ use core::fmt; use std::io::Write as _; use bun_alloc::AllocError; +use bun_ast::Index; use bun_ast::{ImportKind, ImportRecord}; use bun_ast::{Ref, Stmt}; use bun_collections::{ArrayHashMap, AutoBitSet, VecExt}; use bun_core::{FeatureFlags, Output}; -// PORT NOTE: `bun.ast.Index` is mirrored as both `crate::Index` -// (`bun_ast::Index`) and `bun_ast::Index` via a -// TYPE_ONLY split. `CssImportOrderKind::SourceIndex` carries the js_parser -// flavor because its sole producer (`findImportedFilesInCSSOrder`) constructs -// it from parser-side indices; all consumers only call `.get()`. -use bun_ast::Index; use bun_core::{immutable as strings, string_joiner::StringJoiner}; use bun_sourcemap as source_map; @@ -45,12 +40,6 @@ pub struct Chunk { /// for more info on this technique. pub unique_key: &'static [u8], - /// Maps source index to bytes contributed to this chunk's output (for metafile). - /// The value is updated during parallel chunk generation to track bytesInOutput. - /// CONCURRENCY: the key set is frozen before codegen starts; worker threads - /// only `fetch_add` the per-source counters (see - /// `generate_compile_result_for_{js,css}_chunk`), so the value type is - /// `AtomicUsize` rather than `usize` to avoid materializing aliased `&mut`. pub files_with_parts_in_chunk: ArrayHashMap, /// We must not keep pointers to this type until all chunks have been allocated. @@ -76,10 +65,6 @@ pub struct Chunk { pub intermediate_output: IntermediateOutput, pub isolated_hash: u64, - // TODO(port): was `= undefined` in Zig (set before use). The Zig field is - // the `renamer.Renamer` union; the Rust enum borrows from the symbol table - // (`Renamer<'r,'src>`), which can't live in a 'static-ish struct yet. - // `ChunkRenamer` is an owned-erased placeholder (see `crate::bun_renamer`). pub renamer: bun_renamer::ChunkRenamer, pub compile_results_for_chunk: CompileResultSlots, @@ -142,15 +127,6 @@ unsafe impl Send for Chunk {} // the Zig single-pointer fan-out it ports. unsafe impl Sync for Chunk {} -/// Disjoint-slot output buffer for [`Chunk::compile_results_for_chunk`]. -/// -/// Allocated single-threaded in `generate_chunks_in_parallel` *before* the -/// `generate_compile_result_for_*_chunk` fan-out, written concurrently by -/// worker threads at **disjoint** indices (one slot per `PendingPartRange.i`), -/// then read single-threaded after `worker_pool.wait_for_all()`. Wrapping each -/// slot in `UnsafeCell` makes the per-task write sound through a shared view — -/// worker callbacks never need to materialize an aliased `&mut Chunk` or -/// `&mut [CompileResult]` to publish their result. #[derive(Default)] #[repr(transparent)] pub struct CompileResultSlots(Box<[UnsafeCell]>); @@ -243,11 +219,6 @@ impl Chunk { // Project to the slots field with no intermediate `&`/`&mut Chunk`. let slots: *mut CompileResultSlots = core::ptr::addr_of_mut!((*chunk).compile_results_for_chunk); - // `CompileResultSlots` is `repr(transparent)` over - // `Box<[UnsafeCell]>`; reading the boxed-slice fat - // pointer in place (no move/drop) yields `*mut [UnsafeCell<_>]` - // without forming `&Box`. `Box` is documented to have the same - // layout/ABI as `*mut T` (and `NonNull`). let cells: *mut [UnsafeCell] = core::ptr::read(slots.cast::<*mut [UnsafeCell]>()); debug_assert!( @@ -291,12 +262,6 @@ impl Chunk { } pub fn get_css_chunk_for_html<'a>(&self, chunks: &'a mut [Chunk]) -> Option<&'a mut Chunk> { - // Look up the CSS chunk via the JS chunk's css_chunks indices. - // This correctly handles deduplicated CSS chunks that are shared - // across multiple HTML entry points (see issue #23668). - // PORT NOTE: reshaped for borrowck — Zig calls getJSChunkForHTML(chunks) and then - // indexes into the same `chunks`. Here we scan immutably for the JS chunk, copy the - // css-chunk index into a local, drop the borrow, then re-borrow mutably. let entry_point_id = self.entry_point.entry_point_id(); let css_idx: Option = 'find: { for other in chunks.iter() { @@ -361,17 +326,8 @@ impl Order { } } -/// TODO: rewrite this -/// This implementation is just slow. -/// Can we make the JSPrinter itself track this without increasing -/// complexity a lot? #[derive(Default)] pub enum IntermediateOutput { - /// If the chunk has references to other chunks, then "pieces" contains - /// the contents of the chunk. Another joiner will have to be - /// constructed later when merging the pieces together. - /// - /// See OutputPiece's documentation comment for more details. Pieces(OutputPieces), /// If the chunk doesn't have any references to other chunks, then @@ -383,17 +339,6 @@ pub enum IntermediateOutput { Empty, } -/// Owns the joined output buffer alongside the `OutputPiece` slices that -/// point into it. -/// -/// PORT NOTE: In Zig, `breakOutputIntoPieces` calls `j.done(alloc)` with the -/// per-worker arena, so the joined buffer outlives the chunk by construction -/// and `OutputPiece.data` stays valid. The Rust `StringJoiner::done()` -/// returns a `Box<[u8]>`; if that box is dropped at the end of -/// `break_output_into_pieces`, every piece's `data` slice dangles (ASAN -/// use-after-poison in `generate_isolated_hash`). Keep the box alive next to -/// the pieces so their raw-pointer slices remain valid for the chunk's -/// lifetime. pub struct OutputPieces { pieces: Vec, /// Backing storage for every `OutputPiece::data` in `pieces`. @@ -426,24 +371,10 @@ pub struct CodeResult { pub shifts: Vec, } -// PORT NOTE: Zig used `std.mem.Allocator`; the Rust crate exposes a global -// mimalloc — we don't need a vtable here yet. `()` is kept as a token so the -// caller's `Option<&DynAlloc>` plumbing matches the Zig signature; the actual -// allocation goes through `alloc_buf` (global mimalloc) regardless. Real -// arena threading (page_allocator vs default_allocator) lands when -// `bun_alloc::Allocator` is a stable trait object. type DynAlloc = (); -/// `arena.alloc(u8, n)` — until `DynAlloc` is a real trait object, route -/// through the global arena. PERF(port): Zig picked page_allocator for -/// `n >= 512KiB`; mimalloc handles large allocations via mmap already so this -/// is a behavior match in practice. #[inline] fn alloc_buf(_arena: DynAlloc, n: usize) -> Result, AllocError> { - // Zero-fill is required for soundness: `set_len` over uninit bytes violates - // `Vec`'s safety contract, and `into_boxed_slice` may shrink-realloc (memcpy - // of uninit). The memset cost is negligible next to the subsequent memcpy - // that fully overwrites the buffer. let mut v: Vec = Vec::new(); v.try_reserve_exact(n).map_err(|_| AllocError)?; v.resize(n, 0); @@ -585,10 +516,6 @@ impl IntermediateOutput { } } - /// Like `code()` but with standalone HTML support. - /// When `standalone_chunk_contents` is provided, chunk piece references are - /// resolved to inline code content instead of file paths. Asset references - /// are resolved to data: URIs from url_for_css. #[allow(clippy::too_many_arguments)] pub fn code_standalone<'d>( &mut self, @@ -648,11 +575,6 @@ impl IntermediateOutput { force_absolute_path: bool, standalone_chunk_contents: Option<&[Option>]>, ) -> Result { - // `Graph.input_files` SoA accessors live in `Graph::InputFileColumns`; - // `LinkerGraph.files` SoA (`items_entry_point_chunk_index`) lands with - // the LinkerGraph work. `bun_paths` / `bun_core::fmt::count` / - // `bun_alloc::alloc_slice` surfaces are tracked upstream. - // TODO(port): MultiArrayList SoA accessors — assuming `.items(.field)` → method returning slice let additional_files = graph.input_files.items_additional_files(); let unique_key_for_additional_files = graph.input_files.items_unique_key_for_additional_file(); @@ -938,12 +860,6 @@ impl IntermediateOutput { _ => unreachable!(), }; - // normalize windows paths to '/' - // Zig does `@constCast(file_path)` and mutates the bundler-owned - // storage in place. In Rust the source slices are reachable only - // through `&Graph` / `&[Chunk]` here; materialising `&mut` from a - // shared-provenance pointer is UB regardless of whether the write - // happens. Copy into a pooled scratch buffer and normalise that. let file_path: &[u8] = { let n = file_path.len(); let dst = &mut file_path_buf[..n]; @@ -1063,24 +979,6 @@ impl IntermediateOutput { } } -/// An issue with asset files and server component boundaries is they -/// contain references to output paths, but those paths are not known until -/// very late in the bundle. The solution is to have a magic word in the -/// bundle text (BundleV2.unique_key, a random u64; impossible to guess). -/// When a file wants a path to an emitted chunk, it emits the unique key -/// in hex followed by the kind of path it wants: -/// -/// `74f92237f4a85a6aA00000009` --> `./some-asset.png` -/// ^--------------^|^------- .query.index -/// unique_key .query.kind -/// -/// An output piece is the concatenation of source code text and an output -/// path, in that order. An array of pieces makes up an entire file. -/// -/// PORT NOTE: Zig split ptr+u32 len to shave 8 bytes. The Rust port stores a -/// `RawSlice` (encapsulates the unsafe re-borrow) — the per-chunk piece count -/// is bounded by the number of unique-key boundaries, so the extra word per -/// piece is negligible against the safety win. pub(crate) struct OutputPiece { /// Borrows `OutputPieces::_buffer`; `RawSlice` invariant (backing outlives /// holder) is upheld by `OutputPieces` keeping the box alongside `pieces`. @@ -1183,11 +1081,6 @@ pub(crate) const UNIQUE_KEY_PREFIX_LEN: usize = 16; /// Total byte length of a [`UniqueKey`] on the wire: `hex16 + KIND + idx08`. pub(crate) const UNIQUE_KEY_LEN: usize = UNIQUE_KEY_PREFIX_LEN + 1 + 8; -/// 25-byte unique-key wire format `{hex16(prefix)}{KIND}{index:08}` shared by -/// every emitter (ParseTask file/napi/sqlite loaders, server-component -/// boundaries, HTML-import manifest, chunk IDs) and consumed by exactly one -/// scanner (`LinkerContext::break_output_into_pieces`). Mirrors Zig -/// `"{f}{LETTER}{d:0>8}"` with `bun.fmt.hexIntLower` byte-for-byte. #[derive(Clone, Copy)] pub(crate) struct UniqueKey { pub prefix: u64, @@ -1291,11 +1184,6 @@ pub struct JavaScriptChunk { pub cross_chunk_prefix_stmts: Vec, pub cross_chunk_suffix_stmts: Vec, - /// Indexes to CSS chunks. Currently this will only ever be zero or one - /// items long, but smarter css chunking will allow multiple js entry points - /// share a css file, or have an entry point contain multiple css files. - /// - /// Mutated while sorting chunks in `computeChunks` pub css_chunks: Box<[u32]>, /// Serialized ModuleInfo for ESM bytecode (--compile --bytecode --format=esm) @@ -1306,33 +1194,17 @@ pub struct JavaScriptChunk { pub struct CssChunk { pub imports_in_chunk_in_order: Vec, - /// When creating a chunk, this is to be an uninitialized slice with - /// length of `imports_in_chunk_in_order` - /// - /// Multiple imports may refer to the same file/stylesheet, but may need to - /// wrap them in conditions (e.g. a layer). - /// - /// When we go through the `prepareCssAstsForChunk()` step, each import will - /// create a shallow copy of the file's AST (just dereferencing the pointer). pub asts: Box<[bun_css::BundlerStyleSheet]>, } impl Drop for CssChunk { fn drop(&mut self) { - // Zig `asts: []BundlerStyleSheet` is an arena slice of bitwise shallow - // copies (see `prepareCssAstsForChunk` `ptr::read`). Multiple slots may - // alias the same source AST's heap buffers when a file is imported more - // than once, so element-wise drop would double-free. let mut asts = core::mem::take(&mut self.asts).into_vec(); // SAFETY: `set_len(0)` then `Vec::drop` frees the slab without running element destructors. unsafe { asts.set_len(0) }; } } -/// Zig: `const CssImportKind = enum { source_index, external_path, import_layers }` is the -/// (private) tag enum for `CssImportOrder.kind: union(enum) { ... }`. In Rust the tagged -/// union is `CssImportOrderKind`; callers that switch on `css_import.kind` reference it via -/// the Zig-spelled name, so re-export it here. pub type CssImportKind = CssImportOrderKind; pub struct CssImportOrder { @@ -1344,24 +1216,12 @@ pub struct CssImportOrder { impl Drop for CssImportOrder { fn drop(&mut self) { - // `conditions`: bitwise-shared across multiple order entries by - // `findImportedFilesInCSSOrder` (`bitwise_copy(wrapping_conditions)`); - // freeing here would double-free. The slab is allocated from the - // `LinkerGraph` arena and is bulk-freed with it. let _ = core::mem::ManuallyDrop::new(core::mem::take(&mut self.conditions)); - // `condition_import_records`: every populated value is uniquely owned - // (moved `all_import_records`) or an empty-Vec bitwise copy (cap == 0, - // drop is a no-op). Normal drop frees the owned buffers; no - // double-free path exists. } } #[derive(strum::IntoStaticStr)] pub enum CssImportOrderKind { - /// Represents earlier imports that have been made redundant by later ones (see `isConditionalImportRedundant`) - /// We don't want to redundantly print the rules of these redundant imports - /// BUT, the imports may include layers. - /// We'll just print layer name declarations so that the original ordering is preserved. #[strum(serialize = "layers")] Layers(Layers), #[strum(serialize = "external_path")] @@ -1370,11 +1230,6 @@ pub enum CssImportOrderKind { SourceIndex(Index), } -// TODO(port): bun.ptr.Cow(Vec, { copy = deepCloneInfallible, deinit = clearAndFree }) -// LayerName payload allocations live in the arena, so the Zig deinit is a shallow clearAndFree. -// `std::borrow::Cow<'_, Vec<_>>` requires `Vec: Clone` (not implemented). Port the -// Zig `bun.ptr.Cow` shape directly: a tag + raw pointer for the borrowed arm. Should -// thread `'bump` (arena-borrowed) and confirm Clone semantics match deepCloneInfallible. pub enum Layers { /// Borrowed from another `CssImportOrder`'s `Layers` or the parsed stylesheet. Borrowed(bun_ptr::BackRef>), @@ -1390,15 +1245,6 @@ impl Layers { } } - /// Zig: `Chunk.CssImportOrder.Layers.borrow(ptr)` — Cow::Borrowed. - /// - /// Takes `NonNull` (not `&Vec`) because the sole caller in - /// `findImportedFilesInCSSOrder.rs` type-puns the lifetime-erased shadow - /// `crate::bun_css::LayerName` to the real `::bun_css::LayerName` via a - /// raw-pointer cast — that nominal-type erasure cannot go through `&`. - /// The pointee is arena-owned storage that outlives the chunk pipeline - /// (see TODO(port) above re: `'bump`); `BackRef` encapsulates that - /// invariant so `inner()`/`to_owned()` deref sites are safe. #[inline] pub(crate) fn borrow(p: core::ptr::NonNull>) -> Self { Layers::Borrowed(bun_ptr::BackRef::from(p)) @@ -1475,11 +1321,6 @@ impl CssImportOrder { #[allow(dead_code)] pub(crate) struct CssImportOrderDebug<'a, 'ctx> { inner: &'a CssImportOrder, - // PORT NOTE: split lifetimes — `LinkerContext<'ctx>` is invariant over `'ctx`, - // so coupling the borrow lifetime to the struct param (`&'a LinkerContext<'a>`) - // forces every caller's `&CssImportOrder` and `&LinkerContext` to share one - // region. The Display impl only reads `ctx.parse_graph` (a raw `*mut Graph`), - // so the inner `'ctx` need not relate to `'a`. ctx: &'a LinkerContext<'ctx>, } diff --git a/src/bundler/Graph.rs b/src/bundler/Graph.rs index 824d7ba2fb4..a3308c46505 100644 --- a/src/bundler/Graph.rs +++ b/src/bundler/Graph.rs @@ -18,13 +18,6 @@ use bun_ast::Index; pub(crate) use crate::IndexInt; pub struct Graph<'a> { - // TODO(port): lifetime — no direct LIFETIMES.tsv row for Graph.pool, but row 170 - // (ThreadPool.v2, BACKREF) evidence states "BundleV2.graph.pool owns ThreadPool". - // bundle_v2.zig:992 allocates it from `this.arena()` (the `self.heap` arena) and - // bundle_v2.zig:2248 calls `pool.deinit()`, so this is arena-owned but self-referential - // (sibling field). `BackRef` (not raw `NonNull`) so the read accessor `pool()` is - // safe — the BACKREF invariant (pointee outlives holder) holds for the entire - // bundle pass. pub pool: bun_ptr::BackRef, pub heap: &'a ThreadLocalArena, @@ -35,30 +28,9 @@ pub struct Graph<'a> { pub entry_point_original_names: IndexStringMap, /// Every source index has an associated InputFile pub input_files: MultiArrayList, - /// Every source index has an associated Ast - /// When a parse is in progress / queued, it is `Ast.empty` - // PORT NOTE: BundledAst<'arena> borrows from self.heap (sibling-field self-ref); - // 'static here is a placeholder. TODO(refactor): thread the lifetime via raw ptr or Ouroboros. pub ast: MultiArrayList>, - /// During the scan + parse phase, this value keeps a count of the remaining - /// tasks. Once it hits zero, the scan phase ends and linking begins. Note - /// that if `deferred_pending > 0`, it means there are plugin callbacks - /// to invoke before linking, which can initiate another scan phase. - /// - /// Increment and decrement this via `incrementScanCounter` and - /// `decrementScanCounter`, as asynchronous bundles check for `0` in the - /// decrement function, instead of at the top of the event loop. - /// - /// - Parsing a file (ParseTask and ServerComponentParseTask) - /// - onResolve and onLoad functions - /// - Resolving an onDefer promise pub pending_items: u32, - /// When an `onLoad` plugin calls `.defer()`, the count from `pending_items` - /// is "moved" into this counter (pending_items -= 1; deferred_pending += 1) - /// - /// When `pending_items` hits zero and there are deferred pending tasks, those - /// tasks will be run, and the count is "moved" back to `pending_items` pub deferred_pending: u32, /// A map of build targets to their corresponding module graphs. @@ -68,10 +40,6 @@ pub struct Graph<'a> { /// files. This happens for all files with a "use " directive. pub server_component_boundaries: server_component_boundary::List, - /// Track HTML imports from server-side code - /// Each entry represents a server file importing an HTML file that needs a client build - /// - /// OutputPiece.Kind.HTMLManifest corresponds to indices into the array. pub html_imports: HtmlImports, pub estimated_file_loader_count: usize, @@ -181,22 +149,8 @@ impl<'a> Graph<'a> { } impl<'a> Graph<'a> { - /// Shared borrow of the bundler `ThreadPool`. - /// - /// `pool` is arena-allocated in `BundleV2::init` (bundle_v2.zig:992) and - /// torn down in `BundleV2::deinit` (bundle_v2.zig:2248). It is non-null - /// and valid for the entire bundle pass; see LIFETIMES.tsv row 170 - /// (BACKREF). All `ThreadPool` driver methods (`schedule`, `start`, - /// `worker_pool`, `schedule_inside_thread_pool`) take `&self`, so callers - /// can use this in place of the prior open-coded - /// `unsafe { self.pool.as_ref() }` / `as_mut()`. #[inline] pub fn pool(&self) -> &ThreadPool { - // BackRef invariant: `pool` is set in `BundleV2::init` to an - // arena-owned `ThreadPool` and remains valid until `BundleV2::deinit`; - // no `&mut ThreadPool` is live across any `pool()` borrow (the only - // `&mut` site is `deinit`, called after all schedule/worker activity - // has drained). self.pool.get() } @@ -218,10 +172,6 @@ impl<'a> Graph<'a> { &mut self.build_graphs[target] } - /// Schedule a task to be run on the JS thread which resolves the promise of - /// each `.defer()` called in an onLoad plugin. - /// - /// Returns true if there were more tasks queued. pub fn drain_deferred_tasks(&mut self, transpiler: &mut BundleV2) -> bool { transpiler.thread_lock.assert_locked(); @@ -239,10 +189,6 @@ impl<'a> Graph<'a> { } } -// Spec: `side_effects: _resolver.SideEffects` (Graph.zig:74). The resolver -// crate re-exports the canonical enum from `bun_options_types`; re-export it -// here so `InputFile` and the derived `items_side_effects()` SoA accessor share -// the same type that `LinkerContext::mark_file_live_for_tree_shaking` expects. use bun_ast::SideEffects; // ported from: src/bundler/Graph.zig diff --git a/src/bundler/HTMLImportManifest.rs b/src/bundler/HTMLImportManifest.rs index 18716fb9a96..794456158bc 100644 --- a/src/bundler/HTMLImportManifest.rs +++ b/src/bundler/HTMLImportManifest.rs @@ -243,10 +243,6 @@ pub fn write( let file_entry_bits: &[AutoBitSet] = linker_graph.files.items_entry_bits(); let mut already_visited_output_file = AutoBitSet::init_empty(additional_output_files.len())?; - // Write all chunks that have files associated with this entry point. - // Also include browser chunks from server builds (lazy-loaded chunks from dynamic imports). - // When there's only one HTML import, all browser chunks belong to that manifest. - // When there are multiple HTML imports, only include chunks that intersect with this entry's bits. let has_single_html_import = graph.html_imports.html_source_indices.len() == 1; for ch in chunks.iter() { if ch.entry_bits().has_intersection(&entry_point_bits) @@ -282,10 +278,6 @@ pub fn write( writer, input, path, - // The HTML chunk's body embeds the hashed paths of its JS/CSS - // chunks, so its etag must change when those do. `isolated_hash` - // by design excludes those substitutions; the placeholder hash - // folds them in via `appendIsolatedHashesForImportedChunks`. ch.template.placeholder.hash.unwrap_or(ch.isolated_hash), ch.content.loader(), if ch.entry_point.is_entry_point() { diff --git a/src/bundler/HTMLScanner.rs b/src/bundler/HTMLScanner.rs index 9f14e7a16a6..1d560d132c5 100644 --- a/src/bundler/HTMLScanner.rs +++ b/src/bundler/HTMLScanner.rs @@ -266,13 +266,6 @@ pub(crate) const TAG_HANDLERS: [TagHandler; 16] = [ const SELECTOR_CAP: usize = TAG_HANDLERS.len() + 3; -// ── lol-html DirectiveCallback / OutputSink adapters ────────────────────── -// `lol_html::DirectiveCallback` allows one impl per (UserData, -// Container) pair, but Zig registered 16 distinct comptime fn-values against -// the same `*T`. We instead allocate one user-data record *per selector* -// holding `(*mut T, tag_index)`; the trait body is `generateHandlerForTag`'s -// body with `tag_info` looked up at runtime via the index. - struct TagUserData { this: *mut T, tag_index: usize, @@ -481,10 +474,6 @@ impl // Zig: defer last_error.deinit() let _last_error_guard = scopeguard::guard(last_error, |e| e.deinit()); if last_error.len > 0 { - // The rewriter (sole user of `this_ptr`-derived aliases) was - // destroyed when the inner closure returned; reasserting the - // original `&mut T` borrow here is sound and avoids the raw - // deref entirely. this.on_html_parse_error(last_error.slice()); } } diff --git a/src/bundler/LinkerContext.rs b/src/bundler/LinkerContext.rs index 98a8dd7261f..625be51c65c 100644 --- a/src/bundler/LinkerContext.rs +++ b/src/bundler/LinkerContext.rs @@ -19,14 +19,10 @@ use bun_threading::{WaitGroup, thread_pool as ThreadPoolLib}; use crate::bake_types as bake; use crate::BundledAst as JSAst; +use crate::Index; use bun_ast::{ Binding, DeclaredSymbol, Dependency, ExportsKind, Expr, NamedImport, Part, Ref, Stmt, TlaCheck, }; -// PORT NOTE: `crate::Index` (= `bun_ast::Index`) — the -// bundler's source-index newtype. `bun_ast::Index` is layout-identical -// but a distinct type; LinkerGraph/JSMeta/etc. are typed against the crate -// re-export, so use that here. -use crate::Index; use bun_ast::{E, G, S}; use bun_js_parser::lexer as lex; use bun_js_printer::{self as js_printer, renamer}; @@ -42,22 +38,11 @@ use crate::{ LinkerGraph, MangledProps, PartRange, StableRef, WrapKind, }; -/// `bun.jsc.AnyEventLoop` (LinkerContext.zig:28). `bun_event_loop` is a -/// lower-tier crate, so the bundler can name the real enum (the `Js` arm -/// holds an erased `*mut jsc::EventLoop` driven through a vtable). Stored as -/// a pointer because the linker borrows the loop owned by the -/// `BundleThread` / runtime. pub type EventLoop = Option>>; bun_core::declare_scope!(LinkerCtx, visible); bun_core::declare_scope!(TreeShake, hidden); -// ══════════════════════════════════════════════════════════════════════════ -// CYCLEBREAK(b0): vtable instance for `bun_crash_handler::BundleGenerateChunkVTable` -// (cold-path §Dispatch — crash trace only). crash_handler (T1) holds erased -// `(*const LinkerContext, *const Chunk, *const PartRange)`; bundler supplies -// the formatter that knows their layout. Mirrors src/crash_handler/crash_handler.zig:135. -// ══════════════════════════════════════════════════════════════════════════ #[cfg(feature = "show_crash_trace")] bun_crash_handler::link_impl_BundleGenerateChunkCtx! { Linker for LinkerContext => |this| { @@ -116,11 +101,6 @@ bun_core::define_scoped_log!(debug, crate::linker_context_mod::LinkerCtx); pub(crate) use debug; bun_core::define_scoped_log!(debug_tree_shake, crate::linker_context_mod::TreeShake); -// Re-exports from sibling modules in `linker_context/`. -// `LinkerGraph` SoA accessors are real now (`` on -// `JSAst`/`JSMeta`/`File`); the submodule bodies un-gate against those. Module -// declarations live in `lib.rs::linker_context` — each re-export below is -// gated alongside its module declaration so partial un-gates compile. pub use crate::linker_context::scan_imports_and_exports::scan_imports_and_exports; pub use crate::linker_context::compute_chunks::compute_chunks; @@ -160,18 +140,8 @@ pub use crate::ParseTask; pub struct LinkerContext<'a> { pub parse_graph: *mut Graph<'a>, pub graph: LinkerGraph<'a>, - /// Backref into `Transpiler.log`, assigned in [`Self::load`]. Stored as a - /// raw pointer (like `parse_graph` / `resolver`) so `Default` can be - /// `null_mut()` instead of a dangling `&mut` (instant UB). Use - /// [`Self::log`] / [`Self::log_mut`]; deref the field directly only for - /// split-borrow patterns that hold other `self` borrows across the access. pub log: *mut Log, - /// Backref into `BundleV2.transpiler.resolver` (LIFETIMES.tsv: - /// GRAPHBACKED). `ParentRef` (not `*mut`) so the accessor and the - /// split-borrow sites in `linker_context/*.rs` deref it via safe `Deref` - /// instead of open-coding a raw deref. `Option` because `Default` precedes - /// [`Self::load`]. Read-only — never `assume_mut`. pub resolver: Option>>, pub cycle_detector: Vec, @@ -261,18 +231,6 @@ impl<'a> LinkerContext<'a> { bun_core::from_field_ptr!(BundleV2, linker, linker) } - /// Shared-read accessor for the parse-side graph. - /// - /// `parse_graph` is a backref into `BundleV2.graph`, a sibling field of - /// `BundleV2.linker` (= `*self`), assigned in [`Self::load`]. It is - /// non-null and valid for the entire link step; the pointee is disjoint - /// from `*self` (LIFETIMES.tsv: GRAPHBACKED). - /// - /// The returned borrow is tied to `&self`. Callers that need to hold a - /// `&Graph` across a `&mut self` borrow (split-borrow patterns — e.g. - /// `process_html_import_files`, TLA-check column caching, or - /// `generate_isolated_hash`) must continue to deref the raw - /// `self.parse_graph` field directly. #[inline] pub fn parse_graph(&self) -> &Graph<'_> { debug_assert!( @@ -284,10 +242,6 @@ impl<'a> LinkerContext<'a> { unsafe { &*self.parse_graph } } - /// Exclusive accessor for the parse-side graph. See [`Self::parse_graph`] - /// for the lifetime invariant. Prefer the raw `self.parse_graph` field for - /// split-borrow patterns that interleave `&mut Graph` with other `self` - /// borrows. #[inline] pub fn parse_graph_mut(&mut self) -> &mut Graph<'a> { debug_assert!( @@ -299,11 +253,6 @@ impl<'a> LinkerContext<'a> { unsafe { &mut *self.parse_graph } } - /// Shared-read accessor for the resolver. - /// - /// `resolver` is a backref into `BundleV2.transpiler.resolver`, assigned - /// in [`Self::load`] (LIFETIMES.tsv: GRAPHBACKED). Non-null and valid for - /// the link step; never mutated through this pointer. #[inline] pub fn resolver(&self) -> &Resolver<'a> { self.resolver @@ -312,15 +261,6 @@ impl<'a> LinkerContext<'a> { .get() } - /// Mutable projection of the `r#loop` BACKREF for `AnyEventLoop` dispatch - /// (`enqueue_task_concurrent*`, `tick`). Centralises the raw `NonNull` - /// deref so the three callers (`BundleV2::any_loop_mut`, `ParseTask` / - /// `ServerComponentParseTask` completion) are safe. - /// - /// `&self` receiver (not `&mut self`): the loop storage is **disjoint** - /// from `LinkerContext` (it lives in the `BundleThread` / runtime arena — - /// see [`EventLoop`]), and worker-thread completions reach this through a - /// `BackRef` (`&` only). #[inline] #[allow(clippy::mut_from_ref)] pub fn any_loop_mut(&self) -> Option<&mut bun_event_loop::AnyEventLoop<'static>> { @@ -333,10 +273,6 @@ impl<'a> LinkerContext<'a> { self.r#loop.map(|p| unsafe { &mut *p.as_ptr() }) } - /// Shared-read accessor for the bundler log. - /// - /// `log` is a backref into `Transpiler.log`, assigned in [`Self::load`] - /// (LIFETIMES.tsv: GRAPHBACKED). Non-null and valid for the link step. #[inline] pub fn log(&self) -> &Log { debug_assert!( @@ -361,20 +297,6 @@ impl<'a> LinkerContext<'a> { unsafe { &mut *self.log } } - /// Detached mutable borrow of the bundler log for split-borrow contexts. - /// - /// `self.log` is a backref into `Transpiler.log`, a sibling allocation of - /// `BundleV2.linker` (= `*self`) — it is allocation-disjoint from every - /// `self.graph` / `self.parse_graph` / `self.mangled_props` borrow. This - /// accessor exists for the diagnostic paths (`match_import_with_export`, - /// `scan_imports_and_exports`, CSS validation) that hold SoA-column borrows - /// of `self.graph` while emitting an error; [`Self::log_mut`] would - /// needlessly conflict on `&mut self`. - /// - /// `#[allow(clippy::mut_from_ref)]` follows the same precedent as - /// [`GenerateChunkCtx::c`]: the pointee is a set-once GRAPHBACKED backref, - /// not interior storage of `*self`, so `&self` cannot alias the returned - /// `&mut Log`. Do not call this twice with overlapping live borrows. #[inline] #[allow(clippy::mut_from_ref)] pub(crate) fn log_disjoint(&self) -> &mut Log { @@ -389,10 +311,6 @@ impl<'a> LinkerContext<'a> { unsafe { &mut *self.log } } - /// Safe accessor for the underlying `bun_threading::ThreadPool` driving - /// link-phase parallel work. Chains [`Self::parse_graph`] → - /// [`Graph::pool`] → [`ThreadPool::worker_pool`](crate::ThreadPool::worker_pool), - /// keeping the `unsafe` deref centralized in those accessors. #[inline] pub fn worker_pool(&self) -> &bun_threading::ThreadPool { self.parse_graph().pool().worker_pool() @@ -412,26 +330,10 @@ impl<'a> LinkerContext<'a> { && record.source_index.get() != source_index } - /// Spec: `LinkerContext.zig:checkForMemoryCorruption`. - /// - /// PORT NOTE: the Zig body calls `parse_graph.heap.helpCatchMemoryIssues()` - /// (a `MimallocArena` debug hook). `Graph.heap` is currently - /// `bun_alloc::Arena = bumpalo::Bump`, which has no such hook, so this is a - /// no-op until the arena type is swapped to the real `MimallocArena`. The - /// call sites are already gated on `FeatureFlags::HELP_CATCH_MEMORY_ISSUES`. #[inline] - pub fn check_for_memory_corruption(&self) { - // For this to work, you need mimalloc's debug build enabled. - // make mimalloc-debug - // TODO(port): `unsafe { (*self.parse_graph).heap.help_catch_memory_issues() }` - // once `Graph.heap: MimallocArena`. - } + pub fn check_for_memory_corruption(&self) {} } -// Local re-exports for the tree-shaking impl below. `EntryPoint::Kind` -// and `SideEffects` live in sibling modules; code here used to reference them -// via Zig-style nested paths. Re-export so `EntryPoint::Kind` here is the -// *same type* `items_entry_point_kind()` returns. #[allow(non_snake_case)] pub mod EntryPoint { pub use crate::entry_point::Kind; @@ -440,12 +342,6 @@ use crate::bundled_ast::Flags as AstFlags; use crate::generic_path_with_pretty_initialized; type DeclaredSymbolList = bun_ast::DeclaredSymbolList; -// TODO(port): method bodies depend on `LinkerGraph` SoA accessors -// (`graph.files.items_*()`, `graph.ast.items_*()`, `graph.meta.items_*()`), -// `crate::thread_pool::Worker`, `generic_path_with_pretty_initialized`, and the gated -// `linker_context/` submodules. The struct + LinkerOptions + SourceMapData -// above are real; this impl block un-gates with `LinkerGraph.rs`. - impl<'a> LinkerContext<'a> { pub fn arena(&self) -> &Bump { // TODO(port): bundler is an AST crate; LinkerGraph owns the arena @@ -572,11 +468,6 @@ impl<'a> LinkerContext<'a> { } if self.options.output_format == Format::Cjs || self.options.output_format == Format::Iife { - // PORT NOTE: reshaped for borrowck — `Slice` is a value-type - // snapshot of column pointers (does not borrow `self.graph.ast`), - // so `split_mut()` on the local can coexist with the - // `self.graph.meta` borrow below. The slab does not reallocate for - // the duration of this loop. let mut ast_slice = self.graph.ast.slice(); let ast_cols = ast_slice.split_mut(); let exports_kind: &mut [ExportsKind] = ast_cols.exports_kind; @@ -593,10 +484,6 @@ impl<'a> LinkerContext<'a> { exports_kind[entry_point.get() as usize] = ExportsKind::Cjs; } - // Entry points with ES6 exports must generate an exports object when - // targeting non-ES6 formats. Note that the IIFE format only needs this - // when the global name is present, since that's the only way the exports - // can actually be observed externally. if ast_flags.contains(AstFlags::USES_EXPORT_KEYWORD) { ast_flags_list[entry_point.get() as usize].insert(AstFlags::USES_EXPORTS_REF); meta_flags_list[entry_point.get() as usize] @@ -902,11 +789,6 @@ impl<'a> LinkerContext<'a> { Vec::with_capacity(parts_col.len()); for (i, parts) in parts_col.iter().enumerate() { let mut bits = bun_collections::AutoBitSet::init_empty(parts.len())?; - // The HTML loader's `ParseTask` builds its synthetic part 1 already - // live (so the JS-chunk visitor follows every embedded import record). - // `mark_file_live_for_tree_shaking` short-circuits for HTML and never - // walks its parts, so seed the bit here to preserve the old - // `Part::is_live = true` initializer. if loaders.get(i).is_some_and(|l| *l == Loader::Html) && parts.len() > 1 { bits.set(1); } @@ -915,10 +797,6 @@ impl<'a> LinkerContext<'a> { self.graph.parts_live = parts_live; } - // PORT NOTE: reshaped for borrowck — these slices alias into self.graph; - // Zig held them simultaneously. The SoA columns are physically disjoint - // and the underlying slabs don't reallocate during tree-shaking, so we - // cache raw column base pointers and reborrow at each recursive call. let parts: *mut [bun_ast::PartList<'a>] = self.graph.ast.items_parts_mut(); let parts_live: *mut [bun_collections::AutoBitSet] = self.graph.parts_live.as_mut_slice(); let import_records: *const [bun_ast::import_record::List<'a>] = @@ -1002,10 +880,6 @@ impl<'a> LinkerContext<'a> { css_reprs, }; - // Code splitting: Determine which entry points can reach which files. This - // has to happen after tree shaking because there is an implicit dependency - // between live parts within the same file. All liveness has to be computed - // first before determining which entry points can reach which files. for i in 0..entry_points_len { let entry_point = entry_points[i]; self.mark_file_reachable_for_code_splitting(&mut ctx, entry_point, i, 0); @@ -1015,13 +889,6 @@ impl<'a> LinkerContext<'a> { Ok(()) } - // CONCURRENCY: `each_ptr` callback — runs on worker threads, one task per - // `chunk_index`. Writes: `chunk.intermediate_output`, `chunk.isolated_hash`, - // `chunk.output_source_map` (per-chunk, disjoint by `*mut Chunk`). Reads - // `ctx.c`/`ctx.chunks` shared. Never forms `&mut LinkerContext` — the - // `post_process_*` callees take `GenerateChunkCtx` by value and deref - // `ctx.c` to `&LinkerContext` for read-only graph access plus per-chunk - // raw-ptr writes (see `postProcessJSChunk.rs`). pub(crate) fn generate_chunk(ctx: &GenerateChunkCtx, chunk: *mut Chunk, chunk_index: usize) { // SAFETY: `each_ptr` hands us a unique `*mut Chunk` per task; deref for // the duration of this body. ctx.c points into BundleV2.linker; @@ -1045,12 +912,6 @@ impl<'a> LinkerContext<'a> { } } - // CONCURRENCY: `each_ptr` callback — runs on worker threads, one task per - // `chunk_index`. Writes: `chunk.renamer` only (per-chunk, disjoint by - // `*mut Chunk`). Reads `ctx.c.graph.{ast,meta,symbols}` SoA columns and - // `ctx.c.options` shared. `rename_symbols_in_chunk` takes `*mut - // LinkerContext` raw and never materializes `&mut LinkerContext` while - // peer renamer tasks are live (see its CONCURRENCY note). pub(crate) fn generate_js_renamer( ctx: &GenerateChunkCtx, chunk: *mut Chunk, @@ -1073,10 +934,6 @@ impl<'a> LinkerContext<'a> { chunk_index: usize, ) { let _ = chunk_index; - // PORT NOTE: reshaped for borrowck — `rename_symbols_in_chunk` needs - // `&mut Chunk` and a borrow of `chunk.content.javascript.files_in_chunk_order` - // simultaneously; cache the files slice via raw pointer (it lives in - // the chunk arena, address-stable for the renamer pass). let files: *const [u32] = match &chunk.content { crate::chunk::Content::Javascript(js) => &raw const *js.files_in_chunk_order, _ => unreachable!(), @@ -1108,15 +965,6 @@ impl<'a> LinkerContext<'a> { let sources = self.parse_graph().input_files.items_source(); let quoted_source_map_contents = self.graph.files.items_quoted_source_contents(); - // Entries in `results` do not 1:1 map to source files, the mapping - // is actually many to one, where a source file can have multiple chunks - // in the sourcemap. - // - // This hashmap is going to map: - // `source_index` (per compilation) in a chunk - // --> - // Which source index in the generated sourcemap, referred to - // as the "mapping source index" within this function to be distinct. let mut source_id_map: ArrayHashMap = ArrayHashMap::new(); // PERF(port): was arena bulk-free — source_id_map drops at scope exit @@ -1129,11 +977,6 @@ impl<'a> LinkerContext<'a> { let path = &sources[index as usize].path; source_id_map.put_no_clobber(index, 0)?; - // PORT NOTE: Zig mutated a local copy's `path.pretty` from the - // worker arena; we keep the relative path in a local owned - // buffer instead (drops at scope exit — same lifetime as the - // arena slice). - // let rel_path_storage; let pretty: &[u8] = if path.is_file() { rel_path_storage = @@ -1269,10 +1112,6 @@ impl<'a> LinkerContext<'a> { .extend_from_slice(&done[mapping_start..mapping_end]); pieces.suffix.extend_from_slice(&done[mapping_end..]); } else { - // No shifts → `finalize()` returns `prefix` verbatim. Move the - // joined buffer instead of allocating a fresh `Vec` and memcpying - // it; for the bundled three.js x100 case the source map JSON is - // ~300 MB, so this alloc+copy was ~20% of the build. pieces.prefix = done.into_vec(); } @@ -1404,14 +1243,6 @@ impl Default for SourceMapDataTask { } impl SourceMapDataTask { - // CONCURRENCY: thread-pool callback — runs on worker threads, one task per - // `source_index`. Writes: `ctx.graph.files[source_index].line_offset_table` - // (per-row disjoint), `ctx.pending_task_count` (atomic), - // `ctx.source_maps.line_offset_wait_group` (atomic). Reads - // `ctx.parse_graph.input_files[source_index].source` shared. Never forms - // `&mut LinkerContext` — `compute_line_offsets` takes a `ParentRef` (yields - // `&LinkerContext` only) and writes the single SoA cell via raw per-row - // pointer. pub(crate) fn run_line_offset(thread_task: *mut ThreadPoolLib::Task) { // SAFETY: thread_task points to SourceMapDataTask.thread_task let task: &mut SourceMapDataTask = unsafe { @@ -1441,13 +1272,6 @@ impl SourceMapDataTask { worker.unget(); } - // CONCURRENCY: thread-pool callback — runs on worker threads, one task per - // `source_index`. Writes: `ctx.graph.files[source_index].quoted_source_contents` - // (per-row disjoint), `ctx.pending_task_count` (atomic), - // `ctx.source_maps.quoted_contents_wait_group` (atomic). Never forms - // `&mut LinkerContext` — `compute_quoted_source_contents` takes a - // `ParentRef` (yields `&LinkerContext` only) and writes the single SoA cell - // via raw per-row pointer. pub(crate) fn run_quoted_source_contents(thread_task: *mut ThreadPoolLib::Task) { // SAFETY: thread_task points to SourceMapDataTask.thread_task let task: &mut SourceMapDataTask = unsafe { @@ -1469,18 +1293,6 @@ impl SourceMapDataTask { // borrow is formed for `Worker::get`, which reads `graph.pool` under a mutex. let worker = crate::thread_pool::Worker::get(unsafe { &*bundle }); - // Use the default arena when using DevServer and the file - // was generated. This will be preserved so that remapping - // stack traces can show the source code, even after incremental - // rebuilds occur. - // - // PORT NOTE: Zig branched on `worker.ctx.transpiler.options.dev_server` - // to pick `dev.arena()` vs `worker.arena`, but - // `computeQuotedSourceContents` discards the arena parameter - // (`_: std.mem.Allocator`) — it always allocates via - // `bun.default_allocator` internally. The branch is a no-op, so we - // pass the worker arena unconditionally; `DevServerHandle` does not - // expose an arena accessor (§Dispatch). SourceMapData::compute_quoted_source_contents(ctx, worker.arena(), task.source_index); worker.unget(); } @@ -1489,14 +1301,6 @@ impl SourceMapDataTask { // TODO(port): see SourceMapDataTask above. impl SourceMapData { - /// Runs concurrently across the worker pool (one task per `source_index`). - /// Takes [`ParentRef`](bun_ptr::ParentRef) (not `&mut`) - /// because Zig's `*LinkerContext` freely aliases across threads — - /// materializing `&mut LinkerContext` here while peer tasks hold the same - /// pointer would be aliased-mut UB. `ParentRef::Deref` yields - /// `&LinkerContext` (SharedReadOnly) for all SoA-header reads; each task - /// writes only `graph.files[source_index].line_offset_table` (disjoint by - /// `source_index`) via a raw column pointer. pub fn compute_line_offsets( this: bun_ptr::ParentRef>, alloc: &Bump, @@ -1582,11 +1386,6 @@ impl SourceMapData { } let source: &Source = &parse_graph.input_files.items_source()[source_index as usize]; - // Allocate from the worker's AST allocation state (installed by - // `Worker::get`); ~12.5% escape-expansion slack matches `quote_for_json`'s - // heuristic so the writer rarely reallocs. The slack is dropped with - // the arena at bundle end, and `StringJoiner` only borrows a `&[u8]` - // view downstream. let contents: &[u8] = &source.contents; let mut buf = bun_alloc::AstAlloc::vec_with_capacity::( contents.len() + (contents.len() >> 3) + 8, @@ -1642,24 +1441,10 @@ pub struct ChunkMeta { pub(crate) type ChunkMetaMap = ArrayHashMap; -/// PORT NOTE: raw-pointer fields (was `&'a mut`) because `each_ptr` requires -/// `Ctx: Sync + Copy` and the same context is observed from every worker -/// thread. Each task only writes to its own `*mut Chunk` slot; reads of -/// `c`/`chunks` are disjoint or read-only per the Zig spec. #[derive(Clone, Copy)] pub struct GenerateChunkCtx<'a> { pub c: bun_ptr::ParentRef>, - /// Backref to the full `chunks: &mut [Chunk]` slice owned by - /// `generate_chunks_in_parallel`. The slice outlives every - /// `GenerateChunkCtx` (joined via `wait_for_all`), so [`bun_ptr::BackRef`]'s - /// owner-outlives-holder invariant holds and per-task reads go through - /// safe `Deref`. Tasks that need write provenance (HTML loader) recover - /// the raw `*mut [Chunk]` via [`bun_ptr::BackRef::as_ptr`]. pub chunks: bun_ptr::BackRef<[Chunk]>, - /// Backref to this task's `Chunk` (an element of `chunks`). Constructed - /// via [`bun_ptr::BackRef::new_mut`] so the stored `NonNull` carries write - /// provenance; per-task slot writes recover the raw `*mut Chunk` via - /// [`bun_ptr::BackRef::as_ptr`], shared reads go through safe `Deref`. pub chunk: bun_ptr::BackRef, } // SAFETY: see PORT NOTE above — mirrors Zig's freely-aliased `*LinkerContext`. @@ -1668,12 +1453,6 @@ unsafe impl<'a> Send for GenerateChunkCtx<'a> {} unsafe impl<'a> Sync for GenerateChunkCtx<'a> {} impl<'a> GenerateChunkCtx<'a> { - /// Recover a shared borrow of the owning `BundleV2` via container_of from - /// the embedded `LinkerContext` pointer (`BundleV2.linker == *self.c`). - /// Used solely to call `Worker::get`, which only reads `bundle.graph.pool` - /// (shared) and serializes via mutex — so a `&BundleV2` is sufficient and - /// no `&mut` is ever materialized over the shared bundle while peer - /// per-chunk tasks run concurrently. #[inline] pub fn bundle(&self) -> &BundleV2<'a> { // SAFETY: `self.c` is `&raw mut bundle.linker` set in @@ -1682,11 +1461,6 @@ impl<'a> GenerateChunkCtx<'a> { unsafe { &*LinkerContext::bundle_v2_ptr(self.c.as_mut_ptr()) } } - /// Mutable view of the owning `LinkerContext`. Centralizes the `unsafe` - /// deref of the `c: *mut LinkerContext` backref (set in - /// `generate_chunks_in_parallel`); callers previously open-coded - /// `unsafe { &mut *ctx.c }`. The per-chunk tasks each touch a disjoint - /// chunk, so the linker fields they write don't alias across tasks. #[inline] #[allow(clippy::mut_from_ref)] pub fn c(&self) -> &mut LinkerContext<'a> { @@ -1752,14 +1526,6 @@ pub(crate) unsafe fn pending_part_range_prologue<'a>( (part_range, c_ptr, chunk_ptr, worker) } -/// `Environment.show_crash_trace` scoped-action guard for the -/// `generate_compile_result_for_{js,css}_chunk` callbacks. Thin wrapper over -/// [`bundle_generate_chunk_action`] + [`bun_crash_handler::scoped_action`]. -/// -/// Callers materialise the `&LinkerContext` / `&Chunk` from the worker-task -/// raw pointers (see [`pending_part_range_prologue`]); the borrows are only -/// used to derive erased `*const ()` for the crash-trace vtable and are not -/// retained past the `scoped_action` expression. #[cfg(feature = "show_crash_trace")] #[inline] #[must_use] @@ -1771,22 +1537,12 @@ pub(crate) fn crash_guard_for_part_range( bun_crash_handler::scoped_action(bundle_generate_chunk_action(c, chunk, part_range)) } -// TODO(port): scan/tree-shake/link method bodies. These reach into -// `LinkerGraph` SoA fields (`graph.files`, `graph.meta`, `graph.ast`), the -// gated `linker_context/scanImportsAndExports.rs`, `bun_resolve_builtins`, -// and `css::css_modules`. The bodies are real ports of `LinkerContext.zig` -// and un-gate together with `LinkerGraph.rs`. - impl<'a> LinkerContext<'a> { pub fn generate_isolated_hash(&mut self, chunk: &Chunk) -> u64 { let _trace = bun::perf::trace("Bundler.generateIsolatedHash"); let mut hasher = ContentHasher::default(); - // Mix the file names and part ranges of all of the files in this chunk into - // the hash. Objects that appear identical but that live in separate files or - // that live in separate parts in the same file must not be merged. This only - // needs to be done for JavaScript files, not CSS files. if let crate::chunk::Content::Javascript(js) = &chunk.content { // SAFETY: parse_graph backref; exclusive access via &mut *. let sources = unsafe { (*self.parse_graph).input_files.items_source_mut() }; @@ -1809,10 +1565,6 @@ impl<'a> LinkerContext<'a> { break 'brk source.path.pretty; } else { - // If this isn't in the "file" namespace, just use the full path text - // verbatim. This could be a source of cross-platform differences if - // plugins are storing platform-specific information in here, but then - // that problem isn't caused by esbuild itself. break 'brk source.path.text; } }; @@ -1849,11 +1601,6 @@ impl<'a> LinkerContext<'a> { self.options.public_path }; - // Also hash the public path. If provided, this is used whenever files - // reference each other such as cross-chunk imports, asset file references, - // and source map comments. We always include the hash in all chunks instead - // of trying to figure out which chunks will include the public path for - // simplicity and for robustness to code changes in the future. if !public_path.is_empty() { hasher.write(public_path); } @@ -1875,22 +1622,6 @@ impl<'a> LinkerContext<'a> { crate::chunk::IntermediateOutput::Empty => {} } - // Also include the source map data in the hash. The source map is named the - // same name as the chunk name for ease of discovery. So we want the hash to - // change if the source map data changes even if the chunk data doesn't change. - // Otherwise the output path for the source map wouldn't change and the source - // map wouldn't end up being updated. - // - // Note that this means the contents of all input files are included in the - // hash because of "sourcesContent", so changing a comment in an input file - // can now change the hash of the output file. This only happens when you - // have source maps enabled (and "sourcesContent", which is on by default). - // - // The generated positions in the mappings here are in the output content - // *before* the final paths have been substituted. This may seem weird. - // However, I think this shouldn't cause issues because a) the unique key - // values are all always the same length so the offsets are deterministic - // and b) the final paths will be folded into the final hash later. hasher.write(&chunk.output_source_map.prefix); hasher.write(&chunk.output_source_map.mappings); hasher.write(&chunk.output_source_map.suffix); @@ -2213,10 +1944,6 @@ impl<'a> LinkerContext<'a> { // across `RequireOrImportMetaCallback::init(self)` (`&mut self`) below. let parse_graph = unsafe { &*self.parse_graph }; - // PORT NOTE: `Options.arena` / `source_map_allocator` were removed in - // the Rust port (printer uses global mimalloc + the explicit `bump` - // argument to `print_with_writer`). The dev-server source-map-arena - // selection is folded into TODO(port) until arena threading lands. let _ = self.dev_server.is_some() && parse_graph.input_files.items_loader()[source_index.get() as usize] .is_javascript_like(); @@ -2294,10 +2021,6 @@ impl<'a> LinkerContext<'a> { }; writer.buffer.reset(); - // PORT NOTE: Zig moved `*writer` into the printer by value and wrote it - // back via `defer writer.* = printer.ctx;`. `BufferWriter` isn't - // `Clone`/`Default` in Rust; move it through `mem::replace` with a - // freshly-initialized writer instead. let mut printer = js_printer::BufferPrinter::init(core::mem::replace( writer, js_printer::BufferWriter::init(), @@ -2469,10 +2192,6 @@ impl<'a> LinkerContext<'a> { index: u32, chunk_visit_map: &mut AutoBitSet, ) { - // Only visit each chunk at most once. This is important because there may be - // cycles in the chunk import graph. If there's a cycle, we want to include - // the hash of every chunk involved in the cycle (along with all of their - // dependencies). This depth-first traversal will naturally do that. if chunk_visit_map.is_set(index as usize) { return; } @@ -2495,14 +2214,6 @@ impl<'a> LinkerContext<'a> { ); } - // Mix in hashes for content referenced via output pieces. JS chunks - // express cross-chunk dependencies via `cross_chunk_imports` above, but - // HTML (and CSS) chunks only reference other chunks through pieces, so - // recurse on those too. - // PORT NOTE: reshaped for borrowck — collect piece queries first so the - // `&chunks[index]` borrow is dropped before the recursive `&mut chunks` - // calls in the Chunk/Scb arms below. `final_rel_path` is re-indexed per - // Asset arm (not hoisted) because it is now `Box<[u8]>` (not `Copy`). let piece_queries: Vec<(crate::chunk::QueryKind, u32)> = if let crate::chunk::IntermediateOutput::Pieces(pieces) = &chunks[index as usize].intermediate_output @@ -2629,18 +2340,6 @@ impl<'a> js_printer::RequireOrImportMetaSource for LinkerContext<'a> { } } -// ══════════════════════════════════════════════════════════════════════════ -// Tree-shaking primitives. These reach into `LinkerGraph` SoA columns -// (`files_live`, `meta.items_flags()`) and the `Graph::InputFileColumns` -// accessors. -// ══════════════════════════════════════════════════════════════════════════ - -// The three `mark_*` functions below are mutually recursive and hot. Passing -// the five SoA-column slices as separate arguments costs 10 words of fat -// pointers per call; with `&mut self` + indices that overflows the 6 SysV -// integer-arg registers and spills to the stack on every recursive step. -// Packing the slices into a borrowed context struct keeps each call at 3-4 -// register-sized arguments. pub struct TreeShakeCtx<'a, 'r> { pub side_effects: &'r [SideEffects], pub parts: &'r [bun_ast::PartList<'a>], @@ -2777,10 +2476,6 @@ impl<'a> LinkerContext<'a> { .path .pretty ), - // PORT NOTE: Zig printed `target.bakeGraph()` (a `bake.Graph` tag); - // `bake_graph()` lives in `bun_bake` (tier-6 — would back-edge). - // The debug log only needs a stable label, so print the `Target` - // tag directly via its `IntoStaticStr` derive. <&'static str>::from(parse_graph.ast.items_target()[source_index as usize]), if self.graph.files_live.is_set(source_index as usize) { "already seen" @@ -2840,11 +2535,6 @@ impl<'a> LinkerContext<'a> { } } - // Also include any statement-level imports. Iterate by index so we - // don't hold a borrow of `part`/`parts` across the recursive call — - // the recursion never resizes this part's `import_record_indices`, - // so re-slicing each iteration is sound and matches Zig's plain - // `for (part.import_record_indices.slice())`. let import_indices_len = part.import_record_indices.len(); for ii in 0..import_indices_len { let import_index = ctx.parts[source_index as usize].as_slice()[part_index] @@ -2954,10 +2644,6 @@ impl<'a> LinkerContext<'a> { // Include the file containing this part self.mark_file_live_for_tree_shaking(ctx, source_index); - // The recursion above/below only flips bits in `ctx.parts_live`; it never - // resizes any part's `dependencies`, so the slice's len/ptr are stable. - // Iterate by index and re-borrow per iteration to satisfy borrowck without - // the per-call `Vec` clone the original port did. let dependencies_len = ctx.parts[source_index as usize].as_slice()[part_index as usize] .dependencies .len(); @@ -2994,24 +2680,11 @@ impl<'a> LinkerContext<'a> { } } // end tree-shaking impl -// ══════════════════════════════════════════════════════════════════════════ -// `scanImportsAndExports.rs` callees. -// -// `linker_context/scanImportsAndExports.rs` calls these `LinkerContext` -// methods inherently. Real ports of the `LinkerContext.zig` / -// `linker_context/doStep5.zig` / `linker_context/generateCodeForLazyExport.zig` -// bodies. -// ══════════════════════════════════════════════════════════════════════════ - // Local imports. `AstFlags` / `DeclaredSymbolList` // already imported at the top of the file. use bun_ast::symbol::Use as SymbolUse; use bun_ast::{DependencyList, ImportItemStatus, PartSymbolUseMap}; -// `bundle_v2.zig:ImportTracker.{Status,Iterator}` — canonical definition lives -// in `bundle_v2.rs` (matches Zig spec location). Re-exported here so the 30+ -// unqualified uses in `advance_import_tracker` / `match_import_with_export` -// below resolve unchanged. pub use crate::bundle_v2::{ImportTrackerIterator, ImportTrackerStatus}; /// Field-wise eq for `ImportTracker`, matching Zig's `eql(ImportTracker)` shape. @@ -3041,11 +2714,6 @@ impl<'a> LinkerContext<'a> { self.top_level_symbols_to_parts(Index::RUNTIME.get(), r#ref) } - /// Spec: `LinkerContext.zig:489 source_`. - /// - /// PORT NOTE: returns `'static` so callers can hold the source across a - /// `&mut self.log` borrow; the underlying `parse_graph.input_files` slab - /// is append-only and outlives the link step (LIFETIMES.tsv: GRAPHBACKED). #[inline] pub fn get_source>(&self, index: I) -> &'static Source { // PORT NOTE: Zig spec is `index: anytype`; callers pass both `u32` and @@ -3062,11 +2730,6 @@ impl<'a> LinkerContext<'a> { unsafe { &*core::ptr::from_ref(&(*self.parse_graph).input_files.items_source()[index]) } } - /// Spec: `LinkerContext.zig:496 scanCSSImports`. - /// - /// `log` is an explicit parameter (not `self.log`) because the dev-server - /// caller (`finish_from_bake_dev_server`) runs this *before* `load()` has - /// initialized `self.log`, passing a stack-local `Log` instead. pub(crate) fn scan_css_imports( file_source_index: u32, file_import_records: &[ImportRecord], @@ -3139,28 +2802,10 @@ impl<'a> LinkerContext<'a> { source_index: crate::IndexInt, ) { match wrap { - // If this is a CommonJS file, we're going to need to generate a wrapper - // for the CommonJS closure. That will end up looking something like this: - // - // var require_foo = __commonJS((exports, module) => { - // ... - // }); - // - // However, that generation is special-cased for various reasons and is - // done later on. Still, we're going to need to ensure that this file - // both depends on the "__commonJS" symbol and declares the "require_foo" - // symbol. Instead of special-casing this during the reachability analysis - // below, we just append a dummy part to the end of the file with these - // dependencies and let the general-purpose reachability analysis take care - // of it. WrapKind::Cjs => { let common_js_parts = self.top_level_symbols_to_parts_for_runtime(self.cjs_runtime_ref); - // PORT NOTE: reshaped for borrowck — Zig held `runtime_parts` - // simultaneously with the mutable graph borrows below; the inner - // loop is empty (`if r#ref.eql(...) continue;` only) so it's a - // no-op kept for parity with the original. for &part_id in common_js_parts { let runtime_parts = self.graph.ast.items_parts()[Index::RUNTIME.get() as usize].as_slice(); @@ -3238,17 +2883,6 @@ impl<'a> LinkerContext<'a> { } WrapKind::Esm => { - // If this is a lazily-initialized ESM file, we're going to need to - // generate a wrapper for the ESM closure. That will end up looking - // something like this: - // - // var init_foo = __esm(() => { - // ... - // }); - // - // This depends on the "__esm" symbol and declares the "init_foo" symbol - // for similar reasons to the CommonJS closure above. - // Count async dependencies to determine if we need __promiseAll let mut async_import_count: usize = 0; { @@ -3542,27 +3176,12 @@ impl<'a> LinkerContext<'a> { re_exports: &mut bun_alloc::AstVec, ) -> MatchImport { let cycle_detector_top = self.cycle_detector.len(); - // PORT NOTE: Zig's `defer cycle_detector.shrinkRetainingCapacity` is - // lowered to an explicit `truncate` after the `'loop_` below — the only - // exits are the three `return`s that follow it, so a single post-loop - // truncate covers every path. A scopeguard holding a raw `*mut` into - // `self.cycle_detector` would be invalidated by the `&mut self` - // reborrows inside the loop (Stacked Borrows), so we don't use one. let mut tracker = init_tracker; let mut ambiguous_results: Vec = Vec::new(); let mut result: MatchImport = MatchImport::default(); 'loop_: loop { - // Make sure we avoid infinite loops trying to resolve cycles: - // - // // foo.js - // export {a as b} from './foo.js' - // export {b as c} from './foo.js' - // export {c as a} from './foo.js' - // - // This uses a O(n^2) array scan instead of a O(n) map because the vast - // majority of cases have one or two elements for prev_tracker in &self.cycle_detector[cycle_detector_top..] { if import_tracker_eq(&tracker, prev_tracker) { result = MatchImport { @@ -3585,10 +3204,6 @@ impl<'a> LinkerContext<'a> { let advanced = self.advance_import_tracker(&tracker); let next_tracker = advanced.value; let status = advanced.status; - // `advanced.import_data` borrows - // `graph.meta[..].resolved_exports[..].potentially_ambiguous_export_star_refs`; - // that storage is never reallocated while this loop runs (only - // `cycle_detector`, `log`, and `graph.symbols` are mutated below). let potentially_ambiguous_export_star_refs: &[crate::ImportData] = advanced.import_data.get(); @@ -3605,10 +3220,6 @@ impl<'a> LinkerContext<'a> { break; } - // If it's a CommonJS or external file, rewrite the import to a - // property access. Don't do this if the namespace reference is invalid - // though. This is the case for star imports, where the import is the - // namespace. let named_import: &NamedImport = self.graph.ast.items_named_imports() [prev_source_index as usize] .get(&tracker.import_ref) @@ -3729,13 +3340,6 @@ impl<'a> LinkerContext<'a> { // Report mismatched imports and exports if symbol.import_item_status == ImportItemStatus::Generated { - // This is a debug message instead of an error because although it - // appears to be a named import, it's actually an automatically- - // generated named import that was originally a property access on an - // import star namespace object. Normally this property access would - // just resolve to undefined at run-time instead of failing at binding- - // time, so we emit a debug message and rewrite the value to the literal - // "undefined" instead of emitting an error. symbol.import_item_status = ImportItemStatus::Missing; if self.resolver().opts.target == Target::Browser @@ -3824,11 +3428,6 @@ impl<'a> LinkerContext<'a> { } } - // Defer the actual binding of this import until after we generate - // namespace export code for all files. This has to be done for all - // import-to-export matches, not just the initial import to the final - // export, since all imports and re-exports must be merged together - // for correctness. result = MatchImport { kind: MatchImportKind::Normal, source_index: next_tracker.source_index.get(), @@ -4071,13 +3670,6 @@ impl<'a> LinkerContext<'a> { } } - /// Spec: `linker_context/generateCodeForLazyExport.zig`. - /// - /// Thin inherent-method shim so callers can write - /// `this.generate_code_for_lazy_export(id)` (matches Zig's - /// `pub const generateCodeForLazyExport = @import(...)`). The full body — - /// including the CSS-modules `composes`/`local_scope` Visitor — lives in - /// `linker_context/generateCodeForLazyExport.rs`. #[inline] pub fn generate_code_for_lazy_export( &mut self, @@ -4152,22 +3744,11 @@ impl<'a> LinkerContext<'a> { type OutputPiece = crate::chunk::OutputPiece; if !j.contains(&self.unique_key_prefix) { - // There are like several cases that prohibit this from being checked more trivially, example: - // 1. dynamic imports - // 2. require() - // 3. require.resolve() - // 4. externals return Ok(crate::chunk::IntermediateOutput::Joiner(core::mem::take(j))); } // PORT NOTE: Zig had `errdefer j.deinit()` around the initCapacity — Drop handles it. let mut pieces: Vec = Vec::with_capacity(count as usize); - // errdefer pieces.deinit() — Drop handles it - // PORT NOTE: Zig used `j.done(alloc)` (worker arena), so the joined - // buffer outlived this function. The Rust `StringJoiner::done()` - // returns a `Box<[u8]>`; we must keep it alive alongside the pieces - // (each `OutputPiece` stores a raw `*const u8` into it). It is moved - // into the returned `OutputPieces` below. let complete_output: Box<[u8]> = j.done()?; let mut output: &[u8] = &complete_output; diff --git a/src/bundler/LinkerGraph.rs b/src/bundler/LinkerGraph.rs index a03b43881c8..404374a2f33 100644 --- a/src/bundler/LinkerGraph.rs +++ b/src/bundler/LinkerGraph.rs @@ -11,11 +11,6 @@ use bun_core::PathString; use crate::IndexStringMap::IndexStringMap; use crate::{ImportTracker, Index, JSAst, Part, Ref, UseDirective, import_record, index, part}; -// `items_()` column accessors — bring the `*ListExt` traits into scope. -// PORT NOTE: `BundledAstColumns` is emitted by `` -// on `BundledAst`; un-gating here is paired with that derive landing in -// `crate::bundled_ast` (same dependency `scanImportsAndExports.rs` -// already imports as `BundledAstField`). bun_core::declare_scope!(LinkerGraph, visible); pub mod entry_point { @@ -189,23 +184,10 @@ pub use js_meta::{ pub struct LinkerGraph<'a> { pub files: FileList, pub files_live: BitSet, - /// Per-part liveness — `parts_live[source_index].is_set(part_index)`. - /// One bitset per source file, sized to that file's `parts.len()`. - /// Populated by `tree_shaking_and_code_splitting` (regular link) or by - /// the DevServer chunk path (which marks every JS-file part live); - /// read-only thereafter. Replaces the former `Part::is_live: bool` so the - /// tree-shaking visited-check doesn't pull a full 272-byte `Part` into - /// cache for a 1-bit answer. pub parts_live: Vec, pub entry_points: entry_point::List, pub symbols: symbol::Map, - // PORT NOTE: lifetime-erased. Zig stores `std.mem.Allocator`; the Rust - // arena is owned by `BundleV2` and outlives every `LinkerGraph` — kept as - // a raw pointer (matching `LinkerContext.parse_graph: *mut Graph`) so the - // struct stays `'static`-ish and `LinkerContext`/`Chunk` callers don't - // grow a `'bump` parameter yet. TODO(refactor): thread `'bump` once `Chunk` - // and `html_import_manifest` gain lifetimes. pub bump: bun_ptr::BackRef, pub code_splitting: bool, @@ -215,13 +197,6 @@ pub struct LinkerGraph<'a> { pub ast: MultiArrayList>, pub meta: MultiArrayList, - /// We should avoid traversing all files in the bundle, because the linker - /// should be able to run a linking operation on a large bundle where only - /// a few files are needed (e.g. an incremental compilation scenario). This - /// holds all files that could possibly be reached through the entry points. - /// If you need to iterate over all files in the linking operation, iterate - /// over this array. This array is also sorted in a deterministic ordering - /// to help ensure deterministic builds (source indices are random). pub reachable_files: Vec, /// Index from `.parse_graph.input_files` to index in `.files` @@ -318,17 +293,6 @@ impl Default for LinkerGraph<'_> { } } -// ────────────────────────────────────────────────────────────────────────── -// Symbol/part graph mutation surface needed by -// `linker_context/scanImportsAndExports.rs` and `LinkerContext::do_step5`. -// -// Expressed as free fns over individual SoA column slices so callers that -// already hold a `BundledAstColumnsMut` / `JSMetaColumnsMut` split-borrow -// can hand in just the columns these touch without re-borrowing -// `&mut LinkerGraph` (RUST_IDIOMS_AUDIT.md §3). The `&mut self` methods are -// thin forwarders for call sites that don't have a split in hand. -// ────────────────────────────────────────────────────────────────────────── - pub(crate) fn runtime_function(named_exports: &[bundled_ast::NamedExports], name: &[u8]) -> Ref { named_exports[Index::RUNTIME.get() as usize] .get(name) @@ -393,15 +357,6 @@ pub(crate) fn add_part_to_file( let part_id = parts[id as usize].len() as u32; // @truncate (u32) parts[id as usize].push(part); - // PORT NOTE: borrowck reshape. The Zig closure simultaneously holds - // * `&mut parts[part_id].declared_symbols` (column `parts` of `ast`) - // * `&meta.top_level_symbol_to_parts_overlay[id]` (`meta`) - // * `&ast.top_level_symbols_to_parts[id]` (another `ast` column) - // and additionally caches `*?*TopLevelSymbolToParts` across calls. - // The two `ast` columns now arrive pre-split, so no detach/reattach is - // needed. The overlay-pointer cache is dropped — re-index `meta` each - // call (O(1); the cache was a Zig micro-opt that does not survive - // Stacked Borrows). let declared_symbols: &mut DeclaredSymbolList = &mut parts[id as usize][part_id as usize].declared_symbols; @@ -529,10 +484,6 @@ impl<'a> LinkerGraph<'a> { runtime_function(self.ast.items_named_exports(), name) } - /// Shared-ref view of a symbol that is known to exist (the `Ref` was - /// produced by the symbol table itself). Thin wrapper over - /// [`symbol::Map::get_const`]; callers previously open-coded - /// `unsafe { &*graph.symbols.get(r).expect(..) }`. #[inline] pub fn symbol(&self, ref_: Ref) -> &Symbol { self.symbols @@ -671,10 +622,6 @@ impl<'a> LinkerGraph<'a> { // SAFETY: capacity reserved above; columns zeroed by `zero()`. unsafe { self.files.set_len(sources.len()) }; - // PORT NOTE: `Slice` caches raw column pointers and does not borrow - // `self.files`, so the `split_mut()` borrows (tied to the local - // `files_slice`) can stay live across other `&mut self.*` accesses - // below. The columns are not reallocated during `load`. let mut files_slice = self.files.slice(); let files_cols = files_slice.split_mut(); let entry_point_kinds: &mut [entry_point::Kind] = files_cols.entry_point_kind; @@ -690,13 +637,6 @@ impl<'a> LinkerGraph<'a> { // SAFETY: capacity reserved; columns initialized below. unsafe { self.entry_points.set_len(entry_points.len()) }; - // PORT NOTE: borrowck reshape — Zig held `source_indices` / - // `path_strings` / `output_path_was_auto_generated` simultaneously - // (disjoint columns of the same `MultiArrayList`). `split_mut()` - // hands out all three at once; `self.entry_points` is not - // reallocated until after `path_strings`/`source_indices` are done - // with (the next `append_assume_capacity` is within the - // pre-reserved capacity, so no realloc). let mut ep_slice = self.entry_points.slice(); let ep_cols = ep_slice.split_mut(); let source_indices: &mut [index::Int] = ep_cols.source_index; @@ -746,12 +686,6 @@ impl<'a> LinkerGraph<'a> { let import_records_len = self.ast.items_import_records().len(); self.meta.set_capacity(import_records_len)?; - // PORT NOTE: Zig does `meta.len = ast.len; meta.zero()` — a raw - // memset(0) is the valid empty state for Zig's unmanaged - // containers. Rust `Vec`/`Box` require a non-null dangling - // pointer when empty, so zeroed bytes violate their invariants - // (`slice::from_raw_parts` null-check trips on first read). Fill - // each slot with `Default` instead. let ast_len = self.ast.len(); debug_assert!(ast_len <= import_records_len); for _ in 0..ast_len { @@ -822,10 +756,6 @@ impl<'a> LinkerGraph<'a> { // Setup files { - // set it to max value so that if we access an invalid one, it crashes - // PORT NOTE: Zig used `@memset(sliceAsBytes(...), 255)` to fill raw - // bytes; here we fill with `Index::INVALID` whose bytes are all - // 0xFF (`#[repr(transparent)]` over `u32::MAX`). let stable_source_indices = self .arena() .alloc_slice_fill_copy(sources.len() + 1, Index::INVALID); @@ -842,11 +772,6 @@ impl<'a> LinkerGraph<'a> { } { - // PORT NOTE: Zig built a borrowed `Symbol.NestedList` over the - // `ast.items(.symbols)` column then `clone`d it (memcpy). The Rust - // `Vec::clone` requires `T: Clone` which `Symbol` does not - // derive (it carries a raw `*const [u8]`), so spell out the - // bitwise copy explicitly — `Symbol` has no `Drop` impl. let src_symbols: &[symbol::List] = self.ast.items_symbols(); let mut symbols: symbol::NestedList = Vec::with_capacity(src_symbols.len()); for src in src_symbols { @@ -863,27 +788,6 @@ impl<'a> LinkerGraph<'a> { self.symbols = symbol::Map::init_list(symbols); } - // TODO: const_values - // { - // var const_values = this.const_values; - // var count: usize = 0; - // - // for (this.ast.items(.const_values)) |const_value| { - // count += const_value.count(); - // } - // - // if (count > 0) { - // try const_values.ensureTotalCapacity(this.arena, count); - // for (this.ast.items(.const_values)) |const_value| { - // for (const_value.keys(), const_value.values()) |key, value| { - // const_values.putAssumeCapacityNoClobber(key, value); - // } - // } - // } - // - // this.const_values = const_values; - // } - { let mut count: usize = 0; for ts_enums in self.ast.items_ts_enums().iter() { @@ -894,11 +798,6 @@ impl<'a> LinkerGraph<'a> { for ts_enums in self.ast.items_ts_enums().iter() { debug_assert_eq!(ts_enums.keys().len(), ts_enums.values().len()); for (key, value) in ts_enums.keys().iter().zip(ts_enums.values().iter()) { - // PERF(port): was assume_capacity_no_clobber - // PORT NOTE: Zig copied the inner `StringHashMap` by - // value (shallow struct copy). Rust clones the backing - // `HashMap`; the per-file maps are not mutated after - // this point so aliasing is not required. self.ts_enums.put_assume_capacity(*key, value.clone()); } } @@ -937,21 +836,6 @@ impl<'a> LinkerGraph<'a> { Ok(()) } - /// Port of `LinkerGraph.zig:takeAstOwnership`. `clone_ast` left each - /// `PartList`/import-record list with its allocator handle pointing at - /// the per-worker `mi_heap` that built it; re-tag to `heap` (the - /// bundle-thread arena) so linker-side `add_part_to_file` pushes call - /// `mi_heap_realloc_aligned(heap, worker_ptr, ..)` from the thread that - /// owns `heap`. Zero-copy: only files the linker actually grows pay a - /// (lazy, mimalloc-internal) cross-heap migration on first realloc. - /// - /// Zig is a release no-op because `BabyList` passes the allocator at each - /// `append` call site; the Rust `Vec` stores it, so swap here. - /// Zig also transfers `part.dependencies` and `symbols`; the Rust port's - /// `DependencyList` is `Vec<_, AstAlloc>` (linker-side grows just route - /// through whichever thread's `AstAlloc` state is active — `AstAlloc` is a - /// ZST, so there is nothing to retag) and new symbols feed through - /// `self.symbols: symbol::Map` (global) — neither needs transfer here. pub fn take_ast_ownership(&mut self, heap: &'a Arena) { for v in self.ast.items_import_records_mut() { bun_alloc::transfer_arena(v, heap); @@ -989,13 +873,6 @@ impl<'a> LinkerGraph<'a> { match import_record.kind { ImportKind::Stmt => {} - // Any use of `import()` that makes the parent async will necessarily use - // top-level await, so this will have already been detected by `validateTLA`, - // and `is_async_or_has_async_dependency` will already be true. - // - // We don't want to process these imports here because `import()` can appear in - // non-top-level contexts (like inside an async function) or in contexts that - // don't use `await`, which don't necessarily make the parent module async. ImportKind::Dynamic => continue, // `require()` cannot import async modules. @@ -1045,18 +922,8 @@ pub struct File { /// to this file pub distance_from_entry_point: u32, - /// This file is an entry point if and only if this is not ".none". - /// Note that dynamically-imported files are allowed to also be specified by - /// the user as top-level entry points, so some dynamically-imported files - /// may be ".user_specified" instead of ".dynamic_import". pub entry_point_kind: EntryPoint::Kind, - /// If "entry_point_kind" is not ".none", this is the index of the - /// corresponding entry point chunk. - /// - /// This is also initialized for files that are a SCB's generated - /// reference, pointing to its destination. This forms a lookup map from - /// a Source.Index to its output path inb reakOutputIntoPieces pub entry_point_chunk_index: u32, pub line_offset_table: bun_sourcemap::line_offset_table::List, diff --git a/src/bundler/OutputFile.rs b/src/bundler/OutputFile.rs index 5a14a5bacc5..f755c94b57d 100644 --- a/src/bundler/OutputFile.rs +++ b/src/bundler/OutputFile.rs @@ -13,11 +13,6 @@ use bun_sys::Fd; use crate::bun_fs::RealFS; -// Instead of keeping files in-memory, we: -// 1. Write directly to disk -// 2. (Optional) move the file to the destination -// This saves us from allocating a buffer - pub struct OutputFile { pub loader: Loader, pub input_loader: Loader, @@ -122,11 +117,6 @@ pub struct BakeExtra { pub type Index = bun_core::GenericIndex; pub type IndexOptional = bun_core::GenericIndexOptional; -// Depending on: -// - The target -// - The number of open file handles -// - Whether or not a file of the same name exists -// We may use a different system call #[derive(Clone)] pub struct FileOperation { // TODO(port): lifetime — Zig never frees `pathname`; may be borrowed from @@ -203,11 +193,6 @@ pub enum Value { Saved(SavedFile), } -// Zig `bun.copy(OutputFile, dst, src)` is a bitwise memcpy used to splice -// finished output files into the final list. The `Pending` arm is never present -// at that stage (only `buffer`/`copy`/`saved` are produced by `init`), so its -// clone is intentionally unreachable rather than forcing `resolver::Result` to -// be `Clone`. impl Clone for Value { fn clone(&self) -> Self { match self { @@ -238,13 +223,6 @@ impl Value { if bytes.is_empty() { return BunString::EMPTY; } - // Use ExternalStringImpl to avoid cloning the string, at - // the cost of allocating space to remember the arena. - // - // Zig boxed a `FreeContext { arena }` and passed an `extern "C"` - // callback that frees the slice via that arena then destroys the - // context. With the global arena, the context collapses to the - // (ptr, len) pair already passed to the callback. extern "C" fn on_free(_ctx: *mut c_void, buffer: *mut c_void, len: usize) { // SAFETY: `buffer`/`len` were produced by `heap::alloc` on a // `Box<[u8]>` below; reconstructing and dropping is sound. @@ -275,17 +253,6 @@ impl Value { } } - /// Borrowing variant of [`Self::to_bun_string`]: wraps the buffer in a - /// `WTF::ExternalStringImpl` that aliases `bytes` with a **no-op** free - /// callback (zero-copy). Caller guarantees `self` outlives every use of the - /// returned string. - /// - /// This is the faithful port of Zig's `Value.toBunString` as called from - /// `bake/production.zig` (`pt.bundled_outputs[i].value.toBunString()`): Zig - /// passes the union by value so the slice is aliased in place, and - /// `PerThread.bundled_outputs` owns the bytes for the entire prerender - /// phase. The consuming [`Self::to_bun_string`] cannot be used there - /// because the Rust `Vec` is only borrowed. pub fn to_bun_string_ref(&self) -> BunString { match self { Value::Noop => BunString::EMPTY, @@ -327,10 +294,6 @@ pub struct SavedFile { impl OutputFile { pub fn init_pending(loader: Loader, pending: bun_resolver::Result) -> OutputFile { - // PORT NOTE: Zig copied the whole `Fs.Path` struct (`pending.pathConst().?.*`). - // The Rust `bun_paths::fs::Path<'static>` and `bun_resolver::fs::Path<'static>` are - // distinct nominal types with identical layout; re-init from `text` (the - // resolver path borrows arena/static memory, so the `'static` bound holds). let src_path = fs::Path::init(pending.path_const().expect("path").text); OutputFile { loader, @@ -543,9 +506,4 @@ impl OutputFile { } } -// Zig: `pub const toJS = @import("../bundler_jsc/output_file_jsc.zig").toJS;` -// Zig: `pub const toBlob = @import("../bundler_jsc/output_file_jsc.zig").toBlob;` -// Deleted per PORTING.md — `to_js` / `to_blob` become extension-trait methods that -// live in `bun_bundler_jsc`; the base type carries no jsc reference. - // ported from: src/bundler/OutputFile.zig diff --git a/src/bundler/ParseTask.rs b/src/bundler/ParseTask.rs index fd55fd00b8f..1bad029efa1 100644 --- a/src/bundler/ParseTask.rs +++ b/src/bundler/ParseTask.rs @@ -50,21 +50,6 @@ mod EventLoop { pub(super) type Task = bun_event_loop::ConcurrentTask::ConcurrentTask; } -// PORT NOTE: the per-file parse arena is held as `bump: &'static Bump` (the -// worker arena is pinned for the entire bundle pass — see `run_with_source_code`), -// so `bump.alloc_*` / `ArenaString::into_bump_str` already yield `&'static` -// borrows directly. No erasure helper is needed; `StoreStr::new` covers the -// remaining AST-string sites (`E::String.data`, `FileLoaderHash.key`). - -// `JSBundlerPlugin::{has_on_before_parse_plugins, call_on_before_parse_plugins}` -// live on the canonical `impl Plugin` in `bundle_v2.rs::api::JSBundler` next to -// the other FFI wrappers; `bundler::JSBundlerPlugin` re-exports that type. -// -// PORT NOTE: `FileMap::get` now lives on the real `JSBundler::FileMap` in -// bundle_v2.rs (no longer an opaque forward-decl). The placeholder -// always-miss `get` shim that used to sit here has been removed so the two -// inherent impls don't collide. - // ─────────────────────────────────────────────────────────────────────────── // ContentsOrFd // ─────────────────────────────────────────────────────────────────────────── @@ -332,12 +317,6 @@ impl Default for ParseTask { } } -// ─────────────────────────────────────────────────────────────────────────── -// taskCallback / ioTaskCallback — thread-pool entry points. Safe-fn wrappers -// (coerce to the `ThreadPoolLib::Task.callback` field type at the struct-init -// sites); bodies dispatch to `parse_worker::run_from_thread_pool`. -// ─────────────────────────────────────────────────────────────────────────── - // CONCURRENCY: thread-pool callback — runs on worker (or IO-pool) threads, // one task per `ParseTask`. Each `ParseTask` is a heap node owned by the // bundle graph; the `&mut ParseTask` recovered here is unique per task (no @@ -394,17 +373,6 @@ import { createRequire } from \"node:module\"; export var __require = /* @__PURE__ */ createRequire(import.meta.url); "; -// Copied from esbuild's runtime.go: -// -// > This fallback "require" function exists so that "typeof require" can -// > naturally be "function" even in non-CommonJS environments since esbuild -// > emulates a CommonJS environment (issue #1202). However, people want this -// > shim to fall back to "globalThis.require" even if it's defined later -// > (including property accesses such as "require.resolve") so we need to -// > use a proxy (issue #1614). -// -// When bundling to node, esbuild picks this code path as well, but `globalThis.require` -// is not always defined there. The `createRequire` call approach is more reliable. const RUNTIME_REQUIRE_OTHER: &str = "\ export var __require = /* @__PURE__ */ (x => typeof require !== 'undefined' ? require : @@ -417,12 +385,6 @@ export var __require = /* @__PURE__ */ (x => }); "; -// JavaScriptCore supports `using` / `await using` natively (see -// `lower_using = !target.isBun()` below), so these helpers are unused -// when bundling for Bun and will be tree-shaken. They are still defined -// here so the runtime module exports a consistent shape across targets. -// Bun's WebKit also has Symbol.asyncDispose, Symbol.dispose, and -// SuppressedError, so no polyfills are needed. const RUNTIME_USING_BUN: &str = "\ export var __using = (stack, value, async) => { if (value != null) { @@ -494,14 +456,6 @@ export var __callDispose = (stack, error, hasError) => { } "; -// ══════════════════════════════════════════════════════════════════════════ -// Per-file parse worker — `getAST`/`getCodeForParseTask`/`runFromThreadPool`. -// The struct/FFI surface and `get_runtime_source` are real. Bodies -// that touch the still-gated `crate::ThreadPool` Worker module or the opaque -// `JSBundlerPlugin`/`FileMap` forward-decls remain ``-gated -// per-function below with explicit `// blocked_on:` notes; they un-gate by -// deletion once those modules land. -// ══════════════════════════════════════════════════════════════════════════ pub mod parse_worker { use super::*; @@ -538,10 +492,6 @@ pub mod parse_worker { ) } }; - // PERF(port): Zig built one comptime string per Target variant via - // `inline else`. Here we use `const_format::concatcp!` per arm; the match - // itself is runtime but each arm yields a &'static str. Profile if the - // extra match matters (it shouldn't — called once). let parse_task = ParseTask { // TODO(port): Zig used `undefined` for ctx; using None. @@ -611,11 +561,6 @@ pub mod parse_worker { // (`Parser::to_lazy_export_ast`); `bun_css::BundlerStyleSheet` (gated // upstream); `Expr::init` overload set for arbitrary `E::*` defaults. - // PORT NOTE: `transpiler: *mut Transpiler` (raw, Zig `*Transpiler`). Callers - // (`get_ast`, `run_with_source_code`) may also hold a raw pointer to - // `(*transpiler).resolver`; materializing `&mut Transpiler` here would assert - // exclusive access to the whole struct and invalidate that sibling pointer. - // We only touch the disjoint `options.define` field. fn get_empty_css_ast( log: &mut Log, transpiler: *mut Transpiler, @@ -660,16 +605,6 @@ pub mod parse_worker { pub content_hash: u64, } - // ─────────────────────────────────────────────────────────────────────────── - // CSS Symbol bridge — `bun_ast::Symbol` ↔ `bun_ast::Symbol` - // - // Both port the same Zig `js_ast.Symbol` (Symbol.zig). - // `StylesheetExtra.symbols` is `Vec`; - // `new_lazy_export_ast_impl` takes `Vec`. Convert - // field-by-field so CSS-module local refs (`ref.inner_index()`) index a - // populated symbol table (.zig:613). - // ─────────────────────────────────────────────────────────────────────────── - fn css_symbols_to_parser_symbols( src: &[bun_ast::Symbol], bump: &'static Bump, @@ -714,22 +649,6 @@ pub mod parse_worker { // getAST // ─────────────────────────────────────────────────────────────────────────── - // blocked_on: per-loader branches require: - // - `resolver.caches.js.parse` / `resolver.caches.json.parse_json` (gated in - // `bun_resolver::cache_set`); - // - `bun_parsers::{toml,yaml,json5}` parser entry points; - // - `bun_css::BundlerStyleSheet::parse_bundler` (gated upstream); - // - `crate::HTMLScanner` (gated module); - // - `bun_core::fmt::bytes_to_hex_lower` Display adaptor; - // - `js_parser::new_lazy_export_ast` body. - // The signature now names the real `ParserOptions`; body un-gates in lockstep - // with the above. - - // PORT NOTE: `transpiler`/`resolver` are raw `*mut` (Zig `*Transpiler` / - // `*Resolver`). In Zig the caller passes `resolver = &transpiler.resolver`, so - // the two may point into the same allocation. Taking `&mut Transpiler` + - // `&mut Resolver` would be aliased-`&mut` UB. We instead reborrow only the - // disjoint `(*transpiler).options` field, never the whole struct. #[allow(clippy::too_many_arguments)] fn get_ast( log: &mut Log, @@ -753,12 +672,6 @@ pub mod parse_worker { match loader { Loader::Jsx | Loader::Tsx | Loader::Js | Loader::Ts => { let _trace = perf::trace("Bundler.ParseJS"); - // PORT NOTE: `ParserOptions` is not `Clone` (holds `&'a mut MacroContext`). - // Zig (.zig:335-342) passes the *same* `opts` (bitwise copy) to the - // empty-AST fallback; since Rust moves `opts` into `.parse()`, - // snapshot a faithful field-by-field copy via - // `Options::clone_for_lazy_export` (co-located with the struct so - // field drift is a hard error) before the move. let fallback_opts = opts.clone_for_lazy_export(); let module_type = opts.module_type; return if let Some(res) = (crate::cache::JavaScript {}).parse( @@ -768,10 +681,6 @@ pub mod parse_worker { log, source, )? { - // PORT NOTE: Zig's `js_parser.Result` is a bare-union whose - // `.ast` field is read unconditionally. The Rust port models it - // as an enum; `Cached`/`AlreadyBundled` are runtime-loader - // states that never reach the bundler's `getAST`, so unwrap. match res { bun_js_parser::Result::Ast(ast) => Ok(JSAst::init(*ast)), bun_js_parser::Result::Cached @@ -815,11 +724,6 @@ pub mod parse_worker { Loader::Toml => { let _trace = perf::trace("Bundler.ParseTOML"); let mut temp_log = Log::init(); - // PORT NOTE: Zig `defer { temp_log.cloneToWithRecycled(log, true); - // temp_log.msgs.clearAndFree() }` runs on the error path too. - // scopeguard would alias `log`/`temp_log` (both borrowed mutably - // below); reshape as a closure so every `?` exits through one - // post-amble that flushes `temp_log`. let result = (|| -> core::result::Result, AnyError> { let root: Expr = bun_parsers::toml::TOML::parse(source, &mut temp_log, bump, false)?; @@ -989,10 +893,6 @@ pub mod parse_worker { break 'brk source.path.text; }; - // This injects the following code: - // - // import.meta.require(unique_key).db - // let import_path = Expr::init( E::String { data: path_to_use.into(), @@ -1097,10 +997,6 @@ pub mod parse_worker { ) .expect("unreachable"); let unique_key = buf.into_bump_str().as_bytes(); - // This injects the following code: - // - // require(unique_key) - // let import_path = Expr::init( E::String { data: unique_key.into(), @@ -1168,15 +1064,6 @@ pub mod parse_worker { .unwrap(); ast.import_records = bun_alloc::vec_from_iter_in(import_records, bump); - // We're banning import default of html loader files for now. - // - // TLDR: it kept including: - // - // var name_default = ...; - // - // in the bundle because of the exports AST, and - // gave up on figuring out how to fix it so that - // this feature could ship. ast.has_lazy_export = false; // Liveness for this synthetic part is seeded in // `tree_shaking_and_code_splitting` (the per-part bitset @@ -1217,11 +1104,6 @@ pub mod parse_worker { let enable_css_modules = source.path.pretty.len() > CSS_MODULE_SUFFIX.len() && &source.path.pretty[source.path.pretty.len() - CSS_MODULE_SUFFIX.len()..] == CSS_MODULE_SUFFIX; - // PORT NOTE: `parse_bundler` takes `ParserOptions<'static>` (the - // `'a` on `ParserOptions` is PhantomData-only; storage is a raw - // `NonNull`). Construct via `default(None)` to get `'static`, - // then poke the logger pointer in directly — `temp_log` outlives - // all parsing/minification below (mirrors Zig's `*Log` aliasing). let parser_options = { let mut parseropts = bun_css::ParserOptions::default(None); parseropts.logger = Some(core::ptr::NonNull::from(&mut temp_log)); @@ -1272,16 +1154,7 @@ pub mod parse_worker { } // If this is a css module, the final exports object wil be set in `generateCodeForLazyExport`. let root = Expr::init(E::Object::default(), Loc { start: 0 }); - // PORT NOTE: `StylesheetExtra.symbols` is - // `Vec`; `new_lazy_export_ast_impl` takes - // `Vec`. Both port the same Zig - // `js_ast.Symbol`; convert field-by-field so CSS-module local refs - // index a populated symbol table (.zig:613). let symbols = css_symbols_to_parser_symbols(&extra.symbols, bump); - // PORT NOTE: Zig `defer temp_log.appendToMaybeRecycled(log, source)` - // (.zig:564-566) flushes on EVERY exit including this `try`; mirror - // by matching explicitly so accumulated CSS-module diagnostics are - // not dropped on the error path. let lazy = js_parser::new_lazy_export_ast_impl( bump, &mut topts.define, @@ -1311,12 +1184,6 @@ pub mod parse_worker { let content_hash = ContentHasher::run(&source.contents); let unique_key: &[u8] = if topts.has_dev_server() { - // With DevServer, the actual URL is added now, since it can be - // known this far ahead of time, and it means the unique key code - // does not have to perform an additional pass over files. - // - // To avoid a mutex, the actual insertion of the asset to DevServer - // is done on the bundler thread. let mut buf = bun_alloc::ArenaString::new_in(bump); write!( &mut buf, @@ -1380,16 +1247,6 @@ pub mod parse_worker { // getCodeForParseTaskWithoutPlugins // ─────────────────────────────────────────────────────────────────────────── - // blocked_on: `BundleV2.file_map` is `Option>` where `FileMap` - // is an opaque forward-decl (`_opaque: [u8; 0]`); `.get(path)` - // requires the real T6 `jsc::api::JSBundler::FileMap` surface. Also blocked on - // `bake_types::Framework.built_in_modules` value variant carrying `&[u8]` (vs - // `Box<[u8]>` here) and `resolver.caches.fs.read_file_with_allocator` shape. - - // PORT NOTE: `transpiler`/`resolver` are raw `*mut` (Zig `*Transpiler` / - // `*Resolver`). Callers pass `resolver = &mut (*transpiler).resolver`; taking - // `&mut Transpiler` + `&mut Resolver` would be aliased-`&mut` UB. We only - // touch the disjoint `(*transpiler).fs` and `(*resolver).caches.fs` fields. fn get_code_for_parse_task_without_plugins( task: &mut ParseTask, log: &mut Log, @@ -1458,12 +1315,6 @@ pub mod parse_worker { } } - // Always read into the worker arena: it is pinned for the - // entire bundle pass (freed only via `pool.deinit()` inside - // `deinit_without_freeing_arena`, after `process_files_to_copy` - // has already deep-copied every additional-file body into its - // `OutputFile`). This avoids churning the global allocator with - // one `Vec` per file. let read_arena: Option<&Bump> = Some(bump); // SAFETY: `transpiler` is a live worker-owned `*mut Transpiler`; // `(*transpiler).fs` is a live `*mut FileSystem` BACKREF. @@ -1545,12 +1396,6 @@ pub mod parse_worker { // getCodeForParseTask // ─────────────────────────────────────────────────────────────────────────── - // blocked_on: `BundleV2.plugins` is `Option>` where - // `JSBundlerPlugin` is an opaque forward-decl; `.has_on_before_parse_plugins()` - // requires the real T6 `jsc::api::JSBundler::Plugin` surface (or a - // `dispatch::PluginVTable` slot). Also calls the gated - // `get_code_for_parse_task_without_plugins`. - // PORT NOTE: `transpiler`/`resolver` are raw `*mut` — see // `get_code_for_parse_task_without_plugins`. #[allow(clippy::too_many_arguments)] @@ -1619,32 +1464,15 @@ pub mod parse_worker { pub struct OnBeforeParsePlugin<'a, 'b: 'a> { task: &'a mut ParseTask, log: &'a mut Log, - // PORT NOTE: raw `*mut` (Zig `*Transpiler` / `*Resolver`). Callers pass - // `resolver = &mut (*transpiler).resolver`; storing `&'a mut Transpiler<'b>` - // alongside `&'a mut Resolver<'b>` would be aliased-`&mut` UB. The data - // lifetime `'b` is retained on the pointee for variance only. transpiler: *mut Transpiler<'b>, resolver: *mut Resolver<'b>, bump: &'a Bump, file_path: &'a mut Fs::Path<'b>, loader: &'a mut Loader, deferred_error: Option, - // Zig `*i32`. `fetch_source_code` and `OnBeforeParsePlugin__isDone` re-enter - // via FFI while the outer `run` call has already handed this same i32 to - // C++, so a `&'a mut i32` here would be aliased-`&mut` UB. `Cell` is - // `repr(transparent)` over `UnsafeCell`; FFI receives `Cell::as_ptr()` - // (a real `*mut i32`) and Rust callers use safe `.get()/.set()`. should_continue_running: &'a core::cell::Cell, - // Raw pointer (Zig: `?*OnBeforeParseResult`). Must stay raw — the pointee - // is `OnBeforeParseResultWrapper.result`, and `get_wrapper` walks back to - // the parent via offset_of; a `&mut` here would (a) shrink provenance to - // the inner field and (b) alias with any `&`/`&mut` to the wrapper. result: *mut OnBeforeParseResult, - // Owns the `Contents` fetched by `fetch_source_code` so the buffer the - // native plugin reads through `wrapper.original_source` stays alive for - // the duration of `run`. Returned to the caller when the plugin keeps - // the original source, dropped otherwise. original_contents: Option, } @@ -1713,11 +1541,6 @@ pub mod parse_worker { } } - // Restore the Zig comptime asserts (`ParseTask.zig:808-818`) the port dropped. - // These structs are passed by-pointer to **third-party** native plugins via - // `packages/bun-native-bundler-plugin-api/bundler_plugin.h`, so layout drift - // is a silent ABI break for every plugin in the wild. Literals are the 64-bit - // C layout; TODO(port): replace with codegen-probed constants. bun_core::assert_ffi_layout!( OnBeforeParseArguments, 64, 8; struct_size @ 0, context @ 8, path_ptr @ 16, path_len @ 24, @@ -1778,14 +1601,6 @@ pub mod parse_worker { } pub(crate) fn append(&self, log: &mut Log, namespace: &'static [u8]) { - // Zig (ParseTask.zig:874-884) passes `this.path()` through and dupes - // `source_line_text` via `log.msgs.arena`. `Location.{file,line_text}` - // are `&'static [u8]` here; `Log::dupe` copies into Log-owned storage - // (freed when the Log drops) and returns a lifetime-erased borrow — - // the "alloc-dupe into the log arena" pattern. We dupe `path` too: - // Zig stored it unduped (a raw slice into C-plugin memory that may be - // freed after `log_fn` returns — a latent UAF in the spec); duping is - // strictly safer and matches the intent. let source_line_text = self.source_line_text(); let file = log.dupe(self.path()); let line_text = if !source_line_text.is_empty() { @@ -1951,17 +1766,6 @@ pub mod parse_worker { return 1; } }; - // PORT NOTE: in Zig (`.zig:953-975`) `entry.contents` is a slice into - // the worker arena (`this.arena`) with no destructor, so storing - // `entry.contents.ptr` and letting `entry` go out of scope is sound. - // In Rust `Contents::Owned(Vec)` (the file-read path — see - // `.rs:1287` / `resolver/lib.rs:2285`) frees on drop, which would - // leave `result.source_ptr` / `wrapper.original_source` dangling for - // the native plugin and `OnBeforeParsePlugin::run` to read through. - // Stash ownership on `this.original_contents` so the bytes outlive - // the wrapper; `OnBeforeParsePlugin::run` returns it when the - // plugin keeps the original source, or drops it when the plugin - // replaces the source. let fd = entry.fd; this.original_contents = Some(core::mem::take(&mut entry.contents)); let contents_slice = this @@ -2048,11 +1852,6 @@ pub mod parse_worker { 0 } - // blocked_on: `crate::api::JSBundler::Plugin` (T6) — `call_on_before_parse_plugins` - // is an `extern "C"` JSC dispatch; needs a `dispatch` vtable slot or the real - // `bun_bundler_jsc::JSBundler::Plugin` re-export. Also references the gated - // `fetch_source_code` callback above. - impl<'a, 'b: 'a> OnBeforeParsePlugin<'a, 'b> { pub fn run( &mut self, @@ -2061,10 +1860,6 @@ pub mod parse_worker { from_plugin: &mut bool, ) -> core::result::Result { let mut args = OnBeforeParseArguments { - // `context` is filled in immediately before the FFI call below — - // deriving it here would create a raw from `&mut self` that gets - // popped (Stacked Borrows) by the `&mut self` reads/writes that - // follow, making the callback's `&mut *args.context` UB. path_ptr: self.file_path.text.as_ptr(), path_len: self.file_path.text.len(), default_loader: *self.loader, @@ -2093,12 +1888,6 @@ pub mod parse_worker { }, }; - // Raw pointer with provenance over the whole `wrapper` local so - // `get_wrapper`'s offset_of walk-back stays in-bounds. Never form - // `&mut wrapper.result` while this must reach the wrapper — that - // retags and shrinks provenance to the inner `OnBeforeParseResult` - // only, making `from_field_ptr!` in `get_wrapper` out-of-provenance - // UB (and pushes a Unique tag that invalidates this raw under SB). let result_ptr = core::ptr::addr_of_mut!(wrapper.result); let namespace_str; let namespace = if self.file_path.namespace == b"file" { @@ -2112,10 +1901,6 @@ pub mod parse_worker { // `&mut self` after `self_ptr` is derived. let should_continue_running = self.should_continue_running; self.result = result_ptr; - // Derive `args.context` *after* the last `&mut self` access above so - // no parent-`&mut` use pops its SharedRW tag before the FFI callbacks - // (`fetch_source_code` / `log_fn`) dereference it. Reuse the same raw - // for the `ctx` argument instead of re-deriving from `&mut self`. let self_ptr = std::ptr::from_mut(self).cast::>(); args.context = self_ptr; let count = plugin.call_on_before_parse_plugins( @@ -2193,11 +1978,6 @@ pub mod parse_worker { } *from_plugin = true; *self.loader = wrapper.result.loader; - // If the plugin called `fetch_source_code` and left the - // source unchanged, hand the original `Contents` back to - // the caller so the buffer is reclaimed instead of leaked. - // Otherwise the plugin replaced the source; the original - // (if any) drops with `self`. let contents = if !wrapper.original_source.is_null() && ptr == wrapper.original_source { self.original_contents @@ -2292,15 +2072,6 @@ pub mod parse_worker { // runWithSourceCode // ─────────────────────────────────────────────────────────────────────────── - // blocked_on: `crate::ThreadPool::Worker` (gated module) for - // `this.{arena, transpiler_for_target, ctx}`; `bake_types::Framework` - // missing `server_components` field; `ParserOptions` field-type mismatches - // (`allow_unresolved`, `framework`, `unwrap_commonjs_packages`, - // `server_components` — bundler's `BundleOptions` types diverge from the - // js_parser-local `parser::options` shims); `get_ast`/`get_empty_*` (gated). - // Signature is real; body un-gates once the `ThreadPool` module + the - // `parser::options` ↔ `BundleOptions` type unification land. - fn run_with_source_code( task: &mut ParseTask, this: &mut crate::Worker, @@ -2308,20 +2079,6 @@ pub mod parse_worker { log: &mut Log, entry: &mut CacheEntry, ) -> core::result::Result { - // PORT NOTE: reshaped for borrowck — `transpiler_for_target` borrows `this` - // mutably; we may need to call it again below (server-components branch), - // so hold it as a raw pointer and reborrow per use site. - // - // Stacked-Borrows: derive a single raw `*mut Worker` once and route every - // subsequent worker access through it. The second `transpiler_for_target` - // call (server-components/browser branch) must NOT autoref `&mut *this` - // directly — that retag of the parent `&mut` pops the SharedRW tag chain - // backing the first `transpiler` (and the `resolver` field-projection - // derived from it), making the later `(*resolver)` derefs in `get_ast` UB. - // Zig (.zig:1124, .zig:1189) rebinds `transpiler` while keeping `resolver` - // pointing into the original — valid in Zig (no aliasing model); in Rust - // both calls' `&mut self` must be children of the same raw so neither pops - // the other's tag chain. let worker_raw: *mut crate::Worker = this; // SAFETY: see `get_source_code` — worker arena pinned for the bundle pass. // `'static` matches `JSAst = BundledAst<'static>`; the arena outlives all @@ -2351,15 +2108,6 @@ pub mod parse_worker { .or_else(|| file_path.loader(unsafe { &(*transpiler).options.loaders })) .unwrap_or(Loader::File); - // WARNING: Do not change the variant of `task.contents_or_fd` from - // `.fd` to `.contents` (or back) after this point! - // - // When `task.contents_or_fd == .fd`, `entry.contents` is an owned string. - // When `task.contents_or_fd == .contents`, `entry.contents` is NOT owned! Freeing it here will cause a double free! - // - // Changing from `.contents` to `.fd` will cause a double free. - // This was the case in the situation where the ParseTask receives its `.contents` from an onLoad plugin, which caused it to be - // allocated by `bun.default_allocator` and then freed in `BundleV2.deinit` (and also by `entry.deinit(arena)` below). #[cfg(debug_assertions)] let debug_original_variant_check: ContentsOrFdTag = task.contents_or_fd.tag(); @@ -2444,12 +2192,6 @@ pub mod parse_worker { is_symlink: file_path.is_symlink, }, index: bun_ast::Index(task.source_index.get()), - // PORT NOTE: `entry.contents` is owned by `task.stage` (written back by - // the caller after parse — see `ParseTask::run`). `Source` is stored in - // `Success` which lives no longer than the `ParseTask` itself, so this - // borrow is sound. Routed through the audited `StoreStr` arena-erasure - // path (single `from_raw_parts` in `StoreStr::slice`); replace with - // `Source<'arena>` once that lifetime is threaded through `Success`/Graph. contents: std::borrow::Cow::Borrowed(ast::StoreStr::new(entry_contents).slice()), contents_is_recycled: false, ..Default::default() @@ -2479,10 +2221,6 @@ pub mod parse_worker { let output_format = topts.output_format; - // D042: `crate::options::jsx::Pragma` IS `bun_js_parser::options::JSX::Pragma` - // (both re-export `bun_options_types::jsx::Pragma`). `to_parser_jsx_pragma` - // applies the `_None → Automatic` runtime fold the old `From` bridge did so - // parser-side `== Automatic` checks keep their semantics (.zig:1207). let mut opts = ParserOptions::init( crate::transpiler::to_parser_jsx_pragma(task.jsx.clone()), loader, @@ -2537,11 +2275,6 @@ pub mod parse_worker { opts.features.standard_decorators = !loader.is_typescript() || !(task.experimental_decorators || task.emit_decorator_metadata); opts.features.unwrap_commonjs_packages = topts.unwrap_commonjs_packages; - // PORT NOTE: Zig stores a `*const StringSet` (shared); Rust models it as - // `Option>` on both sides, so we deep-clone (small — - // CLI-supplied flag set). PERF(port): retype - // `RuntimeFeatures.bundler_feature_flags` to `Option<&'a StringSet>` so - // this clone disappears. opts.features.bundler_feature_flags = topts .bundler_feature_flags .as_deref() @@ -2584,11 +2317,6 @@ pub mod parse_worker { bun_ast::runtime::ServerComponentsMode::None }; - // `transpiler.options.framework: Option<&bake_types::Framework>` - // vs `opts.framework: Option<&js_parser::options::Framework>` — both - // TYPE_ONLY mirrors of `bake.Framework`. Project the fields the parser - // reads (Parser.zig:1415,1433) into the parser-side mirror and bump-alloc - // so `opts` can borrow it. opts.framework = topts.framework.map(|f| { // `Framework` is bump-allocated below, so `Drop` never runs — use arena-owned slices. let projected = js_parser::options::Framework { @@ -2683,10 +2411,6 @@ pub mod parse_worker { let mut ast = match ast_result { Ok(a) => a, Err(e) => { - // Zig errdefers (.zig:1123, .zig:1148): reset the AST store - // unconditionally, and free the owned `entry.contents` only when - // it was sourced from `.fd` (the `.contents` variant is borrowed — - // freeing it would double-free in `BundleV2.deinit`). #[cfg(debug_assertions)] if task.contents_or_fd.tag() != debug_original_variant_check { panic!( @@ -2740,13 +2464,6 @@ pub mod parse_worker { // runFromThreadPool // ─────────────────────────────────────────────────────────────────────────── - /// Live entry point for `task_callback` / `io_task_callback` (hoisted to - /// `super::*`). Thin shim over `run_from_thread_pool_impl` so the public - /// symbol stays stable while the body lives in a private fn for borrowck - /// reshaping. - // CONCURRENCY: see `task_callback` — `&mut ParseTask` is unique per callback - // invocation; all shared state is accessed via `&BundleV2` (read-only) or - // the per-OS-thread `Worker` arena. pub(crate) fn run_from_thread_pool(this: &mut ParseTask) { run_from_thread_pool_impl(this); } @@ -2802,13 +2519,6 @@ pub mod parse_worker { } } - // PORT NOTE: reshaped for borrowck — `this` and `this.stage.needs_parse` - // both borrowed mutably. Zig (.zig:1369) passes `&this.stage.needs_parse` - // in-place so the entry's `Contents::Owned` buffer survives in - // `task.stage` for the bundle's lifetime (Success.source.contents - // borrows it via the arena-erased `StoreStr` path). Take it out, parse, then *write it - // back* on every path before `break 'value` so dropping the local - // can't free the buffer underneath the borrowed source. let mut entry = match core::mem::replace(&mut this.stage, ParseTaskStage::NeedsSourceCode) { ParseTaskStage::NeedsParse(e) => e, @@ -2872,15 +2582,6 @@ pub mod parse_worker { // `ParseTask` is arena-owned (no Drop); `jsx` may hold owned slices from tsconfig. drop(core::mem::take(&mut this.jsx)); - // Zig matched `worker.ctx.loop().*` on `AnyEventLoop::{js, mini}`. - // `worker.ctx` is a `BackRef` (safe `Deref`); the BACKREF deref - // of `linker.r#loop` is centralised in `LinkerContext::any_loop_mut`. - // - // Zig `worker.ctx.loop().*` is non-optional (.zig:1416) — `BundleV2::init` - // always sets `linker.r#loop` before scheduling any ParseTask. Running - // `on_complete` inline on the worker thread would violate - // `BundleV2::on_parse_task_complete`'s threading contract (it mutates the - // bundler graph, which is owned by the main/bundler thread). match worker .ctx .linker diff --git a/src/bundler/ServerComponentParseTask.rs b/src/bundler/ServerComponentParseTask.rs index 72a46ff7772..4e99910e932 100644 --- a/src/bundler/ServerComponentParseTask.rs +++ b/src/bundler/ServerComponentParseTask.rs @@ -60,16 +60,6 @@ pub struct ClientEntryWrapper { pub path: Box<[u8]>, } -/// Raw thread-pool callback. Recovers `&mut ServerComponentParseTask` from the -/// intrusive `task` field and dispatches the parse, then posts the result back -/// to the owning event loop. -// CONCURRENCY: thread-pool callback — runs on worker threads, one task per -// `ServerComponentParseTask` (heap-allocated, scheduled exactly once). Writes: -// own fields + `Log` (local) + result is posted via -// `ctx.loop_.enqueue_task_concurrent` (MPSC). Reads `ctx: &BundleV2` shared. -// `ServerComponentParseTask` is `Send` because `ctx: *mut BundleV2` is a -// backref to a `Send` type and `Source`/`Data` payloads are bundle-arena -// slices. fn task_callback_wrap(thread_pool_task: *mut ThreadPoolTask) { // SAFETY: `thread_pool_task` points to the `task` field of a heap-allocated // `ServerComponentParseTask` enqueued by BundleV2; offset_of recovers the parent. @@ -107,15 +97,6 @@ fn task_callback_wrap(thread_pool_task: *mut ThreadPoolTask) { }); let result = bun_core::heap::into_raw(result); - // Zig matched `worker.ctx.loop().*` on `AnyEventLoop::{js, mini}`. - // `worker.ctx` is a `BackRef` (safe `Deref`); the BACKREF deref - // of `linker.r#loop` is centralised in `LinkerContext::any_loop_mut`. - // - // Zig `worker.ctx.loop().*` is non-optional (.zig:52) — `BundleV2::init` - // always sets `linker.r#loop` before scheduling any ServerComponentParseTask. - // Running `on_complete` inline on the worker thread would violate - // `BundleV2::on_parse_task_complete`'s threading contract (it mutates the - // bundler graph, which is owned by the main/bundler thread). match worker .ctx .linker @@ -266,11 +247,6 @@ fn generate_client_reference_proxy( b.add_import_stmt(runtime_import.slice(), [register_ref.slice()])?[0]; let module_path = b.new_expr(E::String::init( - // In development, the path loaded is the source file: Easy! - // - // In production, the path here must be the final chunk path, but - // that information is not yet available since chunks are not - // computed. The unique_key replacement system is used here. if ctx.transpiler().options.has_dev_server() { b.bump.alloc_slice_copy(data.other_source.path.pretty) } else { @@ -340,11 +316,6 @@ fn generate_client_reference_proxy( ..Default::default() }); - // registerClientReference( - // () => { throw new Error(...) }, - // "src/filepath.tsx", - // "Comp" - // ); let throw_stmt = b.new_stmt(S::Throw { value: err_msg }); let arrow_body_stmts: &mut [Stmt] = b.bump.alloc_slice_copy(&[throw_stmt]); let value = b.new_expr(E::Call { diff --git a/src/bundler/ThreadPool.rs b/src/bundler/ThreadPool.rs index 236f17412b9..f1b80272918 100644 --- a/src/bundler/ThreadPool.rs +++ b/src/bundler/ThreadPool.rs @@ -17,14 +17,9 @@ use bun_core::{self, env_var, output as Output}; use bun_sys::Fd; use bun_threading::{Mutex, thread_pool as ThreadPoolLib}; +use crate::BundleV2; use crate::cache::{Contents, Entry as CacheEntry, ExternalFreeFunction}; use crate::linker_context_mod::StmtList; -// PORT NOTE: `crate::options::Target` is the lower-tier `bun_options_types` -// enum (re-exported for downstream crates); `BundleOptions.target` is the -// file-backed `options_impl::Target`. Compare against the latter so -// `primary.options.target == target` type-checks. TODO(refactor): collapse -// the two enums into one (see lib.rs `pub mod options` shadow note). -use crate::BundleV2; use crate::options_impl::Target; use crate::parse_task::{ContentsOrFd, ParseTask, ParseTaskStage}; use crate::transpiler::Transpiler; @@ -37,29 +32,12 @@ bun_core::declare_scope!(ThreadPool, visible); pub(crate) type ThreadId = u64; pub struct ThreadPool { - /// macOS holds an IORWLock on every file open. - /// This causes massive contention after about 4 threads as of macOS 15.2 - /// On Windows, this seemed to be a small performance improvement. - /// On Linux, this was a performance regression. - /// In some benchmarks on macOS, this yielded up to a 60% performance improvement in microbenchmarks that load ~10,000 files. - // PORT NOTE: Zig left this `undefined` when `!uses_io_pool()`; `Option` makes - // that explicit. `ParentRef` (not raw `NonNull`) because the pointee is the - // module-static `io_thread_pool::THREAD_POOL`, live while `ref_count > 0`, - // and all `ThreadPoolLib` driver methods (`schedule`, `warm`, - // `wake_for_idle_events`) take `&self` — so the safe `Deref` projection is - // sufficient and the per-read `unsafe { p.as_ref() }` disappears. pub io_pool: Option>, // TODO(port): lifetime — TSV class UNKNOWN. Conditionally owned via // `worker_pool_is_owned`; kept raw so callers (bundle_v2.rs draft) can // dereference for `wake_for_idle_events()` without a borrow on `ThreadPool`. pub worker_pool: *mut ThreadPoolLib::ThreadPool, pub worker_pool_is_owned: bool, - // PORT NOTE: Zig had `workers_assignments` + sibling `workers_assignments_lock`. - // Per PORTING.md §Concurrency ("Mutex owns T"), the lock is folded into - // the field so `get_worker` can take `&self` — `Worker::get` is entered - // concurrently from arbitrary worker-pool threads, and a `&mut self` here - // would alias `&mut ThreadPool` across threads (UB before the lock is even - // reached). pub workers_assignments: bun_threading::Guarded>, /// Monotonic per-pool stamp for the [`TLS_WORKER`] fast-path key. Pointer /// identity is unsound across `Bun.build()` calls (mimalloc reuses the @@ -176,11 +154,6 @@ mod io_thread_pool { } pub(super) fn shutdown() -> bool { - // Acquire instead of AcqRel is okay because we only need to ensure that other - // threads are done using the IO pool if we read 1 from the ref count. - // - // Relaxed is okay because this function is only guaranteed to succeed when we - // can ensure that no `ThreadPool`s exist. if REF_COUNT .compare_exchange(1, 0, Ordering::Acquire, Ordering::Relaxed) .is_err() @@ -214,18 +187,8 @@ impl ThreadPool { pub fn init( v2: &BundleV2<'_>, - // `Option>` (not `Option<&mut _>`): callers pass the - // process-wide `WorkPool` singleton (`OnceLock`-backed, shared across - // worker threads). Materializing `&mut` from that provenance is UB - // under Stacked Borrows even if the body never writes through it; the - // pool is stored as `*mut` in the struct anyway, so keep it raw - // end-to-end. worker_pool: Option>, ) -> Result { - // PORT NOTE: Spec ThreadPool.zig:85 allocated via the bundle arena - // (`v2.arena().create`), so the `false` ownership flag was - // harmless — the arena reclaimed it. Here we `heap::alloc` (global - // heap), so `deinit()` must `heap::take` it back; record ownership. let owned = worker_pool.is_none(); let pool: *mut ThreadPoolLib::ThreadPool = match worker_pool { Some(p) => p.as_ptr(), @@ -347,12 +310,6 @@ impl ThreadPool { let ContentsOrFd::Contents(contents) = parse_task.contents_or_fd else { unreachable!() }; - // PORT NOTE: Zig moved the `[]const u8` slice into the cache entry - // by value. `cache::Contents` has no borrowed-slice variant; the - // contract (see ParseTask.rs `run_with_source_code` defer) is that - // `entry.deinit()` is *skipped* when `contents_or_fd == .contents`, - // so an `External` provenance tag (no-op deinit) is the correct - // mapping for these unowned bytes. parse_task.stage = ParseTaskStage::NeedsParse(CacheEntry { contents: if contents.is_empty() { Contents::Empty @@ -406,20 +363,6 @@ impl ThreadPool { self.schedule_with_options(parse_task, true); } - // PORT NOTE: returns `&'static mut` — the `Worker` is `heap::alloc`'d - // below and lives until `Worker::deinit`; detaching from `&self` lets - // callers re-borrow `ThreadPool` while holding the worker (Zig: `*Worker`). - // Takes `&self` (not `&mut`) because this is called concurrently from - // worker-pool threads via `Worker::get`; mutation goes through the - // `bun_threading::Guarded` on `workers_assignments`. - // - // Fast path is a per-thread `(pool, worker)` cache: the map lookup is a - // pure `current_thread_id() → *mut Worker` re-read after first touch, so - // every subsequent call from the same thread for the same pool is a TLS - // load + pointer compare. The lock is only taken on first touch per - // `(thread, pool)` pair. Zig (ThreadPool.zig:180) takes the lock on every - // call; on a 19 K-module build that's ~100 K contended acquisitions, which - // perf attributes ~97 % of the build's futex traffic to. #[inline] pub fn get_worker(&self, id: ThreadId) -> &'static mut Worker { let (generation, worker) = TLS_WORKER.get(); @@ -448,13 +391,6 @@ impl ThreadPool { return unsafe { &mut *w }; } MapEntry::Vacant(v) => { - // Allocate raw uninitialized storage; fully written via - // `worker.write(...)` below before any read. Keep it as - // `*mut MaybeUninit` → `.cast()` instead of - // `assume_init()` so we never materialize a `Box` - // whose payload violates `Worker`'s validity invariants - // (niche-optimized `Option<_>` discriminants, the non-null - // fn-pointer in `deinit_task.callback`, `bool` fields). worker = bun_core::heap::into_raw(Box::::new_uninit()).cast::(); v.insert(worker); } @@ -511,20 +447,8 @@ pub struct Worker { /// `undefined`); every read site is post-`has_created`. pub heap: Option, - /// Thread-local memory arena - /// All allocations are freed in `deinit` at the very end of bundling. - // PORT NOTE: self-referential borrow of `heap` — `BackRef` (not a real - // `&'self ThreadLocalArena`) so it can be reseated in `create()` without a - // self-borrow and so call sites read it via safe `Deref` instead of - // open-coding a raw deref. Zig stored the `std.mem.Allocator` vtable; here - // it's just `&heap`. Dangling until `create()` runs; every read site is - // post-`has_created`. pub arena: bun_ptr::BackRef, - /// BACKREF (LIFETIMES.tsv): the owning `BundleV2` strictly outlives every - /// `Worker` it creates (workers are torn down in `deinit_without_freeing_arena` - /// before the bundle is dropped). `BackRef` so call sites read - /// `worker.ctx.field` via safe `Deref` instead of open-coding a raw deref. pub ctx: bun_ptr::BackRef>, /// `None` until [`Worker::create`] populates it; every read site is @@ -534,10 +458,6 @@ pub struct Worker { pub ast_memory_store: ManuallyDrop, pub has_created: bool, - /// `ThreadPoolLib.Thread.current` — `None` when called off a pool thread. - /// `ParentRef` (not raw `*mut`) because the pool-owned `Thread` strictly - /// outlives the per-thread `Worker` it created, and the only access is - /// `push_idle_task(&self)` — so the safe `Deref` projection suffices. pub thread: Option>, pub deinit_task: ThreadPoolLib::Task, @@ -547,18 +467,6 @@ pub struct Worker { } impl Worker { - /// Reborrow the self-referential `arena` (= `&self.heap`) as a shared - /// reference. `BackRef` field, so the deref is encapsulated in - /// [`bun_ptr::BackRef::get`]; see PORT NOTE on the field. - /// - /// `arena` is set to `&self.heap` in [`Worker::create`] before any caller - /// can observe the `Worker`, and is never dangling after that point. The - /// pointee is the worker's own `heap` field, which is pinned for the - /// worker's lifetime. - /// The worker-owned bump arena. Returns `&'static` because the arena is - /// pinned for the worker's lifetime and `Worker::get` already hands out a - /// `&'static mut Worker`; centralising the erasure here avoids per-call-site - /// `detach_lifetime_ref` (the previous pattern at `ParseTask::run`). #[inline] pub fn arena(&self) -> &'static ThreadLocalArena { // SAFETY: `self.arena` is a `BackRef` to `self.heap`, set in @@ -575,30 +483,11 @@ pub struct WorkerData { // raw because the arena is the sibling field `Worker.heap`. pub log: *mut bun_ast::Log, pub estimated_input_lines_of_code: usize, - // PORT NOTE: lifetime erased to `'static` — the inner `&'a Arena` borrows - // `Worker.heap`, which Rust can't express on a sibling field. Zig used - // `transpiler: Transpiler` with a copied `std.mem.Allocator`. - // - // Owned (no `MaybeUninit`): `Transpiler::for_worker` deep-clones every - // `Drop`-carrying field, so `WorkerData`'s drop (via - // `ptr::drop_in_place(data)` in `Worker::deinit`) is sound and frees the - // per-worker `options`/`resolver.caches`/etc. without touching the parent. pub transpiler: Transpiler<'static>, pub other_transpiler: Option>>, } impl Worker { - // CONCURRENCY: thread-pool callback — runs on the worker's own OS thread - // during pool drain (scheduled via `deinit_soon`). Writes: own `Worker` - // fields only (`heap`, `data`, `ast_memory_store` teardown). The `Worker` - // is per-OS-thread (`Thread::current()`-keyed), so `&mut *this` is unique. - // `Worker` is `Send` because its arena/backref pointers are - // owned-heap or per-thread; the `unsafe impl Send for ThreadPool` (the - // bundler pool that owns the workers vec) covers the cross-thread move. - /// Thread-pool idle-task callback (safe fn — coerces to the - /// `Task.callback` field type at the struct-init site). `task` is the - /// `deinit_task` field of a live boxed `Worker` — guaranteed by - /// [`Self::deinit_soon`], the sole scheduler of this callback. fn deinit_callback(task: *mut ThreadPoolLib::Task) { bun_core::scoped_log!(ThreadPool, "Worker.deinit()"); // SAFETY: `task` points to `Worker.deinit_task` (intrusive field) — @@ -640,12 +529,6 @@ impl Worker { // SAFETY: caller contract. let worker = unsafe { &mut *this }; if worker.has_created { - // `wire_after_move` boxed a `bun_js_parser_jsc::Macro::MacroContext` - // behind `macro_context.data` (raw `*mut`, no `Drop` glue); - // `Transpiler` has no `Drop` impl, so `worker.data = None` below - // would strand it. Free both transpilers' boxes explicitly — the - // box only owns a `MacroMap` and a lazy `bun_alloc::Arena`, no JSC - // handles, so the worker thread tearing it down is safe. if let Some(data) = worker.data.as_mut() { if let Some(ctx) = data.transpiler.macro_context.take() { ctx.deinit(); @@ -683,10 +566,6 @@ impl Worker { unsafe { bun_core::heap::destroy(this) }; } - // PORT NOTE: returns `&'static mut` (detached) — the `Worker` is - // heap-pinned (heap::alloc in `get_worker`) and outlives any `ctx` - // borrow; Zig returned `*Worker`. Tying it to `ctx`'s lifetime would - // forbid the `worker` ↔ `ctx` re-borrows in `ParseTask::run_*`. pub fn get(ctx: &BundleV2<'_>) -> &'static mut Worker { // SAFETY: `ctx` is a BACKREF; `graph.pool` is a `NonNull` // pointing at the bundle-owned pool that outlives every worker. We only @@ -715,11 +594,6 @@ impl Worker { } fn create(&mut self, ctx: &BundleV2<'_>) { - // PORT NOTE: `bun_perf::trace` takes a generated `PerfEvent` enum, and - // the generator hasn't emitted `Bundler.Worker.create` yet (only - // `_Stub`). Dropped to avoid mis-attributing the span. - // let _trace = bun_perf::trace("Bundler.Worker.create"); - self.has_created = true; Output::Source::configure_thread(); // Self-referential — `arena` borrows `self.heap`. `Option::insert` @@ -759,12 +633,6 @@ impl Worker { bun_core::scoped_log!(ThreadPool, "Worker.create()"); } - /// Build a per-worker `Transpiler` from `from` (Zig: `transpiler.* = from.*`). - /// - /// PORT NOTE: reshaped for borrowck — associated fn (no `&mut self`) so - /// callers can borrow `self.data.log` disjointly. The returned value is a - /// fully-owned `Transpiler` whose `Drop` is sound; `wire_after_move` must - /// be called once it is at its final address. fn initialize_transpiler( log: *mut bun_ast::Log, from: &Transpiler<'_>, diff --git a/src/bundler/analyze_transpiled_module.rs b/src/bundler/analyze_transpiled_module.rs index 8c5b6b30821..62ba9860676 100644 --- a/src/bundler/analyze_transpiled_module.rs +++ b/src/bundler/analyze_transpiled_module.rs @@ -4,18 +4,6 @@ use core::ptr::NonNull; use bun_core::{self, err, slice_as_bytes}; -// ────────────────────────────────────────────────────────────────────────── -// Re-exports from the printer crate -// -// `js_printer` is the sole *producer* of ModuleInfo records (it walks the AST -// during printing); the bundler/runtime only consume the resulting bytes. The -// canonical builder type therefore lives in `bun_js_printer` (moved down to -// bun_js_printer), and is re-exported here so that bundler-side callers — which -// thread a `&mut ModuleInfo` into `js_printer::Options { module_info }` — see -// the *same* nominal type. The duplicate that used to live in this file caused -// `expected ModuleInfo, found analyze_transpiled_module::ModuleInfo` (E0308) at -// the print boundary. -// ────────────────────────────────────────────────────────────────────────── pub use bun_js_printer::analyze_transpiled_module::{ FetchParameters, ModuleInfo, ModulePhase, StringID, VarKind, }; @@ -146,16 +134,6 @@ pub enum ModuleInfoError { } bun_core::named_error_set!(ModuleInfoError); -/// All slice fields are **self-referential** views into `owner` -/// (`Owner::AllocatedSlice`) or into the parent `ModuleInfo`'s `Vec` storage -/// (`Owner::ModuleInfo`). They are stored as [`bun_ptr::RawSlice`] (raw fat -/// pointers) because Rust references cannot express the self-borrow. -/// -/// Alignment: the on-disk format pads every multi-byte field to a 4-byte -/// offset, and [`Self::create`] allocates the backing buffer with 4-byte -/// alignment ([`MODULE_INFO_ALIGN`]), so every `RawSlice` here is properly -/// aligned for `T` and `.slice()` is sound. (Zig used `[]align(1) const T` -/// because its allocator didn't guarantee the base; we do instead.) pub struct ModuleInfoDeserialized { pub strings_buf: bun_ptr::RawSlice, pub strings_lens: bun_ptr::RawSlice, @@ -179,18 +157,6 @@ pub enum Owner { } impl ModuleInfoDeserialized { - // ── safe accessors ─────────────────────────────────────────────────── - // All slice fields are non-null self-referential views into `self.owner` - // (see struct docs). They are initialized in every constructor (`create` / - // `into_deserialized`), the backing allocation is immutable and outlives - // `&self`, and no `&mut` alias to that storage is ever handed out — so - // materialising `&[T]` for `'_ self` (via `RawSlice::slice`) is sound. - // - // Alignment: every constructor guarantees each view is aligned for its - // element type — `create` allocates a `MODULE_INFO_ALIGN`-aligned buffer - // and `bytes_as_slice` rejects misaligned sub-slices; `into_deserialized` - // borrows from typed `Vec` storage which is naturally aligned. - #[inline] pub fn strings_buf(&self) -> &[u8] { self.strings_buf.slice() @@ -232,10 +198,6 @@ impl ModuleInfoDeserialized { unsafe { match (*this).owner { Owner::ModuleInfo(mi) => { - // PORT NOTE: Zig recovered the parent via - // `@fieldParentPtr("_deserialized", self)`. The Rust port - // stores the `*mut ModuleInfo` directly because the printer - // crate's `ModuleInfo` no longer embeds this struct. drop(bun_core::heap::take(mi)); drop(bun_core::heap::take(this)); } @@ -320,11 +282,6 @@ impl ModuleInfoDeserialized { // Disarm the errdefer: ownership moves into the result. let duped_raw = scopeguard::ScopeGuard::into_inner(guard); - // All seven views borrow `duped_raw` (the boxed allocation moved into - // `owner` below); they stay valid and at a stable address for the - // lifetime of every `RawSlice` copied from this struct. `RawSlice::new` - // erases the borrow lifetime — the structural invariant is upheld by - // `owner` outliving the views. Ok(Box::new(ModuleInfoDeserialized { strings_buf: bun_ptr::RawSlice::new(strings_buf), strings_lens: bun_ptr::RawSlice::new(strings_lens), @@ -381,10 +338,6 @@ impl ModuleInfoDeserialized { } } -/// Maximum element alignment appearing in the serialized format -/// (`u32` / `StringID` / `FetchParameters`). The writer pads every multi-byte -/// field to this boundary, and [`dupe_aligned`] allocates the backing buffer -/// at this alignment, so every typed sub-slice is properly aligned. const MODULE_INFO_ALIGN: usize = core::mem::align_of::(); // Compile-time guard: if a wider element type is ever added to the format, @@ -433,14 +386,6 @@ unsafe fn free_aligned_dup(slice: *mut [u8]) { } } -/// Reinterpret a byte sub-slice of the [`MODULE_INFO_ALIGN`]-aligned backing -/// buffer as `&[T]`. Returns `BadModuleInfo` if `bytes` is not aligned for `T` -/// or its length is not a multiple of `size_of::()` (i.e. the format's -/// internal padding was violated). -/// -/// (Zig used `std.mem.bytesAsSlice` → `[]align(1) const T`; Rust has no -/// under-aligned reference type, so we guarantee alignment instead via -/// `bytemuck::try_cast_slice`, which checks both alignment and size.) #[inline] fn bytes_as_slice(bytes: &[u8]) -> Result<&[T], ModuleInfoError> { bytemuck::try_cast_slice(bytes).map_err(|_| ModuleInfoError::BadModuleInfo) @@ -482,10 +427,6 @@ impl ModuleInfoExt for ModuleInfo { drop(unsafe { bun_core::heap::take(this) }); } fn into_deserialized(mut self: Box) -> Box { - // PORT NOTE: Zig wrote a self-referential `_deserialized` view inside - // `ModuleInfo` during `finalize()`. The Rust printer-crate `ModuleInfo` - // exposes a borrowed `as_deserialized()` instead; here we materialise the - // raw-pointer FFI shape and tie its lifetime to the leaked `Box`. if !self.finalized { let _ = self.finalize(); } diff --git a/src/bundler/barrel_imports.rs b/src/bundler/barrel_imports.rs index c58fcf28a43..065eb009864 100644 --- a/src/bundler/barrel_imports.rs +++ b/src/bundler/barrel_imports.rs @@ -52,11 +52,6 @@ impl RequestedExports { } } -// PORT NOTE: `original_alias` is stored as the raw arena `*const [u8]` (the -// `NamedImport.alias` representation) instead of a tied `&'a [u8]` so the BFS -// loop can hold a `BarrelExportResolution` across a `&mut graph.ast` reborrow -// without the borrow checker seeing an overlap. The pointee outlives the -// bundler arena, so dereferencing later is sound. struct BarrelExportResolution { import_record_index: u32, /// The original alias in the source module (e.g. "d" for `export { d as c }`) @@ -83,15 +78,6 @@ fn resolve_barrel_export( }) } -/// Analyze a parsed file to determine if it's a barrel and mark unneeded -/// import records as is_unused so they won't be resolved. Runs BEFORE resolution. -/// -/// A file qualifies as a barrel if: -/// 1. It has `sideEffects: false` or is in `optimize_imports`, AND -/// 2. All named exports are re-exports (no local definitions), AND -/// 3. It is not an export star target of another barrel. -/// -/// Export * records are never deferred (always resolved) to avoid circular races. pub(crate) fn apply_barrel_optimization( this: &mut BundleV2, parse_result: &mut parse_task::Result, @@ -142,10 +128,6 @@ fn apply_barrel_optimization_impl( return Ok(()); } - // Check requested_exports to see which exports were already requested by - // files parsed before this barrel. scheduleBarrelDeferredImports records - // requests eagerly as each file is processed, so we don't need to scan - // the graph. if let Some(existing) = RequestedExports::lookup(&this.requested_exports, source_index) { match existing { RequestedExports::All => return Ok(()), // import * already seen — load everything @@ -177,12 +159,6 @@ fn apply_barrel_optimization_impl( } } - // Dev server: also include exports persisted from previous builds. This - // handles the case where file A imports Alpha from the barrel (previous - // build) and file B adds Beta (current build). Without this, Alpha would - // be re-deferred because only B's requests are in requested_exports. - // PORT NOTE: `DevServerHandle` is `Copy`; copied out so the `&self` borrow - // doesn't conflict with later `&mut this.requested_exports`. let dev_handle = this.dev_server_handle().copied(); if let Some(dev) = dev_handle { // SAFETY: barrel_needed_exports is owned by DevServer; bundler runs on the bundle @@ -199,13 +175,6 @@ fn apply_barrel_optimization_impl( } } - // When HMR is active, ConvertESMExportsForHmr deduplicates import records - // by path — two `export { ... } from './utils.js'` blocks get merged into - // one record. The surviving record might be the one barrel optimization - // would mark as unused (its exports not needed), while the other record - // (whose exports ARE needed) gets marked unused by HMR dedup. To prevent - // both records from ending up unused, promote needed_records to cover ALL - // import records that share a path with any needed record. if dev_handle.is_some() { // Collect paths of needed records. // PERF(port): was stack-fallback (4096) — profile if hot. @@ -359,30 +328,11 @@ fn resolve_barrel_records( scheduled } -/// After a new file's import records are patched with source_indices, -/// record what this file requests from each target in requested_exports -/// (eagerly, before barrels are known), then BFS through barrel chains -/// to un-defer needed records. Un-deferred records are re-resolved through -/// resolveImportRecords (same path as initial resolution). -/// Returns the number of newly scheduled parse tasks. pub(crate) fn schedule_barrel_deferred_imports( this: &mut BundleV2, result_source_index: u32, result_ast_target: bun_ast::Target, ) -> Result { - // PORT NOTE: Zig passed `*ParseTask.Result.Success` and read `result.ast` - // after `graph.ast.set(idx, result.ast)` value-copied it. Rust *moves* - // `result.ast` into `graph.ast`, so this fn reads the just-written - // `graph.ast[result_source_index]` instead. Phase 1/2 only read - // `import_records` / `named_imports` for THIS index; the BFS (Phase 3) - // takes fresh `&mut graph.ast` borrows after these raw reads are dead. - // - // `graph.ast` SoA columns are not reallocated for the duration of this fn - // (no `graph.ast.append`/`set`); `items_*_mut()` in the BFS only re-slices - // the existing backing. The backrefs below are dereferenced only during - // Phase 1/2 (queue seeding), strictly before the BFS takes any `&mut` to - // the same column. `BackRef` detaches the borrowck lifetime so later - // `&mut this.*` borrows don't conflict. let file_import_records: bun_ptr::BackRef = bun_ptr::BackRef::new(&this.graph.ast.items_import_records()[result_source_index as usize]); let file_named_imports: bun_ptr::BackRef = @@ -392,21 +342,6 @@ pub(crate) fn schedule_barrel_deferred_imports( // don't conflict with the `&self` accessor. let dev_handle = this.dev_server_handle().copied(); - // Phase 1: Seed — eagerly record what this file requests from each target. - // This runs for every file, even before any barrels are known. When a barrel - // is later parsed, applyBarrelOptimization reads these pre-recorded requests - // to decide which exports to keep. O(file's imports) per file. - - // In dev server mode, patchImportRecordSourceIndices skips saving source_indices - // on import records (the dev server uses path-based identifiers instead). But - // barrel optimization requires source_indices to seed requested_exports and to - // BFS un-defer records. Resolve paths → source_indices here as a fallback. - // - // PORT NOTE: reshaped for borrowck — `path_to_source_index_map` borrows - // `&mut this.graph`; wrap in `BackRef` so the long-lived read borrow - // doesn't conflict with `&mut this.requested_exports` / - // `&mut this.graph.ast` below. The map is not mutated for the duration of - // this fn. let path_to_source_index_map: Option< bun_ptr::BackRef, > = if dev_handle.is_some() { @@ -424,15 +359,6 @@ pub(crate) fn schedule_barrel_deferred_imports( // so we can detect bare imports (those with no specific export bindings). let mut named_ir_indices = AutoBitSet::init_empty(file_import_records.len())?; - // In HMR, ConvertESMExportsForHmr deduplicates import records by path: - // two `import { X } from 'mod'` statements become one, and the second - // record is marked is_unused=true. resolveImportRecords then skips those - // records, so their path.text stays as the raw specifier while the - // surviving record's path.text becomes the resolved absolute path. - // named_imports entries created for the dedup'd record still point at - // its index, so the direct path lookup below fails for those entries. - // Build a fallback: raw specifier → surviving record's resolved path - // text, using non-unused records in this file. See #28886. let mut dedup_fallback: StringArrayHashMap<&'static [u8]> = StringArrayHashMap::default(); if dev_handle.is_some() { for ir_probe in file_import_records.as_slice() { @@ -458,13 +384,6 @@ pub(crate) fn schedule_barrel_deferred_imports( } named_ir_indices.set(ni.import_record_index as usize); let ir = &file_import_records.as_slice()[ni.import_record_index as usize]; - // In dev server mode, source_index may not be patched — resolve via - // path map as a read-only fallback. Do NOT write back to the import - // record — the dev server intentionally leaves source_indices unset - // and other code (IncrementalGraph, printer) depends on that. - // For dedup'd HMR records (is_unused), fall back to a sibling's - // resolved path text since the record itself still has the raw - // specifier in path.text. let resolved_path_text = if ir.flags.contains(import_record::Flags::IS_UNUSED) { dedup_fallback .get(ir.path.text) @@ -503,12 +422,6 @@ pub(crate) fn schedule_barrel_deferred_imports( } } - // Handle import records without named bindings (not in named_imports). - // - `import "x"` (bare statement): tree-shakeable with sideEffects: false — skip. - // - `require("x")`: synchronous, needs full module — always mark as .all. - // - `import("x")`: returns the full module namespace at runtime — consumer - // can destructure or access any export. Must mark as .all. We cannot - // safely assume which exports will be used. for (idx, ir) in file_import_records.as_slice().iter().enumerate() { let target = if ir.source_index.is_valid() { ir.source_index.get() @@ -539,11 +452,6 @@ pub(crate) fn schedule_barrel_deferred_imports( } } - // Phase 2: BFS — un-defer barrel records that are now needed. - // Build work queue from this file's named_imports, then propagate - // through chains of barrels. Only runs real work when barrels exist - // (targets with deferred records). - // PERF(port): was stack-fallback (8192) — profile if hot. let mut queue: Vec = Vec::new(); // See PORT NOTE above — read-only deref valid through Phase 2. @@ -621,11 +529,6 @@ pub(crate) fn schedule_barrel_deferred_imports( } } - // Also seed the BFS with exports previously requested from THIS file - // that couldn't propagate because this file wasn't parsed yet. - // This handles the case where file A requests export "d" from file B, - // but B hadn't been parsed when A's BFS ran, so B's export * records - // were empty and the propagation stopped. let this_source_index = result_source_index; if let Some(existing) = RequestedExports::lookup(&this.requested_exports, this_source_index) { match existing { @@ -654,11 +557,6 @@ pub(crate) fn schedule_barrel_deferred_imports( return Ok(0); } - // Items [0, initial_queue_len) are from this file's imports and were - // already recorded in requested_exports during seeding (phase 1). - // Skip dedup for them so un-deferral proceeds correctly. - // Items added during BFS propagation (>= initial_queue_len) use normal - // dedup via requested_exports to prevent cycles. let initial_queue_len = queue.len(); // PERF(port): was stack-fallback (1024) — profile if hot. @@ -813,10 +711,6 @@ pub(crate) fn schedule_barrel_deferred_imports( Ok(newly_scheduled) } -/// Persist an export name for a barrel file on the DevServer. Called during -/// seeding so that exports requested in previous builds are not lost when the -/// barrel is re-parsed in an incremental build where the requesting file is -/// not stale. fn persist_barrel_export(dev: &crate::dispatch::DevServerHandle, barrel_path: &[u8], alias: &[u8]) { dev.register_barrel_export(barrel_path, alias) } diff --git a/src/bundler/bundle_v2.rs b/src/bundler/bundle_v2.rs index c51715b569c..b9fdc7ed5e1 100644 --- a/src/bundler/bundle_v2.rs +++ b/src/bundler/bundle_v2.rs @@ -61,28 +61,8 @@ pub struct PendingImport { } pub struct BundleV2<'a> { - // PORT NOTE: Zig stored `*Transpiler` (and aliased the same pointer into - // `ssr_transpiler` when SSR graph isn't separate). `ssr_transpiler` stays - // `*mut` so the alias is legal; `transpiler` is `&'a mut` for ergonomic - // field access throughout the bundler bodies. pub transpiler: &'a mut Transpiler<'a>, - /// When Server Components is enabled, this is used for the client bundles - /// and `transpiler` is used for the server bundles. - /// - /// `ParentRef` (not raw `NonNull`): set once in `init` (from `BakeOptions` - /// or `initialize_client_transpiler`), the pointee is live for `'a`, and - /// the read-only projection (`client_transpiler_ref`) is the common path — - /// so the safe `Deref` removes the per-accessor `unsafe { p.as_ref() }`. - /// The two `&mut` sites in `transpiler_for_target` go through the explicit - /// `unsafe assume_mut` escape hatch. pub client_transpiler: Option>>, - /// Owns the storage backing `client_transpiler` when it was lazily created - /// by `initialize_client_transpiler` (browser-target request from a - /// server-side build). Stays `None` when `client_transpiler` is borrowed - /// from `BakeOptions` (DevServer owns that one). Dropped in - /// `deinit_without_freeing_arena` so the deep-cloned `BundleOptions` / - /// `Resolver` global-heap fields are released — `arena.alloc` would leak - /// them since bumpalo never runs `Drop`. pub owned_client_transpiler: Option>>, /// See `bake.Framework.ServerComponents.separate_ssr_graph`. pub ssr_transpiler: *mut Transpiler<'a>, @@ -131,16 +111,6 @@ pub struct BundleV2<'a> { /// If false we can skip TLA validation and propagation. pub has_any_top_level_await_modules: bool, - /// Barrel optimization: tracks which exports have been requested from each - /// module encountered during barrel BFS. Keys are source indices. Values - /// track requested export names for deduplication and cycle detection. - /// Persists across calls to `scheduleBarrelDeferredImports` so cross-file - /// deduplication is free. - /// - /// Indexed by `source_index` (dense `0..module_count`); a `Vec>` - /// instead of the Zig `AutoArrayHashMap` because the key space is - /// dense and this is probed once per import in `on_parse_task_complete` - /// (the main-thread parse-phase throughput limiter). pub requested_exports: Vec>, } @@ -161,10 +131,6 @@ pub struct BakeOptions<'a> { } impl<'a> BundleV2<'a> { - // ── raw-ptr accessors ───────────────────────────────────────────────── - // PORT NOTE: `transpiler`/`ssr_transpiler` are `*mut` because Zig stored - // aliased `*Transpiler` (same pointer in both slots when no SSR graph). - // Callers go through these accessors so the unsafe deref is centralized. #[inline] pub fn transpiler(&self) -> &Transpiler<'a> { &*self.transpiler @@ -288,48 +254,6 @@ pub mod bv2_impl { use super::ResolveQueue; use crate::IndexInt; use crate::mal_prelude::*; - // This is Bun's JavaScript/TypeScript bundler - // - // A lot of the implementation is based on the Go implementation of esbuild. Thank you Evan Wallace. - // - // # Memory management - // - // Zig is not a managed language, so we have to be careful about memory management. - // Manually freeing memory is error-prone and tedious, but garbage collection - // is slow and reference counting incurs a performance penalty. - // - // Bun's bundler relies on mimalloc's threadlocal heaps as arena allocators. - // - // When a new thread is spawned for a bundling job, it is given a threadlocal - // heap and all allocations are done on that heap. When the job is done, the - // threadlocal heap is destroyed and all memory is freed. - // - // There are a few careful gotchas to keep in mind: - // - // - A threadlocal heap cannot allocate memory on a different thread than the one that - // created it. You will get a segfault if you try to do that. - // - // - Since the heaps are destroyed at the end of bundling, any globally shared - // references to data must NOT be allocated on a threadlocal heap. - // - // For example, package.json and tsconfig.json read from the filesystem must be - // use the global arena (bun.default_allocator) because bun's directory - // entry cache and module resolution cache are globally shared across all - // threads. - // - // Additionally, `LinkerContext`'s arena is also threadlocal. - // - // - Globally allocated data must be in a cache & reused, or we will create an infinite - // memory leak over time. To do that, we have a DirnameStore, FilenameStore, and the other - // data structures related to `BSSMap`. This still leaks memory, but not very - // much since it only allocates the first time around. - // - // In development, it is strongly recommended to use either a debug build of - // mimalloc or Valgrind to help catch memory issues - // To use a debug build of mimalloc: - // - // make mimalloc-debug - // use core::ffi::c_void; use core::ptr::NonNull; @@ -461,12 +385,6 @@ pub mod bv2_impl { } } - /// Mirrors src/bake/bake.zig `Framework`. TYPE_ONLY subset of the fields - /// the bundler/parser actually consult (see ParseTask.zig:1253 - /// `opts.framework = transpiler.options.framework`); `file_system_router_types` - /// stays in T6 because only `bake::FrameworkRouter` reads it. - // TODO(b0-genuine): remaining Framework field `file_system_router_types` - // stays in T6; only bake::FrameworkRouter reads it. #[non_exhaustive] pub struct Framework { pub built_in_modules: bun_collections::StringArrayHashMap, @@ -485,11 +403,6 @@ pub mod bv2_impl { pub client_css_in_js: crate::options::ClientCssInJs, } impl Framework { - /// Construct the bundler-side TYPE_ONLY view. Called from - /// `bun_runtime::bake::Framework::init_transpiler_with_options` - /// (spec bake.zig:778 `out.options.framework = framework`); the - /// runtime owns the canonical `bake.Framework` and projects the - /// fields the bundler reads. pub fn new( built_in_modules: bun_collections::StringArrayHashMap, server_components: Option, @@ -505,10 +418,6 @@ pub mod bv2_impl { } } } - /// Mirrors src/bake/bake.zig `Framework.ServerComponents` — full string - /// surface so the parser-side projection (ParseTask.rs `run_with_source_code`) - /// can forward user-configured `serverRegisterServerReference` / - /// `clientRegisterServerReference` instead of hardcoding defaults. #[derive(Default, Clone)] pub struct ServerComponents { pub separate_ssr_graph: bool, @@ -551,17 +460,6 @@ pub mod bv2_impl { /// Alias used at the crate root (`crate::HmrRuntimeSide`); identical to `Side`. pub type HmrRuntimeSide = Side; - /// Mirrors src/bake/bake.zig:855 `getHmrRuntime`. MOVE_DOWN bake→bundler: - /// the codegen'd `bake.client.js` / `bake.server.js` are loaded via - /// `bun_core::runtime_embed_file!` (same per-site `OnceLock` cache - /// `js_parser/runtime.rs` uses for `runtime.out.js`), so the storage lives - /// HERE — no upward link to `bun_runtime`. `bun_runtime::bake` keeps its - /// own `&'static ZStr` flavour for JSC/C++ handoff; this bundler-side copy - /// only needs `&[u8]` for the chunk preamble + sourcemap line skip, so the - /// NUL-termination dance is unnecessary. Per-side `OnceLock` - /// memoizes the `\n` count (Zig const-eval'd it on the `@embedFile` arm; - /// `runtime_embed_file!` already caches the file load, this caches the - /// `init` scan so repeat calls are a `Copy`). pub fn get_hmr_runtime(side: Side) -> HmrRuntime { static CLIENT: std::sync::OnceLock = std::sync::OnceLock::new(); static SERVER: std::sync::OnceLock = std::sync::OnceLock::new(); @@ -613,18 +511,9 @@ pub mod bv2_impl { ..Default::default() }); - /// Canonical port of src/bake/production.zig:844 `EntryPointMap`. - /// Lives in the bundler (lower tier) so both `bun_runtime::bake::production` - /// and `BundleV2::generate_from_bake_production_cli` share ONE nominal type - /// (PORTING.md §Layering). Router-integration methods (`InsertionHandler`) - /// are added by `bun_runtime::bake` via a local trait impl. pub mod production { use super::Side; - /// `OpaqueFileId` is the insertion index into `EntryPointMap.files`. - /// This is the same newtype as `framework_router::OpaqueFileId`; the - /// bake crate re-exports that one and converts via `.get()` only at - /// the FFI boundary. #[repr(transparent)] #[derive(Copy, Clone, Eq, PartialEq, Hash)] pub struct OpaqueFileId(pub u32); @@ -639,11 +528,6 @@ pub mod bv2_impl { } } - /// `EntryPointMap.InputFile`. Zig packed `Side` into the slice-len word - /// for a 16-byte key; the Rust `Hash`/`Eq` impls below are content-based - /// (not byte-layout), so that packing is not load-bearing here — store a - /// `RawSlice` instead and let `bun_ptr` encapsulate the unsafe re-borrow. - /// `RawSlice: Send + Sync`, so no manual auto-trait impls are needed. #[derive(Copy, Clone)] pub struct InputFile { abs_path: bun_ptr::RawSlice, @@ -688,10 +572,6 @@ pub mod bv2_impl { pub root: Box<[u8]>, /// `OpaqueFileId` is the insertion index into this map. pub files: EntryPointHashMap, - /// Owned backing storage for the duped path bytes that `InputFile` - /// keys point into (raw ptr+len). Mirrors Zig's `map.arena.dupe` - /// against `bun.default_allocator` — kept here so the allocations - /// drop with the map (PORTING.md §Forbidden: no `Box::leak`). pub owned_paths: Vec>, } impl EntryPointMap { @@ -725,15 +605,7 @@ pub mod bv2_impl { // TODO(b0): jsc::api arrives from move-in (TYPE_ONLY → bundler) use self::api as jsc_api; - /// CYCLEBREAK(b0) TYPE_ONLY: data-only halves of `jsc::api::JSBundler` and - /// `jsc::api::BuildArtifact` that the bundler reads/constructs without touching - /// JSC. The JS-thread halves (dispatch onto the JS event loop, `toJS`, plugin - /// FFI bodies) stay in tier-6 (`bun_runtime::api`) and re-export these. pub mod api { - /// Mirrors src/runtime/api/JSBundler.zig:1799 `BuildArtifact.OutputKind`. - /// Canonical definition lives in `crate::options::OutputKind`; re-exported - /// here so the documented CYCLEBREAK path `api::build_artifact::OutputKind` - /// keeps resolving. pub mod build_artifact { pub use crate::options::OutputKind; } @@ -751,18 +623,8 @@ pub mod bv2_impl { use bun_core::String as BunString; use bun_resolver::fs::PathResolverExt as _; - // `Plugin = opaque {}` — backed by C++ `BunPlugin`. The bundler calls - // `has_any_matches` / `match_on_load` / `match_on_resolve` directly - // (no JSC types needed — only `BunString` / raw context ptrs). The - // JSC-aware methods (`create`, `add_plugin`, `global_object`, …) are - // added by `bun_runtime` via the `PluginJscExt` extension trait so - // this crate stays free of `JSValue` / `JSGlobalObject`. bun_opaque::opaque_ffi! { pub struct Plugin; } unsafe extern "C" { - // The three `safe fn`s below take only Rust references / by-value - // scalars: every pointer the C++ side reads is guaranteed valid by - // the type system, so there is no caller-side precondition left to - // discharge (mirrors the `safe fn` pattern in `lolhtml_sys`). #[link_name = "JSBundlerPlugin__anyMatches"] safe fn JSBundlerPlugin__anyMatches( this: &Plugin, @@ -795,13 +657,6 @@ pub mod bv2_impl { safe fn JSBundlerPlugin__drainDeferred(this: &mut Plugin, rejected: bool); #[link_name = "JSBundlerPlugin__hasOnBeforeParsePlugins"] safe fn JSBundlerPlugin__hasOnBeforeParsePlugins(this: &Plugin) -> i32; - // `ctx`/`args`/`result` are opaque cookies the C++ side round-trips - // to Rust-registered native-plugin callbacks without dereferencing - // in `JSBundlerPlugin.cpp` itself (same posture as `matchOnLoad` - // above); `&Plugin`/`&BunString` discharge the only direct C++-side - // dereferences, and `should_continue_running` validity is upheld by - // the `&Cell` borrow in the safe wrapper — no caller-side - // precondition remains, so `safe fn`. #[link_name = "JSBundlerPlugin__callOnBeforeParsePlugins"] safe fn JSBundlerPlugin__callOnBeforeParsePlugins( this: &Plugin, @@ -814,12 +669,6 @@ pub mod bv2_impl { ) -> i32; } impl Plugin { - /// `Plugin.drainDeferred` (JSBundler.zig) — resolve every onLoad - /// `.defer()` promise. Zig wraps the FFI in `fromJSHostCallGeneric` - /// for exception-scope tracking and returns `JSError!void`; the - /// only bundler caller (`DeferredBatchTask::run_on_js_thread`) is - /// `catch return`, so the void FFI call is the observable - /// behaviour at this tier. pub fn drain_deferred(&mut self, rejected: bool) { JSBundlerPlugin__drainDeferred(self, rejected) } @@ -924,11 +773,6 @@ pub mod bv2_impl { } } - /// Mirrors `JSBundler.FileMap` — virtual in-memory files for the build. - /// The Zig value type is `jsc.Node.BlobOrStringOrBuffer` (T6); bundler - /// only ever reads `.slice()`, so the moved-down map stores raw bytes. - /// `bun_runtime`'s `from_js` parses JS values via `BlobOrStringOrBuffer` - /// in async (owning-copy) mode and inserts the extracted bytes here. #[derive(Default)] pub struct FileMap { pub map: bun_collections::StringHashMap>, @@ -967,16 +811,6 @@ pub mod bv2_impl { self.map.contains_key(normalized) } } - /// Returns a `resolver::Result` for a file in the map, or `None` if - /// not found. Handles direct key matches and relative specifiers - /// joined against `dirname(source_file)` (with Windows - /// drive-letter / separator normalization). - /// - /// `arena` is the build's bump arena (`BundleV2::arena()`); - /// the matched key is copied into it so the returned - /// `bun_resolver::Result`'s `Path<'static>` borrows arena memory - /// (lives for the entire build pass) instead of the map's key - /// storage. pub fn resolve( &self, arena: &bun_alloc::Arena, @@ -1079,11 +913,6 @@ pub mod bv2_impl { None } - /// Build a `bun_resolver::Result` for a matched key. `key` must - /// already satisfy `'static` — see [`resolve`], which copies the - /// map-owned key into the build's bump arena before calling here so - /// the resulting `Path<'static>` borrows arena memory rather than - /// forging a `'static` from a map borrow. #[inline] fn result_for_key(key: &'static [u8]) -> bun_resolver::Result { bun_resolver::Result { @@ -1168,11 +997,6 @@ pub mod bv2_impl { task: bun_event_loop::AnyTaskWithExtraContext::AnyTaskWithExtraContext::default(), } } - /// Hops to the JS thread to call the `onResolve` plugin chain. - /// Zig spec (JSBundler.zig:1311): - /// `this.js_task = AnyTask.init(this); - /// bv2.jsLoopForPlugins().enqueueTaskConcurrent( - /// jsc.ConcurrentTask.create(this.js_task.task()))` pub fn dispatch(&mut self) { self.js_task = bun_event_loop::AnyTask::AnyTask { ctx: core::ptr::NonNull::new( @@ -1289,12 +1113,6 @@ pub mod bv2_impl { pub fn bv2_ptr(&self) -> *mut BundleV2<'static> { self.bv2 } - /// Shared access to the heap-allocated `ParseTask` this load wraps. - /// - /// `parse_task` is a `BackRef` set from `&mut ParseTask` in `init` - /// (never null) and the task outlives the `Load` — it is only - /// handed to the thread-pool *after* the plugin load resolves, so - /// no concurrent mutation overlaps a `&` borrow here. #[inline] pub fn parse_task(&self) -> &ParseTask { self.parse_task.get() @@ -1314,11 +1132,6 @@ pub mod bv2_impl { pub fn bake_graph(&self) -> crate::bake_types::Graph { self.parse_task().known_target.bake_graph() } - /// Hops to the JS thread to call the `onLoad` plugin chain. - /// Zig spec (JSBundler.zig:1449): - /// `this.js_task = AnyTask.init(this); - /// let concurrent_task = jsc.ConcurrentTask.createFrom(&this.js_task); - /// bv2.jsLoopForPlugins().enqueueTaskConcurrent(concurrent_task)` pub fn dispatch(&mut self) { self.js_task = bun_event_loop::AnyTask::AnyTask { ctx: core::ptr::NonNull::new( @@ -1366,10 +1179,6 @@ pub mod bv2_impl { } } - /// `SavedFile` is a unit struct in Zig - /// (src/bundler_jsc/output_file_jsc.zig:4) — its only member is `toJS`, which - /// is JSC-bound and stays in T6. The bundler stores it as an `OutputFile` value - /// tag, so a unit struct here is sufficient. pub mod saved_file { #[derive(Default, Clone, Copy)] pub struct SavedFile; @@ -1410,11 +1219,6 @@ pub mod bv2_impl { pub use bun_js_printer::MangledProps; - // ══════════════════════════════════════════════════════════════════════════ - // CYCLEBREAK §Dispatch — vtables/hooks for T6 GENUINE deps (jsc/bake/runtime). - // Low tier (bundler) names no high-tier types. High tier (runtime) provides - // static instances and registers hooks at init. See PORTING.md §Dispatch. - // ══════════════════════════════════════════════════════════════════════════ pub mod dispatch { pub use crate::{DevServerHandle, DevServerHandleKind}; @@ -1435,12 +1239,6 @@ pub mod bv2_impl { } unsafe extern "Rust" { - /// Defined `#[no_mangle]` in `bun_jsc::cached_bytecode`. Generic - /// "generate JSC bytecode off the main JS thread" helper — marks the - /// calling thread as bytecode-only, initializes JSC, generates, and - /// returns an owned copy of the bytes. Definer-prefixed (`__bun_jsc_*`). - /// All arguments are safe Rust types (no raw-pointer preconditions), - /// so the link-time-resolved body upholds Rust's invariants on its own. safe fn __bun_jsc_generate_cached_bytecode( format: crate::options_impl::Format, source: &[u8], @@ -1449,16 +1247,6 @@ pub mod bv2_impl { } unsafe extern "Rust" { - /// Defined `#[no_mangle]` in `bun_jsc::hot_reloader`. Installs a - /// `NewHotReloader` watcher on the given - /// `BundleV2` (Zig: `Watcher.enableHotModuleReloading(this, null)` in - /// `BundleV2.init` — bundle_v2.zig:994). The bundler can't name the - /// reloader generic (T6), so this is a definer-prefixed extern hook. - /// `'static` matches the impl-side signature; the sole caller - /// (`bun build --watch`) leaks the `Box` via - /// `Box::into_raw` once `generate_from_cli` returns. The watcher is - /// installed after the last fallible step in `BundleV2::init`, so the - /// box is never dropped while the watcher holds a pointer to it. fn __bun_jsc_enable_hot_module_reloading_for_bundler( bv2: core::ptr::NonNull>, ); @@ -1489,13 +1277,6 @@ pub mod bv2_impl { __bun_jsc_generate_cached_bytecode(format, source, source_provider_url) } - /// CYCLEBREAK GENUINE: `JSBundleCompletionTask` (JSBundler.zig) — the - /// concrete struct lives in `bun_runtime` (its fields name `Config`/ - /// `Plugin`/`HTMLBundle::Route`). The bundler reads exactly two things - /// from it (`result == .err` and `jsc_event_loop.enqueueTaskConcurrent`), - /// so the high tier hands the bundler an erased owner + `&'static` vtable - /// pair (same shape as [`DevServerHandle`]). PERF(port): was direct field - /// access in Zig. pub struct CompletionDispatch { /// Zig: `completion.result == .err` pub result_is_err: unsafe fn(core::ptr::NonNull) -> bool, @@ -1515,13 +1296,6 @@ pub mod bv2_impl { // thread, read by the bundle thread; `enqueue_task_concurrent` is the only // cross-thread call and it goes through `jsc::EventLoop`'s lock-free queue. unsafe impl Send for CompletionHandle {} - // Intentionally not `Sync`: the opaque owner (`JSBundleCompletionTask`) - // is modeled as `!Sync`, and this wrapper exposes `result_is_err(&self)` - // in addition to the lock-free enqueue path, so blanket `&CompletionHandle` - // sharing across threads is not justified. The handle only needs to *move* - // to the bundle thread (`Send`), not be shared. If a cross-thread `&` ever - // becomes necessary, split out an enqueue-only wrapper and make only that - // type `Sync`. impl CompletionHandle { #[inline] pub fn result_is_err(&self) -> bool { @@ -1543,13 +1317,6 @@ pub mod bv2_impl { /// (`Option>`). pub use crate::linker_context_mod::EventLoop; - // `JSBundleCompletionTask` (JSBundler.zig) — typed-ptr marker for - // `BundleV2.completion`. The concrete struct lives in `bun_runtime` (its - // fields name `Config`/`Plugin`/`HTMLBundle::Route`); the bundler only ever - // holds a `NonNull` inside [`dispatch::CompletionHandle`] - // and never dereferences it. Nomicon opaque-FFI pattern: ZST with - // `PhantomData<(*mut u8, PhantomPinned)>` so it is `!Send + !Sync + !Unpin` - // and has no usable size/layout in this crate. bun_opaque::opaque_ffi! { pub struct JSBundleCompletionTask; } /// Erase `&[u8]` to `&'static [u8]` for storage in lifetime-erased @@ -1579,18 +1346,9 @@ pub mod bv2_impl { unsafe { (*p).into_static() } } - // Unified with the canonical definitions at the parent module level (this - // avoids two distinct nominal `BundleV2`/`PendingImport`/`BakeOptions` types - // that previously caused widespread "expected `BundleV2`, found `BundleV2`" - // errors in cross-module call sites). pub use super::{BakeOptions, BundleV2, PendingImport}; impl<'a> BundleV2<'a> { - /// Zig: `jsLoopForPlugins().enqueueTaskConcurrent(task)`. The Rust port - /// folds the lookup + enqueue so the bundler never dereferences - /// `JSBundleCompletionTask` (its layout lives in `bun_runtime`); the - /// `completion` handle carries the `&'static` vtable. - /// PERF(port): was inline `switch (this.loop().*)` + direct field access. pub fn enqueue_on_js_loop_for_plugins( &mut self, task: NonNull, @@ -1625,20 +1383,6 @@ pub mod bv2_impl { } pub fn initialize_client_transpiler(&mut self) -> Result<&mut Transpiler<'a>, Error> { - // bundle_v2.zig:198-241. - // - // PORT NOTE: Zig does `client_transpiler.* = this_transpiler.*` (bitwise - // struct copy into an arena slot — no destructors). The Rust port - // builds a fresh owned `Transpiler` via `Transpiler::for_worker` - // (per-field deep clone), mutates the browser-specific options with - // ordinary assignment (every field is owned by the clone, so `Drop` on - // the overwritten value is correct), then boxes it on the global heap - // (NOT the bump arena — the clone holds `Box`/`Vec`/`MimallocArena` - // fields that need `Drop` to run) and wires the self-referential - // `linker`/`macro_context`. The box is parked on - // `self.owned_client_transpiler` so `deinit_without_freeing_arena` - // releases it. - // `arena` is only the scratch param for `Transpiler::for_worker`; the // returned `Transpiler` itself is NOT placed in it. // SAFETY: `graph.heap` outlives the bundle pass; erase the `&self` @@ -1683,15 +1427,7 @@ pub mod bv2_impl { ct.options.public_path = b"/".to_vec().into_boxed_slice(); } - // Move into a stable heap slot, then wire self-refs at the final - // address. `Box` (global mimalloc heap) so `Drop` runs on the - // deep-cloned `BundleOptions`/`Resolver` fields; `arena.alloc` would - // leak them (bumpalo never drops). let mut boxed: Box> = Box::new(ct); - // Zig: `setLog` / `setAllocator` / `linker.resolver = &resolver` / - // `macro_context = MacroContext.init(transpiler)` / - // `resolver.caches = CacheSet.init(alloc)` — all handled by - // `for_worker` + `wire_after_move`. boxed.wire_after_move(); // `configure_defines` early-returns on `options.defines_loaded` (cloned @@ -1705,11 +1441,6 @@ pub mod bv2_impl { // Zig: `client_transpiler.resolver.env_loader = client_transpiler.env;` boxed.resolver.env_loader = NonNull::new(this_env.cast()); - // Park the owning Box first, then derive both the published `NonNull` - // and the returned `&mut` from its final resting place. Taking the - // pointer *before* moving `boxed` into `self` would give it stale - // provenance under Stacked Borrows (Box retags on move and asserts - // uniqueness, invalidating any previously-derived raw pointer). self.owned_client_transpiler = Some(boxed); let ct: &mut Transpiler<'a> = self.owned_client_transpiler.as_deref_mut().unwrap(); self.client_transpiler = Some(NonNull::from(&mut *ct).into()); @@ -1760,10 +1491,6 @@ pub mod bv2_impl { impl<'a> ReachableFileVisitor<'a> { const MAX_REDIRECTS: usize = 64; - // Find all files reachable from all entry points. This order should be - // deterministic given that the entry point order is deterministic, since the - // returned order is the postorder of the graph traversal and import record - // order within a given file is deterministic. pub fn visit( &mut self, source_index: Index, @@ -1903,10 +1630,6 @@ pub mod bv2_impl { } } - /// RAII guard returned by [`BundleV2::decrement_scan_counter_on_drop`]. - /// Decrements the bundle's pending-scan counter when dropped, mirroring Zig's - /// `defer this.decrementScanCounter()` without holding a unique borrow across - /// the body. Stores a raw pointer; caller guarantees the `BundleV2` outlives it. pub struct ScanCounterGuard { bv2: *mut BundleV2<'static>, } @@ -1947,22 +1670,10 @@ pub mod bv2_impl { self.dynamic_import_entry_points = ArrayHashMap::new(); - // PORT NOTE: reshaped for borrowck — hoist the values that would - // otherwise re-borrow `self`/`self.graph` while the visitor holds - // disjoint column refs (Zig pulled multiple `items(.field)` columns at - // once with no aliasing model). let redirect_map: *const PathToSourceIndexMap = std::ptr::from_ref(self.path_to_source_index_map(self.transpiler.options.target)); - // Always materialize a valid slice; when the boundary list is empty - // this is a cheap `{ list: empty, map: &map }`. Avoids constructing a - // null `&Map` via `mem::zeroed()` (UB even though it was never read - // when `scb_bitset` is `None`). let scb_list = self.graph.server_component_boundaries.slice(); - // PORT NOTE: reshaped for borrowck — `Slice` is a value-type - // snapshot of column pointers (does not borrow `self.graph.ast`), so - // `split_mut()` on the local can coexist with the shared borrows - // below. The slab does not resize for the duration of this function. let mut ast_slice = self.graph.ast.slice(); let all_import_records: &mut [import_record::List<'_>] = ast_slice.split_mut().import_records; @@ -2072,12 +1783,6 @@ pub mod bv2_impl { } pub fn wait_for_parse(&mut self) { - // bundle_v2.zig:488-491 — `this.loop().tick(this, &isDone)`. - // - // PORT NOTE: `tick_raw` (not `tick`) — `is_done` reborrows `*ctx` as - // `&mut BundleV2`, and `BundleV2` (via `linker.r#loop`) owns the - // `AnyEventLoop` slot, so holding `&mut AnyEventLoop` across the - // callback would be a Stacked-Borrows violation. let self_ptr: *mut Self = self; let any_loop = self .r#loop() @@ -2120,21 +1825,6 @@ pub mod bv2_impl { return; } - // Now that all files have been scanned, look for packages that are imported - // both with "import" and "require". Rewrite any imports that reference the - // "module" package.json field to the "main" package.json field instead. - // - // This attempts to automatically avoid the "dual package hazard" where a - // package has both a CommonJS module version and an ECMAScript module - // version and exports a non-object in CommonJS (often a function). If we - // pick the "module" field and the package is imported with "require" then - // code expecting a function will crash. - // - // PORT NOTE: reshaped for borrowck — Zig pulled the mutable - // `import_records` column alongside shared columns. `split_mut()` on a - // value-type `Slice` snapshot yields the one mutable column without - // borrowing `self.graph.ast`; read the per-target map through the - // disjoint `build_graphs` field instead of the `&mut self` accessor. let mut ast_slice = self.graph.ast.slice(); let ast_import_records: &mut [import_record::List<'_>] = ast_slice.split_mut().import_records; @@ -2329,10 +2019,6 @@ pub mod bv2_impl { .flags .contains(bun_ast::ImportRecordFlags::HANDLES_IMPORT_ERRORS); - // Disable failing packages from being printed. - // This may cause broken code to write. - // However, doing this means we tell them all the resolve errors - // Rather than just the first one. record.path.is_disabled = true; } let source: Option<&bun_ast::Source> = Some( @@ -2409,10 +2095,6 @@ pub mod bv2_impl { let record: &mut ImportRecord = &mut self.graph.ast.items_import_records_mut() [import_record.importer_source_index as usize] .as_mut_slice()[import_record.import_record_index as usize]; - // Disable failing packages from being printed. - // This may cause broken code to write. - // However, doing this means we tell them all the resolve errors - // Rather than just the first one. record.path.is_disabled = true; return; } @@ -2446,11 +2128,6 @@ pub mod bv2_impl { path = self .path_with_pretty_initialized(&path, target) .expect("oom"); - // PORT NOTE: Zig wrote through `path.* = …` (a `*Fs.Path` into - // `resolve_result.path_pair`); the borrowck-reshape above cloned - // `path` out, so write the prettified path back so - // `ParseTask::init(&resolve_result, ..)` (via `enqueue_parse_task`) - // sees the relativized `pretty`. if let Some(p) = resolve_result.path() { *p = path; } @@ -2659,29 +2336,7 @@ pub mod bv2_impl { .into_static() }; path.assert_pretty_is_valid(); - // PORT NOTE: intern via `dupe_alloc` BEFORE writing back into `result` / - // the path-to-source-index map. Zig didn't need this — its dev-server - // `EntryPointList` keys borrow `dev.server_graph.bundled_files.keys()` - // (DevServer-owned), and `genericPathWithPrettyInitialized` returns the - // input `Path` unchanged for `node`-namespace built-ins (e.g. - // `bun-framework-react/server.tsx`), so `path.text` stayed a borrow of - // long-lived storage. The Rust port rebuilds a fresh - // `bake_types::EntryPointList` with `Box<[u8]>` keys (DevServer.rs:3027) - // that drops as soon as `enqueue_entry_points_dev_server` returns; - // `resolve_with_framework` then lifetime-erases that key into the - // returned `Path`, so without interning here `ParseTask.path.text` (and - // the map key) would dangle once the entry-point list is freed — - // surfacing as "Failed to load bundled module - // 'bun-framework-react/server.tsx'" when the worker can no longer match - // `built_in_modules`. path = path.dupe_alloc().expect("oom"); - // PORT NOTE: Zig's `var path = result.path()` is a `*Fs.Path` *into* - // `result.path_pair`, so the `path.* = pathWithPrettyInitialized(...)` - // assignment mutates the resolver result in place. The borrowck-reshape - // above cloned `path` out, which left `result.path_pair` with the - // unrelativized `pretty` — and `ParseTask::init(&result, ..)` reads - // exactly that field, so the source comment header lost its - // `top_level_dir`-relative path. Write the prettified path back here. if let Some(p) = result.path() { *p = path; } @@ -2750,10 +2405,6 @@ pub mod bv2_impl { _alloc: &bun_alloc::Arena, event_loop: EventLoop, cli_watch_flag: bool, - // Raw `NonNull` (not `&mut`): the JS-API path threads `WorkPool::get()` - // (a `&'static` from `OnceLock`, concurrently read by workers) through - // here into `ThreadPool::init`, which stores it as `*mut`. Creating a - // `&mut` along the way would violate Stacked Borrows. thread_pool: Option>, heap: &'a ThreadLocalArena, ) -> Result>, Error> { @@ -2828,11 +2479,6 @@ pub mod bv2_impl { } } } - // PORT NOTE: Zig wired `heap.arena()` into `transpiler.arena` / - // `resolver.arena` / `linker.arena` / `log.msgs.arena`. The - // Rust `Transpiler<'a>`/`Resolver<'a>` store `&'a Arena` and `Log.msgs` - // is a `Vec` (global alloc), so only `linker.graph.bump` needs the - // backref into the now-stable `this.graph.heap` slot. this.linker.graph.bump = bun_ptr::BackRef::new(this.graph.heap); this.transpiler.log_mut().clone_line_text = true; @@ -2901,16 +2547,7 @@ pub mod bv2_impl { } this.graph.pool = bun_ptr::BackRef::from(NonNull::new(pool).expect("arena allocation is non-null")); - // Install the watcher only after `ThreadPool::init()` has succeeded — - // the `?` above is the last early-return in this fn, so the watcher's - // raw `*mut BundleV2` can't outlive the box it points at. (Zig installs - // it before `pool.* = try .init(..)`, but the Rust caller drops the box - // on every error path until `generate_from_cli` leaks it.) if cli_watch_flag { - // CYCLEBREAK GENUINE: hot_reloader is T6; runtime constructs the - // `dispatch::WatcherHandle` (erased owner + `&'static WatcherVTable`) - // via this extern hook and writes `bun_watcher` (Zig: - // `Watcher.enableHotModuleReloading(this, null)` — bundle_v2.zig:994). dispatch::enable_hot_module_reloading_for_bundler(core::ptr::from_mut(&mut *this)); } // `Graph::pool` wraps the `BackRef` deref; `start()` takes `&self`. @@ -3322,18 +2959,6 @@ pub mod bv2_impl { .zip(scbs.items_ssr_source_index().iter()) { if *r#use == bun_ast::UseDirective::Client { - // TODO: this file is being generated far too early. we - // don't know which exports are dead and which exports are - // live. Tree-shaking figures that out. However, tree-shaking - // happens after import binding, which would require this ast. - // - // The plan: change this to generate a stub ast which only has - // `export const serverManifest = undefined;`, and then - // re-generate this file later with the properly decided - // manifest. However, I will probably reconsider how this - // manifest is being generated when I write the whole - // "production build" part of Bake. - let keys = named_exports_array[*source_id as usize].keys(); // PORT NOTE: `G::Property: !Clone` — build via iterator instead of `vec![v; n]`. let mut client_manifest_items: Box<[G::Property]> = @@ -3592,21 +3217,7 @@ pub mod bv2_impl { side_effects: loader.side_effects(), ..Default::default() })?; - // `core::mem::take` moved the real `Source` into `graph.input_files`, - // leaving `*source` as `Default`. Read path/contents back from the - // graph's stored copy (where the data now lives for the rest of the - // bundle pass) so the `ParseTask` below sees the actual source bytes — - // matches Zig, which copies `source.*` by value and then reads the - // still-intact original. let stored = &self.graph.input_files.items_source()[source_index.get() as usize]; - // PORT NOTE: Zig had a single `fs.Path`; Rust split it into - // `bun_paths::fs::Path<'static>` (on `Source`) and `bun_resolver::fs::Path` - // (on `ParseTask`). Convert field-by-field — `pretty`/`namespace` MUST - // be preserved here (the SCB `separate_ssr_graph=false` caller passes a - // source whose path went through `path_with_pretty_initialized`, and - // `ParseTask::run` builds the `Source` from `task.path` then swaps it - // back into `input_files`, so dropping `pretty` would surface the - // absolute path as the dev-server module key). let task_path: Fs::Path<'static> = stored.path; // SAFETY: `graph.input_files` owns `stored.contents` for the bundle // pass (arena lifetime); erase the borrow to `'static` to fit @@ -3744,10 +3355,6 @@ pub mod bv2_impl { pub bundle_v2: &'r mut BundleV2<'a>, } - /// Callback contract for [`DependenciesScanner`]. Each call site's local - /// `Analyzer` struct implements this; [`DependenciesScanner::new`] erases the - /// concrete type behind a monomorphized trampoline — Rust's analogue of Zig's - /// one-liner `.onFetch = @ptrCast(&Analyzer.onAnalyze)` (bundle_v2.zig:1492). pub trait OnDependenciesAnalyze { fn on_analyze( &mut self, @@ -3756,10 +3363,6 @@ pub mod bv2_impl { } impl DependenciesScanner { - /// Type-erase `analyzer` into the `(ctx, on_fetch)` pair. The returned - /// scanner borrows `*analyzer` for its lifetime: caller must keep - /// `analyzer` alive and exclusively owned until the scan completes - /// (mirrors Zig's stack-local `Analyzer` + `*anyopaque` ctx pattern). pub fn new( analyzer: &mut A, entry_points: Box<[Box<[u8]>]>, @@ -3897,12 +3500,6 @@ pub mod bv2_impl { // `Graph::entry_points: Vec` and `link()` takes `&[Index]` — // both are `crate::Index` (= `bun_ast::Index`), so no cast is needed. let ep = (*bundle_ptr).graph.entry_points.as_slice(); - // Spec passes `this.graph.server_component_boundaries` by value-copy - // (Zig struct copy), leaving the original intact for - // `StaticRouteVisitor` (generateChunksInParallel) to read via - // `parse_graph`. Borrow — do NOT `take`, which would empty the - // graph slot and drop the moved-out `MultiArrayList` heap inside - // `load()` (ASAN use-after-poison / wrong `fully_static`). let scbs = &(*bundle_ptr).graph.server_component_boundaries; // Project `.linker` via `bundle_ptr` (not `this.linker`) so no // second `Box::deref_mut` retag invalidates `ep`/`scbs` (SB). @@ -3952,12 +3549,6 @@ pub mod bv2_impl { }) })(); - // Under `--watch` the watcher thread holds `*mut BundleV2` (via the - // reloader's `ctx`) and dereferences it in `on_file_update` after this - // function returns. In Zig the `BundleV2` is arena-allocated and the - // arena is never freed (the caller diverges into `exitOrWatch`); in - // Rust it's `Box`-allocated, so leak it here to match the spec lifetime. - // Bounded leak: the next file change `execve()`s the process anyway. if enable_reloading { let _ = Box::into_raw(this); } else { @@ -3967,18 +3558,6 @@ pub mod bv2_impl { result } - /// Build only the parse graph for the given entry points and return the - /// BundleV2 instance. No linking or code generation is performed; this is - /// used by `bun test --changed` to walk import records and compute which - /// test entry points transitively depend on a given set of source files. - /// - /// The caller owns the returned BundleV2. Dupe anything needed out of - /// the graph and then call `deinit_without_freeing_arena()` — in the - /// Rust port the AST columns (`Vec` / `Vec` / …) live on - /// the global heap, not in `graph.heap`, so leaving the bundle alive is - /// no longer the bounded arena leak the Zig original described. The - /// worker pool is owned (created with `thread_pool: None`), so tearing - /// it down does not touch the runtime VM's parse threads. pub fn scan_module_graph_from_cli( transpiler: &'a mut Transpiler<'a>, alloc: &'a bun_alloc::Arena, @@ -3992,10 +3571,6 @@ pub mod bv2_impl { return Err(bun_core::err!("BuildFailed")); } - // enqueueEntryPoints schedules the runtime task before any fallible - // allocation. If a later allocation fails we must still drain the - // pool so workers aren't left holding pointers into the caller's - // stack-allocated Transpiler. if let Err(err) = this.enqueue_entry_points_normal(entry_points) { this.wait_for_parse(); return Err(err); @@ -4091,12 +3666,6 @@ pub mod bv2_impl { pub fn add_server_component_boundaries_as_extra_entry_points( &mut self, ) -> Result<(), Error> { - // Prepare server component boundaries. Each boundary turns into two - // entry points, a client entrypoint and a server entrypoint. - // - // TODO: This should be able to group components by the user specified - // entry points. This way, using two component files in a route does not - // create two separate chunks. (note: bake passes each route as an entrypoint) { let scbs = self.graph.server_component_boundaries.slice(); self.graph.entry_points.reserve(scbs.list.len() * 2); @@ -4221,10 +3790,6 @@ pub mod bv2_impl { let loader = loaders[index]; - // Zig hands the existing `source.contents` buffer to the - // OutputFile (with its allocator) — no copy. Mirror that by - // moving the contents out instead of `to_vec()`-cloning, - // which is prohibitively expensive for large assets. let contents_len = source.contents.len(); let contents = match core::mem::take(&mut source.contents) { std::borrow::Cow::Owned(v) => v.into_boxed_slice(), @@ -4261,10 +3826,6 @@ pub mod bv2_impl { } pub fn on_load_async(&mut self, load: &mut jsc_api::JSBundler::Load) { - // Dispatch to the loop that *owns* `BundleV2` (Zig: `switch (this.loop().*)`). - // For `Bun.build` this is a Mini loop running on the bundler thread, so - // `on_load` must land there — not on the JS plugin loop — or it will - // mutate `graph` / allocate from `graph.heap` off-thread. match self.any_loop_mut() { bun_event_loop::AnyEventLoop::Js { owner } => { owner.enqueue_task_concurrent( @@ -4849,41 +4410,12 @@ pub mod bv2_impl { drop(on_parse_finalizers); } - // Plugin file/asset-loader bytes that `process_files_to_copy` will - // `mem::take` are stored as `Cow::Owned` so that handoff is zero-copy. - // Everything else is `Cow::Borrowed` (file reads land in the worker - // arena; non-asset plugin bytes are owned by `free_list`), so this loop - // is N×branch with no work for plugin-free bundles. for s in self.graph.input_files.items_source_mut() { if matches!(s.contents, std::borrow::Cow::Owned(_)) { s.contents = std::borrow::Cow::Borrowed(b""); } } - // Zig spec (bundle_v2.zig:2229): `defer { this.graph.{ast,input_files, - // entry_points,entry_point_original_names}.deinit(this.allocator()) }`. - // In Zig those `MultiArrayList`s only free their slab — every per-element - // payload (file contents, quoted source-map JSON, line-offset tables, …) - // lives in `this.graph.heap` / a per-worker `mi_heap_t`, so the caller's - // `defer heap.deinit()` bulk-frees them. The Rust port now matches that: - // `LinkerGraph.File.line_offset_table` - // is `List` (slab + `columns_for_non_ascii` payloads in the - // worker AST heap, see `compute_line_offsets`), and every - // `MultiArrayList` / `JSMeta` / `InputFile` column — - // `quoted_source_contents`, `Part`s, `NamedImport`s, `Scope`s, - // `ImportData`/`ExportData`, `ArrayHashMap` buckets — is `AstAlloc`- - // backed. The slab-only `MultiArrayList::drop` strands nothing on the - // global heap; `mi_heap_destroy` on the AST arenas reclaims all of it. - // - // Only `css` still needs an explicit pass: `BundlerStyleSheet` is the - // CSS crate's tree-of-`Vec`s/`Box`es and is not `AstAlloc`-parameterised - // (that refactor would touch every `CssRuleList`/selector/declaration - // type). The arena-allocated stylesheet never has `Drop` run by the - // slab. For JS-only bundles every `css` slot is `None`, so this loop - // is N×branch with no work; only CSS entries pay a real drop. The - // macro takes only one side (`linker.graph.ast` is a bitwise SoA - // `memcpy` of `graph.ast`), and `CssChunk::asts` `forget()`s its - // aliases, so this is the unique drop. { macro_rules! take_ast_cols { ($ast:expr) => {{ @@ -4903,19 +4435,7 @@ pub mod bv2_impl { } } - // Drop the lazily-created client transpiler (if any) before tearing - // down workers — matches the .zig spec ordering where the arena slot - // is invalidated ahead of `pool.workers_assignments` so no worker can - // observe a half-torn-down transpiler. Clear the `client_transpiler` - // alias first so it never dangles past the Box drop; in the - // `BakeOptions`-borrowed path `owned_client_transpiler` is `None` and - // the DevServer-owned pointer is left untouched. if let Some(ct) = self.owned_client_transpiler.as_deref_mut() { - // `wire_after_move` boxed a higher-tier - // `bun_js_parser_jsc::Macro::MacroContext` behind - // `macro_context.data`; the parser-level struct has no `Drop` - // (and can't — `RuntimeTranspilerStore` bytewise-clones it), - // so the `Box` drop below would strand it. if let Some(ctx) = ct.macro_context.take() { ctx.deinit(); } @@ -5172,10 +4692,6 @@ pub mod bv2_impl { Ok(ctx) } - // TODO(b0-genuine): body has deep DevServer field access (current_bundle.start_data, - // css_entry_points, etc.). After tier-6 collapse this fn should be HOISTED into - // bun_runtime::bake (which can name DevServer concretely) and call back into BundleV2 - // helpers. Until then the entry-point fields are reached through the vtable. pub fn finish_from_bake_dev_server( &mut self, dev_server: &dispatch::DevServerHandle, @@ -5208,13 +4724,6 @@ pub mod bv2_impl { let asts = self.graph.ast.slice(); let css_asts = asts.items_css(); - // PORT NOTE: SoA columns are physically disjoint slabs but rustc cannot - // see that through `&Slice`. Route the two columns we mutate (`parts`, - // `import_records`) through `split_raw()` (root-provenance `*mut [T]`, - // no `&mut` intermediate) so the per-index `&mut` does not conflict - // with the `&asts` reads (`css`, `target`). Mirrors the pattern at - // `find_reachable_files` (~L1457). The slab does not resize for the - // duration of this loop and no other `&mut` to these columns exists. let ast_raw = asts.split_raw(); let parts_col: *mut bun_ast::PartList = ast_raw.parts.cast::(); let import_records_col: *mut import_record::List = @@ -5231,10 +4740,6 @@ pub mod bv2_impl { let import_records = unsafe { &mut *import_records_col.add(index) }; let maybe_css = &css_asts[index]; let target = asts.items_target()[index]; - // Dev Server proceeds even with failed files. - // These files are filtered out via the lack of any parts. - // - // Actual empty files will contain a part exporting an empty object. if part_list.len() != 0 { if maybe_css.is_some() { // CSS has restrictions on what files can be imported. @@ -5246,10 +4751,6 @@ pub mod bv2_impl { if LinkerContext::scan_css_imports( u32::try_from(index).expect("int cast"), import_records.as_slice(), - // PORT NOTE: `scan_css_imports` takes the column as a raw - // `*const` slice (the scanImportsAndExports caller holds raw - // SoA pointers); it only reads via `is_none()`. Zig spec - // (`LinkerContext.zig:496`) types this `[]const ?*...`. std::ptr::from_ref(css_asts), sources, loaders, @@ -5359,16 +4860,6 @@ pub mod bv2_impl { /* arena: help_catch_memory_issues — no-op (mimalloc TLH check) */ - // HMR skips most of the linker! All linking errors are converted into - // runtime errors to avoid a more complicated dependency graph. For - // example, if you remove an exported symbol, we only rebuild the - // changed file, then detect the missing export at runtime. - // - // Additionally, notice that we run this code generation even if we have - // files that failed. This allows having a large build graph (importing - // a new npm dependency), where one file that fails doesnt prevent the - // passing files to get cached in the incremental graph. - // The linker still has to be initialized as code generation expects // much of its state to be valid memory, even if empty. // SAFETY: `LinkerContext::load` takes `bundle` as a raw `*mut BundleV2` and only @@ -5532,10 +5023,6 @@ pub mod bv2_impl { original_target: options::Target, ) -> bool { if let Some(plugins) = self.plugins_ref() { - // PORT NOTE: `ImportRecord.path` is `bun_paths::fs::Path`; `has_any_matches` - // takes the structurally-identical `bun_resolver::fs::Path`. Rebuild the - // resolver-crate variant from the same backing slices (Zig has a single - // `Fs.Path` type — the FFI side only reads `.text` / `.namespace`). let match_path = Fs::Path::init_with_namespace( import_record.path.text, import_record.path.namespace, @@ -5636,11 +5123,6 @@ pub mod bv2_impl { let Ok(maybe_decoded) = data_url.decode_data() else { return false; }; - // Zig: `this.free_list.append(decoded); parse.contents_or_fd = .{ .contents = decoded };` - // — the SAME allocation is both tracked for free at `deinit` and - // borrowed as the parse-task contents. `free_list` owns it for the - // bundle's lifetime; `ParseTask` is strictly shorter-lived, so the - // raw-slice borrow is sound. No clone, no leak. self.free_list.push(maybe_decoded.into_boxed_slice()); // SAFETY: `free_list` is append-only until `deinit_without_freeing_arena` // (after all ParseTasks have completed); the `Box<[u8]>` is heap-stable. @@ -5761,12 +5243,6 @@ pub mod bv2_impl { Ok(()) } - // See barrel_imports.rs for barrel optimization implementation. - // PORT NOTE: Zig `pub usingnamespace`-style method aliases. `pub use` is not - // permitted in `impl` blocks; the underlying fns live in `barrel_imports` and - // take `&mut BundleV2` directly — callers reach them as free functions. - // (was: pub use barrel_imports::{apply_barrel_optimization, schedule_barrel_deferred_imports}) - /// Returns true when barrel optimization is enabled. Barrel optimization /// can apply to any package with sideEffects: false or listed in /// optimize_imports, so it is always enabled during bundling. @@ -5774,12 +5250,6 @@ pub mod bv2_impl { true } - // TODO: remove ResolveQueue - // - // Moving this to the Bundle thread was a significant perf improvement on Linux for first builds - // - // The problem is that module resolution has many mutexes. - // The downside is cached resolutions are faster to do in threads since they only lock very briefly. fn run_resolution_for_parse_task( parse_result: &mut parse_task::Result, this: &mut BundleV2, @@ -5803,15 +5273,6 @@ pub mod bv2_impl { bun_core::scoped_log!(Bundle, "failed with error: {}", err.name()); resolve_result.resolve_queue.clear(); - // Preserve the parsed import_records on the graph so any plugin - // onResolve tasks already dispatched for *other* records in this - // same file can still dereference - // `graph.ast.items(.import_records)[importer_source_index]` when - // they complete. Without this, the graph entry stays at - // JSAst.empty and the deferred plugin callback index-out-of- - // bounds crashes in BundleV2.onResolve / runResolver. The linker - // never runs because `transpiler.log.errors > 0` aborts the - // build before link time, so saving the AST is safe. let result_heap = *result.ast.import_records.allocator(); this.graph.ast.items_import_records_mut()[source_index.0 as usize] = core::mem::replace( @@ -5849,10 +5310,6 @@ pub mod bv2_impl { } impl<'a> BundleV2<'a> { - /// Resolve all unresolved import records for a module. Skips records that - /// are already resolved (valid source_index), unused, or internal. - /// Returns a resolve queue of new modules to schedule, plus any fatal error. - /// Used by both initial parse resolution and barrel un-deferral. pub fn resolve_import_records( &mut self, ctx: &mut ResolveImportRecordCtx, @@ -5870,11 +5327,6 @@ pub mod bv2_impl { import_record.source_index = Index::RUNTIME; } - // For non-dev-server builds, barrel-deferred records need their - // source_index cleared so they don't get linked. For dev server, - // skip this — is_unused is also set by ConvertESMExportsForHmr - // deduplication, and clearing those source_indices breaks module - // identity (e.g., __esModule on ESM namespace objects). if import_record .flags .contains(bun_ast::ImportRecordFlags::IS_UNUSED) @@ -5959,10 +5411,6 @@ pub mod bv2_impl { rewrite_jest_for_tests: self.transpiler.options.rewrite_jest_for_tests, }, ) { - // When bundling node builtins, remove the "node:" prefix. - // This supports special use cases where the bundle is put - // into a non-node module resolver that doesn't support - // node's prefix. https://github.com/oven-sh/bun/issues/18545 import_record.path.text = if replacement.node_builtin && !replacement.node_only_prefix { &replacement.path.as_bytes()[5..] @@ -6015,11 +5463,6 @@ pub mod bv2_impl { continue; } - // PORT NOTE: borrowck — `transpiler_for_target` returns `&mut Transpiler` - // tied to `&mut self`, but the underlying storage is raw `*mut Transpiler` - // backrefs valid for `'a` (see `init`). Compute the raw ptr first, then - // deref once, so the `&mut self` borrow doesn't span the rest of the loop - // body (Zig held all of these as raw ptrs and aliased freely). let (transpiler_ptr, bake_graph, target): ( *mut Transpiler<'a>, bake::Graph, @@ -6192,10 +5635,6 @@ pub mod bv2_impl { } } - // Disable failing packages from being printed. - // This may cause broken code to write. - // However, doing this means we tell them all the resolve errors - // Rather than just the first one. import_record.path.is_disabled = true; if err == bun_core::err!("ModuleNotFound") { @@ -6316,11 +5755,6 @@ pub mod bv2_impl { continue; } - // PORT NOTE: borrowck — Zig `Result.path()` returns `?*Path` (raw), - // letting the loop body keep reading other `resolve_result` fields - // (`.flags`, `.path_pair`, `.primary_side_effects_data`, `.jsx`). - // The Rust port returns `Option<&mut Path>`, which would lock the - // whole struct. Detach via raw ptr to mirror the Zig aliasing. let path: &mut Fs::Path = match resolve_result.path() { // SAFETY: `resolve_result` outlives this borrow; see PORT NOTE above. Some(p) => unsafe { bun_ptr::detach_lifetime_mut::(p) }, @@ -6355,10 +5789,6 @@ pub mod bv2_impl { && (import_record.loader.is_none() || import_record.loader.unwrap() == Loader::Html) { - // This use case is currently not supported. This error - // blocks an assertion failure because the DevServer - // reserves the HTML file's spot in IncrementalGraph for the - // route definition. let log = self.log_for_resolution_failures(source.path.text, bake_graph); log.add_range_error_fmt( @@ -6431,11 +5861,6 @@ pub mod bv2_impl { path.loader(&transpiler.options.loaders) .unwrap_or(Loader::File) }); - // When an HTML file references a URL asset (e.g. ), - // the file must be copied to the output directory as-is. If the resolved loader would - // parse/transform the file (e.g. .json, .toml) rather than copy it, force the .file loader - // so that `shouldCopyForBundling()` returns true and the asset is emitted. - // Only do this for HTML sources — CSS url() imports should retain their original behavior. if loader == Loader::Html && import_record.kind == ImportKind::Url && !resolved_loader.should_copy_for_bundling() @@ -6750,11 +6175,6 @@ pub mod bv2_impl { import_record: &mut ImportRecord, path_text: &[u8], ) -> Result<(), Error> { - // 1. Create the ast right here - // 2. Create a separate "virutal" module that becomes the manifest later on. - // 3. Add it to the graph - // PORT NOTE: Zig aliased `graph = &this.graph;` — re-borrow `self.graph` - // at each use so the `self.*` method calls below don't conflict. let heap = self.graph.heap; let empty_html_file_source: &mut bun_ast::Source = self.arena_create(bun_ast::Source { path: path_as_static(path), @@ -6955,12 +6375,6 @@ pub mod bv2_impl { &mut this.graph.input_files.items_source_mut()[result_source_index], &mut result.source, ); - // `on_load` (copy-for-bundling path) parks plugin asset bytes - // as `Cow::Owned` directly in this slot and gives the ParseTask - // a borrowed alias. The full-Source swap just moved that owner - // into `result.source`; move it back so `parse_worker::on_complete`'s - // `drop(heap::take(result))` doesn't free the buffer - // `process_files_to_copy` will later `mem::take`. if matches!(result.source.contents, std::borrow::Cow::Owned(_)) { core::mem::swap( &mut this.graph.input_files.items_source_mut()[result_source_index] @@ -6968,11 +6382,6 @@ pub mod bv2_impl { &mut result.source.contents, ); } - // PORT NOTE: Zig kept `source` as a stable pointer into the SoA. - // Borrowck forbids holding `&input_files.source[i]` while writing - // other `input_files` columns through the MultiArrayList accessor - // methods (each takes `&mut input_files`), so copy out the - // `'static` path text now and re-borrow `source` per-use below. let source_path_text: &'static [u8] = this.graph.input_files.items_source() [result_source_index] .path @@ -7052,12 +6461,6 @@ pub mod bv2_impl { }, ); - // Set is_export_star_target for barrel optimization. - // In dev server mode, source_index is not saved on JS import - // records, so fall back to resolving via the path map. - // PORT NOTE: split-borrow `Graph` fields directly so the - // `&build_graphs[target]` lookup doesn't lock out - // `input_files.items_flags_mut()` (disjoint columns). let result_ast_target = result.ast.target; for star_record_idx in result.ast.export_star_import_records.iter() { if (*star_record_idx as usize) < import_records.len() as usize { @@ -7077,12 +6480,6 @@ pub mod bv2_impl { } result.ast.import_records = import_records; - // PORT NOTE: Zig reads `result.ast.named_exports` / - // `result.source` *after* `graph.ast.set(…)` (Zig structs are - // value types so the `set` is a shallow copy). The Rust port - // moves `result.ast` into `graph.ast` and swapped `result.source` - // earlier, so snapshot the data the use-directive block needs - // *before* the move. Only paid for files that hit the SCB gate. let named_exports_for_scb = if result.use_directive != crate::UseDirective::None && { let separate = this @@ -7163,10 +6560,6 @@ pub mod bv2_impl { let mut ssr_source = this.graph.input_files.items_source()[result_source_index].clone(); - // PORT NOTE: `path_with_pretty_initialized` takes/returns - // `Fs::Path` (`bun_resolver::fs::Path`); bridge through - // `fs_path_from_logger`/`fs_path_to_logger` until the - // three `Path` mirrors unify. ssr_source.path.pretty = ssr_source.path.text; ssr_source.path = path_as_static( &this @@ -7660,20 +7053,9 @@ pub mod bv2_impl { ProbablyTypescriptType, } - /// `bundle_v2.zig:ImportTracker.Iterator`. - /// - /// `import_data` is a raw slice into - /// `graph.meta[i].resolved_exports[..].potentially_ambiguous_export_star_refs`. - /// The graph SoA is never reallocated during `match_import_with_export`, so - /// the pointer stays valid for the iterator's lifetime; the caller only reads - /// `.data` from each entry. pub struct ImportTrackerIterator { pub status: ImportTrackerStatus, pub value: crate::ImportTracker, - /// Backref into the link-graph SoA (`graph.meta[..].resolved_exports[..]. - /// potentially_ambiguous_export_star_refs`). `BackRef` (not `*const [T]`) - /// so the single read site in `match_import_with_export` is a safe `Deref`; - /// the pointee slab is never reallocated while the iterator is live. pub import_data: bun_ptr::BackRef<[crate::ImportData]>, } @@ -7779,12 +7161,6 @@ pub mod bv2_impl { free: |ctx, buf, a, ra| ExternalFreeFunctionAllocator::free(ctx, buf, a, ra), }; - // LAYERING: `BuildResult` / `BundleV2Result` are defined once in - // `BundleThread.rs` (the trait that consumes them lives there). The previous - // duplicate here meant `CompletionStruct::set_result` and `BundleV2:: - // run_from_js_in_new_thread` named two distinct types with identical fields. - // Re-export the canonical defs so `bundle_v2::` and `BundleThread::` paths - // resolve to the same nominal type. pub use crate::BundleThread::{BuildResult, BundleV2Result, CompletionStruct, singleton}; // re-exports diff --git a/src/bundler/bundled_ast.rs b/src/bundler/bundled_ast.rs index 02a103c1daf..f6e5836cb1c 100644 --- a/src/bundler/bundled_ast.rs +++ b/src/bundler/bundled_ast.rs @@ -11,16 +11,6 @@ // erasure that forced every bundler/linker call site to `.cast()` back. pub use bun_css::BundlerStyleSheet; -/// Arena-owned handle to a parsed CSS stylesheet (Zig: `*bun.css.BundlerStyleSheet`). -/// -/// The pointee lives in a per-file `Bump` whose ownership is held by -/// `Graph.heap` (bumps are `Pin>` owned by the -/// graph and outlive every `BundledAst` row by struct drop order). No `'arena` -/// lifetime is threaded — N files imply N distinct per-worker bumps with no -/// single common lifetime — so the invariant is enforced by drop order, not -/// the type system. `StoreRef`'s `Deref`/`DerefMut` encapsulate the single -/// documented `unsafe` deref justified by that invariant; callers use `&*r` -/// (or `Option::as_deref`) instead of open-coded `unsafe { &*ptr }`. pub(crate) type CssAstRef = bun_ast::StoreRef; /// Element type of the `css` SoA column (`items_css()`). Exposed so bundler @@ -39,16 +29,6 @@ pub(crate) type NamedExports = bun_ast::ast_result::NamedExports; pub(crate) type NamedImports = bun_ast::ast_result::NamedImports; pub type TopLevelSymbolToParts = bun_ast::ast_result::TopLevelSymbolToParts; -// PORT NOTE: Zig stores `MultiArrayList(BundledAst)` on `Graph.ast` / -// `LinkerGraph.ast` and the bundler indexes columns via `.items(.field)` -// (see `linker_context/scanImportsAndExports.zig`, `LinkerContext.zig`). -// `` generates the `BundledAstField` enum + -// `BundledAstColumns`/`BundledAstColumns` (`items_named_imports()`, -// `items_named_exports()`, …) that those callers expect at -// `crate::bundled_ast::*`. -// -// 26 fields ≤ `multi_array_list::MAX_FIELDS` (32). - pub struct BundledAst<'arena> { pub approximate_newline_count: u32, pub nested_scope_slot_counts: SlotCounts, @@ -154,10 +134,6 @@ bitflags::bitflags! { } impl<'arena> BundledAst<'arena> { - // Zig: `pub const empty = BundledAst.init(Ast.empty)` (comptime). The three `ArenaVec` - // fields prevent `const fn` here, but spell out the defaults directly instead of - // round-tripping through `Ast::empty_in` + `init` — this runs once per discovered - // module on the main thread. pub fn empty_in(arena: &'arena bun_alloc::Arena) -> Self { Self { approximate_newline_count: 0, diff --git a/src/bundler/cache.rs b/src/bundler/cache.rs index e5986bbbc69..a3eea7b5083 100644 --- a/src/bundler/cache.rs +++ b/src/bundler/cache.rs @@ -12,26 +12,12 @@ use bun_sys::{self, Fd}; // `BundleOptions.define` share the same nominal type. use js_parser::defines::Define; -// ══════════════════════════════════════════════════════════════════════════ -// B-3 UNIFIED: `RuntimeTranspilerCache` is canonical in `bun_js_parser` -// (lower tier) so `Features.runtime_transpiler_cache: Option<*mut RTC>` and -// `ParseOptions.runtime_transpiler_cache: Option<&mut RTC>` are the same -// nominal type. This crate adds the env-var-gated `disabled`/`set_disabled` via -// the `RuntimeTranspilerCacheExt` trait below — those need `bun_core::env_var` -// which sits a tier above js_parser. `Entry` / `Metadata` stay concrete here; -// the canonical -// struct stores them type-erased as `*mut ()`. -// ══════════════════════════════════════════════════════════════════════════ use bun_ast::RuntimeTranspilerCache; /// Bump when the cache wire format or parser output changes. Mirrors /// `expected_version` in src/jsc/RuntimeTranspilerCache.zig. pub const RUNTIME_TRANSPILER_CACHE_VERSION: u32 = 20; -/// Mirrors the Zig `pub var is_disabled` mutable global — written by T6 -/// (src/runtime/cli/Arguments.zig:1603, src/jsc/VirtualMachine.zig:1383) and -/// flipped lazily on cache-dir resolution failure. Module-level so those -/// writers can reach it; `disabled()` reads it. pub static DISABLED: AtomicBool = AtomicBool::new(false); /// Extension surface for the canonical `RuntimeTranspilerCache` (defined in @@ -60,13 +46,6 @@ impl RuntimeTranspilerCacheExt for RuntimeTranspilerCache { } } -/// Mirrors `RuntimeTranspilerCache.Encoding` (RuntimeTranspilerCache.zig:405). -/// -/// PORT NOTE: this is the on-disk wire enum for `Metadata.output_encoding` — -/// NOT `js_parser::ExportsKind` (an unrelated `#[repr(u8)]` enum that happens -/// to start at 0). The bundler-side cache loader maps `Latin1`/`Utf16` blobs -/// into a `bun.String` (RuntimeTranspilerCache.zig:310-318) and only feeds -/// `Utf8` through `cloneUTF8`; callers must dispatch on these discriminants. #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum CacheEncoding { @@ -77,12 +56,6 @@ pub enum CacheEncoding { Latin1 = 3, } -/// Mirrors `RuntimeTranspilerCache.ModuleType` (RuntimeTranspilerCache.zig:399). -/// -/// PORT NOTE: NOT `options::ModuleType` — the on-disk wire enum has `Esm`/`Cjs` -/// **swapped** relative to the in-memory parser/options enum (`Unknown=0, -/// Cjs=1, Esm=2`). Comparing `metadata.module_type` against -/// `options::ModuleType::Cjs as u8` would test for `.esm`. #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum MetadataModuleType { @@ -124,10 +97,6 @@ pub struct RuntimeTranspilerCacheMetadata { } impl Default for RuntimeTranspilerCacheMetadata { - /// Spec (src/jsc/RuntimeTranspilerCache.zig:42) defaults - /// `cache_version: u32 = expected_version` — derived `Default` would zero it, - /// causing every freshly-written entry to be rejected as `error.StaleCache` - /// on first read. fn default() -> Self { Self { cache_version: RUNTIME_TRANSPILER_CACHE_VERSION, @@ -156,10 +125,6 @@ pub struct Set { } impl Set { - /// Port of `Set.init` (cache.zig:6). PORT NOTE: `arena` is unused — - /// `MutableString::init`/`JavaScript::init` source from the global heap in - /// the Rust port; param kept so callers match the Zig signature - /// (`crate::cache::Set::init(alloc)`). pub fn init(_arena: &Bump) -> Set { Set { js: JavaScript::init(), @@ -195,15 +160,6 @@ impl Default for Fs { } } -// ══════════════════════════════════════════════════════════════════════════ -// `Entry`/`Contents`/`ExternalFreeFunction` are defined -// canonically in `bun_resolver::cache` (lower tier) because `Resolver.caches` -// is typed by them and the resolver crate cannot depend on the bundler. -// Re-export here so `crate::cache::Entry` and `bun_resolver::cache::Entry` -// are the SAME nominal type — `ParseTask::get_code_for_parse_task_*` receives -// a resolver-produced `Entry` and hands it to bundler-typed consumers without -// a structural shim. See src/resolver/lib.rs `pub mod cache`. -// ══════════════════════════════════════════════════════════════════════════ pub use bun_resolver::cache::{Contents, Entry, ExternalFreeFunction}; /// Legacy alias — several call sites import `crate::cache::CacheEntry` @@ -222,15 +178,6 @@ impl Fs { } } - /// When we need to suspend/resume something that has pointers into the shared buffer, we need to - /// switch out the shared buffer so that it is not in use. - /// - /// Ownership transfer: in Zig (cache.zig:77/79) the field is overwritten WITHOUT freeing - /// the old buffer, because the suspended parse keeps pointers into it (see ModuleLoader.zig:488, - /// "this shared buffer is about to become owned by the AsyncModule struct"). In Rust, plain - /// field assignment would drop+free the old buffer → use-after-free on resume. So we return - /// the detached buffer; the caller MUST take ownership of it and keep it alive for as long as - /// `parse_result.source.contents` may be read. pub fn reset_shared_buffer(&mut self, buffer: *const MutableString) -> MutableString { if core::ptr::eq(buffer, &raw const self.shared_buffer) { core::mem::replace(&mut self.shared_buffer, MutableString::init_empty()) @@ -323,24 +270,6 @@ impl Fs { self.read_file_with_allocator(_fs, path, dirname_fd, use_shared_buffer, _file_handle, None) } - /// Port of `Fs.readFileWithAllocator` (cache.zig:146). - /// - /// PORT NOTE: `comptime use_shared_buffer` is taken at runtime — the live - /// callers (`ParseTask::get_code_for_parse_task_without_plugins`, - /// `Transpiler::parse`) pass a value computed from runtime state, and the - /// resolver's `FsCache` forward-decl already pinned this shape. - /// PERF(port): re-monomorphize once both callers stabilize. - /// - /// `arena` restores the Zig `allocator` param: when - /// `!use_shared_buffer && arena.is_some()` the file body is read straight - /// into `arena` (`Contents::Arena`), so the bytes are bulk-freed by - /// `mi_heap_destroy` when the per-call `MimallocArena` (the per-job arena - /// from `RuntimeTranspilerStore` / `ParseTask`) drops — instead of round- - /// tripping through the worker thread's *default* mimalloc heap, which is - /// never destroyed and retains the fresh page for the process lifetime. - /// `None` keeps the global-heap `Contents::Owned(Vec)` path. Zig: - /// `transpiler.zig:838-839` passed `if (use_shared_buffer) - /// bun.default_allocator else this_parse.allocator`. pub fn read_file_with_allocator( &mut self, _fs: &mut fs_mod::FileSystem, @@ -410,12 +339,6 @@ impl Fs { let stream = self.stream; let contents = match (use_shared_buffer, arena) { - // Zig: `readFileWithHandleAndAllocator(this_parse.allocator, …)` — - // read straight into the per-call arena so the source bytes are - // reclaimed by `mi_heap_destroy` instead of pinning a fresh page in - // the worker thread's default heap (one `mi_malloc` + `munmap` pair - // per transpiled module → one bump allocation in a wholesale-reset - // heap). (false, Some(arena)) => { match fs_mod::read_file_contents_in_arena(file_handle, path, arena) { Ok((_, 0)) => Contents::Empty, @@ -525,14 +448,6 @@ impl JavaScript { let result = match parser.parse() { Ok(r) => r, Err(err) => { - // PORT NOTE: `Parser::parse` consumes `self` (Zig took `*Parser` - // — by-ref — but the Rust port owns the inner `P` by value), so - // `parser` is gone in this arm. The `&'a mut temp_log` it held - // is released, so read `temp_log.errors` directly. The lexer - // range is lost; fall back to `Range::None` (Zig used - // `parser.lexer.range()`). - // TODO(port): thread the failing token range through the - // `Err` payload once `_parse` returns a `(Error, Range)` pair. if temp_log.errors == 0 { log.add_range_error(Some(source), bun_ast::Range::None, err.name().as_bytes()); } diff --git a/src/bundler/defines.rs b/src/bundler/defines.rs index 9d06e128995..ab459c74b06 100644 --- a/src/bundler/defines.rs +++ b/src/bundler/defines.rs @@ -9,17 +9,6 @@ use crate::defines_table::{ GLOBAL_NO_SIDE_EFFECT_PROPERTY_ACCESSES as global_no_side_effect_property_accesses, }; -// ══════════════════════════════════════════════════════════════════════════ -// B-3 UNIFIED: `Define` / `DefineData` / `DotDefine` / `Flags` / `Options` / -// `RawDefines` / `UserDefines` / `UserDefinesArray` are canonical in -// `bun_js_parser::defines` (lower tier) so the parser's `P.define: &'a Define` -// and `BundleOptions.define: Box` are the *same* nominal type. This -// crate adds the json-parse / dotenv-vtable bodies that need -// `bun_interchange` / `bun_dotenv` (tiered above js_parser) via the -// `DefineExt` / `DefineDataExt` extension traits below. The pure-global table -// moved down to `bun_js_parser::defines_table`, so `for_identifier` reads it -// directly with no cross-crate hook. -// ══════════════════════════════════════════════════════════════════════════ pub use bun_js_parser::defines::{ Define, DefineData, DotDefine, Flags, IdentifierDefine, Options, RawDefines, UserDefines, UserDefinesArray, are_parts_equal, @@ -67,27 +56,12 @@ fn defines_path() -> FsPath<'static> { // TODO(port): inherent associated type aliases are unstable; expose as module-level alias. pub type Data = DefineData; -// ══════════════════════════════════════════════════════════════════════════ -// `bun_dotenv::DefineStore` impls. dotenv (T2) calls through the link-interface -// handle; bundler (T5) owns the concrete `E::String` + `DefineData` construction. -// Mirrors src/dotenv/env_loader.zig:399 `copyForDefine` — `to_string` is a -// `StringHashMap` (= UserDefines), `to_json` is a -// `StringHashMap>` (= RawDefines / framework defaults). -// ══════════════════════════════════════════════════════════════════════════ - fn env_string_store_put( store: &mut UserDefinesArray, bump: &bun_alloc::Arena, key: &[u8], value: &[u8], ) -> Result<(), bun_core::Error> { - // Zig (env_loader.zig:461) allocates the `E.String` slab via the passed - // `allocator` (= `Transpiler.allocator`), NOT the thread-local - // `Expr.Data.Store` — `configureDefines` resets that store on return, so - // the env-define payloads must outlive it. Mirror with `bump` (the - // transpiler arena) so the slab is bulk-freed with the `Define` table - // instead of leaking a `Box` per env var. Value bytes alias the long-lived - // env-map storage. let value: ExprData = ExprData::EString(bun_ast::StoreRef::from_bump( bump.alloc(bun_ast::E::EString::init(value)), )); @@ -101,13 +75,6 @@ fn env_string_store_put( Ok(()) } -/// Port of `Loader.copyForDefine` (env_loader.zig:399). Moved up from -/// `bun_dotenv` so it can name `DefineData` / `E::String` directly instead of -/// dispatching through a vtable — it only reads `loader.map.map.{keys,values}()`, -/// all of which are public. -/// -/// `to_json` is the framework-defaults `RawDefines` map; `to_string` is the -/// per-env `UserDefinesArray`. pub fn copy_env_for_define( env: &bun_dotenv::Loader<'_>, to_json: &mut RawDefines, @@ -139,10 +106,6 @@ pub fn copy_env_for_define( debug_assert!(!prefix.is_empty()); } - // PORT NOTE: Zig's `if (key_buf_len > 0)` gate (env_loader.zig:455) is behavioral, - // not just a sizing optimization — when `behavior == .prefix` and NO env key starts - // with `prefix`, the entire second walk (including the framework-hash `else` arm) - // is skipped. Mirror that by pre-scanning for a prefix match before emitting. let any_prefix_match = if behavior == DotEnvBehavior::Prefix { env.map .map @@ -319,15 +282,6 @@ impl DefineExt for Define { } } -/// Pre-built `ExprData` for the literal define *values* Bun auto-injects on -/// every transpiler init: `"development"` / `"production"` / `"test"` (the -/// `process.env.NODE_ENV` & `process.env.BUN_ENV` defaults) and `true` / -/// `false` (the `process.browser` default). The `E::EString` payloads live in -/// process-lifetime `static`s — producing one is allocation-free and never -/// touches the thread-local AST store `json_parser::parse_env_json` writes into. -/// Returns `None` for everything else (user `--define` values, env-file -/// `NODE_ENV` overrides like `staging`, JSON object/array literals, …), which -/// falls through to the general `parse_env_json` path in `DefineData::parse`. fn const_default_define_value(value_str: &[u8]) -> Option { static DEVELOPMENT: bun_ast::E::EString = bun_ast::E::EString::from_static(b"development"); static PRODUCTION: bun_ast::E::EString = bun_ast::E::EString::from_static(b"production"); @@ -466,10 +420,6 @@ impl DefineDataExt for DefineData { return Ok(DefineData { value, - // PORT NOTE: upstream `DefineData` now owns `original_name: - // Option>` (js_parser/lib.rs:1369) instead of the - // borrowed `ptr`/`len` pair (Zig's 48→40-byte packing). Dupe - // the value bytes — these are tiny startup-time copies. original_name: if !value_str.is_empty() { Some(Box::<[u8]>::from(value_str)) } else { @@ -485,15 +435,6 @@ impl DefineDataExt for DefineData { }); } - // Fast path for the compile-time-constant literal define *values* that - // Bun auto-injects on every transpiler init (`"development"` / - // `"production"` / `"test"` for `process.env.NODE_ENV` & `BUN_ENV`, and - // `true` / `false` for `process.browser`) — the entire default define - // set on the `bun run` path. Build the `DefineData` straight from - // process-lifetime statics: skips the `bump.alloc_slice_copy` + - // `json_parser::parse_env_json` + `Expr::deep_clone` round-trip and, - // crucially, never touches the thread-local AST `Expr`/`Stmt` stores - // (created lazily below only when a value really needs JSON parsing). if let Some(value) = const_default_define_value(value_str) { let can_be_removed_if_unused = bun_ast::expr::Tag::is_primitive_literal(value.tag()); return Ok(DefineData { @@ -513,38 +454,15 @@ impl DefineDataExt for DefineData { }); } - // Zig parsed against a stack-local `Source` then `Expr.Data.deepClone`d - // into the arena. We dupe `value_str` into `bump` first so every string - // slice the JSON lexer hands back already points into the long-lived - // arena (the `E::String.data` bytes survive without per-string dup). - // - // `parse_env_json` builds `E::String`/`E::Object` nodes in the - // thread-local AST `Expr`/`Stmt` stores, so create them now — done - // lazily here (idempotent no-ops once created) instead of eagerly in - // `Transpiler::configure_defines`, since most inits resolve every define - // through the fast path above and never need an AST store. bun_ast::Expr::data_store_create(); bun_ast::Stmt::data_store_create(); let arena_value: &[u8] = bump.alloc_slice_copy(value_str); let source = bun_ast::Source { - // `Source.contents` is typed `&'static [u8]` as a stand-in for an - // arena lifetime (see logger/lib.rs `Str` note). `arena_value` lives in `bump`, - // which the caller (`Define::init`) owns for the lifetime of the - // `Define` table — i.e. as long as any `ExprData` produced here is - // reachable. Route through `StoreStr` for the lifetime erasure. contents: std::borrow::Cow::Borrowed(bun_ast::StoreStr::new(arena_value).slice()), path: defines_path(), ..Default::default() }; let expr = bun_parsers::json_parser::parse_env_json(&source, log, bump)?; - // The `deep_clone` is load-bearing even though `.data` bytes already - // live in `bump`: `parse_env_json` → `new_expr` → `Expr::init` allocates - // the `E::String` *payload* (the `StoreRef` target) in the thread-local - // AST store, which `configure_defines` resets on return via - // `StoreResetGuard`. Before the `bun_ast` unification this was masked by - // `.into()` deep-walking T2→T4 and re-boxing the payload; now `.into()` - // is identity, so without `deep_clone` the `DefineData.value` dangles - // into a freed slab and `process.env.NODE_ENV` reads garbage. let data: ExprData = expr.data.deep_clone(bump)?; let can_be_removed_if_unused = bun_ast::expr::Tag::is_primitive_literal(data.tag()); Ok(DefineData { diff --git a/src/bundler/entry_points.rs b/src/bundler/entry_points.rs index d345e0d9d67..47ef778b7c8 100644 --- a/src/bundler/entry_points.rs +++ b/src/bundler/entry_points.rs @@ -10,10 +10,6 @@ use bun_wyhash::{self, Wyhash11}; use crate::Transpiler; use bun_js_parser as js_ast; -// PORT NOTE: `Path`/`PathName` come from the lower-tier `bun_paths::fs` shim -// (lifetime-erased `'static` slices) so `bun_ast::Source` field types line up; -// `FileSystem` is the real `bun_resolver::fs` singleton now that -// `bun_resolver` is in this crate's dep set. pub mod Fs { pub use bun_paths::fs::{Path, PathName}; pub use bun_resolver::fs::FileSystem; @@ -52,16 +48,6 @@ impl FallbackEntryPoint { // TODO(port): TranspilerType trait bound — body reads `.options.framework` and `.arena`. TranspilerType: TranspilerLike, { - // This is *extremely* naive. - // The basic idea here is this: - // -- - // import * as EntryPoint from 'entry-point'; - // import boot from 'framework'; - // boot(EntryPoint); - // -- - // We go through the steps of printing the code -- only to then parse/transpile it because - // we want it to go through the linker and the rest of the transpilation process - let disable_css_imports = transpiler .options() .framework @@ -70,15 +56,6 @@ impl FallbackEntryPoint { .client_css_in_js != ClientCssInJs::AutoOnImportCss; - // PORT NOTE: self-referential — when the rendered code fits in - // `entry.code_buffer` the Source borrows it (disjoint-field write to - // `entry.source` while `entry.code_buffer` is shared-borrowed). On - // overflow the Source owns the bytes via `Cow::Owned` (Zig allocated - // from `transpiler.arena`; here the Source owns it directly so Drop - // frees it). - // PORT NOTE: assemble bytes directly (not `write!`+`BStr`) so a - // non-UTF-8 byte in `input_path` is emitted verbatim like Zig `{s}`, - // not lossily replaced with U+FFFD by `BStr as Display`. macro_rules! render_into_entry { ($prefix:expr, $suffix:expr) => {{ let prefix: &[u8] = $prefix; @@ -138,10 +115,6 @@ impl ClientEntryPoint { strings::starts_with(b"entry.", extname) } - // PORT NOTE: takes the lifetime-generic `bun_paths::fs::PathName<'_>` (not the - // `'static`-field `bun_paths::fs::PathName<'static>`) so callers with a borrowed path - // (e.g. `bun_runtime::filesystem_router::get_script_src_string`) needn't forge - // `'static`. The body only copies `dir`/`base`/`ext` into `outbuffer`. pub fn generate_entry_point_path<'a>( outbuffer: &'a mut [u8], original_path: &bun_paths::fs::PathName<'_>, @@ -191,15 +164,6 @@ impl ClientEntryPoint { TranspilerType: TranspilerLike, { let entry = self; - // This is *extremely* naive. - // The basic idea here is this: - // -- - // import * as EntryPoint from 'entry-point'; - // import boot from 'framework'; - // boot(EntryPoint); - // -- - // We go through the steps of printing the code -- only to then parse/transpile it because - // we want it to go through the linker and the rest of the transpilation process let dir_to_use: &[u8] = original_path.dir_with_trailing_slash(); let disable_css_imports = transpiler @@ -279,11 +243,6 @@ impl ServerEntryPoint { is_hot_reload_enabled: bool, path_to_use: &[u8], ) -> Result<(), bun_core::Error> { - // TODO(port): narrow error set - // Use the global arena so this buffer's lifetime is decoupled - // from whichever arena the caller's VM happens to be using; the - // slice is read later from `getHardcodedModule` which outlives any - // per-transpile arena. let code: Vec = 'brk: { if is_hot_reload_enabled { let mut v: Vec = Vec::new(); @@ -360,11 +319,6 @@ impl ServerEntryPoint { } } -// This is not very fast. The idea is: we want to generate a unique entry point -// per macro function export that registers the macro Registering the macro -// happens in VirtualMachine We "register" it which just marks the JSValue as -// protected. This is mostly a workaround for being unable to call ESM exported -// functions from C++. When that is resolved, we should remove this. pub struct MacroEntryPoint { pub code_buffer: [u8; MAX_PATH_BYTES * 2 + 500], pub output_code_buffer: [u8; MAX_PATH_BYTES * 8 + 500], diff --git a/src/bundler/lib.rs b/src/bundler/lib.rs index 49775a49070..01b38741125 100644 --- a/src/bundler/lib.rs +++ b/src/bundler/lib.rs @@ -197,11 +197,6 @@ pub mod linker_context { pub use static_route_visitor::StaticRouteVisitor; } -// --------------------------------------------------------------------------- -// Public surface for downstream crates. Re-exports the real types from the -// modules above. -// --------------------------------------------------------------------------- - pub use Graph::Graph as GraphStruct; /// See `bundle_v2`. pub use bundle_v2::BundleV2; @@ -236,11 +231,6 @@ pub enum AdditionalFile { /// `*.zig` in this crate aliases it as `pub const Index = bun.ast.Index`. pub(crate) use bun_ast::{Index, IndexInt}; -// Re-export the `options` module. `Loader`/`Target` live in -// `bun_options_types::bundle_enums` — `options_impl` re-exports the canonical -// defs, so there is exactly ONE nominal type for each across -// bundler/resolver/js_parser. Bundler-only behaviour hangs off -// `TargetExt`/`LoaderExt` extension traits in `options_impl`. pub mod options { pub use super::OutputFile; pub use super::options_impl::*; @@ -256,12 +246,6 @@ pub mod options { pub use bun_options_types::schema::api::DotEnvBehavior as EnvBehavior; pub type Options<'a> = super::BundleOptions<'a>; - /// `jsc.API.BuildArtifact.OutputKind` (JSBundler.zig:1799). Re-exported by - /// `options.zig` callers via `OutputFile.output_kind`. - /// - /// `IntoStaticStr` provides the JS-facing tag (`"entry-point"` etc.) so - /// `bun_runtime::api::BuildArtifact` can spell `<&str>::from(kind)` without - /// a duplicate enum. #[repr(u8)] #[derive(Clone, Copy, PartialEq, Eq, Debug, Default, strum::IntoStaticStr)] pub enum OutputKind { @@ -306,35 +290,13 @@ pub mod options { /// `FrameworkRouterPair`). The full struct is `bun.bake.Framework` which /// lives in a higher-tier crate; minimal real struct lives in `bake_types`. pub use crate::bake_types::Framework; - - // `Env`, `EnvEntry`, `RouteConfig`, `jsx`/`JSX` are intentionally NOT - // redefined here — the `pub use super::options_impl::*` glob above exposes - // the single canonical defs (options.rs:1141/2493/2501/2722). The previous - // inline shadows produced 4+ incompatible `jsx::Pragma`/`Runtime` types and - // a `&'static [&'static [u8]]` `factory`/`fragment` that could not hold the - // heap allocation from `member_list_to_components_if_different` - // (options.zig:1296) without `Box::leak` (PORTING.md §Forbidden patterns). } -/// Re-export so `crate::RuntimeTranspilerCache` resolves for `transpiler::ParseOptions` -/// and downstream callers (`jsc_hooks` / `RuntimeTranspilerStore`). The struct -/// is canonical in `bun_js_parser`; the bundler-tier `disabled`/`set_disabled` -/// live on `RuntimeTranspilerCacheExt`. pub use cache::RuntimeTranspilerCacheExt; pub use cache::Set as Cache; -// ────────────────────────────────────────────────────────────────────────── -// Re-export the canonical `bake_types` defs from -// `bundle_v2` so there is exactly ONE nominal `Side`/`Graph`/`Framework` etc. -// across the crate (the previous inline copy here diverged and produced -// "expected `bake_types::Graph`, found `bake_types::Graph`" errors). -// ────────────────────────────────────────────────────────────────────────── pub use bundle_v2::bake_types; -// ────────────────────────────────────────────────────────────────────────── -// Re-export the canonical `dispatch` module from -// `bundle_v2` (full vtable slot set) so there is one `DevServerHandle` type. -// ────────────────────────────────────────────────────────────────────────── pub use bundle_v2::dispatch; // ── link-interfaces (must be at crate root so `$crate::__alias` resolves) ── @@ -387,10 +349,6 @@ bun_dispatch::link_interface! { } } -// `OutputFile.Options` defaults (`options.zig:OutputFile.Options` field -// default-initializers). Kept here rather than in `OutputFile.rs` so the -// derive-free struct stays codegen-friendly while every `init(..)` call site -// can use struct-update syntax. impl Default for output_file::OptionsData { fn default() -> Self { output_file::OptionsData::Buffer { diff --git a/src/bundler/linker.rs b/src/bundler/linker.rs index 20c38df88bf..439c88e98c3 100644 --- a/src/bundler/linker.rs +++ b/src/bundler/linker.rs @@ -7,14 +7,9 @@ use std::io::Write as _; use bun_ast::Log; use bun_ast::{ImportKind, ImportRecord, ImportRecordFlags, ImportRecordTag}; use bun_collections::HashMap; -use bun_paths::{self, SEP}; -// PORT NOTE: two `fs` shapes are in play here. `bun_resolver::fs` (`Fs`) holds -// the singleton `FileSystem` / `DirnameStore`; `bun_paths::fs` (`PFs`) defines -// the `Path`/`PathName` value types that `ImportRecord.path` is typed against. -// Both port `src/resolver/fs.zig`; B-3 collapses them. Until then, construct -// `import_record.path` via `PFs::Path` so the field assignment unifies. use bun_core::strings; use bun_paths::fs as PFs; +use bun_paths::{self, SEP}; use bun_resolver::fs as Fs; use bun_resolver::{self as resolver, Resolver}; use bun_sys::Fd; @@ -35,21 +30,9 @@ bun_core::named_error_set!(CSSResolveError); type HashedFileNameMap = HashMap; -// PORT NOTE: `_transpiler.Transpiler.isCacheEnabled` is gated in the draft body -// (`transpiler.rs:1111`). The Zig value is a hard `false` (`const isCacheEnabled -// = false;`); inline it here so `get_hashed_filename` compiles without depending -// on the gated `Transpiler` impl. const IS_CACHE_ENABLED: bool = false; pub struct Linker { - // arena field dropped — global mimalloc (callers pass `bun.default_allocator`) - // PORT NOTE: Zig stored borrowed `*BundleOptions` / `*Log` / `*Resolver` / - // `*ResolveQueue` / `*ResolveResults` / `*FileSystem`. The un-gated - // `Transpiler` struct owns those values directly and also owns `linker: - // crate::Linker` by value, so storing Rust references here would alias - // `&mut self` on every `transpiler.linker.link(...)` call. Use raw - // pointers (matching Zig's `*T`) and dereference at use-site; same - // contract as `transpiler::set_log`'s `linker.log = log as *mut _`. pub options: *mut BundleOptions<'static>, pub fs: *mut Fs::FileSystem, pub log: *mut Log, @@ -70,31 +53,10 @@ pub(crate) const RUNTIME_SOURCE_PATH: &[u8] = b"bun:wrap"; #[derive(Default)] pub struct TaggedResolution { pub react_refresh: Option, - // These tags cannot safely be used - // Projects may use different JSX runtimes across folders - // jsx_import: Option, - // jsx_classic: Option, } -// ── relative_paths_list singleton ──────────────────────────────────────── -// Zig: `const ImportPathsList = allocators.BSSStringList(512, 128); -// pub var relative_paths_list: *ImportPathsList = undefined;` -// -// `bun_alloc::BSSStringList` encodes the Zig generics as -// `COUNT = _COUNT * 2`, `ITEM_LENGTH = _ITEM_LENGTH + 1` (see `bun_alloc/lib.rs`). -// PORT NOTE: `bss_string_list!` would be the canonical declare-site macro but -// expands to `core::cell::SyncUnsafeCell`, and `bun_bundler` does not (yet) -// enable `#![feature(sync_unsafe_cell)]`. Use the heap-allocating `init()` -// fallback under a `LazyLock` instead — same lifetime semantics -// (process-static, never freed), just not BSS-backed. Swap to the macro once -// the crate-level feature flag lands. pub(crate) type ImportPathsList = bun_alloc::BSSStringList<{ 512 * 2 }, { 128 + 1 }>; -/// `Send + Sync` newtype around the leaked `BSSStringList` heap allocation so -/// it can sit inside a `LazyLock`. The underlying list serializes its own -/// mutation through an internal `Mutex` (see `BSSStringList::append`), so -/// sharing the raw pointer across threads is sound; the `&mut self` receiver -/// on `append` is a Zig-port artifact, not an exclusivity requirement. struct ImportPathsListPtr(core::ptr::NonNull); // SAFETY: `BSSStringList` guards every mutating method with `self.mutex`, and // the allocation is process-lifetime (never freed). The pointer is therefore @@ -112,12 +74,6 @@ fn relative_paths_list_ptr() -> *mut ImportPathsList { RELATIVE_PATHS_LIST.0.as_ptr() } -// ── HardcodedModule alias lookup ──────────────────────────────────────── -// Thin adapter over `bun_resolve_builtins::Alias::get` so the call site keeps -// `&'static [u8]` for `import_record.path.text` (the table stores `&'static -// ZStr`). `BundleTarget` and `bun_resolve_builtins::Target` are the same -// `bun_ast::Target`; ditto `ImportRecordTag` / -// `import_record::Tag`, so no bridge is needed. mod hardcoded_module { use super::*; #[derive(Default, Clone, Copy)] @@ -143,16 +99,6 @@ mod hardcoded_module { } } -/// Intern a byte buffer into the process-lifetime `relative_paths_list` -/// `BSSStringList` singleton. -/// -/// Zig used `linker.arena.dupe(u8, ...)` / `allocPrint` with -/// `bun.default_allocator` and never frees the result — the linker is a -/// per-transpile singleton whose output paths flow into `ImportRecord.path: -/// Path<'static>`. PORTING.md §Forbidden bans `Vec::leak`/`Box::leak` for -/// fabricating `&'static [u8]`; route through the `relative_paths_list` -/// interner instead so the bytes are owned by a true process-lifetime -/// singleton (the `OnceLock`-style exception PORTING.md carves out). #[inline] pub(crate) fn dupe(src: &[u8]) -> &'static [u8] { // SAFETY: `relative_paths_list_ptr()` is Once-initialized and never freed @@ -169,14 +115,6 @@ fn intern(buf: Vec) -> &'static [u8] { r } impl Linker { - // ── raw-pointer field accessors ────────────────────────────────────── - // The pointer fields are self-referential backrefs into the owning - // `Transpiler` (sibling fields), wired in `configure_linker*`. They are - // briefly null between `Transpiler::init` and `configure_linker`, but the - // contract is that no `link()`/`generate_import_path()`/`enqueue_*` call - // happens before `configure_linker` runs. Centralize the deref + invariant - // here so call sites are safe-Rust. - /// Shared borrow of the owning `Transpiler.options`. /// /// SAFETY: `self.options` points at the sibling `Transpiler.options` field @@ -271,10 +209,6 @@ impl Linker { resolve_results: *mut ResolveResults, fs: *mut Fs::FileSystem, ) -> Self { - // Zig wrote `relative_paths_list = ImportPathsList.init(arena);` - // here; the `LazyLock` accessor handles that lazily on first - // `intern_path()` / `relative_paths_list()` call, so no eager poke - // is needed (it was startup overhead for non-bundling code paths). Self { options, fs, @@ -291,13 +225,6 @@ impl Linker { } } - /// Re-seat the self-referential back-pointers after the owning - /// `Transpiler` has been moved to its final address. Port of the - /// post-copy fixups in ThreadPool.zig:310 / bundle_v2.zig:230 — those - /// only re-assign the pointer fields and do NOT reset - /// `import_counter` / `plugin_runner` / `tagged_resolutions` / - /// `any_needs_runtime`, so neither does this. Use instead of `init` from - /// `Transpiler::wire_after_move`. pub fn reseat_self_refs( &mut self, log: *mut Log, @@ -315,22 +242,11 @@ impl Linker { self.fs = fs; } - /// Accessor for the `relative_paths_list` singleton (Zig: - /// `Linker.relative_paths_list`). Returns `*mut` because the Zig contract - /// is a global `*Self` pointer — fabricating `&'static mut` here would - /// alias on every call. #[inline] pub fn relative_paths_list() -> *mut ImportPathsList { relative_paths_list_ptr() } - // ── getModKey / getHashedFilename ──────────────────────────────────── - // PORT NOTE: Zig's `Fs.FileSystem.RealFS.ModKey` is a nested decl; the - // Rust port hoists `ModKey` to module scope (`bun_resolver::fs::ModKey`) - // alongside `RealFS`. `file_path` is typed `PFs::Path` (not `Fs::Path`) - // so `get_hashed_filename` — whose callers all build `PFs::Path` — can - // forward directly; only `.text` is read, and both ports define it as - // `&[u8]`. pub fn get_mod_key( &mut self, file_path: &PFs::Path<'_>, @@ -352,12 +268,6 @@ impl Linker { }; let file = bun_sys::File::borrow(&raw_fd); Fs::FileSystem::set_max_fd(file.handle().native()); - // PORT NOTE: spec called `Fs.FileSystem.RealFS.ModKey.generate(&this.fs.fs, - // path, file)`; both leading args are unread (fs.rs:1386). The inline - // `bun_resolver::fs::RealFS` (which `self.fs.fs` is) and the full-port - // `fs_full::RealFS` are distinct types, so route through the - // RealFS-agnostic `from_file` wrapper added alongside the `ModKey` - // re-export. Fs::ModKey::from_file(file) } @@ -374,10 +284,6 @@ impl Linker { } let modkey = self.get_mod_key(file_path, fd)?; - // PORT NOTE: `ModKey::hash_name` writes into a 1 KiB threadlocal and - // returns a `'static` slice into it (matches Zig's `hash_name_buf` - // threadlocal). Spec passes `file_path.text` even though the param is - // named `basename`; preserved verbatim. let hash_name = modkey.hash_name(file_path.text)?; if IS_CACHE_ENABLED { @@ -388,14 +294,6 @@ impl Linker { Ok(hash_name) } - /// This modifies the Ast in-place! It resolves import records and - /// generates paths. - /// - /// PORT NOTE: `comptime import_path_format` demoted to a runtime arg — - /// `options::ImportPathFormat` doesn't derive `ConstParamTy`, and the - /// crate doesn't enable `adt_const_params`. All callers pass a literal, - /// and the inner `generate_import_path` body is a single `match` either - /// way, so codegen is equivalent. pub fn link( &mut self, file_path: &Fs::Path<'_>, @@ -423,13 +321,6 @@ impl Linker { | options::Loader::Js | options::Loader::Ts | options::Loader::Tsx => { - // PORT NOTE: reshaped for borrowck — Zig iterated - // `result.ast.import_records.slice()` while also reading other - // `result.*` fields and (in the not-found branch) borrowing - // `&result.source`. Iterate by index, take field-disjoint - // borrows (`&result.source` + `&mut result.ast.*`) where - // needed, and hoist `is_pending_import` (which borrows the - // whole `result`) before any `ast` mut borrow. let len = result.ast.import_records.as_slice().len(); for record_i in 0..len { let record_index = u32::try_from(record_i).expect("int cast"); @@ -660,11 +551,6 @@ impl Linker { } if namespace == b"bun" || namespace == b"file" || namespace.is_empty() { - // PORT NOTE: `linker.fs.relative` is a thin wrapper over - // `bun.path.relative`; the inline `bun_resolver::fs` - // module doesn't expose it yet, so call the path layer - // directly. The threadlocal-buffer result must be - // dup'd to outlive this call (Zig leaked into Path). let relative_name = dupe(bun_paths::resolve_path::relative(source_dir, source_path)); Ok(PFs::Path::init_with_pretty(source_path, relative_name)) @@ -780,10 +666,6 @@ impl Linker { ) -> Result { let hash_key = self.resolve_result_hash_key(&resolve_result); - // PORT NOTE: Zig `getOrPut` → `HashMap::entry`; `found_existing` is - // whether the key was already present. Matches Zig - // `linker.resolve_results.getOrPut` / `linker.resolve_queue.writeItem` - // (linker.zig:387-390). let found_existing = self.resolve_results_mut().contains_key(&hash_key); if !found_existing { self.resolve_results_mut().insert(hash_key, ()); diff --git a/src/bundler/linker_context/MetafileBuilder.rs b/src/bundler/linker_context/MetafileBuilder.rs index f7ecff60a64..dbf13e7c502 100644 --- a/src/bundler/linker_context/MetafileBuilder.rs +++ b/src/bundler/linker_context/MetafileBuilder.rs @@ -81,10 +81,6 @@ pub fn generate_chunk_json( let chunk_values = chunk.files_with_parts_in_chunk.values(); for (file_source_index, bytes_in_output) in chunk_keys.iter().zip(chunk_values.iter()) { let file_source_index = *file_source_index; - // Counters are `AtomicUsize` because they're populated by the parallel - // codegen workers; metafile emission runs strictly after the - // `wait_for_all` join in `generate_chunks_in_parallel`, so a relaxed - // load observes the final value. let bytes_in_output = bytes_in_output.load(core::sync::atomic::Ordering::Relaxed); if file_source_index as usize >= sources.len() { continue; @@ -198,10 +194,6 @@ pub fn generate_chunk_json( Ok(json.into_boxed_slice()) } -/// Assembles the final metafile JSON from pre-built chunk fragments. -/// Called after all chunks have been generated in parallel. -/// Chunk references (unique_keys) are resolved to their final output paths. -/// The caller is responsible for freeing the returned slice. pub fn generate(c: &mut LinkerContext, chunks: &mut [Chunk]) -> Result, bun_core::Error> { // Use StringJoiner so we can use breakOutputIntoPieces to resolve chunk references let mut j = StringJoiner::default(); @@ -421,10 +413,6 @@ pub fn generate(c: &mut LinkerContext, chunks: &mut [Chunk]) -> Result u32::try_from(chunks.len()).expect("int cast"), )?; - // Get final output with all chunk references resolved. - // PORT NOTE: Zig passes `&chunks[0]` as the dummy chunk and `chunks` as the - // full slice (aliased). `code()` takes both as `&` now, so pass `&chunks[0]` - // directly — overlapping shared borrows are fine. let code_result = intermediate.code( None, parse_graph, @@ -448,16 +436,6 @@ fn write_json_string(writer: &mut impl Write, str: &[u8]) -> std::io::Result<()> ) } -// ────────────────────────────────────────────────────────────────────────── -// Minimal `std.json.Value`-shaped tree for `generate_markdown`. -// -// PORT NOTE: Zig's `generateMarkdown` re-parses the metafile JSON via -// `std.json.parseFromSlice(std.json.Value, …)` — a generic dynamic-tree parse. -// The Rust crates available here (`bun_parsers::json`) only expose an -// AST-expr parser, so a small self-contained Value/parser is provided below -// covering exactly the subset the metafile format uses. -// ────────────────────────────────────────────────────────────────────────── - enum JsonValue { Null, Bool(bool), @@ -710,13 +688,6 @@ impl<'a> JsonParser<'a> { } } -// ────────────────────────────────────────────────────────────────────────── -// generate_markdown helper structs (local to the function in Zig; hoisted here) -// PORT NOTE: lifetime <'a> ties borrowed slices to the parsed JSON value's -// lifetime. The Zig originals were anonymous structs holding []const u8 that -// borrowed from the std.json parse arena. -// ────────────────────────────────────────────────────────────────────────── - struct InputFileInfo<'a> { path: &'a [u8], bytes_in_output: u64, @@ -739,10 +710,6 @@ struct PathOnly<'a> { path: &'a [u8], } -/// Generates a markdown visualization of the module graph from metafile JSON. -/// This is a post-processing step that parses the JSON and produces LLM-friendly output. -/// Designed to help diagnose bundle bloat, dependency chains, and entry point analysis. -/// The caller is responsible for freeing the returned slice. pub fn generate_markdown(metafile_json: &[u8]) -> Result, bun_core::Error> { let root = match JsonParser::parse(metafile_json) { Ok(v) => v, diff --git a/src/bundler/linker_context/StaticRouteVisitor.rs b/src/bundler/linker_context/StaticRouteVisitor.rs index d5a63f4238b..d3122e42dfb 100644 --- a/src/bundler/linker_context/StaticRouteVisitor.rs +++ b/src/bundler/linker_context/StaticRouteVisitor.rs @@ -33,10 +33,6 @@ impl<'a> StaticRouteVisitor<'a> { return false; } - // PORT NOTE: `self.c` is `&'a LinkerContext` (Copy), so these slice - // borrows are tied to `'a`, not to `&self`, and do not conflict with - // the `&mut self` call below. `parse_graph()` is the safe backref - // accessor (one centralized `unsafe`, see `LinkerContext::parse_graph`). let parse_graph = self.c.parse_graph(); let all_import_records: &[import_record::List<'_>] = parse_graph.ast.items_import_records(); let referenced_source_indices: &[u32] = parse_graph @@ -56,12 +52,6 @@ impl<'a> StaticRouteVisitor<'a> { ) } - /// 1. Get AST for `source_index` - /// 2. Recursively traverse its imports in import records - /// 3. If any of the imports match any item in - /// `referenced_source_indices` which has `use_directive == - /// .client`, then we know `source_index` is NOT fully - /// static. fn has_transitive_use_client_impl( &mut self, all_import_records: &[import_record::List<'_>], diff --git a/src/bundler/linker_context/computeChunks.rs b/src/bundler/linker_context/computeChunks.rs index 2079e74be18..eab1ca9d880 100644 --- a/src/bundler/linker_context/computeChunks.rs +++ b/src/bundler/linker_context/computeChunks.rs @@ -63,17 +63,8 @@ pub fn compute_chunks( // link step. Raw deref (not `this.parse_graph()`) because the loop below // needs disjoint `&mut this.graph.*` borrows while `parse_graph` is held. let parse_graph = unsafe { &*this.parse_graph }; - // `bump` is a `BackRef` into `BundleV2.graph.arena`, valid for the link step. - // Hoisted so the loop can hold disjoint &mut borrows into `this.graph`. - // PORT NOTE: `BundlerStyleSheet::empty()` no longer takes an arena in Rust; kept for - // when arena threading lands. let _arena: &Arena = this.graph.arena(); - // PORT NOTE: borrowck escape hatch — the SoA column slices below hold disjoint - // immutable borrows into `this.graph` while several helpers (and the BundleV2 - // back-pointer recovery) still want `&mut LinkerContext`. The Zig original - // freely aliases; TODO(refactor): thread split borrows through `LinkerGraph` - // instead of laundering through a raw pointer. let this_ptr: *mut LinkerContext = this; let entry_source_indices = this.graph.entry_points.items_source_index(); @@ -102,11 +93,6 @@ pub fn compute_chunks( let has_html_chunk = loaders[source_index as usize] == Loader::Html; - // For code splitting, entry point chunks should be keyed by ONLY the entry point's - // own bit, not the full entry_bits. This ensures that if an entry point file is - // reachable from other entry points (e.g., via re-exports), its content goes into - // a shared chunk rather than staying in the entry point's chunk. - // https://github.com/evanw/esbuild/blob/cd832972927f1f67b6d2cc895c06a8759c1cf309/internal/linker/linker.go#L3882 let mut entry_point_chunk_bits = AutoBitSet::init_empty(this.graph.entry_points.len())?; entry_point_chunk_bits.set(entry_bit as usize); @@ -212,12 +198,6 @@ pub fn compute_chunks( }; { - // If this JS entry point has an associated CSS entry point, generate it - // now. This is essentially done by generating a virtual CSS file that - // only contains "@import" statements in the order that the files were - // discovered in JS source order, where JS source order is arbitrary but - // consistent for dynamic imports. Then we run the CSS import order - // algorithm to determine the final CSS file order for the chunk. let css_source_indices = find_imported_css_files_in_js_order(this, temp, Index::init(source_index)); if css_source_indices.len() > 0 { @@ -228,10 +208,6 @@ pub fn compute_chunks( css_source_indices.slice(), ); - // Always use content-based hashing for CSS chunk deduplication. - // This ensures that when multiple JS entry points import the - // same CSS files, they share a single CSS output chunk rather - // than producing duplicates that collide on hash-based naming. let hash_to_use = { let mut hasher = Wyhash::init(5); bun_core::write_any_to_hasher(&mut hasher, order.len()); @@ -331,10 +307,6 @@ pub fn compute_chunks( .contains(chunk::Flags::IS_BROWSER_CHUNK_FROM_SERVER_BUILD) && ast_targets[source_index.get() as usize] == Target::Browser { - // If any file in the chunk has browser target, mark the whole chunk as browser. - // This handles the case where a lazy-loaded chunk (code splitting chunk, not entry point) - // contains browser-targeted files but was first created by a non-browser file. - // We only apply this to non-entry-point chunks to preserve the correct side for server entry points. js_chunk_entry .value_ptr .flags @@ -540,11 +512,6 @@ pub fn compute_chunks( let unique_key_item_len = chunk::UNIQUE_KEY_LEN; let mut unique_key_builder = bun_core::StringBuilder::init_capacity(unique_key_item_len * chunks.len()); - // PORT NOTE: in Zig `unique_key_buf` aliases the builder's backing buffer and - // every `chunk.unique_key` is a slice into it. Mirror that: the builder never - // reallocates after `init_capacity`, so each `fmt()` returns a stable subslice - // that we detach to `&'static [u8]` (BACKREF) and transfer ownership of the - // single allocation into `this.unique_key_buf` afterwards. let prefix_len = chunk::UNIQUE_KEY_PREFIX_LEN; // SAFETY: `this` points to LinkerContext which is the `linker` field of BundleV2. @@ -691,10 +658,6 @@ pub fn compute_chunks( } } - // Transfer ownership of the single backing buffer; every `chunk.unique_key` - // above borrows into it. (Zig's `errdefer` freed the builder and cleared - // `unique_key_buf`; in Rust the builder `Drop`s on error and `unique_key_buf` - // is only assigned here on success, so no rollback guard is needed.) this.unique_key_buf = unique_key_builder.move_to_slice(); Ok(sorted_chunks.to_owned_slice()) diff --git a/src/bundler/linker_context/computeCrossChunkDependencies.rs b/src/bundler/linker_context/computeCrossChunkDependencies.rs index 92507d00716..7155e4c3fa6 100644 --- a/src/bundler/linker_context/computeCrossChunkDependencies.rs +++ b/src/bundler/linker_context/computeCrossChunkDependencies.rs @@ -31,23 +31,6 @@ pub fn compute_cross_chunk_dependencies( // defer { meta.*.deinit(); free(chunk_metas) } — handled by Drop { - // PORT NOTE: Zig heap-allocated this via c.arena().create() and destroyed it at - // scope end; in Rust we construct on the stack and let it drop. - // - // `ctx` / `symbols` / `chunks` are stored as raw pointers so the struct does not - // hold a borrow on `c` or `chunks` across the sequential `walk` loop below. - // - // Derive `ctx_ptr` from the `&mut` (not `from_ref`) so the raw carries `c`'s own - // Unique provenance: under Stacked Borrows the subsequent `split_mut` reborrows - // are children of that tag, so `&*ctx_ptr` in `walk()` (which reads - // `c.graph.files.{ptrs,len}` via `is_external_dynamic_import`) stays valid. - // `from_ref(c)` would push a SharedRO tag that the `&mut c.graph.X` reborrows - // pop, leaving the raw dangling under SB. - // - // Lifetime-erase the `LinkerContext<'_>` so the struct's `'a` (which - // ties only the local SoA-column borrows) is not forced to equal the - // LinkerContext's invariant `'_`. `NonNull::from(&mut *c)` preserves - // `c`'s Unique provenance (see PORT NOTE above). let ctx_ref = bun_ptr::BackRef::from( core::ptr::NonNull::from(&mut *c).cast::>(), ); @@ -86,10 +69,6 @@ pub fn compute_cross_chunk_dependencies( pub(crate) struct CrossChunkDependencies<'a, 'bump> { chunk_meta: &'a mut [ChunkMeta], - // PORT NOTE: `BackRef` — the same `[Chunk]` slice is also iterated mutably by - // the caller's sequential `walk` loop; `walk` only reads `chunks[other].unique_key` - // (disjoint from the per-iteration `&mut Chunk`). The slice outlives the struct - // (caller stack frame). chunks: bun_ptr::BackRef<[Chunk]>, parts: &'a [bun_ast::PartList<'bump>], import_records: &'a mut [bun_ast::import_record::List<'bump>], @@ -100,31 +79,11 @@ pub(crate) struct CrossChunkDependencies<'a, 'bump> { exports_refs: &'a [Ref], sorted_and_filtered_export_aliases: &'a [js_meta::SortedAndFilteredExportAliases], resolved_exports: &'a [ResolvedExports], - // PORT NOTE: `BackRef` — Zig stores `*LinkerContext` / `*Symbol.Map` and freely - // aliases `c.graph` columns alongside; borrowck cannot express that split, so - // opt out here via `BackRef` (safe `Deref` at each use site in `walk`). Lifetime - // erased (`'static`) so the outer `CrossChunkDependencies<'_>` borrow is not tied - // to the LinkerContext's own invariant lifetime parameter. ctx: bun_ptr::BackRef>, - // `BackRef` — `walk` mutates per-chunk symbol slots via - // `Map::assign_chunk_index(&self)`, which is a Relaxed store to - // `Symbol.chunk_index: AtomicU32`, so a shared `&Map` suffices. Holding - // `&mut Map` here would conflict with the `&LinkerContext` deref of `ctx` - // (which also reaches `c.graph.symbols`); `BackRef::Deref` yields the - // shared `&Map` each `walk` call needs. symbols: bun_ptr::BackRef, } impl<'a, 'bump> CrossChunkDependencies<'a, 'bump> { - // Called once per chunk from the sequential loop above. Writes: - // `self.chunk_meta[chunk_index]` (per-chunk disjoint), - // `self.import_records[source_index][rec].{path,source_index}` (per-chunk - // disjoint via `chunk.files_with_parts_in_chunk`), - // `symbols.assign_chunk_index(ref)` (Relaxed atomic store to - // `Symbol.chunk_index: AtomicU32`; per-symbol-ref disjoint by chunk - // membership — debug-asserted in `assign_chunk_index`). - // Reads `ctx`/`chunks`/SoA columns shared. Never forms `&mut - // LinkerContext` (`ctx` is a `BackRef`, deref'd to `&`). pub(crate) fn walk(&mut self, chunk: &mut Chunk, chunk_index: usize) { let deps = self; // `ctx` / `chunks` are `BackRef`s into `LinkerContext` / the caller's chunk @@ -186,11 +145,6 @@ impl<'a, 'bump> CrossChunkDependencies<'a, 'bump> { } } - // Remember what chunk each top-level symbol is declared in. Symbols - // with multiple declarations such as repeated "var" statements with - // the same name should already be marked as all being in a single - // chunk. In that case this will overwrite the same value below which - // is fine. symbols.assign_chunk_index(&part.declared_symbols, chunk_index as u32); let used_refs = part.symbol_uses.keys(); @@ -224,10 +178,6 @@ impl<'a, 'bump> CrossChunkDependencies<'a, 'bump> { continue 'refs; } - // If this is an ES6 import from a CommonJS file, it will become a - // property access off the namespace symbol instead of a bare - // identifier. In that case we want to pull in the namespace symbol - // instead. The namespace symbol stores the result of "require()". if let Some(namespace_alias) = &symbol.namespace_alias { ref_to_use = namespace_alias.namespace_ref; } @@ -244,11 +194,6 @@ impl<'a, 'bump> CrossChunkDependencies<'a, 'bump> { ); } - // We must record this relationship even for symbols that are not - // imports. Due to code splitting, the definition of a symbol may - // be moved to a separate chunk than the use of a symbol even if - // the definition and use of that symbol are originally from the - // same source file. let _ = chunk_meta.imports.put(ref_to_use, ()); // OOM-only Result (Zig: catch unreachable) } } @@ -276,10 +221,6 @@ impl<'a, 'bump> CrossChunkDependencies<'a, 'bump> { target_ref = import_data.data.import_ref; } - // If this is an ES6 import from a CommonJS file, it will become a - // property access off the namespace symbol instead of a bare - // identifier. In that case we want to pull in the namespace symbol - // instead. The namespace symbol stores the result of "require()". if let Some(namespace_alias) = &symbols.get_const(target_ref).unwrap().namespace_alias { @@ -327,10 +268,6 @@ fn compute_cross_chunk_dependencies_with_chunk_metas( ) -> Result<(), bun_alloc::AllocError> { // TODO(port): narrow error set - // Mark imported symbols as exported in the chunk from which they are declared - // PORT NOTE: reshaped for borrowck — Zig zips (chunks, chunk_metas, 0..) and also indexes - // chunk_metas[other_chunk_index] / chunks[other_chunk_index] inside the loop body. We - // iterate by index and re-borrow per access. debug_assert_eq!(chunks.len(), chunk_metas.len()); for chunk_index in 0..chunks.len() { if !matches!(chunks[chunk_index].content, chunk::Content::Javascript(_)) { @@ -483,10 +420,6 @@ fn compute_cross_chunk_dependencies_with_chunk_metas( .unwrap() .original_name .slice(); - // The alias is stored on the chunk (`exports_to_other_chunks`, - // `cross_chunk_suffix_stmts`) and read later in postProcessJSChunk, - // so it must live in the linker arena — `r`'s internal arena is - // reset per chunk and dropped at the end of this block. let alias: bun_ast::StoreStr = if c.options.minify_identifiers { bun_ast::StoreStr::new( c.arena() @@ -545,13 +478,6 @@ fn compute_cross_chunk_dependencies_with_chunk_metas( { debug!("Generating cross-chunk imports"); let mut list: Vec = Vec::new(); - // defer list.deinit() — handled by Drop - // PORT NOTE: reshaped for borrowck — Zig's `for (chunks) |*chunk|` aliases the same - // slice it passes to `sortedCrossChunkImports`. We move the per-chunk fields we - // mutate (`imports_from_other_chunks`, `cross_chunk_imports`) out via `take`, drop - // the `chunk` borrow, hand the whole `chunks` slice to `sorted_cross_chunk_imports` - // (which only reads `chunks[other].exports_to_other_chunks` — disjoint), then write - // the fields back at loop end. for chunk_index in 0..chunks.len() { if !matches!(chunks[chunk_index].content, chunk::Content::Javascript(_)) { continue; diff --git a/src/bundler/linker_context/convertStmtsForChunk.rs b/src/bundler/linker_context/convertStmtsForChunk.rs index 7b7dbf328bb..9b7b705f859 100644 --- a/src/bundler/linker_context/convertStmtsForChunk.rs +++ b/src/bundler/linker_context/convertStmtsForChunk.rs @@ -14,31 +14,6 @@ use crate::chunk::Chunk; use crate::linker_context_mod::{LinkerContext, LinkerOptionsMode, StmtList, StmtListWhich}; use crate::options::Format; -/// Code we ultimately include in the bundle is potentially wrapped -/// -/// In that case, we do a final pass over the statements list to figure out -/// where it needs to go in the wrapper, following the syntax of the output -/// format ESM import and export statements to always be top-level, so they -/// can never be inside the wrapper. -/// -/// prefix - outer -/// ... -/// var init_foo = __esm(() => { -/// prefix - inner -/// ... -/// suffix - inenr -/// }); -/// ... -/// suffix - outer -/// -/// Keep in mind that we may need to wrap ES modules in some cases too -/// Consider: -/// import * as foo from 'bar'; -/// foo[computedProperty] -/// -/// In that case, when bundling, we still need to preserve that module -/// namespace object (foo) because we cannot know what they are going to -/// attempt to access statically pub fn convert_stmts_for_chunk( c: &mut LinkerContext<'_>, source_index: u32, @@ -56,13 +31,6 @@ pub fn convert_stmts_for_chunk( let output_format = c.options.output_format; - // If this file is a CommonJS entry point, double-write re-exports to the - // external CommonJS "module.exports" object in addition to our internal ESM - // export namespace object. The difference between these two objects is that - // our internal one must not have the "__esModule" marker while the external - // one must have the "__esModule" marker. This is done because an ES module - // importing itself should not see the "__esModule" marker but a CommonJS module - // importing us should see the "__esModule" marker. let mut module_exports_for_export: Option = None; if output_format == Format::Cjs && chunk.is_entry_point() { module_exports_for_export = Some(Expr::allocate( diff --git a/src/bundler/linker_context/convertStmtsForChunkForDevServer.rs b/src/bundler/linker_context/convertStmtsForChunkForDevServer.rs index 2b1cf2e540c..bfe57d3bd3e 100644 --- a/src/bundler/linker_context/convertStmtsForChunkForDevServer.rs +++ b/src/bundler/linker_context/convertStmtsForChunkForDevServer.rs @@ -12,36 +12,6 @@ use bun_collections::VecExt; use crate::linker_context_mod::{LinkerContext, StmtList, StmtListWhich}; -/// For CommonJS, all statements are copied `inside_wrapper_suffix` and this returns. -/// The conversion logic is completely different for format .internal_bake_dev -/// -/// For ESM, this function populates all three lists: -/// 1. outside_wrapper_prefix: all import statements, unmodified. -/// 2. inside_wrapper_prefix: a var decl line and a call to `module.retrieve` -/// 3. inside_wrapper_suffix: all non-import statements -/// -/// The imports are rewritten at print time to fit the packed array format -/// that the HMR runtime can decode. This encoding is low on JS objects and -/// indentation. -/// -/// 1 ┃ "module/esm": [ [ -/// ┃ 'module_1', 1, "add", -/// ┃ 'module_2', 2, "mul", "div", -/// ┃ 'module_3', 0, // bare or import star -/// ], [ "default" ], [], (hmr) => { -/// 2 ┃ var [module_1, module_2, module_3] = hmr.imports; -/// ┃ hmr.onUpdate = [ -/// ┃ (module) => (module_1 = module), -/// ┃ (module) => (module_2 = module), -/// ┃ (module) => (module_3 = module), -/// ┃ ]; -/// -/// 3 ┃ console.log("my module", module_1.add(1, module_2.mul(2, 3)); -/// ┃ module.exports = { -/// ┃ default: module_3.something(module_2.div), -/// ┃ }; -/// }, false ], -/// ----- "is the module async?" pub fn convert_stmts_for_chunk_for_dev_server<'bump>( c: &mut LinkerContext, stmts: &mut StmtList, diff --git a/src/bundler/linker_context/doStep5.rs b/src/bundler/linker_context/doStep5.rs index 1742f67874c..84500b0f406 100644 --- a/src/bundler/linker_context/doStep5.rs +++ b/src/bundler/linker_context/doStep5.rs @@ -77,12 +77,6 @@ impl LinkerContext<'_> { // `worker.heap`; valid for the worker's lifetime. let arena: &Bump = worker.arena(); - // ── raw SoA column pointers (root provenance) ───────────────────── - // `split_raw()` derives `*mut [T]` directly from the buffer base with - // no `&mut` intermediate, so per-row writes through these pointers are - // sound under Stacked Borrows even with N concurrent tasks. We index - // by raw `.add(id)` (never `(*col)[id]`, which would form a transient - // `&[T]` over the whole column and race with other rows' writes). let ast = c.graph.ast.split_raw(); let meta = c.graph.meta.split_raw(); macro_rules! row_mut { @@ -246,15 +240,6 @@ impl LinkerContext<'_> { let our_imports_to_bind: &RefImportData = &imports_to_bind[id as usize]; // SAFETY: see above. 'outer: for (part_index, part) in unsafe { (*parts_slice).iter_mut().enumerate() } { - // Now that all files have been parsed, determine which property - // accesses off of imported symbols are inlined enum values and - // which ones aren't - // PORT NOTE: reshaped for borrowck — Zig iterates keys()/values() while - // holding a mutable getPtr into part.symbol_uses; collect refs first. - // PERF(port): the property-use map is empty for the overwhelming - // majority of parts (it only fills for `import * as ns`/enum - // property accesses); skip the `to_vec()` alloc-round-trip in - // that case. let prop_use_refs: Vec = if part.import_symbol_property_uses.is_empty() { Vec::new() } else { @@ -303,44 +288,6 @@ impl LinkerContext<'_> { // TODO: inline function calls here - // TODO: Inline cross-module constants - // if (c.graph.const_values.count() > 0) { - // // First, find any symbol usage that points to a constant value. - // // This will be pretty rare. - // const first_constant_i: ?usize = brk: { - // for (part.symbol_uses.keys(), 0..) |ref, j| { - // if (c.graph.const_values.contains(ref)) { - // break :brk j; - // } - // } - // - // break :brk null; - // }; - // if (first_constant_i) |j| { - // var end_i: usize = 0; - // // symbol_uses is an array - // var keys = part.symbol_uses.keys()[j..]; - // var values = part.symbol_uses.values()[j..]; - // for (keys, values) |ref, val| { - // if (c.graph.const_values.contains(ref)) { - // continue; - // } - // - // keys[end_i] = ref; - // values[end_i] = val; - // end_i += 1; - // } - // part.symbol_uses.entries.len = end_i + j; - // - // if (part.symbol_uses.entries.len == 0 and part.can_be_removed_if_unused) { - // part.tag = .dead_due_to_inlining; - // part.dependencies.len = 0; - // continue :outer; - // } - // - // part.symbol_uses.reIndex(arena) catch unreachable; - // } - // } if false { break 'outer; } // this `if` is here to preserve the unused @@ -399,14 +346,6 @@ impl LinkerContext<'_> { } } - /// Spec: `linker_context/doStep5.zig:createExportsForFile`. - /// - /// WARNING: This method is run in parallel over all files. Do not mutate data - /// for other files within this method or you will create a data race. - /// - /// PORT NOTE: takes `&self` (read-only) plus the three SoA row cells it - /// mutates as explicit `&mut` params, so the parallel `do_step5` dispatch - /// never forms a concurrent `&mut LinkerContext` / whole-column `&mut [T]`. #[allow(clippy::too_many_arguments)] pub fn create_exports_for_file( &self, @@ -420,10 +359,6 @@ impl LinkerContext<'_> { ast_flags: &mut AstFlags, ast_parts: &mut bun_ast::PartList, ) { - // PORT NOTE: Zig toggled `Stmt.Disabler`/`Expr.Disabler` (debug-only - // re-entrancy guards around the global Store). `Disabler::scope()` - // calls `disable()` and re-`enable()`s on drop — currently no-op stubs - // until the thread-local toggle lands (`js_parser/ast/mod.rs`). let _stmt_guard = bun_ast::stmt::Disabler::scope(); let _expr_guard = bun_ast::expr::Disabler::scope(); @@ -451,14 +386,6 @@ impl LinkerContext<'_> { // + 1 if we need to do module.exports = __toCommonJS(exports) force_include_exports_for_entry_point as usize; - // PORT NOTE: Zig used `Stmt.Batcher` (preallocated arena slice + - // cursor). `Batcher::::init` requires `T: Default` which `Stmt` - // doesn't satisfy, so we hand-roll the same shape: one arena slab of - // `stmts_count` `MaybeUninit`, sliced front-to-back. `eat1` - // becomes a `write` + sub-slice carve. The slab is held as a safe - // `&mut [MaybeUninit]` borrow of the arena allocation; each - // carve borrows it briefly and hands the result to `StoreSlice` - // (raw-ptr wrapper, no lifetime), so successive carves don't alias. let stmts_slab: &mut [MaybeUninit] = arena.alloc_slice_fill_with(stmts_count, |_| MaybeUninit::uninit()); let mut stmts_head: usize = 0; @@ -477,10 +404,6 @@ impl LinkerContext<'_> { let exp = resolved_exports.get_mut(alias).unwrap(); let mut exp_data = exp.data; - // If this is an export of an import, reference the symbol that the import - // was eventually resolved to. We need to do this because imports have - // already been resolved by this point, so we can't generate a new import - // and have that be resolved later. if let Some(import_data) = imports_to_bind[exp_data.source_index.get() as usize].get(&exp_data.import_ref) { @@ -647,11 +570,6 @@ impl LinkerContext<'_> { ast_flags.insert(AstFlags::USES_EXPORTS_REF); } - // Decorate "module.exports" with the "__esModule" flag to indicate that - // we used to be an ES module. This is done by wrapping the exports object - // instead of by mutating the exports object because other modules in the - // bundle (including the entry point module) may do "import * as" to get - // access to the exports object and should NOT see the "__esModule" flag. if force_include_exports_for_entry_point { let to_common_js_ref = self.runtime_function(b"__toCommonJS"); emit_export_stmt!(Stmt::assign( diff --git a/src/bundler/linker_context/findAllImportedPartsInJSOrder.rs b/src/bundler/linker_context/findAllImportedPartsInJSOrder.rs index 37e2a974744..8943d73e3b9 100644 --- a/src/bundler/linker_context/findAllImportedPartsInJSOrder.rs +++ b/src/bundler/linker_context/findAllImportedPartsInJSOrder.rs @@ -70,13 +70,6 @@ pub fn find_imported_parts_in_js_order( let with_code_splitting = this.graph.code_splitting; let with_scb = this.graph.is_scb_bitset.bit_length > 0; - // PORT NOTE: the Zig visitor holds a *LinkerContext alongside SoA column slices - // borrowed from it, and mutates one column (`entry_point_chunk_index`). Rust - // borrowck forbids the latter through a shared `&LinkerContext`, so cache that - // single mutable column as a raw `*mut [u32]` (provenance via the - // `MultiArrayList.bytes: *mut u8` raw-pointer field — see - // `scanImportsAndExports.rs` for the same pattern). All other `c.*` accesses - // are read-only. let entry_point_chunk_indices: *mut [u32] = this.graph.files.slice().split_raw().entry_point_chunk_index; diff --git a/src/bundler/linker_context/findImportedCSSFilesInJSOrder.rs b/src/bundler/linker_context/findImportedCSSFilesInJSOrder.rs index 722922097c6..6b0a284b084 100644 --- a/src/bundler/linker_context/findImportedCSSFilesInJSOrder.rs +++ b/src/bundler/linker_context/findImportedCSSFilesInJSOrder.rs @@ -8,23 +8,6 @@ use crate::{Index, LinkerContext}; use bun_ast::PartList; use bun_collections::DynamicBitSet as BitSet; -/// JavaScript modules are traversed in depth-first postorder. This is the -/// order that JavaScript modules were evaluated in before the top-level await -/// feature was introduced. -/// -/// A -/// / \ -/// B C -/// \ / -/// D -/// -/// If A imports B and then C, B imports D, and C imports D, then the JavaScript -/// traversal order is D B C A. -/// -/// This function may deviate from ESM import order for dynamic imports (both -/// "require()" and "import()"). This is because the import order is impossible -/// to determine since the imports happen at run-time instead of compile-time. -/// In this case we just pick an arbitrary but consistent order. pub fn find_imported_css_files_in_js_order( this: &LinkerContext, _temp: &Arena, @@ -61,12 +44,6 @@ pub fn find_imported_css_files_in_js_order( // Iterate over each part in the file in order for part in p.as_slice() { - // Traverse any files imported by this part. Note that CommonJS calls - // to "require()" count as imports too, sort of as if the part has an - // ESM "import" statement in it. This may seem weird because ESM imports - // are a compile-time concept while CommonJS imports are a run-time - // concept. But we don't want to manipulate >>` frees via `Drop` (global mimalloc); if `allocatorForSize` - // returns a distinct arena for large buffers, matched-arena dealloc must be - // restored here. let mut standalone_chunk_contents: Option>>> = None; if is_standalone { @@ -636,12 +593,6 @@ pub fn generate_chunks_in_parallel( continue; } let mut ds: usize = 0; - // Pass `scc` so that `.asset` pieces (e.g. `import logo from "./logo.svg"` with - // the file loader) are resolved to data: URIs from `url_for_css` instead of - // being written as paths to sidecar files that don't exist in standalone mode. - // Sibling JS/CSS chunks may still be null at this point; `.chunk` pieces fall - // back to file paths when their entry in `scc` is null, matching the previous - // behavior for inter-chunk imports. let mut intermediate_output = core::mem::take(&mut chunks[ci].intermediate_output); let buffer = intermediate_output .code_standalone( @@ -675,11 +626,6 @@ pub fn generate_chunks_in_parallel( standalone_chunk_contents.as_deref(), )?; } else { - // In-memory build (also used for standalone mode) - // PORT NOTE: `code()` / `code_standalone()` read `chunk` (= `&chunks[i]`) - // and the full `&[Chunk]` slice simultaneously. Iterate by index so both - // can be safe shared reborrows of `chunks`; the only per-chunk mutation - // is the `intermediate_output` take/restore, done via `chunks[i]`. for chunk_index_in_chunks_list in 0..chunks.len() { // In standalone mode, non-HTML chunks were already resolved in the first pass. // Insert a placeholder output file to keep chunk indices aligned. @@ -898,14 +844,6 @@ pub fn generate_chunks_in_parallel( && loader.is_javascript_like() { let mut fdpath = bun_paths::PathBuffer::uninit(); - // For --compile builds, the bytecode URL must match the module name - // that will be used at runtime. The module name is: - // public_path + final_rel_path (e.g., "/$bunfs/root/app.js") - // Without this prefix, the JSC bytecode cache key won't match at runtime. - // Use the per-chunk public_path (already computed above) for browser chunks - // from server builds, and normalize with cheapPrefixNormalizer for consistency - // with module_info path fixup. - // For non-compile builds, use the normal .jsc extension. let source_provider_url = if c.options.compile { let normalizer = cheap_prefix_normalizer(public_path, &chunk.final_rel_path); @@ -972,12 +910,6 @@ pub fn generate_chunks_in_parallel( ..Default::default() })); } else { - // an error - // logger OOM-only (Zig: catch unreachable) - // Split-borrow — `static_route_visitor.c` holds a - // detached `&LinkerContext`; `log_disjoint` returns the - // disjoint `Transpiler.log` backref so no `&mut c` is - // materialized. let _ = c.log_disjoint().add_error_fmt( None, bun_ast::Loc::EMPTY, diff --git a/src/bundler/linker_context/generateCodeForFileInChunkJS.rs b/src/bundler/linker_context/generateCodeForFileInChunkJS.rs index c84642f660b..46d6db4de4e 100644 --- a/src/bundler/linker_context/generateCodeForFileInChunkJS.rs +++ b/src/bundler/linker_context/generateCodeForFileInChunkJS.rs @@ -59,18 +59,8 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( Index::INVALID }; - // referencing everything by array makes the code a lot more annoying :( - // - // PORT NOTE: `MultiArrayList::get` returns `ManuallyDrop` — the - // storage retains ownership of every Drop field (`parts`, `symbols`, - // `named_imports`, …). The local `flags` mutation below is intentional and - // stays scoped to this read view. let mut ast = c.graph.ast.get(source_index); - // For HMR, part generation is entirely special cased. - // - export wrapping is already done. - // - imports are split from the main code. - // - one part range per file if c.options.output_format == OutputFormat::InternalBakeDev { 'brk: { if part_range.source_index.is_runtime() { @@ -187,10 +177,6 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( // TODO: there is a weird edge case where the pretty path is not computed // it does not reproduce when debugging. let source_ref = c.get_source(source_index as u32); - // PORT NOTE: reshaped for borrowck — Zig copies the `Source` by value, - // mutates `.path`, and passes `&source`. `bun_ast::Source` is not `Clone` - // (its `Cow` fields would deep-copy `Owned` data); instead, build a - // borrowed-field shadow only when the path needs fixing. let source_storage: bun_ast::Source; let source: &bun_ast::Source = if core::ptr::eq( source_ref.path.text.as_ptr(), @@ -355,33 +341,6 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( let mut single_stmts_list: [Stmt; 1] = [Stmt::empty()]; let mut part_stmts: &[Stmt] = part.stmts.slice(); - // If this could be a JSON or TOML file that exports a top-level object literal, go - // over the non-default top-level properties that ended up being imported - // and substitute references to them into the main top-level object literal. - // So this JSON file: - // - // { - // "foo": [1, 2, 3], - // "bar": [4, 5, 6], - // } - // - // is initially compiled into this: - // - // export var foo = [1, 2, 3]; - // export var bar = [4, 5, 6]; - // export default { - // foo: [1, 2, 3], - // bar: [4, 5, 6], - // }; - // - // But we turn it into this if both "foo" and "default" are imported: - // - // export var foo = [1, 2, 3]; - // export default { - // foo, - // bar: [4, 5, 6], - // }; - // if index == part_index_for_lazy_default_export { debug_assert!(index != u32::MAX); @@ -400,11 +359,6 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( // Be careful: the top-level value in a JSON file is not necessarily an object if let ExprData::EObject(e_object) = default_expr.data { - // PORT NOTE: Zig `properties.clone(temp_arena)` is a memcpy into the - // temp arena. `G::Property` is not `Clone` (it embeds a `Vec`), so - // mirror the Zig bitwise copy directly. JSON object properties carry no - // owned heap data (`ts_decorators` is always empty, `class_static_block` - // is `None`), so the duplicated bits do not alias any allocation. let src_len = e_object.properties.len(); let mut new_properties = Vec::::init_capacity(src_len); // SAFETY: `new_properties` has capacity `src_len`; source slice is live @@ -448,14 +402,6 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( .top_level_symbol_to_parts(source_index as u32, export_ref)[0] as usize; if parts_live.is_set(part_idx) { - // PTR_AUDIT(#1): `*prop` is a bitwise copy of - // `e_object.properties[i]` (see `copy_nonoverlapping` - // above). A plain `*prop = …` would run `Drop` on the - // aliased old value — specifically `prop.ts_decorators: - // Vec`, which (if non-empty) would free the - // *original AST's* allocation. The "JSON ⇒ ts_decorators - // empty" invariant makes that drop a no-op today, but - // `ptr::write` enforces it structurally. let key = prop.key; let value_loc = prop.value.as_ref().expect("infallible: prop has value").loc; @@ -511,12 +457,6 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( } } - // Hoist all import statements before any normal statements. ES6 imports - // are different than CommonJS imports. All modules imported via ES6 import - // statements are evaluated before the module doing the importing is - // evaluated (well, except for cyclic import scenarios). We need to preserve - // these semantics even when modules imported via ES6 import statements end - // up being CommonJS modules. stmts .all_stmts .reserve(stmts.inside_wrapper_prefix.stmts.len() + stmts.inside_wrapper_suffix.len()); @@ -640,10 +580,6 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( } } WrapKind::Esm => { - // The wrapper only needs to be "async" if there is a transitive async - // dependency. For correctness, we must not use "async" if the module - // isn't async because then calling "require()" on that module would - // swallow any exceptions thrown during module initialization. let is_async = flags.is_async_or_has_async_dependency; struct ExportHoist { @@ -699,10 +635,6 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( let stmt = stmts.all_stmts[i]; let transformed = match stmt.data { StmtData::SLocal(local) => 'stmt: { - // "using" / "await using" declarations have disposal - // side-effects tied to the scope they appear in, so - // they must stay inside the closure rather than being - // hoisted to `var` + assignment. if local.kind.is_using() { break 'stmt stmt; } @@ -752,10 +684,6 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( continue 'hoist; } StmtData::SClass(mut class) => 'stmt: { - // PORT NOTE: `class` is `StoreRef` — an arena-owned pointer. - // `&mut class.class` (via DerefMut) yields a `&mut G::Class` into arena - // memory, so wrapping it in a StoreRef for `EClass` is sound and matches - // Zig's `&class.class`. if class.class.can_be_moved() { stmts.append(StmtListWhich::OutsideWrapperPrefix, stmt); continue 'hoist; @@ -864,19 +792,6 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( // // `init_foo` without it actually existing. // debug_assert!(ast.wrapper_ref.is_empty()); - // TODO: the edge case where we are wrong is when there - // are references to other ESM modules, but those get - // fully hoisted. The look like side effects, but they - // are removed. - // - // It is too late to retroactively delete the - // wrapper_ref, since printing has already begun. The - // most we can do to salvage the situation is to print - // an empty arrow function. - // - // This is marked as a TODO, because this can be solved - // via a count of external modules, decremented during - // linking. if !ast.wrapper_ref.is_empty() { let value = Expr::init( E::Arrow { @@ -929,11 +844,6 @@ pub fn generate_code_for_file_in_chunk_js<'r, 'src>( }); } - // Collect top-level declarations from the converted statements. - // This is done here (after convertStmtsForChunk) rather than in - // postProcessJSChunk, because convertStmtsForChunk transforms the AST - // (e.g. export default expr → var, export stripping) and the converted - // statements reflect what actually gets printed. let mut r = r; if let Some(dc) = decl_collector { dc.collect_from_stmts(out_stmts, &mut r, c); @@ -973,16 +883,6 @@ impl Default for DeclCollector { } impl DeclCollector { - /// Collect top-level declarations from **converted** statements (after - /// `convertStmtsForChunk`). At that point, export statements have already - /// been transformed: - /// - `s_export_default` → `s_local` / `s_function` / `s_class` - /// - `s_export_clause` → removed entirely - /// - `s_export_from` / `s_export_star` → removed or converted to `s_import` - /// - /// Remaining `s_import` statements (external, non-bundled) don't need - /// handling here; their bindings are recorded separately in - /// `postProcessJSChunk` by scanning the original AST import records. pub fn collect_from_stmts( &mut self, stmts: &[Stmt], diff --git a/src/bundler/linker_context/generateCodeForLazyExport.rs b/src/bundler/linker_context/generateCodeForLazyExport.rs index cb3d546178d..82939255599 100644 --- a/src/bundler/linker_context/generateCodeForLazyExport.rs +++ b/src/bundler/linker_context/generateCodeForLazyExport.rs @@ -61,12 +61,6 @@ pub fn generate_code_for_lazy_export( let module_ref = this.graph.ast.items_module_ref()[source_index as usize]; - // Handle css modules - // - // --- original comment from esbuild --- - // If this JavaScript file is a stub from a CSS file, populate the exports of - // this JavaScript stub with the local names from that CSS file. This is done - // now instead of earlier because we need the whole bundle to be present. if let Some(css_ast) = maybe_css_ast { let stmt: Stmt = part.stmts[0]; if !matches!(stmt.data, StmtData::SLazyExport(_)) { @@ -254,10 +248,6 @@ pub fn generate_code_for_lazy_export( } } Some(CssSpecifier::Global) => { - // E.g.: `composes: foo from global` - // - // In this example `foo` is global and won't be rewritten to a locally scoped - // name, so we can just add it as a string. for name in compose.names.slice() { let name_v = name.v(); self.parts.push(E::TemplatePart { @@ -436,10 +426,6 @@ pub fn generate_code_for_lazy_export( if let ExprData::EObject(e_object) = &expr.data { for property in e_object.properties.slice() { let _: &G::Property = property; - // PORT NOTE: `Expr`/`ExprData`/`StoreRef<_>` are `Copy`. Copy `key` out so - // `key_str: StoreRef` is a mutable local — `slice()` resolves - // the rope in-place via `DerefMut` into the arena slot (matches Zig's - // `property.key.?.data.e_string.slice(...)` which takes `*String`). let Some(key) = property.key else { continue }; let ExprData::EString(mut key_str) = key.data else { continue; @@ -463,17 +449,6 @@ pub fn generate_code_for_lazy_export( continue; } - // This initializes the generated variable with a copy of the property - // value, which is INCORRECT for values that are objects/arrays because - // they will have separate object identity. This is fixed up later in - // "generateCodeForFileInChunkJS" by changing the object literal to - // reference this generated variable instead. - // - // Changing the object literal is deferred until that point instead of - // doing it now because we only want to do this for top-level variables - // that actually end up being used, and we don't know which ones will - // end up actually being used at this point (since import binding hasn't - // happened yet). So we need to wait until after tree shaking happens. let generated = this.generate_named_export_in_file(source_index, module_ref, name, name)?; // PERF(port): was `this.arena().alloc(Stmt, 1)` (arena). diff --git a/src/bundler/linker_context/generateCompileResultForCssChunk.rs b/src/bundler/linker_context/generateCompileResultForCssChunk.rs index aa6ae59cf0d..dbc283f07ee 100644 --- a/src/bundler/linker_context/generateCompileResultForCssChunk.rs +++ b/src/bundler/linker_context/generateCompileResultForCssChunk.rs @@ -42,11 +42,6 @@ pub unsafe fn generate_compile_result_for_css_chunk(task: *mut ThreadPoolLib::Ta crate::linker_context_mod::crash_guard_for_part_range(c, chunk, &part_range.part_range) }; - // CONCURRENCY: the CSS impl is read-only over `c`/`chunk` (the - // `bytesInOutput` bump goes through `&AtomicUsize`), so form `&` — never - // `&mut` — to avoid aliased exclusive borrows across peer worker tasks. - // The `&` borrows are scoped to the impl call so they do not overlap the - // raw slot write that follows. let result = { // SAFETY: `c_ptr` is the live `LinkerContext` returned by // `pending_part_range_prologue`; see its contract. @@ -72,10 +67,6 @@ fn generate_compile_result_for_css_chunk_impl( let _trace = bun_core::perf::trace("Bundler.generateCodeForFileInChunkCss"); // `defer trace.end()` — RAII; Drop ends the trace. - // `worker.arena` (= `BackRef` to `worker.heap`) is a disjoint field from - // `worker.temporary_arena` borrowed `&mut` below, so a direct shared - // borrow via `BackRef::get` is fine. The heap is pinned for the worker's - // lifetime; see `Worker::arena`. let arena = worker.arena.get(); // PERF(port): was arena bulk-free (worker.temporary_arena.reset(.retain_capacity)). let _arena_reset = scopeguard::guard(&mut worker.temporary_arena, |arena| { diff --git a/src/bundler/linker_context/generateCompileResultForHtmlChunk.rs b/src/bundler/linker_context/generateCompileResultForHtmlChunk.rs index 0e1b662cc09..fb94e2bcc3a 100644 --- a/src/bundler/linker_context/generateCompileResultForHtmlChunk.rs +++ b/src/bundler/linker_context/generateCompileResultForHtmlChunk.rs @@ -386,11 +386,6 @@ impl<'a> HTMLLoader<'a> { } } -/// `chunk` is the HTML chunk being compiled (held read-only via `BackRef` -/// inside `HTMLLoader`). `chunks` is the raw `*mut [Chunk]` from -/// `GenerateChunkCtx.chunks` (write provenance, valid for the link step) — -/// stored as-is and only deref'd at the guarded `&mut *` sites in -/// `HTMLLoader::{on_tag,get_head_tags}` and the standalone-HTML branch below. fn generate_compile_result_for_html_chunk_impl<'a>( c: &'a LinkerContext<'a>, chunk: &Chunk, @@ -401,11 +396,6 @@ fn generate_compile_result_for_html_chunk_impl<'a>( let import_records = c.graph.ast.items_import_records(); let source_index = chunk.entry_point.source_index(); - // HTML bundles for dev server must be allocated to it, as it must outlive - // the bundle task. See `DevServer.RouteBundle.HTML.bundled_html_text` - // TODO(port): Zig used `dev.arena()` vs `worker.arena` to control output ownership. - // In Rust with global mimalloc this distinction collapses; verify DevServer ownership. - // `c.log` is now `*mut Log` (raw backref); copy directly. The HTMLLoader.log // field is currently dead_code, so no write actually occurs through this // pointer today. @@ -437,12 +427,6 @@ fn generate_compile_result_for_html_chunk_impl<'a>( HTMLProcessor::::run(&mut html_loader, contents) .unwrap_or_else(|_| panic!("unexpected error from HTMLProcessor.run")); - // There are some cases where invalid HTML will make it so is - // never emitted, even if the literal text DOES appear. These cases are - // along the lines of having a self-closing tag for a non-self closing - // element. In this case, head_end_tag_index will be 0, and a simple - // search through the page is done to find the "" - // See https://github.com/oven-sh/bun/issues/17554 let script_injection_offset: u32 = if has_dev_server { 'brk: { if let Some(head) = html_loader.end_tag_indices.head { diff --git a/src/bundler/linker_context/generateCompileResultForJSChunk.rs b/src/bundler/linker_context/generateCompileResultForJSChunk.rs index 81d0301ef48..4601697f4d5 100644 --- a/src/bundler/linker_context/generateCompileResultForJSChunk.rs +++ b/src/bundler/linker_context/generateCompileResultForJSChunk.rs @@ -89,13 +89,6 @@ fn generate_compile_result_for_js_chunk_impl( let _trace = bun_core::perf::trace("Bundler.generateCodeForFileInChunkJS"); // `defer trace.end()` → handled by Drop on _trace - // Client and server bundles for Bake must be globally allocated, as they - // must outlive the bundle task. - // TODO(port): runtime arena selection (dev_server vs default) — - // `DevServerHandle` does not yet expose an arena handle, and - // `BufferWriter::init()` / `DeclCollector.decls` use the global arena - // in the Rust port. Once `dispatch::DevServerHandle::arena()` exists, - // thread it here so dev-server bundles outlive the worker arena. let _ = c.dev_server; // temporary_arena / stmt_list are initialized in Worker::create before any task runs. @@ -104,18 +97,6 @@ fn generate_compile_result_for_js_chunk_impl( .as_mut() .expect("Worker.temporary_arena set in create()"); let mut buffer_writer = js_printer::BufferWriter::init(); - // Zig: `defer _ = arena.reset(.retain_capacity)` on a `std.heap.ArenaAllocator` - // (O(1) bump rewind, chunks retained). `temporary_arena` is a `MimallocArena` - // here because `temp_arena` flows into `Stmt::allocate`/`Expr::allocate`/ - // `Binding::alloc`/`ArenaVec`, all of which take `&MimallocArena` concretely; - // a plain `reset()` would be `mi_heap_destroy + mi_heap_new` *per part_range* - // (perf-probe: 46× for one elysia build). Use `reset_retain_with_limit` — the - // codebase's mapping for Zig's `.retain_*` modes (see `ModuleLoader`'s - // `transpile_source_code_arena`): keep the heap warm across part_ranges and - // only pay the destroy+new round-trip once accumulated scratch exceeds the - // limit. 8 MiB matches the module-arena precedent and comfortably covers a - // worker's full part_range set for typical bundles, so this is ~one - // `mi_heap_new` per worker instead of one per module. let arena = scopeguard::guard(&mut *arena, |a| { let _ = a.reset_retain_with_limit(8 * 1024 * 1024); }); @@ -193,10 +174,6 @@ fn generate_compile_result_for_js_chunk_impl( _ => 0, }; if code_len > 0 && !part_range.source_index.is_runtime() { - // CONCURRENCY: the map's key set is frozen before parallel codegen; we - // only need a shared `&AtomicUsize` to RMW the counter. Using `get` - // (not `get_ptr_mut`) avoids materializing an aliased `&mut` to a slot - // that other worker threads may be updating for the same source. if let Some(bytes) = chunk .files_with_parts_in_chunk .get(&part_range.source_index.get()) diff --git a/src/bundler/linker_context/postProcessCSSChunk.rs b/src/bundler/linker_context/postProcessCSSChunk.rs index b19a7b2e4d8..6fcb0dace68 100644 --- a/src/bundler/linker_context/postProcessCSSChunk.rs +++ b/src/bundler/linker_context/postProcessCSSChunk.rs @@ -33,19 +33,6 @@ pub fn post_process_css_chunk( let mut newline_before_comment = false; - // TODO: css banner - // if len(c.options.CSSBanner) > 0 { - // prevOffset.AdvanceString(c.options.CSSBanner) - // j.AddString(c.options.CSSBanner) - // prevOffset.AdvanceString("\n") - // j.AddString("\n") - // } - - // TODO: (this is where we would put the imports) - // Generate any prefix rules now - // (THIS SHOULD BE SET WHEN GENERATING PREFIX RULES!) - // newline_before_comment = true; - // TODO: meta // Concatenate the generated CSS chunks together @@ -130,15 +117,6 @@ pub fn post_process_css_chunk( // Make sure the file ends with a newline j.ensure_newline_at_end(); - // if c.options.UnsupportedCSSFeatures.Has(compat.InlineStyle) { - // slashTag = "" - // } - // c.maybeAppendLegalComments(c.options.LegalComments, legalCommentList, chunk, &j, slashTag) - - // if len(c.options.CSSFooter) > 0 { - // j.AddString(c.options.CSSFooter) - // j.AddString("\n") - // } // SAFETY: `worker.arena` set by `Worker::create`, outlives the worker step. let alloc = worker.arena(); diff --git a/src/bundler/linker_context/postProcessJSChunk.rs b/src/bundler/linker_context/postProcessJSChunk.rs index 8b964c1f985..b92c7787997 100644 --- a/src/bundler/linker_context/postProcessJSChunk.rs +++ b/src/bundler/linker_context/postProcessJSChunk.rs @@ -27,11 +27,6 @@ use bun_sourcemap as SourceMap; use crate::IndexInt; -/// Move the printed code out of a `PrintResult`. Mirrors Zig -/// `j.push(result.code, worker.allocator)` where the joiner takes ownership of -/// the slice — the Rust `PrintResultSuccess.code` is a `Box<[u8]>` that would -/// otherwise drop at end of `post_process_js_chunk` and leave the deferred -/// `IntermediateOutput::Joiner` path holding freed memory. fn print_result_take_code(r: &mut PrintResult) -> Box<[u8]> { match r { PrintResult::Result(ok) => core::mem::take(&mut ok.code), @@ -115,11 +110,6 @@ pub fn post_process_js_chunk( let worker_arena: &Arena = worker.arena(); { - // PORT NOTE: Zig builds one `print_options` and passes it by-value twice. - // Rust `Options` is not `Copy` (holds `&mut ModuleInfo`), and a closure - // taking `&mut ModuleInfo` can't express "output lifetime = input - // lifetime" — so build a base with `module_info: None` and override it - // via FRU at each call site. let make_print_options = || js_printer::Options { bundling: true, indent: Default::default(), @@ -160,11 +150,6 @@ pub fn post_process_js_chunk( }); } - // PORT NOTE: `MultiArrayList::get` returns `ManuallyDrop` — - // the storage retains ownership of every Drop field (`named_imports`, - // `parts`, `top_level_symbols_to_parts`, …), so the gathered struct - // must NOT run Drop. `to_ast` consumes by value, so unwrap, convert, - // and re-wrap the result (which carries the same heap pointers). let ast_view = core::mem::ManuallyDrop::new( core::mem::ManuallyDrop::into_inner( c.graph.ast.get(chunk.entry_point.source_index() as usize), @@ -246,11 +231,6 @@ pub fn post_process_js_chunk( } } - // 1b. Check if any source in this chunk uses import.meta. The per-part - // parallel printer does not have module_info, so the printer cannot set - // this flag during per-part printing. We derive it from the AST instead. - // Note: the runtime source (index 0) also uses import.meta (e.g. - // `import.meta.require`), so we must not skip it. { let all_ast_flags = c.graph.ast.items_flags(); for part_range in chunk.content.javascript().parts_in_chunk_in_order.iter() { @@ -263,13 +243,6 @@ pub fn post_process_js_chunk( } } - // 1c. Same idea for top-level await. The new JSC module loader decides - // sync vs async evaluation from JSModuleRecord::hasTLA(), which we set - // from this bit when constructing the record from cached module_info - // (BunAnalyzeTranspiledModule). Without it, a bytecode-compiled module - // that contains TLA gets evaluated on the sync path and the suspended - // generator is dropped — the entry promise resolves immediately and the - // process exits before the awaited value lands. { let tla_keywords = c.parse_graph().ast.items_top_level_await_keyword(); let wraps = c.graph.meta.items_flags(); @@ -288,12 +261,6 @@ pub fn post_process_js_chunk( } } - // 2. Collect truly-external imports from the original AST. Bundled imports - // (where source_index is valid) are removed by convertStmtsForChunk and - // re-created as cross-chunk imports — those are already captured by the - // printer when it prints cross_chunk_prefix_stmts above. Only truly-external - // imports (node built-ins, etc.) survive as s_import in per-file parts and - // need recording here. let all_parts = c.graph.ast.items_parts(); let all_flags = c.graph.meta.items_flags(); let all_import_records = c.graph.ast.items_import_records(); @@ -449,10 +416,6 @@ pub fn post_process_js_chunk( }; }; - // Store unserialized ModuleInfo on the chunk. Serialization is deferred to - // generateChunksInParallel after final chunk paths are computed, so that - // cross-chunk import specifiers (which use unique_key placeholders during - // printing) can be resolved to actual paths. if let Some(mi) = module_info { chunk.content.javascript_mut().module_info = Some(mi); } @@ -602,10 +565,6 @@ pub fn post_process_js_chunk( } { - // PORT NOTE: Zig `j.push(code, worker.allocator)` transferred ownership; - // `cross_chunk_prefix` is a local that drops at fn exit, but the joiner - // may be stashed on `chunk.intermediate_output` and consumed later - // (`IntermediateOutput::Joiner` path). Move the Box into the joiner. let code = print_result_take_code(&mut cross_chunk_prefix); if !code.is_empty() { newline_before_comment = true; @@ -753,10 +712,6 @@ pub fn post_process_js_chunk( } { - // PORT NOTE: `entry_point_tail` is a local `CompileResult` whose `code` - // is a `Box<[u8]>`; Zig `j.push(tail_code, worker.allocator)` handed - // ownership to the joiner. Move it so the deferred-joiner path doesn't - // read freed memory after this fn returns. let tail_code = entry_point_tail.into_code(); if !tail_code.is_empty() { // Stick the entry point tail at the end of the file. Deliberately don't @@ -808,11 +763,6 @@ pub fn post_process_js_chunk( line_offset.advance("ed); j.push_owned(quoted.into_boxed_slice()); } - // { - // let str = b"\n react_refresh: "; - // j.push_static(str); - // line_offset.advance(str); - // } { let str = b"\n});"; j.push_static(str); @@ -914,10 +864,6 @@ fn add_binding_vars_to_module_info( } } -// PORT NOTE: `js_printer::print` ties bump/Options/import_records/renamer to a -// single `'a`, and `Renamer<'r, 'src>` is invariant in `'src` — so the caller's -// renamer lifetime fixes `'a`. All by-ref params that flow into `print` must -// share that lifetime. pub fn generate_entry_point_tail_js<'a>( c: &'a mut LinkerContext, to_common_js_ref: Ref, @@ -1019,12 +965,6 @@ pub fn generate_entry_point_tail_js<'a>( let imports_to_bind: &RefImportData = &c.graph.meta.items_imports_to_bind()[source_index as usize]; - // If the output format is ES6 modules and we're an entry point, generate an - // ES6 export statement containing all exports. Except don't do that if this - // entry point is a CommonJS-style module, since that would generate an ES6 - // export statement that's not top-level. Instead, we will export the CommonJS - // exports as a default export later on. - // PERF(port): was arena-backed ArrayList(ClauseItem) — profile if hot. let mut items: Vec = Vec::new(); let cjs_export_copies = &c.graph.meta.items_cjs_export_copies()[source_index as usize]; @@ -1040,10 +980,6 @@ pub fn generate_entry_point_tail_js<'a>( had_default_export = had_default_export || **alias == *b"default"; - // If this is an export of an import, reference the symbol that the import - // was eventually resolved to. We need to do this because imports have - // already been resolved by this point, so we can't generate a new import - // and have that be resolved later. if let Some(import_data) = imports_to_bind.get(&resolved_export_data.import_ref) { @@ -1064,41 +1000,6 @@ pub fn generate_entry_point_tail_js<'a>( } { let temp_ref = cjs_export_copies[i]; - // Create both a local variable and an export clause for that variable. - // The local variable is initialized with the initial value of the - // export. This isn't fully correct because it's a "dead" binding and - // doesn't update with the "live" value as it changes. But ES6 modules - // don't have any syntax for bare named getter functions so this is the - // best we can do. - // - // These input files: - // - // // entry_point.js - // export {foo} from './cjs-format.js' - // - // // cjs-format.js - // Object.defineProperty(exports, 'foo', { - // enumerable: true, - // get: () => Math.random(), - // }) - // - // Become this output file: - // - // // cjs-format.js - // var require_cjs_format = __commonJS((exports) => { - // Object.defineProperty(exports, "foo", { - // enumerable: true, - // get: () => Math.random() - // }); - // }); - // - // // entry_point.js - // var cjs_format = __toESM(require_cjs_format()); - // var export_foo = cjs_format.foo; - // export { - // export_foo as foo - // }; - // stmts.push(Stmt::alloc( S::Local { decls: G::DeclList::from_slice(&[G::Decl { @@ -1130,29 +1031,6 @@ pub fn generate_entry_point_tail_js<'a>( ..Default::default() }); } else { - // Local identifiers can be exported using an export clause. This is done - // this way instead of leaving the "export" keyword on the local declaration - // itself both because it lets the local identifier be minified and because - // it works transparently for re-exports across files. - // - // These input files: - // - // // entry_point.js - // export * from './esm-format.js' - // - // // esm-format.js - // export let foo = 123 - // - // Become this output file: - // - // // esm-format.js - // let foo = 123; - // - // // entry_point.js - // export { - // foo - // }; - // items.push(bun_ast::ClauseItem { name: bun_ast::LocRef { ref_: Some(resolved_export_data.import_ref), @@ -1165,10 +1043,6 @@ pub fn generate_entry_point_tail_js<'a>( } } - // PORT NOTE: arena-owned `*mut [ClauseItem]` — move the - // collected Vec into the linker arena (Zig used - // `c.arena().alloc`). The arena slice is also iterated - // below for the synthetic-default-export path. let items: &mut [bun_ast::ClauseItem] = arena.alloc_slice_fill_iter(items); stmts.push(Stmt::alloc( S::ExportClause { @@ -1313,20 +1187,9 @@ pub fn generate_entry_point_tail_js<'a>( } _ => {} } - - // TODO: - // If we are generating CommonJS for node, encode the known export names in - // a form that node can understand them. This relies on the specific behavior - // of this parser, which the node project uses to detect named exports in - // CommonJS files: https://github.com/guybedford/cjs-module-lexer. Think of - // this code as an annotation for that parser. } } - // Add generated local declarations from entry point tail to module_info. - // This captures vars like `var export_foo = cjs.foo` for CJS export copies. - // PORT NOTE: reshaped for borrowck — reborrow via as_deref_mut so module_info - // remains usable for print_options below. if let Some(mi) = module_info.as_mut() { let mi: &mut ModuleInfo = &mut **mi; for stmt in stmts.iter() { diff --git a/src/bundler/linker_context/prepareCssAstsForChunk.rs b/src/bundler/linker_context/prepareCssAstsForChunk.rs index c473a298841..e49ef993a8e 100644 --- a/src/bundler/linker_context/prepareCssAstsForChunk.rs +++ b/src/bundler/linker_context/prepareCssAstsForChunk.rs @@ -20,12 +20,6 @@ use bun_resolver::DataURL; use crate::chunk::{Content, CssImportOrderKind}; -// PORT NOTE: Zig stores `*Chunk` / `*LinkerContext` (freely-aliasing mutable -// pointers). We mirror that with raw pointers rather than `&mut` / `&` so that -// (a) the container_of `container_of` recovery of `*mut BundleV2` from -// `linker` retains write provenance over the whole bundle, and (b) multiple -// tasks may hold pointers to the same `LinkerContext` concurrently without -// materializing aliased Rust references. pub struct PrepareCssAstTask { pub task: ThreadPoolLib::Task, pub chunk: *mut Chunk, @@ -91,13 +85,6 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & // Remove duplicate rules across files. This must be done in serial, not // in parallel, and must be done from the last rule to the first rule. { - // PORT NOTE: Zig accesses `chunk.content.css.{imports_in_chunk_in_order,asts}` - // through the union field at each use site while also holding `entry` as a raw - // pointer into `imports_in_chunk_in_order`. In Rust, every `chunk.content.css.*` - // re-enters the `Content` enum and re-borrows `chunk.content` as a whole, which - // would alias the live `&mut entry`. Destructure the variant once so borrowck - // can split the disjoint `CssChunk` struct fields (`imports_in_chunk_in_order` - // vs `asts`) without raw pointers. let Content::Css(css_chunk) = &mut chunk.content else { unreachable!() }; @@ -112,19 +99,6 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & let inner = layers.inner(); let len = inner.len(); let rules = if len > 0 { - // PORT NOTE: Zig `SmallList(LayerName,1).fromBabyListNoDeinit(layers.inner().*)` - // is a bitwise Vec→SmallList header transfer. In Rust the - // `Chunk::Layers` payload is the lifetime-erased shadow - // `bun_css::LayerName { v: Vec> }`, - // not the real `css_parser::LayerName { v: SmallList<&'static [u8],1> }`, - // so the layouts differ. Rebuild the real list element-by-element; - // segments are arena-owned (`'bump`-laundered to `'static`) so the - // `&[u8]` reborrows below are valid for the chunk lifetime. - // - // Both `SmallList` levels go into the arena-backed rule list - // that `CssChunk::Drop` `set_len(0)`s without running element - // destructors, so any global heap spill would leak. Build them - // via `from_arena_iter` so the spill (if any) lives in `bump`. let names = SmallList::::from_arena_iter( bump, inner.slice().iter().map(|shadow| LayerName { @@ -162,13 +136,6 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & css_chunk.asts[i] = ast; } CssImportOrderKind::ExternalPath(p) => { - // PORT NOTE: Zig keeps `conditions: ?*ImportConditions` as a raw - // pointer to index 0 while the `while j != 1` loop reads - // `entry.conditions.len` / `.at(j)`. Taking `&mut` at index 0 here - // would exclusively borrow the whole `entry.conditions` Vec for - // the duration, aliasing those reads. The pointer is not actually - // dereferenced until after the loop (.zig:119), so defer acquiring - // the index-0 borrow until `actual_conditions` is built below. let had_conditions = entry.conditions.len() > 0; if had_conditions { entry.condition_import_records.push(ImportRecord { @@ -183,28 +150,10 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & flags: ImportRecordFlags::default(), }); - // Handling a chain of nested conditions is complicated. We can't - // necessarily join them together because a) there may be multiple - // layer names and b) layer names are only supposed to be inserted - // into the layer order if the parent conditions are applied. - // - // Instead we handle them by preserving the "@import" nesting using - // imports of data URL stylesheets. This may seem strange but I think - // this is the only way to do this in CSS. let mut j: usize = entry.conditions.len() as usize; while j != 1 { j -= 1; - // PORT NOTE: Zig has no destructors, so when `ast_import` falls - // out of scope the bitwise-duplicated `ImportConditions` inside - // it (see `ptr::read` below) is simply abandoned. In Rust, - // dropping `ast_import` would run `Drop` on that aliased - // `ImportConditions` — freeing Global-backed buffers - // (`MediaList.media_queries: Vec`, `SupportsCondition::{Box,Vec}`, - // `LayerName.v: SmallList`) that are still owned by - // `entry.conditions[j]`, i.e. a double-free / UAF. Wrap in - // `ManuallyDrop` to mirror Zig's leak-on-scope-exit; the rule - // slab itself is arena-owned so it is reclaimed on arena reset. let ast_import = core::mem::ManuallyDrop::new(BundlerStyleSheet { options: ParserOptions::default(None), license_comments: Default::default(), @@ -338,10 +287,6 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & } CssImportOrderKind::SourceIndex(source_index) => { let source_index = *source_index; - // Multiple imports may refer to the same file/AST, but they - // may wrap or modify the AST in different ways. So we need - // to make a shallow copy and be careful not to modify shared - // references. let ast: &mut BundlerStyleSheet = 'ast: { // asts[idx] is Some for source_index entries (invariant of imports_in_chunk_in_order). let original_stylesheet: &BundlerStyleSheet = asts @@ -357,24 +302,6 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & }; { - // Strip leading "@import" and ".ignored" rules. Any - // "@layer" statement rules interleaved with them are - // preserved, because they carry layer ordering - // information that is not re-emitted elsewhere by - // the bundler (e.g. Tailwind's - // `@layer theme, base, components, utilities;`). - // - // IMPORTANT: `ast` is only a shallow copy of the - // per-source stylesheet, so `ast.rules.v.items` still - // points at the backing array owned by - // `c.graph.ast.items(.css)`. We MUST NOT mutate that - // buffer in place — a second import of the same - // source_index would observe the compacted prefix and - // drop rules. Instead we always build a fresh rules - // list (copying the retained "@layer" prefix rules + - // the tail). - // - // Regression: #28914 let original_rules = ast.rules.v.as_slice(); let mut layer_count: usize = 0; let mut prefix_end: usize = original_rules.len(); @@ -394,10 +321,6 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & // Prefix is all "@layer" (or empty). Nothing to // strip — leave `ast.rules.v` untouched. } else { - // Interleaved case: allocate a fresh rules list - // so we don't mutate the shared backing array. - // Preserve the "@layer" statements from the - // prefix and append the remaining tail. let mut new_rules: ArenaVec = ArenaVec::with_capacity_in( layer_count + (original_rules.len() - prefix_end), @@ -413,12 +336,6 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & // SAFETY: Zig by-value copy of arena-backed rule. new_rules.push(unsafe { core::ptr::read(rule) }); } - // `ast.rules` is the shallow-copied header aliasing the - // source stylesheet's arena buffer (see `ptr::read` above). - // Dropping it would `drop_in_place` the aliased rules and - // free the shared backing array. Leak the header (Zig - // semantics: bitwise overwrite) before installing the - // freshly-allocated list. let _ = core::mem::ManuallyDrop::new(core::mem::replace( &mut ast.rules, arena_rule_list(new_rules), @@ -434,15 +351,6 @@ fn prepare_css_asts_for_chunk_impl(c: &LinkerContext, chunk: &mut Chunk, bump: & } } -/// Builds a `BundlerCssRuleList` whose backing storage is arena-owned. -/// -/// `CssRuleList::v` is a global `Vec`, but every rule slab built here must be -/// arena-backed: the elements bitwise-alias the source AST and must never run -/// `Drop`, and the slab itself must outlive the chunk without a side-channel -/// owner. Reinterpreting the leaked arena slice as a global `Vec` is sound -/// because the resulting `Vec` is never dropped (`CssChunk::Drop` `forget`s -/// the `asts` slab) and never grown after this point; the arena reclaims the -/// storage on `reset`. fn arena_rule_list(rules: ArenaVec<'_, BundlerCssRule>) -> BundlerCssRuleList { let len = rules.len(); if len == 0 { @@ -489,12 +397,6 @@ fn wrap_rules_with_conditions( // Omit an empty "@layer {}" entirely continue; } else { - // Generate "@layer foo;" instead of "@layer foo {}" - // `ast.rules.v` may be the shallow-copied / offset-resliced - // header aliasing the source stylesheet's buffer (see the - // `ptr::read` / `Vec::from_raw_parts` above) — dropping it - // would free into another allocation. Zig's `= .{}` is a - // bitwise overwrite; mirror that by leaking the header. let _ = core::mem::ManuallyDrop::new(core::mem::take(&mut ast.rules.v)); do_block_rule = false; } diff --git a/src/bundler/linker_context/renameSymbolsInChunk.rs b/src/bundler/linker_context/renameSymbolsInChunk.rs index 1d14bde68bc..55ce5d63d94 100644 --- a/src/bundler/linker_context/renameSymbolsInChunk.rs +++ b/src/bundler/linker_context/renameSymbolsInChunk.rs @@ -63,13 +63,6 @@ pub unsafe fn rename_symbols_in_chunk( // SAFETY: see fn safety doc — `c` is live for the call. let c: &LinkerContext<'_> = unsafe { &*c }; - // ── raw SoA column pointers (root provenance) ──────────────────────── - // `split_raw()` derives `*mut [T]` directly from the buffer base with no - // `&mut` intermediate, so per-column derefs here do not pop sibling - // tasks' borrow tags under Stacked Borrows. Read-only columns are - // deref'd to `&[T]`; the two written columns (`module_scope`, `parts`) - // are deref'd to `&mut [T]` — see CONCURRENCY note re: code-splitting - // overlap. let ast = c.graph.ast.split_raw(); let meta = c.graph.meta.split_raw(); @@ -294,27 +287,9 @@ pub unsafe fn rename_symbols_in_chunk( let parts: &mut [Part] = all_parts[source_index as usize].as_mut_slice(); match wrap { - // Modules wrapped in a CommonJS closure look like this: - // - // // foo.js - // var require_foo = __commonJS((exports, module) => { - // exports.foo = 123; - // }); - // - // The symbol "require_foo" is stored in "file.ast.WrapperRef". We want - // to be able to minify everything inside the closure without worrying - // about collisions with other CommonJS modules. Set up the scopes such - // that it appears as if the file was structured this way all along. It's - // not completely accurate (e.g. we don't set the parent of the module - // scope to this new top-level scope) but it's good enough for the - // renaming code. WrapKind::Cjs => { r.add_top_level_symbol(all_wrapper_refs[source_index as usize]); - // External import statements will be hoisted outside of the CommonJS - // wrapper if the output format supports import statements. We need to - // add those symbols to the top-level scope to avoid causing name - // collisions. This code special-cases only those symbols. if c.options.output_format.keep_es6_import_export_syntax() { let import_records = all_import_records[source_index as usize].as_slice(); for part in parts.iter() { @@ -379,20 +354,6 @@ pub unsafe fn rename_symbols_in_chunk( continue; } - // Modules wrapped in an ESM closure look like this: - // - // // foo.js - // var foo, foo_exports = {}; - // __export(foo_exports, { - // foo: () => foo - // }); - // let init_foo = __esm(() => { - // foo = 123; - // }); - // - // The symbol "init_foo" is stored in "file.ast.WrapperRef". We need to - // minify everything inside the closure without introducing a new scope - // since all top-level variables will be hoisted outside of the closure. WrapKind::Esm => { r.add_top_level_symbol(all_wrapper_refs[source_index as usize]); } diff --git a/src/bundler/linker_context/scanImportsAndExports.rs b/src/bundler/linker_context/scanImportsAndExports.rs index ab95027391f..c26c81be71f 100644 --- a/src/bundler/linker_context/scanImportsAndExports.rs +++ b/src/bundler/linker_context/scanImportsAndExports.rs @@ -51,17 +51,6 @@ impl From for crate::linker_context_mod::LinkError { } bun_core::named_error_set!(ScanImportsAndExportsError); -/// Short-lived `&mut [T]` deref of a `split_raw()` column pointer at a single -/// use site. -/// -/// SoA columns are physically disjoint (`COLUMN_OFFSET_PER_CAP`); the backing -/// buffer is never reallocated inside this function (only column *element* -/// contents grow, e.g. `Vec::append`). The pointers come from -/// `split_raw()`, which derives them by raw `add` on the heap buffer base — -/// root/SharedRW provenance, no `&mut` intermediate — so they survive the -/// interleaved `&mut LinkerContext` method calls in steps 3-5 under Stacked -/// Borrows. (Decaying `split_mut()`'s `&mut [T]` to raw would *not*: the child -/// Unique tag is popped the first time another column accessor runs.) macro_rules! col { ($p:expr) => { // SAFETY: see module-level note. Caller ensures no aliasing `&mut` to @@ -89,12 +78,6 @@ pub fn scan_imports_and_exports( // PERF(port): was zero-copy slice; profile. let mut reachable: Vec = this.graph.reachable_files.slice().to_vec(); - // ── cache SoA column base pointers ──────────────────────────────────── - // `MultiArrayList` never reallocates inside this function (only column - // *element* contents grow, e.g. `Vec::append`). So these raw - // column pointers are valid for the whole body. `split_raw()` derives - // each `*mut [T]` directly from the buffer base (root provenance) — see - // the `col!` doc-comment for why `split_mut()` is not used here. let ast = this.graph.ast.split_raw(); let meta = this.graph.meta.split_raw(); let files = this.graph.files.split_raw(); @@ -186,26 +169,6 @@ pub fn scan_imports_and_exports( match record.kind { ImportKind::Stmt => { - // Importing using ES6 syntax from a file without any ES6 syntax - // causes that module to be considered CommonJS-style, even if it - // doesn't have any CommonJS exports. - // - // That means the ES6 imports will become undefined instead of - // causing errors. This is for compatibility with older CommonJS- - // style bundlers. - // - // We emit a warning in this case but try to avoid turning the module - // into a CommonJS module if possible. This is possible with named - // imports (the module stays an ECMAScript module but the imports are - // rewritten with undefined) but is not possible with star or default - // imports: - // - // import * as ns from './empty-file' - // import defVal from './empty-file' - // console.log(ns, defVal) - // - // In that case the module *is* considered a CommonJS module because - // the namespace object must be created. if (record .flags .contains(ImportRecordFlags::CONTAINS_IMPORT_STAR) @@ -259,10 +222,6 @@ pub fn scan_imports_and_exports( let kind = col_ref!(exports_kind)[id]; - // If the output format doesn't have an implicit CommonJS wrapper, any file - // that uses CommonJS features will need to be wrapped, even though the - // resulting wrapper won't be invoked by other files. An exception is - // made for entry point files in CommonJS format (or when in pass-through mode). if kind == ExportsKind::Cjs && (!col_ref!(entry_point_kinds)[id].is_entry_point() || output_format == Format::Iife @@ -295,10 +254,6 @@ pub fn scan_imports_and_exports( ); } - // Step 2: Propagate dynamic export status for export star statements that - // are re-exports from a module whose exports are not statically analyzable. - // In this case the export star must be evaluated at run time instead of at - // bundle time. { let _trace = perf::trace("Bundler.WrapDependencies"); // SAFETY: `split_raw()`-derived column ptrs carry root provenance @@ -337,14 +292,6 @@ pub fn scan_imports_and_exports( let _ = dependency_wrapper.has_dynamic_exports_due_to_export_star(source_index); } - // Even if the output file is CommonJS-like, we may still need to wrap - // CommonJS-style files. Any file that imports a CommonJS-style file will - // cause that file to need to be wrapped. This is because the import - // method, whatever it is, will need to invoke the wrapper. Note that - // this can include entry points (e.g. an entry point that imports a file - // that imports that entry point). - // `import_records` is a `&'a [_]` (Copy) field — copy it out so - // the loop borrow does not overlap `&mut dependency_wrapper`. let import_records = dependency_wrapper.import_records; for record in import_records[id].as_slice() { if record.source_index.is_valid() { @@ -444,10 +391,6 @@ pub fn scan_imports_and_exports( } let export_kind = col_ref!(exports_kind)[source_index]; let mut flag = col_ref!(flags)[source_index]; - // If we're exporting as CommonJS and this file was originally CommonJS, - // then we'll be using the actual CommonJS "exports" and/or "module" - // symbols. In that case make sure to mark them as such so they don't - // get minified. if (output_format == Format::Cjs) && col_ref!(entry_point_kinds)[source_index].is_entry_point() && export_kind == ExportsKind::Cjs @@ -486,20 +429,6 @@ pub fn scan_imports_and_exports( } } - // Step 5: Create namespace exports for every file. This is always necessary - // for CommonJS files, and is also necessary for other files if they are - // imported using an import star statement. - // Note: `do` will wait for all to finish before moving forward - // - // PORT NOTE: Zig dispatched via `worker_pool.each(arena, this, - // doStep5, reachable_files)` (parallel fan-out, blocks until done). - // `do_step5` only touches distinct SoA rows per `source_index` (the - // columns are pre-sized and never reallocate during this step), - // matching the Zig invariant. We pass `*mut LinkerContext` through a - // `Sync` wrapper; the callee derefs it to `&LinkerContext` (shared) - // for reads and writes per-row cells via raw `split_raw()` pointers — - // mirroring `GenerateChunkCtx` (`generate_js_renamer` likewise never - // forms `&mut LinkerContext`). { #[repr(transparent)] struct Step5Ctx<'a>(*mut LinkerContext<'a>); @@ -527,12 +456,6 @@ pub fn scan_imports_and_exports( &mut reachable[..], ); } - - // Zig calls `takeAstOwnership` here because `doStep5` appends to - // `part.dependencies`/`declared_symbols` with the worker allocator. - // In the Rust port those are global-allocator `Vec`s (thread-safe to - // grow) and `do_step_5` never pushes to the arena-backed `PartList`/ - // import-record columns, so no transfer is needed. } if FeatureFlags::HELP_CATCH_MEMORY_ISSUES { @@ -606,20 +529,10 @@ pub fn scan_imports_and_exports( break 'brk count; }; - // Allocate the identifier-name buffer from the linker arena so it is - // reclaimed when the link pass ends (Zig: `this.arena().alloc(u8, ...)`). - // The slices handed out below are stored in `Symbol.original_name: *const [u8]`, - // which is arena-lifetime by construction. let string_buffer: &mut [u8] = this .graph .arena() .alloc_slice_fill_default::(string_buffer_len); - // PORT NOTE: `StringBuilder::drop` reconstructs a `Box<[u8]>` from - // `ptr`/`cap` and frees it via the global arena. Here the - // backing buffer is arena-owned (bumpalo), so dropping would hand - // mimalloc a pointer it never allocated. Wrap in `ManuallyDrop` — - // the arena reclaims the storage on reset, matching Zig's implicit - // no-destructor semantics. let mut builder = core::mem::ManuallyDrop::new(bun_core::StringBuilder { len: 0, cap: string_buffer.len(), @@ -665,11 +578,6 @@ pub fn scan_imports_and_exports( } } - // If this isn't CommonJS, then rename the unused "exports" and "module" - // variables to avoid them causing the identically-named variables in - // actual CommonJS files from being renamed. This is purely about - // aesthetics and is not about correctness. This is done here because by - // this point, we know the CommonJS status will not change further. if wrap != WrapKind::Cjs && export_kind != ExportsKind::Cjs && output_format != Format::InternalBakeDev @@ -733,10 +641,6 @@ pub fn scan_imports_and_exports( let r#ref: Ref = col_ref!(imports_to_bind_list)[id].keys()[itb_i]; let import_source_index; let import_ref; - // `BackRef<[Dependency]>` — points into the `imports_to_bind` - // value column, which is not mutated for the rest of this - // loop body; the backref invariant (pointee outlives holder) - // lets the inner-loop reads go through safe `Deref`. let re_exports_ptr: bun_ptr::BackRef<[Dependency]>; { let import: &ImportData = @@ -747,11 +651,6 @@ pub fn scan_imports_and_exports( } if let Some(named_import) = col_ref!(named_imports)[id].get(&r#ref) { - // `local_parts_with_uses` and the `top_level_symbol_to_parts` - // result are both arena-backed AstVec slices that this loop - // body never resizes; capture them as BackRefs (same - // discipline as `re_exports_ptr` above) so we can take - // `&mut col!(parts_list)[id]` without cloning. let local_parts: bun_ptr::BackRef<[u32]> = bun_ptr::BackRef::new(named_import.local_parts_with_uses.slice()); let parts_declaring_symbol: bun_ptr::BackRef<[u32]> = bun_ptr::BackRef::new( @@ -822,10 +721,6 @@ pub fn scan_imports_and_exports( for part_index in top_to_parts { // PERF(port): was appendAssumeCapacity dependencies.push(Dependency { - // PORT NOTE: `crate::Index` ↔ `bun_ast::Index` are both - // `#[repr(transparent)] u32` newtypes ported from the - // same Zig `ast.Index`; bridge by `.value` until B-3 - // collapses them to a single re-export. source_index: bun_ast::Index(target_source_index.get()), part_index: *part_index, }); @@ -931,13 +826,6 @@ pub fn scan_imports_and_exports( && col_ref!(ast_flags_list)[other_id] .contains(AstFlags::FORCE_CJS_TO_ESM) { - // If the CommonJS module was converted to ESM - // and the developer `import("cjs_module")`, then - // they may have code that expects the default export to return the CommonJS module.exports object - // That module.exports object does not exist. - // We create a default object with getters for each statically-known export - // This is kind of similar to what Node.js does - // Once we track usages of the dynamic import, we can remove this. if !col_ref!(named_exports)[other_id].contains(b"default") { col!(flags)[other_id].needs_synthetic_default_export = true; } @@ -950,18 +838,6 @@ pub fn scan_imports_and_exports( runtime_require_uses += 1; } - // If this wasn't originally a "require()" call, then we may need - // to wrap this in a call to the "__toESM" wrapper to convert from - // CommonJS semantics to ESM semantics. - // - // Unfortunately this adds some additional code since the conversion - // is somewhat complex. As an optimization, we can avoid this if the - // following things are true: - // - // - The import is an ES module statement (e.g. not an "import()" expression) - // - The ES module namespace object must not be captured - // - The "default" and "__esModule" exports must not be accessed - // if kind != ImportKind::Require && (kind != ImportKind::Stmt || rec_flags @@ -1031,11 +907,6 @@ pub fn scan_imports_and_exports( to_esm_uses += 1; } - // If this is an ESM wrapper, also depend on the exports object - // since the final code will contain an inline reference to it. - // This must be done for "require()" and "import()" expressions - // but does not need to be done for "import" statements since - // those just cause us to reference the exports directly. if other_flags.wrap == WrapKind::Esm && kind != ImportKind::Stmt { this.graph.generate_symbol_import_and_use( source_index, @@ -1045,15 +916,6 @@ pub fn scan_imports_and_exports( Index::source(other_source_index), )?; - // If this is a "require()" call, then we should add the - // "__esModule" marker to behave as if the module was converted - // from ESM to CommonJS. This is done via a wrapper instead of - // by modifying the exports object itself because the same ES - // module may be simultaneously imported and required, and the - // importing code should not see "__esModule" while the requiring - // code should see "__esModule". This is an extremely complex - // and subtle set of transpiler interop issues. See for example - // https://github.com/evanw/esbuild/issues/1591. if kind == ImportKind::Require { col!(import_records_list)[id].as_mut_slice() [import_record_index as usize] @@ -1065,11 +927,6 @@ pub fn scan_imports_and_exports( } else if kind == ImportKind::Stmt && export_kind == ExportsKind::EsmWithDynamicFallback { - // This is an import of a module that has a dynamic export fallback - // object. In that case we need to depend on that object in case - // something ends up needing to use it later. This could potentially - // be omitted in some cases with more advanced analysis if this - // dynamic export fallback object doesn't end up being needed. this.graph.generate_symbol_import_and_use( source_index, part_index as u32, @@ -1103,10 +960,6 @@ pub fn scan_imports_and_exports( } if other_export_kind.is_esm_with_dynamic_fallback() { - // This looks like "__reExport(exports_a, exports_b)". Make sure to - // pull in the "exports_b" symbol into this export star. This matters - // in code splitting situations where the "export_b" symbol might live - // in a different chunk than this export star. this.graph.generate_symbol_import_and_use( source_index, part_index as u32, @@ -1309,13 +1162,6 @@ impl<'a> ExportStarContext<'a> { continue; } - // Export stars from a CommonJS module don't work because they can't be - // statically discovered. Just silently ignore them in this case. - // - // We could attempt to check whether the imported file still has ES6 - // exports even though it still uses CommonJS features. However, when - // doing this we'd also have to rewrite any imports of these export star - // re-exports as property accesses off of a generated require() call. if col_ref!(self.exports_kind)[other_id] == ExportsKind::Cjs { continue; } @@ -1402,10 +1248,6 @@ impl<'a> ExportStarContext<'a> { } } -// ────────────────────────────────────────────────────────────────────────── -// CSS "composes:" validation. The body reaches into -// `bun_css::BundlerStyleSheet.{composes,local_scope,local_properties}`. -// ────────────────────────────────────────────────────────────────────────── mod __css_validation { use super::*; use crate::bun_css::css_properties::css_modules::Specifier; @@ -1490,32 +1332,6 @@ mod __css_validation { validate_composes_from_properties(this, id as u32, css_ast, import_records_list, css_asts); } - /// CSS modules spec says that the following is undefined behavior: - /// - /// ```css - /// .foo { - /// composes: bar; - /// color: red; - /// } - /// - /// .bar { - /// color: blue; - /// } - /// ``` - /// - /// Specfically, composing two classes that both define the same property is undefined behavior. - /// - /// We check this by recording, at parse time, properties that classes use in the `PropertyUsage` struct. - /// Then here, we compare the properties of the two classes to ensure that there are no conflicts. - /// - /// There is one case we skip, which is checking the properties of composing from the global scope (`composes: X from global`). - /// - /// The reason we skip this is because it would require tracking _every_ property of _every_ class (not just CSS module local classes). - /// This sucks because: - /// 1. It introduces a performance hit even if the user did not use CSS modules - /// 2. Composing from the global scope is pretty rare - /// - /// We should find a way to do this without incurring performance penalties to the common cases. fn validate_composes_from_properties( this: &mut LinkerContext, index: IndexInt, diff --git a/src/bundler/linker_context/writeOutputFilesToDisk.rs b/src/bundler/linker_context/writeOutputFilesToDisk.rs index dbc202557f2..21f99a1f9b3 100644 --- a/src/bundler/linker_context/writeOutputFilesToDisk.rs +++ b/src/bundler/linker_context/writeOutputFilesToDisk.rs @@ -68,11 +68,6 @@ pub fn write_output_files_to_disk( return Err(e); } }; - // Optimization: when writing to disk, we can re-use the memory - // PERF(port): MaxHeapAllocator reuses the largest allocation between - // iterations. Verify bun_alloc::MaxHeapAllocator semantics - // match (init/reset/deinit). DynAlloc is currently `()` so the arena - // handles below are placeholders; allocation routes through global mimalloc. let mut max_heap_allocator = MaxHeapAllocator::init(); let mut _max_heap_allocator_source_map = MaxHeapAllocator::init(); let mut _max_heap_allocator_inline_source_map = MaxHeapAllocator::init(); @@ -82,11 +77,6 @@ pub fn write_output_files_to_disk( let bv2: &mut BundleV2 = unsafe { &mut *LinkerContext::bundle_v2_ptr(std::ptr::from_mut::(c)) }; - // PORT NOTE: Zig passes `chunk` (an element of `chunks`) and `chunks` - // together into `code()`/`code_standalone()`. The callee now takes - // `&Chunk` / `&[Chunk]` (read-only), so iterate by index and reborrow - // shared; the only per-chunk mutation is the `intermediate_output` - // take/restore done via `chunks[i]`. let chunks_len = chunks.len(); for chunk_index_in_chunks_list in 0..chunks_len { @@ -118,10 +108,6 @@ pub fn write_output_files_to_disk( } let _trace2 = bun_core::perf::trace("Bundler.writeChunkToDisk"); - // PERF(port): Zig `defer max_heap_allocator.reset()` — reset the reusable - // buffer after each chunk. `MaxHeapAllocator::scope()` returns an RAII - // guard that resets on drop and derefs to the arena, so when - // `code_allocator` is wired up it can borrow through `_code_allocator`. let _code_allocator = max_heap_allocator.scope(); let rel_parent = @@ -505,10 +491,6 @@ pub fn write_output_files_to_disk( } }), entry_point_index: if output_kind == options::OutputKind::EntryPoint { - // TODO(port): `bake_types::Framework` is missing - // `server_components`; once it lands, restore the - // `if c.framework.is_some_and(|fw| fw.server_components.is_some()) { 3 } else { 1 }` - // branch. let offset: u32 = 1; Some(chunk.entry_point.source_index() - offset) } else { diff --git a/src/bundler/options.rs b/src/bundler/options.rs index 522cc43b2e8..5b7cca1e63b 100644 --- a/src/bundler/options.rs +++ b/src/bundler/options.rs @@ -11,11 +11,6 @@ use bun_resolver::fs as Fs; use bun_resolver::fs::PathResolverExt as _; use bun_resolver::package_json::{MacroMap as MacroRemap, PackageJSON}; use std::borrow::Cow; -// TODO(b2-blocked): bun_analytics — Cargo.toml does not yet list the dep -// (adding it triggers upstream rebuilds with in-progress breakage). The -// `analytics::features::*` counters are pure telemetry side effects; the -// increment call-sites below are ``-gated until the dep is wired -// so the no-op is explicit (PORTING.md §Forbidden patterns: silent no-ops). mod analytics { #[allow(non_upper_case_globals)] @@ -88,10 +83,6 @@ pub fn validate_path( if rel_path.is_empty() { return Box::default(); } - // TODO: switch to getFdPath()-based implementation - // PORT NOTE: Zig used `std.fs.path.resolve(arena, &.{cwd, rel_path})`; - // `join_abs_string` resolves `.`/`..` against `cwd` into a threadlocal - // buffer which is then boxed (matches the arena.dupe in the Zig path). let _ = path_kind; let out = bun_paths::resolve_path::join_abs_string::(cwd, &[rel_path]); @@ -113,19 +104,8 @@ pub fn validate_path( // PORT NOTE: options.zig `stringHashMapFromArrays` — inline the construction // (see definesFromTransformOptions / loadersFromTransformOptions below). -// `AllowUnresolved` is defined canonically in -// `bun_js_parser::options` (lower tier) because the parser is the consumer -// (`P::should_allow_unresolved_dynamic_specifier`). Re-export here so -// `BundleOptions.allow_unresolved` and `Parser.Options.allow_unresolved` are -// the SAME nominal type and `ParseTask::run_with_source_code` can hand -// `&transpiler.options.allow_unresolved` straight through. pub use bun_js_parser::options::AllowUnresolved; -// Canonical defs live in `bun_resolver::options` (lower tier; resolver is the -// runtime consumer of `.patterns`/`.abs_paths`/`.node_modules`). Re-export so -// `BundleOptions.external` and `Resolver.opts.external` are the SAME nominal -// type and the projection in `transpiler::resolver_bundle_options_subset` is a -// plain `.clone()`. pub use bun_resolver::options::{ExternalModules, WildcardPattern}; /// `options.zig` `ExternalModules.isNodeBuiltin`. Free fn (not an inherent @@ -178,13 +158,7 @@ pub fn init_external_modules( result.node_modules.insert(pattern).expect("unreachable"); } } - Target::Bun => { - // // TODO: fix this stupid copy - // result.node_modules.hash_map.ensureTotalCapacity(BunNodeBuiltinPatternsCompat.len) catch unreachable; - // for (BunNodeBuiltinPatternsCompat) |pattern| { - // result.node_modules.insert(pattern) catch unreachable; - // } - } + Target::Bun => {} _ => {} } @@ -240,20 +214,12 @@ pub use bun_resolve_builtins::node_builtins::{ pub use bun_options_types::BundlePackage; -// Re-export of `bun_options_types::bundle_enums::ModuleType`. -// Re-exported so `crate::options_impl::ModuleType` and `js_ast::parser::options::ModuleType` -// (which also re-exports the BundleEnums def) are the *same* nominal type — kills the -// `to_parser_module_type` shim in transpiler.rs. pub use bun_options_types::bundle_enums::ModuleType; // Kept for callers that reference the module-level static name; forwards to the // canonical const map on the upstream enum. pub static MODULE_TYPE_LIST: phf::Map<&'static [u8], ModuleType> = ModuleType::LIST; -// Re-export of `bun_ast::Target`. -// Spec options.zig:379 has exactly ONE `Target`; re-export the canonical enum so -// `BundleOptions.target`, `js_printer::Options.target`, the resolver, and css -// targets all share one nominal type (kills the `to_bundle_enums_target` shim). pub(crate) use bun_ast::Target; // Forwarded to the canonical assoc-const so there is exactly one phf body. @@ -269,29 +235,9 @@ pub const TARGET_MAIN_FIELD_NAMES: [&[u8]; 4] = [ b"jsnext:main", ]; -// Note that this means if a package specifies "module" and "main", the ES6 -// module will not be selected. This means tree shaking will not work when -// targeting node environments. -// -// Some packages incorrectly treat the "module" field as "code for the browser". It -// actually means "code for ES6 environments" which includes both node and the browser. -// -// For example, the package "@firebase/app" prints a warning on startup about -// the bundler incorrectly using code meant for the browser if the bundler -// selects the "module" field instead of the "main" field. -// -// This is unfortunate but it's a problem on the side of those packages. -// They won't work correctly with other popular bundlers (with node as a target) anyway. const DEFAULT_MAIN_FIELDS_NODE: &[&[u8]] = &[TARGET_MAIN_FIELD_NAMES[2], TARGET_MAIN_FIELD_NAMES[1]]; -// Note that this means if a package specifies "main", "module", and -// "browser" then "browser" will win out over "module". This is the -// same behavior as webpack: https://github.com/webpack/webpack/issues/4674. -// -// This is deliberate because the presence of the "browser" field is a -// good signal that this should be preferred. Some older packages might only use CJS in their "browser" -// but in such a case they probably don't have any ESM files anyway. const DEFAULT_MAIN_FIELDS_BROWSER: &[&[u8]] = &[ TARGET_MAIN_FIELD_NAMES[0], TARGET_MAIN_FIELD_NAMES[1], @@ -304,11 +250,6 @@ const DEFAULT_MAIN_FIELDS_BUN: &[&[u8]] = &[ TARGET_MAIN_FIELD_NAMES[3], ]; -/// Bundler-only `Target` methods. Extension trait per PORTING.md crate-tier -/// rule — the canonical `Target` lives in `bun_options_types` (lower tier) and -/// cannot depend on `bake_types` / `StringHashMap`. Re-exported through -/// `bun_bundler::options` so `use bun_bundler::options::TargetExt;` makes -/// `.bake_graph()` etc. available on the single canonical type. pub trait TargetExt: Copy { // pub const fromJS — deleted: see PORTING.md "*_jsc alias" rule. // TODO(port): move to *_jsc — bun_bundler_jsc::options_jsc::target_from_js @@ -316,11 +257,6 @@ pub trait TargetExt: Copy { fn bake_graph(self) -> crate::bake_types::Graph; fn out_extensions(self) -> StringHashMap<&'static [u8]>; - // Original comment: - // The neutral target is for people that don't want esbuild to try to - // pick good defaults for their platform. In that case, the list of main - // fields is empty by default. You must explicitly configure it yourself. - // array.set(Target.neutral, &listc); fn default_main_fields_map() -> EnumMap { EnumMap::from_fn(|k| match k { Target::Node => DEFAULT_MAIN_FIELDS_NODE, @@ -382,19 +318,10 @@ impl TargetExt for Target { pub use bun_options_types::Format; pub use bun_options_types::WindowsOptions; -// Re-export of `bun_ast::Loader`. -// Spec options.zig:568 has exactly ONE `Loader`; re-export so the bundler's -// `BundleOptions.loaders` and the resolver's `Path::loader()` operate on the -// same nominal type. pub(crate) use bun_ast::Loader; pub use bun_options_types::LOADER_API_NAMES; -/// Bundler-only `Loader` methods. Extension trait per PORTING.md crate-tier -/// rule — the canonical `Loader` lives in `bun_options_types` (lower tier) and -/// cannot depend on `bun_http_types::MimeType`. Re-exported through -/// `bun_bundler::options` so `use bun_bundler::options::LoaderExt;` makes -/// `.to_mime_type()` etc. available on the single canonical type. pub trait LoaderExt: Copy { fn to_mime_type(self, paths: &[&[u8]]) -> bun_http_types::MimeType::MimeType; fn from_mime_type(mime_type: bun_http::MimeType) -> Loader; @@ -426,11 +353,6 @@ pub trait LoaderExt: Copy { // pub const fromJS — deleted: see PORTING.md "*_jsc alias" rule. // TODO(port): move to *_jsc — bun_bundler_jsc::options_jsc::loader_from_js - // PORT NOTE: `is_type_script` / `is_java_script_like*` spelling-aliases - // moved to inherent `impl Loader` in `bun_options_types::bundle_enums` so - // cross-crate callers (bun_jsc / bun_runtime) resolve them without a trait - // import. - // TODO(port): `obj: anytype` — Zig duck-typed `.get(ext) -> Option`. // Monomorphized to the only concrete map type callers pass (`LoaderHashTable`). fn for_file_name(filename: &[u8], obj: &LoaderHashTable) -> Option { @@ -496,14 +418,6 @@ impl LoaderExt for Loader { } } -// ────────────────────────────────────────────────────────────────────────── -// CYCLEBREAK: jsc::VirtualMachine / jsc::WebCore::Blob are T6 GENUINE deps. -// `normalize_specifier` and `get_loader_and_virtual_source` reach into VM -// internals (origin, module_loader, ObjectURLRegistry) and are only called -// from the runtime side. They take an opaque vtable now; runtime supplies the -// static VmLoaderVTable instance (move-in pass). -// ────────────────────────────────────────────────────────────────────────── - /// Opaque erased blob handle. SAFETY: erased jsc::WebCore::Blob; bundler only /// stores/drops via vtable, never dereferences. pub type OpaqueBlob = *mut (); @@ -701,20 +615,6 @@ const DEFAULT_LOADERS_POSIX: &[(&[u8], Loader)] = &[ #[cfg(all(windows, test))] const DEFAULT_LOADERS_WIN32_EXTRA: &[(&[u8], Loader)] = &[(b".sh", Loader::Bunsh)]; -/// File-extension → default [`Loader`] map (options.zig `defaultLoaders`). -/// -/// PERF(port): was `phf::Map<&[u8], Loader>`. phf hashes the full key (SipHash -/// over up to 9 bytes) + probes a displacement table + does a final memcmp on -/// every lookup. With only 22 keys bucketing into 5 distinct lengths -/// (3/4/5/6/9, all `.`-prefixed), a length-gated `match` is cheaper: one -/// `usize` compare rejects every wrong-length probe, and within each bucket -/// rustc lowers the fixed-width byte-slice arms to single u32/u64 compares (no -/// memcmp loop). This sits on the resolver hot path (`loaderFromPath` per -/// import) and on CLI startup (`arguments::parse`, `run_command`). Same -/// pattern as `clap::find_param` (12577e958d71). Unit struct keeps the -/// `DEFAULT_LOADERS.get(ext)` / `.contains_key(ext)` call-site shape so -/// callers in `run_command.rs` / `NodeModuleModule.rs` / `arguments.rs` / -/// `init_command.rs` / `multi_run.rs` are untouched. pub struct DefaultLoaders; pub static DEFAULT_LOADERS: DefaultLoaders = DefaultLoaders; @@ -905,12 +805,6 @@ impl ESMConditions { } } -// D042: canonical `jsx::{Runtime, ImportSource, Pragma, RuntimeDevelopmentPair, -// RUNTIME_MAP, defaults, Defaults, pragma}` lives in `bun_options_types::jsx`. -// Re-exported so existing `crate::options::jsx::*` / `options_impl::jsx::*` -// paths resolve unchanged. The three field-wise `From<>` bridges to the -// resolver-/parser-side nominal copies are gone — all three crates now share -// the single `bun_options_types::jsx::Pragma` type. pub use bun_options_types::jsx; pub use jsx as JSX; @@ -930,12 +824,6 @@ pub use default_user_defines as DefaultUserDefines; pub fn defines_from_transform_options( log: &mut bun_ast::Log, - // PERF(port): borrowed, not owned — the caller (`load_defines`) holds - // `transform_options` behind an `Arc`, so taking the `StringMap` by value - // forced a full deep clone of the `--define` map *every* VM init even though - // each value gets cloned again below on insert. Reading it through `&` keeps - // the per-value clone (the owned `RawDefines` map needs `Box<[u8]>`s) but - // drops the redundant outer `keys.clone() + values.clone()`. maybe_input_define: Option<&api::StringMap>, target: Target, env_loader: Option<&mut DotEnv::Loader>, @@ -1122,10 +1010,6 @@ impl ResolveFileExtensions { } } -/// Convert a static `&[&[u8]]` default into an owned `Box<[Box<[u8]>]>`. -/// PERF(port): the Zig kept these as borrowed `[]const string`; we own them so -/// user-provided lists (e.g. `transform.extension_order`) can be stored without -/// `Box::leak` (PORTING.md §Forbidden patterns). #[inline] pub(crate) fn owned_string_list(s: &[&[u8]]) -> Box<[Box<[u8]>]> { s.iter().map(|b| Box::<[u8]>::from(*b)).collect() @@ -1286,13 +1170,6 @@ pub struct BundleOptions<'a> { pub banner: Cow<'static, [u8]>, pub define: Box, pub drop: Box<[Box<[u8]>]>, - /// Set of enabled feature flags for dead-code elimination via `import { feature } from "bun:bundle"`. - /// Initialized once from the CLI --feature flags. - /// - /// Zig: `*const bun.StringSet = &Runtime.Features.empty_bundler_feature_flags`. - /// `None` ≡ the static empty set; `Some` is the owned `Box` returned by - /// `Runtime::Features::init_bundler_feature_flags` (freed on Drop, matching - /// options.zig:1888-1892 which frees iff distinct from the static empty set). pub bundler_feature_flags: Option>, pub loaders: LoaderHashTable, pub resolve_dir: Cow<'static, [u8]>, @@ -1332,11 +1209,6 @@ pub struct BundleOptions<'a> { pub tsconfig_override: Option>, pub target: Target, pub main_fields: Box<[Box<[u8]>]>, - /// TODO: remove this in favor accessing bundler.log - /// PORT NOTE: raw `*mut` (not `&'a mut`) — Zig aliases the same `*Log` - /// into `Transpiler.log` / `Resolver.log` / `Linker.log`. A stored - /// `&'a mut` here would assert uniqueness for `'a` and make every access - /// through those sibling raw pointers UB under stacked borrows. pub log: *mut bun_ast::Log, pub external: ExternalModules, pub allow_unresolved: AllowUnresolved, @@ -1354,12 +1226,6 @@ pub struct BundleOptions<'a> { pub import_path_format: ImportPathFormat, pub defines_loaded: bool, pub env: Env, - /// The raw API struct as passed to `from_api`. Kept around because a - /// handful of places (jsx auto-detect, resolver `main_fields_is_default`, - /// `configure_defines`, runtime VM/server config) re-read the original - /// user-supplied flags after projection. `Arc` so `for_worker` is a - /// pointer-clone instead of a deep clone of the (large) peechy struct — - /// workers never mutate it. pub transform_options: std::sync::Arc, pub polyfill_node_globals: bool, pub transform_only: bool, @@ -1382,13 +1248,6 @@ pub struct BundleOptions<'a> { pub global_cache: GlobalCache, pub prefer_offline_install: bool, pub prefer_latest_install: bool, - /// Spec `options.zig:1753`: `?*const Api.BunInstall`. Stored as a raw - /// `NonNull` (not `Option<&'a _>`) because every CLI caller borrows the - /// process-lifetime `ctx.install: Box` whose lifetime is - /// unrelated to `'a`; a typed reference forced an `unsafe { &*(p as *const _) }` - /// lifetime-extension cast at every call site (PORTING.md §Forbidden). - /// The sole consumer (`PackageManager::init_with_runtime` via the resolver's - /// `BundleOptions.install`) only reads through it. pub install: Option>, pub inlining: bool, @@ -1430,41 +1289,19 @@ pub struct BundleOptions<'a> { pub serve_plugins: Option]>>, pub bunfig_path: Box<[u8]>, - /// This is a list of packages which even when require() is used, we will - /// instead convert to ESM import statements. - /// - /// This is not normally a safe transformation. - /// - /// So we have a list of packages which we know are safe to do this with. pub unwrap_commonjs_packages: &'static [&'static [u8]], pub supports_multiple_outputs: bool, - /// This is set by the process environment, which is used to override the - /// JSX configuration. When this is unspecified, the tsconfig.json is used - /// to determine if a development jsx-runtime is used (by going between - /// "react-jsx" or "react-jsx-dev-runtime") pub force_node_env: ForceNodeEnv, pub ignore_module_resolution_errors: bool, - /// Package names whose barrel files should be optimized. - /// When set, barrel files from these packages will only load submodules - /// that are actually imported. Also, any file with sideEffects: false - /// in its package.json is automatically a barrel candidate. pub optimize_imports: Option<&'a StringSet>, } -// B-3 UNIFIED: was a local dup of `bun_options_types::bundle_enums::ForceNodeEnv` -// (resolver carried a second FORWARD_DECL copy). Canonical type now lives in -// bun_options_types; re-exported here so `options::ForceNodeEnv` call sites in -// bundle_v2.rs / transpiler.rs are unchanged. pub use bun_options_types::ForceNodeEnv; -/// Manual deep clone for `MacroRemap` (= `StringArrayHashMap>>`). -/// The inner map's `clone()` is an inherent fallible method (not `impl Clone`), -/// so the outer `StringArrayHashMap::::clone()` bound is unmet — -/// rebuild entrywise instead. fn clone_macro_remap(src: &MacroRemap) -> MacroRemap { let mut out = MacroRemap::default(); for (k, v) in src.iter() { @@ -1485,16 +1322,6 @@ impl<'a> BundleOptions<'a> { } } - /// Per-worker deep clone — replaces the prior bitwise - /// `ptr::copy_nonoverlapping` of the parent `Transpiler` (which aliased - /// every `Box`/`Vec` in here between parent and worker; reassigning any of - /// them on the worker dropped the parent's allocation). Every owned field - /// is `Clone`d; raw-pointer / `Copy` / `&'a` fields copy directly. - /// - /// PERF(port): Zig's `transpiler.* = from.*` is a shallow struct copy - /// (slices alias the parent's arena). The Rust port owns these as `Box`, - /// so a per-worker clone allocates. Profile if it shows up on a hot path; - /// the hot fields (`define`, `loaders`, `conditions`) are O(dozens) entries. pub fn for_worker(&self) -> BundleOptions<'a> { debug_assert!( self.defines_loaded, @@ -1700,25 +1527,9 @@ impl<'a> BundleOptions<'a> { arena: &bun_alloc::Arena, loader_: Option<&mut DotEnv::Loader>, ) -> Result<(), bun_core::Error> { - // PORT NOTE: spec `loadDefines(..., env: ?*const options.Env)` had its - // sole caller pass `&this.options.env` (transpiler.zig:334). Forwarding - // that as `Option<&Env>` forced the caller into an aliased-`&mut` UB - // raw-pointer dance under Stacked Borrows. Dropped the param and read - // `&self.env` here instead — disjoint from the `self.define` / - // `self.defines_loaded` writes below, so borrowck splits it cleanly. - // - // The `arena` param is kept (spec passes `this.arena`, i.e. - // `bun.default_allocator`) because `DefineData::from_input` JSON-parses - // each define value into `EString` nodes whose `.data` slices borrow - // the arena — they must outlive `self.define`, not just this call. if self.defines_loaded { return Ok(()); } - // PERF(port): the spec uses borrowed static literals for the three - // constant cases; only the env-loader case needs an owned copy (it has - // to outlive the `&mut loader_` we pass below, so it can't stay a borrow - // into the loader). `Cow` keeps the literals zero-alloc — matters because - // every VM with no `NODE_ENV` in its env hits the `"development"` arm. let node_env: Option> = 'node_env: { if let Some(e) = loader_.as_deref() { if let Some(env_) = e.get_node_env() { @@ -1766,10 +1577,6 @@ impl<'a> BundleOptions<'a> { ) -> Result, bun_core::Error> { use core::sync::atomic::Ordering; - // Keep `transform` behind an `Arc` so stashing it in `transform_options` - // is a refcount bump rather than a deep clone of ~30 heap fields, and so - // dropping the local at the end of this fn is a refcount decrement rather - // than recursive `drop_in_place` over every `Box<[u8]>`/`Vec`. let transform = std::sync::Arc::new(transform); let target = ::from_api(transform.target); @@ -1914,10 +1721,6 @@ impl<'a> BundleOptions<'a> { opts.env.disable_default_env_files = transform.disable_default_env_files; if let Some(origin) = &transform.origin { - // PORT NOTE: ownership — `URL<'_>` borrows its input. The Zig - // `URL.parse` borrowed `transform.origin` (a sibling of - // `opts.transform_options`); here `OwnedURL` owns the href and - // callers borrow via `.url()`. opts.origin = bun_url::OwnedURL::from_href(origin.clone()); } @@ -1939,10 +1742,6 @@ impl<'a> BundleOptions<'a> { } { - // conditions: - // 1. defaults - // 2. node-addons - // 3. user conditions opts.conditions = ESMConditions::init( Target::default_conditions_map()[opts.target], transform.allow_addons.unwrap_or(true), @@ -2056,14 +1855,7 @@ impl<'a> BundleOptions<'a> { } impl Drop for BundleOptions<'_> { - fn drop(&mut self) { - // self.define dropped automatically (Box). - // - // bundler_feature_flags: Zig compared the pointer to - // `&Runtime.Features.empty_bundler_feature_flags` and freed iff distinct. - // In Rust the field is `Option>`; `None` ≡ the static - // empty set (nothing to free), `Some` drops the Box here automatically. - } + fn drop(&mut self) {} } #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -2544,10 +2336,6 @@ pub(crate) fn path_template_needs(data: &[u8], field: PlaceholderField) -> bool strings::contains(data, needle) } -// Shared body for PathTemplate::print / PathTemplateConst::print (D064). -// PORT NOTE: Zig `format(self, comptime _, _, writer: anytype)` writes raw path bytes via -// writer.writeAll; mapped to a byte-writer free fn (not `core::fmt::Display`) per -// PORTING.md "(comptime X: type, arg: X) writer → &mut impl bun_io::Write (bytes)". pub(crate) fn path_template_print( writer: &mut W, data: &[u8], @@ -2757,10 +2545,6 @@ impl PlaceholderConst { } impl PathTemplateConst { - /// Byte-writer form mirroring [`PathTemplate::print`] (Zig - /// `PathTemplate.format`). Kept as an inherent method so callers writing - /// to `Vec` via `write!(.., "{}", template)` resolve through the - /// blanket [`core::fmt::Display`] impl below. pub fn print(&self, writer: &mut W) -> bun_io::Result<()> { path_template_print( writer, @@ -2791,10 +2575,6 @@ impl core::fmt::Display for PathTemplateConst { impl core::fmt::Display for PathTemplate { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { - // PORT NOTE: Zig `PathTemplate.format` writes raw path bytes via - // `writer.writeAll`; route through a Vec then emit via `write_str` - // (paths are UTF-8 in practice; lossy fallback mirrors `bstr::BStr` - // Display semantics). Mirrors `PathTemplateConst` Display above. let mut buf = Vec::::new(); self.print(&mut buf).map_err(|_| core::fmt::Error)?; f.write_str(&String::from_utf8_lossy(&buf)) diff --git a/src/bundler/transpiler.rs b/src/bundler/transpiler.rs index e449029afed..8060080462a 100644 --- a/src/bundler/transpiler.rs +++ b/src/bundler/transpiler.rs @@ -18,16 +18,8 @@ use crate::options; /// Port of `transpiler.zig:ResolveResults` — keyed by source path hash. pub(crate) type ResolveResults = HashMap; -/// Port of `transpiler.zig:ResolveQueue` — `std.fifo.LinearFifo(resolver.Result, .Dynamic)`. -// PORT NOTE: `bun_collections::LinearFifo>` would be exact, -// but `DynamicBuffer` isn't re-exported from `bun_collections` yet. `VecDeque` -// is structurally equivalent (growable ring buffer); swap once the re-export lands. pub(crate) type ResolveQueue = std::collections::VecDeque; -/// Spec `JSGlobalObject.BunPluginTarget` (JSGlobalObject.zig:265). Defined at -/// this tier (lowest crate that needs to name it) and re-exported from -/// `bun_jsc::BunPluginTarget` so there is exactly one enum (no bridge between -/// mirror types). #[repr(u8)] #[derive(Copy, Clone, Eq, PartialEq, Debug)] pub enum BunPluginTarget { @@ -36,21 +28,8 @@ pub enum BunPluginTarget { Browser = 2, } -// Crosses FFI by-value to `JSBundlerPlugin__create` / `Bun__runOn*Plugins` -// (C++: `typedef uint8_t BunPluginTarget`, `headers-handwritten.h`). NB: the -// C++ header's *named* constants (`BunPluginTargetBrowser = 1`, `Node = 2`) -// disagree with Zig `JSGlobalObject.zig:265` (`node = 1`, `browser = 2`); Rust -// matches the Zig spec. The width (`u8`) is what matters at the ABI. bun_core::assert_ffi_discr!(BunPluginTarget, u8; Bun = 0, Node = 1, Browser = 2); -/// Spec PluginRunner.zig:34 `onResolve` — the JSC-aware resolve hook. -/// -/// The body calls `JSGlobalObject.runOnResolvePlugins`, so it cannot be -/// defined at this tier (`bun_jsc` depends on this crate). `bun_jsc` provides -/// the concrete `PluginRunner { global_object: *mut JSGlobalObject }` and -/// implements this trait; `Linker.plugin_runner` holds it as -/// `*mut dyn PluginResolver` so the linker stays JSC-free while the body lives -/// in exactly one place (no fn-ptr field, no `*mut c_void` erasure). pub trait PluginResolver { fn on_resolve( &self, @@ -62,10 +41,6 @@ pub trait PluginResolver { ) -> Result>, bun_core::Error>; } -/// Spec PluginRunner.zig — namespace for the static byte-level helpers -/// (`extractNamespace` / `couldBePlugin`). The stateful struct (with -/// `global_object`) lives in `bun_jsc::PluginRunner` where `JSGlobalObject` is -/// nameable; only the JSC-free helpers stay at this tier. pub struct PluginRunner; impl PluginRunner { @@ -115,12 +90,6 @@ pub const fn default_macro_js_value() -> MacroJSCtx { MacroJSCtx::ZERO } -/// This structure was the JavaScript transpiler before bundle_v2 was written. It -/// now acts mostly as a configuration object, but it also contains stateful -/// logic around logging errors (`log`) and module resolution (`resolve_queue`). -/// -/// This object is not exclusive to bundle_v2/Bun.build; one of these is stored -/// on every VM so that the options can be used for transpilation. pub struct Transpiler<'a> { pub options: options::BundleOptions<'a>, // PORT NOTE: raw ptr — Zig aliased the same `*Log` into `linker.log` and @@ -158,11 +127,6 @@ pub struct Transpiler<'a> { impl<'a> Transpiler<'a> { pub const IS_CACHE_ENABLED: bool = false; - /// Port of `transpiler.zig:95 setLog`. - /// - /// PORT NOTE: takes `*mut Log` (not `&'a mut`) because Zig aliased the same - /// `*Log` into `linker.log` / `resolver.log`; the un-gated struct field is - /// already a raw pointer for that reason. pub fn set_log(&mut self, log: *mut bun_ast::Log) { self.log = log; self.linker.log = log; @@ -196,12 +160,6 @@ impl<'a> Transpiler<'a> { /// `&'static mut`. Owned `Transpiler`s from [`Self::for_worker`] must use /// normal `Drop` instead. pub unsafe fn deinit(&mut self) { - // The lazily-created `Box` was - // intentionally process-lifetime under Zig (`default_allocator`), but - // worker VMs run `destroy()` on thread exit and would otherwise strand - // one box per worker. The box only owns a `MacroMap` and an optional - // `bun_alloc::Arena` — no JSC handles — so freeing it from either - // worker section-5 teardown or main-thread `global_exit` is safe. if let Some(ctx) = self.macro_context.take() { ctx.deinit(); } @@ -227,12 +185,6 @@ impl<'a> Transpiler<'a> { unsafe { &*self.fs } } - /// Mutable reborrow of the `Fs::FileSystem` singleton. The returned - /// lifetime is **decoupled** from `&self` so callers can pass it alongside - /// disjoint `&mut self.resolver` borrows (see `read_file_with_allocator` - /// call sites). Callers must not hold the result across any other - /// `fs()`/`fs_mut()` reborrow or across a resolver call that itself - /// dereferences the shared singleton mutably. #[inline] #[allow(clippy::mut_from_ref)] pub fn fs_mut<'r>(&self) -> &'r mut Fs::FileSystem { @@ -256,12 +208,6 @@ impl<'a> Transpiler<'a> { unsafe { &*self.log } } - /// Reborrow the shared `Log`. The `&self` receiver lets call sites pass - /// other `self.*` fields as arguments without a borrow-checker conflict; - /// callers must not hold two results live at once, nor hold a result - /// across a `self.{resolver,linker}` call that itself writes to the - /// aliased `*mut Log` (see field PORT NOTE — same allocation is threaded - /// into `linker.log` / `resolver.log`). #[inline] #[allow(clippy::mut_from_ref)] pub fn log_mut<'r>(&self) -> &'r mut bun_ast::Log { @@ -286,10 +232,6 @@ impl<'a> Transpiler<'a> { unsafe { &*self.env } } - /// Reborrow the `DotEnv::Loader`. Returned lifetime is decoupled from - /// `&self` so call sites in `configure_defines` / `run_env_loader` can - /// hold it across disjoint `&mut self.options` / `&mut self.resolver` - /// borrows (matching Zig's free `this.env.*` access). #[inline] #[allow(clippy::mut_from_ref)] pub fn env_mut(&self) -> &'a mut dot_env::Loader<'a> { @@ -387,10 +329,6 @@ impl<'a> Transpiler<'a> { } } - /// Wire the self-referential `linker` back-pointers and `macro_context` - /// after this `Transpiler` has reached its final address (post-move into - /// `WorkerData` / arena slot). Port of the post-copy fixups in - /// ThreadPool.zig:309-313 / bundle_v2.zig:228-232. pub fn wire_after_move(&mut self) { // Spec: `transpiler.setLog(log)` already ran inside `for_worker` via // direct field init; re-thread into `options.log` / `resolver.log` / @@ -399,12 +337,6 @@ impl<'a> Transpiler<'a> { self.options.log = log; self.resolver.log = core::ptr::NonNull::new(log).expect("wire_after_move: log is non-null"); self.resolver.fs = self.fs; - // Spec ThreadPool.zig:310 `transpiler.linker.resolver = &transpiler.resolver`. - // Only reseat the back-pointers — do NOT `Linker::init` here: that - // would clobber `import_counter` / `plugin_runner` / - // `tagged_resolutions` / `any_needs_runtime`, which the spec - // preserves across the move (bundle_v2.zig:230 only assigns - // `linker.resolver`). self.linker.reseat_self_refs( log, core::ptr::addr_of_mut!(self.resolve_queue), @@ -427,21 +359,6 @@ impl<'a> Transpiler<'a> { pub fn reset_store(&self) { bun_ast::Expr::data_store_reset(); bun_ast::Stmt::data_store_reset(); - // Side-arena for `AstAlloc` (e.g. `Vec` inside arena - // `E::Object`) — same lifetime as the block-store. Only the bundler - // resets it; install/`--define` (which also use the block-store) hold - // `StoreRef`s across reset, see `store_ast_alloc_heap` doc. Must mirror - // the block-store's FULL early-return gate (`DISABLE_RESET || - // memory_allocator() != null`, Stmt.rs `Store::reset`): macro - // evaluation pins the store via `DisableStoreReset`, and - // `ParseTask`/`RuntimeTranspilerStore` call this from inside an - // `ASTMemoryAllocator::Scope` (where the block-store reset is a no-op - // and the active `AstAlloc` state belongs to that scope, NOT the - // side module). If we ran `store_ast_alloc_heap::reset()` there it - // would bulk-free whatever side-state buffers earlier main-thread - // transpiles left while `--define`/install still hold `StoreRef`s - // into them (and the side module's debug assert that *its* state is - // the installed one would fire). if !bun_ast::stmt::data::Store::disable_reset() && bun_ast::stmt::data::Store::memory_allocator().is_null() { @@ -498,12 +415,6 @@ impl<'a> Transpiler<'a> { Err(err) => { let mut cache_bust_buf = bun_paths::PathBuffer::uninit(); - // Bust directory cache and try again - // PORT NOTE: reshaped for borrowck — Zig's labelled-block - // returned a slice that aliases either `entry_point` (via - // `dirname`) or `cache_bust_buf`. Rust can't unify the two - // disjoint mutable borrows of `cache_bust_buf` across `break`, - // so compute `busted` directly instead. let busted: bool = 'name: { if bun_paths::is_absolute(entry_point) { let dir = bun_paths::resolve_path::dirname::( @@ -578,17 +489,6 @@ impl<'a> Transpiler<'a> { let env_loader = self.env_mut(); let mut is_production = env_loader.is_production(); - // PORT NOTE: spec (`transpiler.zig:314`) eagerly did - // `Expr.Data.Store.create()` / `Stmt.Data.Store.create()` plus a - // `defer Store.reset()` here, purely so `defines.zig`'s `parse_env_json` - // had a thread-local AST store to build `E::String` nodes in. That work - // is now done lazily inside `DefineData::parse`, only on the JSON-parse - // slow path — the common case (`bun run` with no user `--define`) - // resolves every define through the literal fast path and never - // allocates an AST store. A store lazily created on the slow path is - // reclaimed by the next `Store::begin()` (every subsequent file parse), - // so the dropped `defer reset` is a no-op in practice. - // Spec passed `&this.options.env` as a separate arg; `load_defines` now // reads `&self.env` internally so the disjoint borrow is resolved // inside the `&mut self` scope without `unsafe`. @@ -619,12 +519,6 @@ impl<'a> Transpiler<'a> { Ok(()) } - /// Port of the spec idiom `out.resolver.opts = out.options` (transpiler.zig - /// passes the same `BundleOptions` value to both struct fields; bake.zig:788 - /// re-assigns after mutating `out.options`). In the Rust port the resolver - /// crate carries a FORWARD_DECL subset of `BundleOptions`, so re-project - /// rather than `Clone`. Called after `init_transpiler_with_options` mutates - /// `self.options` so the resolver sees the same conditions/target/public_path. pub fn sync_resolver_opts(&mut self) { self.resolver.opts = resolver_bundle_options_subset(&self.options); } @@ -634,12 +528,6 @@ impl<'a> Transpiler<'a> { #[inline(never)] pub fn dump_environment_variables(&self) { use bun_js_printer::{Encoding, write_json_string}; - // PORT NOTE: spec uses `std.json.Stringify` (`.whitespace = .indent_2`) - // to dump `env.map.*`. The Rust `bun_dotenv::Map` doesn't impl - // `serde::Serialize`, so iterate and emit the object by hand. Keys and - // values go through `write_json_string` (the same escaper the printer - // uses for metafile/HTML-manifest JSON) so `"` / `\` / control bytes - // are escaped exactly as `std.json.Stringify` does. bun_core::Output::flush(); let env = self.env_mut(); let w = bun_core::Output::writer(); @@ -661,13 +549,6 @@ impl<'a> Transpiler<'a> { } } -// ══════════════════════════════════════════════════════════════════════════ -// `configure_linker*` / `run_env_loader` — used by -// `RunCommand::configure_env_for_run` (runtime/cli/run_command.rs:527), -// `bun_install::configure_env_for_run`, `JSBundleCompletionTask`, -// `JSTranspiler`, and `bun.js.rs:: bun_main_shell_entry`. -// ══════════════════════════════════════════════════════════════════════════ - use bun_resolver::tsconfig_json::TSConfigJSON; /// D042: resolver-side and bundler-side `jsx::Pragma` are now the SAME @@ -690,18 +571,6 @@ fn merge_tsconfig_jsx_into(tsconfig: &TSConfigJSON, out: &mut crate::options_imp impl<'a> Transpiler<'a> { /// Port of `transpiler.zig:233 configureLinkerWithAutoJSX`. pub fn configure_linker_with_auto_jsx(&mut self, auto_jsx: bool) { - // PORT NOTE: `Linker::init` dropped its `arena` arg (linker.rs:172 - // — global mimalloc). Zig stored borrowed `*T` into the linker; the - // un-gated `crate::linker::Linker` mirrors that with raw pointers so - // `&mut self.options` etc. coerce directly. Self-reference is - // load-bearing — `linker.link()` reads back through these into the - // owning `Transpiler` — hence raw `*mut`, not `&'a mut` (would alias - // `&mut self` on every call). - // PORT NOTE: `.cast()` on the `options`/`resolver` pointers erases the - // `<'a>` lifetime parameter — `Linker` stores them as - // `*mut BundleOptions` / `*mut Resolver` with an (implicit) distinct - // lifetime. Raw-pointer storage is the Zig contract; the linker never - // outlives its owning `Transpiler<'a>`. self.linker = crate::linker::Linker::init( self.log, core::ptr::addr_of_mut!(self.resolve_queue), @@ -745,29 +614,14 @@ impl<'a> Transpiler<'a> { DotEnvBehavior::prefix | DotEnvBehavior::load_all | DotEnvBehavior::load_all_without_inlining => { - // Process always has highest priority. Load process env vars - // unconditionally before attempting directory traversal, so - // that inherited environment variables are always available - // even when a parent directory is not readable. let was_production = self.options.production; env.load_process()?; let has_production_env = env.is_production(); if !was_production && has_production_env { self.options.set_production(true); - // Spec transpiler.zig:275 `this.resolver.opts.setProduction(true)`. - // The resolver's FORWARD_DECL `BundleOptions` now exposes - // `set_production` (flips `production` + `jsx.development` - // and self-guards on `force_node_env`; resolver/lib.rs). - // Call it directly so resolver-side production gating - // (conditional-export `"production"` matching) stays in - // sync, instead of the partial single-field write. self.resolver.opts.set_production(true); } - // Load the project root for .env file discovery. If the cwd - // (or a parent) is unreadable, readDirInfo may return null; - // bail out of .env file loading in that case, but process - // env vars were already loaded above. let top_level_dir = self.fs().top_level_dir; let dir_info = match self.resolver.read_dir_info(top_level_dir) { Ok(Some(d)) => d, @@ -789,10 +643,6 @@ impl<'a> Transpiler<'a> { // sole `&mut` for the call. let dir: &mut bun_resolver::fs::DirEntry = unsafe { &mut *dir }; - // PORT NOTE: `Env.files: Box<[Box<[u8]>]>` but `Loader::load` - // wants `&[&[u8]]`. Re-borrow into a small Vec; the explicit - // `--env-file` list is bounded (CLI args), not hot-path. - // PERF(port): one tiny alloc — Zig passed the slice directly. let env_files: Vec<&[u8]> = self.options.env.files.iter().map(|f| &**f).collect(); let suffix = if self.options.is_test() || env.is_test() { @@ -822,15 +672,6 @@ impl<'a> Transpiler<'a> { } } -// ══════════════════════════════════════════════════════════════════════════ -// `ParseResult` / `AlreadyBundled` / `ParseOptions` + `Transpiler::parse*` -// — used by `ModuleLoader::transpile_source_code` (jsc_hooks.rs) and -// `AsyncModule` / `JSTranspiler`. The body of -// `parse_maybe_return_file_only_allow_shared_buffer` does the source-load -// step (virtual / client-entry / `node:` fallback) and dispatches to the -// per-loader transpile branches. -// ══════════════════════════════════════════════════════════════════════════ - use crate::bun_node_fallbacks as NodeFallbackModules; use crate::entry_points as EntryPoints; use bun_ast::RuntimeTranspilerCache; @@ -872,10 +713,6 @@ impl AlreadyBundled { } } -/// Port of `transpiler.zig:ParseResult`. -// PORT NOTE: lifetime-free — `runtime_transpiler_cache` is a raw pointer (Zig -// `?*RuntimeTranspilerCache`) so `AsyncModule.parse_result` / `JSTranspiler` -// can store this by value without threading a borrow lifetime. pub struct ParseResult<'a> { pub source: bun_ast::Source, pub loader: options::Loader, @@ -883,44 +720,19 @@ pub struct ParseResult<'a> { pub already_bundled: AlreadyBundled, pub input_fd: Option, pub empty: bool, - // PORT NOTE: Zig `_resolver.PendingResolution.List` is - // `MultiArrayList(PendingResolution)`. `PendingResolution` does not yet - // derive `MultiArrayElement` (lives in `bun_resolver`, derive macro is in - // `bun_collections_macros` — orphan rules forbid impl-ing it here), so the - // SoA `len()`/column accessors aren't reachable. Use AoS `Vec` for now; - // `is_pending_import` only scans `import_record_id`, so the layout - // difference is observable only as a SoA→AoS perf delta. - // TODO(b3): switch back to `MultiArrayList` once the - // derive lands upstream in `bun_resolver`. pub pending_imports: Vec, /// Zig: `?*bun.RuntimeTranspilerCache`. SAFETY: erased — bundler stores it /// and hands it back to the runtime side; never dereferenced here. pub runtime_transpiler_cache: Option>, - /// Owns the bytes that `source.contents` points into when they came from - /// `cache::Fs::read_file_with_allocator` (non-shared-buffer path) or a - /// decoded `data:` URL. `bun_ast::Source.contents` is `&'static [u8]` - /// (the AST crate's `Str` convention) so the backing must live at least as long as - /// the `ParseResult`; threading it here means it drops when the result is - /// recycled instead of leaking via `mem::forget` (PORTING.md §Forbidden). - /// `Contents::Empty`/`SharedBuffer` for the virtual-source / shared-buffer - /// paths (no-op on drop). pub source_contents_backing: resolver::cache::Contents, } impl<'a> ParseResult<'a> { - /// Spec transpiler.zig — `ParseResult` is value-copied (e.g. - /// `AsyncModule.resumeLoadingModule` reads/writes `this.parse_result` by - /// value). `Default` lets the Rust port `mem::take` it across that - /// boundary; see `AsyncModule::resume_loading_module`. pub fn empty(arena: &'a bun_alloc::Arena) -> Self { ParseResult { source: Default::default(), - // PORT NOTE: `options::Loader` has no `Default`; Zig field had no - // initializer either. `File` is the resolver's neutral fallback - // (BundleEnums.rs:353), and `Default` here exists only for - // `mem::take` in `AsyncModule::resume_loading_module`. loader: options::Loader::File, ast: bun_ast::Ast::empty_in(arena), already_bundled: Default::default(), @@ -993,11 +805,6 @@ pub struct ParseOptions<'a, 'b> { pub dont_bundle_twice: bool, pub allow_commonjs: bool, - /// `"type"` from `package.json`. Used to make sure the parser defaults - /// to CommonJS or ESM based on what the package.json says, when it - /// doesn't otherwise know from reading the source code. - /// - /// See: https://nodejs.org/api/packages.html#type pub module_type: options::ModuleType, pub runtime_transpiler_cache: Option<&'b mut RuntimeTranspilerCache>, @@ -1008,19 +815,6 @@ pub struct ParseOptions<'a, 'b> { use bun_options_types::schema::api; -// ── type unification (parse_maybe Js/Ts arm) ───────────────────────────── -// `ModuleType`, `Define`, `RuntimeTranspilerCache` are single nominal types -// shared between `bun_js_parser` and this crate (canonical defs live in the -// lower-tier crate; bundler re-exports). There are no by-value conversion -// shims — `to_parser_module_type` is an identity fn and `parse_maybe` -// threads `self.options.define` / `runtime_transpiler_cache` directly. -// -// D042 UNIFIED: `crate::options_impl::jsx::Pragma` IS -// `js_ast::parser::options::JSX::Pragma` (both re-export -// `bun_options_types::jsx::Pragma`). Only the `_None → Automatic` fold is -// applied so parser-side `== Automatic` checks in visitExpr/parseJSXElement -// keep their pre-unification semantics (parser only ever sees a resolved -// runtime; options.zig:1199 default). #[inline] pub(crate) fn to_parser_jsx_pragma( mut p: crate::options_impl::jsx::Pragma, @@ -1042,53 +836,12 @@ fn to_parser_module_type( m } -/// Spec: `fs.zig:FileSystem.init`. -/// -/// PORT NOTE: the inline `bun_resolver::fs` module exposes the `FileSystem` -/// struct + `INSTANCE`/`INSTANCE_LOADED` statics (resolver/lib.rs:120,129) but -/// not the `init` constructor (that lives in the still-gated file-backed -/// `resolver/fs.rs`). All fields are `pub` and `EntriesMap`/`Mutex` have -/// public constructors, so reproduce the singleton-init here. Matches Zig -/// semantics: first call sets `top_level_dir` (defaulting to getcwd), -/// subsequent calls return the existing instance untouched. fn init_file_system( top_level_dir: Option<&'static [u8]>, ) -> Result<*mut Fs::FileSystem, bun_core::Error> { - // Spec fs.zig:90-108 — delegate to `FileSystem.init`, which routes through - // `Implementation.init` (fs.zig:823-837): that path calls `adjustUlimit()` - // to raise RLIMIT_NOFILE and stores the returned limit in - // `file_limit`/`file_quota`, and touches the `DirEntry.EntryStore` - // singleton. The previous hand-built `Implementation { file_limit: 0, .. }` - // skipped both, so `RealFS::need_to_close_files` (resolver/lib.rs:1594) - // evaluated `!(0 > 254 && ..)` → always `true`, defeating directory-fd - // caching, and the process never had its fd ulimit raised — large module - // graphs could hit EMFILE where the spec build does not. Fs::FileSystem::init(top_level_dir) } -/// Project this crate's `options::BundleOptions<'a>` into the -/// resolver-crate FORWARD_DECL subset (`bun_resolver::options::BundleOptions`). -/// The two are nominally distinct until MOVE_DOWN to `bun_options_types` -/// unifies them (resolver/lib.rs `mod options` note). -/// -/// Spec transpiler.zig:214 passes the SAME `bundle_options` value to -/// `Resolver.init1`, so `resolver.opts` must carry user-configured -/// `--external`, `--conditions`, `--main-fields`, and the extension order. -/// Every field the resolver reads is now projected (clone of owned data, no -/// `Box::leak`); the resolver-side FORWARD_DECL types were widened to owned -/// `Box<[Box<[u8]>]>`/`StringSet`/`StringArrayHashMap` so this is a faithful -/// value copy rather than a `Default` stub. -/// -/// TODO(b3): drop this once `bun_options_types::BundleOptions` exists and both -/// crates re-export it — `Resolver::init1` will then take the canonical type -/// directly and Zig's `bundle_options` value can flow through unchanged -/// (transpiler.zig:209 passes the same `options` to both struct fields). -/// -/// `#[cold]`/`#[inline(never)]`: this is a ~100-line struct-construction blob -/// run exactly once per `Transpiler::init` (i.e. once per VM bring-up). Keeping -/// it out-of-line stops it from bloating `init`'s prologue — `init` is on the -/// startup path of every `bun`/`bunx`/`bun --bun` process, where the perf cost -/// is the icache/decode footprint of the prologue, not the cold body itself. #[cold] #[inline(never)] pub(crate) fn resolver_bundle_options_subset( @@ -1155,12 +908,6 @@ pub(crate) fn resolver_bundle_options_subset( load_package_json: src.load_package_json, load_tsconfig_json: src.load_tsconfig_json, main_field_extension_order: ropts::owned_string_list(src.main_field_extension_order), - // Spec resolver.zig `auto_main` compares the pointer of - // `opts.main_fields` against the per-target default; with owned - // storage that pointer test can't hold, so project the predicate as a - // bool: it's "default" iff the user did not pass `--main-fields` - // (`from_api` overwrites `main_fields` only when - // `transform.main_fields` is non-empty — options.rs:2231). main_fields: src.main_fields.clone(), main_fields_is_default: src.transform_options.main_fields.is_empty(), mark_builtins_as_external: src.mark_builtins_as_external, @@ -1171,10 +918,6 @@ pub(crate) fn resolver_bundle_options_subset( tsconfig_override: src.tsconfig_override.clone(), production: src.production, force_node_env: src.force_node_env, - // FORWARD_DECL: bundler-only fields read via `c.resolver.opts` in - // `linker_context/*` (Zig stores the full `BundleOptions` on the - // resolver). Project them so the linker sees the same values it would - // have read off the spec's shared struct. output_dir: src.output_dir.clone(), root_dir: src.root_dir.clone(), public_path: src.public_path.clone(), @@ -1186,16 +929,6 @@ pub(crate) fn resolver_bundle_options_subset( } impl<'a> Transpiler<'a> { - /// Port of `transpiler.zig:Transpiler.init`. - /// - /// Called by [`init_runtime_state`](../runtime/jsc_hooks.rs) (spec - /// `VirtualMachine.zig:1241`) to write `vm.transpiler`. Builds on: - /// * [`options::BundleOptions::from_api`] — `bun_bundler::options` - /// * [`Resolver::init1`] — `bun_resolver` - /// - /// PORT NOTE: `log` / `env_loader_` are raw pointers (not `&'a mut`) to - /// match the un-gated struct field types — Zig aliased the same `*Log` - /// into `linker.log` / `resolver.log` (see `set_log`). pub fn init( arena: &'a Arena, log: *mut bun_ast::Log, @@ -1209,15 +942,6 @@ impl<'a> Transpiler<'a> { Ok(unsafe { slot.assume_init() }) } - /// In-place sibling of [`Self::init`]: builds the `Transpiler` directly into - /// `dst` rather than returning it by value, so callers that already own its - /// final storage — most importantly `VirtualMachine.transpiler`, written by - /// [`init_runtime_state`](../runtime/jsc_hooks.rs) once per VM — avoid the - /// multi-KB `stack temporary → return slot → final home` double `memcpy`. - /// - /// On `Ok(())`, every field of `dst` is initialised. On `Err`, `dst` is - /// untouched (all fallible work happens before the first field write), so the - /// caller must not `assume_init` it. pub fn init_in_place( dst: &mut core::mem::MaybeUninit>, arena: &'a Arena, @@ -1233,34 +957,6 @@ impl<'a> Transpiler<'a> { // TODO(port): narrow error set bun_ast::expr::data::Store::create(); bun_ast::stmt::data::Store::create(); - // These two `create()`s are eager (not deferred to the first `parse()`) - // because option setup below needs the AST stores *unconditionally*: - // `from_api` → `defines_from_transform_options` always materialises at - // least `process.env.NODE_ENV` via `parse_env_json`, whose `E::String` - // payload lands in the thread-local Expr store (then a `StoreResetGuard` - // resets it — which `expect()`s the store exists). So there is no - // "transpile nothing" spawn that skips them. They are *cheap*, though: - // `Store::init()` only allocates the small `Store` header — the first - // `~BLOCK_SIZE` `Block` buffer is malloc'd lazily on the first - // `append()` (`ast/new_store.rs`), so a store that is `create()`d but - // never written to here (the `Stmt` store — `load_defines` only emits - // `E::String` expression nodes) costs nothing beyond that header. - // `store_ast_alloc_heap::enter()` is NOT called here: `--define` - // object-literal JSON is parsed below (during option setup) and the - // bundler holds its `StoreRef` across every `reset_store()`, - // so its embedded `Vec` must stay on the global heap. - // `reset_store()`'s first call lazily `enter()`s (the side arena's - // `reset()` branches to `enter()` on null ARENA), so per-file ASTs - // *do* get the side arena from the first parsed file onward. - - // PORT NOTE: `FileSystem::init` wants `&'static [u8]`; Zig passed a - // borrowed slice (transpiler.zig:179). Intern via `DirnameStore` - // (the same path `FileSystem::init` already uses for the - // `None`/getcwd case — fs.rs:222) so the cwd lives in the - // process-lifetime BSS string store without `Box::leak`. PORTING.md - // §Forbidden bars `Box::leak` even for singletons; on subsequent - // per-worker `Transpiler::init` calls the previous leak was discarded - // (`FileSystem::init` only stores `top_level_dir` on first call). let cwd: Option<&'static [u8]> = match opts.absolute_working_dir.as_deref() { Some(s) => Some(Fs::DirnameStore::instance().append_slice(s)?), None => None, @@ -1272,17 +968,6 @@ impl<'a> Transpiler<'a> { None => match dot_env::instance() { Some(l) => l, None => { - // PORTING.md §Forbidden bars `Box::leak` even for - // process-lifetime singletons. `bun_dotenv::INSTANCE` is an - // `AtomicPtr>` and `Loader` borrows - // an unbounded `&mut Map`, so a `OnceLock` here can't - // be expressed without changing `bun_dotenv`'s API. - // Transfer ownership of both allocations into the global - // singleton via `heap::alloc` (the AtomicPtr becomes the - // owner; matches `MiniEventLoop::init_global`). - // TODO(port): replace with a `OnceLock`-backed - // `bun_dotenv::instance_or_init()` accessor once - // `bun_dotenv` grows one (PORTING.md §Concurrency). let map: *mut dot_env::Map = bun_core::heap::into_raw(Box::new(dot_env::Map::init())); // SAFETY: `map` is a fresh heap allocation with no other @@ -1305,11 +990,6 @@ impl<'a> Transpiler<'a> { (*env_loader).quiet = !log_nn.as_ref().level.at_least(bun_ast::Level::Info); } - // var pool = try arena.create(ThreadPool); - // try pool.init(ThreadPool.InitConfig{ - // .arena = arena, - // }); - // `log` stays raw — `from_api` stores it in `BundleOptions.log: *mut` // and the same pointer is aliased into `Resolver::init1` / `Linker` // / the struct field below (Zig aliased `*Log` everywhere). No `&'a @@ -1320,24 +1000,11 @@ impl<'a> Transpiler<'a> { // borrow for the duration of `from_api`. let bundle_options = options::BundleOptions::from_api(unsafe { &mut *fs }, log, opts)?; - // `Resolver.opts` is the resolver-crate subset - // (`bun_resolver::options::BundleOptions`), nominally distinct from this - // crate's `options::BundleOptions<'a>`. Project the fields the resolver - // reads; the rest stay at `Default` until MOVE_DOWN to - // `bun_options_types` unifies the two (resolver/lib.rs:2773 note). let resolver_opts = resolver_bundle_options_subset(&bundle_options); let outbase = bundle_options.output_dir.clone(); let resolve_results = Box::new(ResolveResults::default()); - // Construct directly into the caller-owned storage instead of building a - // stack temporary and returning it. All fallible work is done; every - // field below is written exactly once. `Linker::init` gets null - // back-pointers (Zig used `undefined`) — `core::mem::zeroed()` is NOT a - // valid analogue (`Linker.hashed_filenames: HashMap` carries a `NonNull` - // niche, so all-zeroes is instant UB); the value fields get their proper - // defaults and `configure_linker_with_auto_jsx` overwrites the - // self-referential pointers before any deref. let p = dst.as_mut_ptr(); // SAFETY: `dst` is an exclusively-borrowed, currently-uninitialised // `MaybeUninit`; each `write` initialises a distinct field @@ -1407,10 +1074,6 @@ impl<'a> Transpiler<'a> { >( &mut self, mut this_parse: ParseOptions<'a, '_>, - // TODO(port): Zig `anytype` + `@hasField(.., "source")` — only ever - // called with `?*EntryPoints.ClientEntryPoint` in this file. If other - // callers pass a different type, introduce a `ClientEntryPointLike` - // trait with `fn source() -> Option<&Source>`. client_entry_point_: Option<&mut EntryPoints::ClientEntryPoint>, ) -> Option> { let arena = this_parse.arena; @@ -1419,19 +1082,9 @@ impl<'a> Transpiler<'a> { let file_hash = this_parse.file_hash; let path = this_parse.path; let loader = this_parse.loader; - // Every `Log` access in this function body goes through the `log` - // binding below (the resolver fs/js caches reached via - // `self.resolver.caches.*` do not touch `resolver.log`), so this is - // the unique live `&mut Log` for the duration of the parse. let log: &mut bun_ast::Log = self.log_mut(); let mut input_fd: Option = None; - // Owns the heap allocation backing `source.contents` for the - // non-shared-buffer file-read and `data:` URL paths. Threaded into the - // returned `ParseResult` so it drops with the result instead of being - // `mem::forget`-ed (PORTING.md §Forbidden patterns). For virtual / - // client-entry / `node:` / shared-buffer paths it stays `Empty` - // (`Drop` is a no-op). let mut source_backing: resolver::cache::Contents = resolver::cache::Contents::Empty; // PORT NOTE: Zig `&brk: { ... }` took the address of a temporary; Rust @@ -1454,10 +1107,6 @@ impl<'a> Transpiler<'a> { break 'brk bun_ast::Source::init_path_string(path.text, b""); } - // Spec transpiler.zig:826-835. The decoded body is owned in - // `source_backing` (below) so `source.contents` re-borrows it - // without leaking; never falls through to `read_file_with_allocator` - // (which would try to open `data:...` as a filesystem path). if strings::has_prefix_comptime(path.text, b"data:") { use bun_resolver::data_url::DataURL; let data_url = match DataURL::parse_without_check(path.text) { @@ -1501,12 +1150,6 @@ impl<'a> Transpiler<'a> { break 'brk bun_ast::Source::init_path_string(path.text, contents); } - // Zig (`transpiler.zig:838-839`): `if (use_shared_buffer) - // bun.default_allocator else this_parse.allocator`. Thread - // `this_parse.arena` (the per-call `MimallocArena` from - // `RuntimeTranspilerStore`) so the source bytes land in the - // job-scoped heap that `TranspilerJob::run` `mi_heap_destroy`s on - // return — not the worker thread's default mimalloc heap. let mut entry = match self.resolver.caches.fs.read_file_with_allocator( self.fs_mut(), path.text, @@ -1533,17 +1176,6 @@ impl<'a> Transpiler<'a> { if let Some(file_fd_ptr) = this_parse.file_fd_ptr { *file_fd_ptr = entry.fd; } - // PORT NOTE: `Source.contents: &'static [u8]` (the AST crate's `Str` - // convention). The bytes live either in the per-thread shared - // buffer (`USE_SHARED_BUFFER` → `Contents::SharedBuffer`, no-op - // drop) or in `this_parse.arena` (`Contents::Arena`, no-op drop — - // bulk-freed by `mi_heap_destroy` when the per-call arena is - // recycled). Thread the - // provenance-tagged backing alongside the `ParseResult` so it - // drops when the result is recycled — no `mem::forget` - // (PORTING.md §Forbidden patterns). Spec transpiler.zig:853 hands - // `entry.contents` to `Source.initRecycledFile` by slice; Zig has - // no implicit drop, so ownership was already with the caller. source_backing = core::mem::take(&mut entry.contents); // SAFETY: `source_backing` outlives every read through // `source.contents` (it is moved into the returned `ParseResult`, @@ -1606,15 +1238,6 @@ impl<'a> Transpiler<'a> { jsx.parse = loader.is_jsx(); let _ = &this_parse.macro_remappings; - // PORT NOTE: `ParserOptions::init` is hard-typed - // `-> Options<'static>` and `Options<'a>` is *invariant* in - // `'a` (it holds `Option<&'a mut MacroContext>`), so an - // `Options<'static>` cannot be passed to - // `cache::JavaScript::parse::<'x>` alongside a non-`'static` - // `bump`/`source`/`define`. Construct the struct literal - // directly (mirroring the body of `Options::init`, - // ast/Parser.rs:144-180) so `'x` is inferred from the borrows - // below instead of pinned to `'static`. use js_ast::parser::options as p_opts; let mut opts = js_ast::ParserOptions::<'_> { ts: loader.is_typescript(), @@ -1656,14 +1279,6 @@ impl<'a> Transpiler<'a> { .trim_unused_imports .unwrap_or_else(|| loader.is_typescript()); opts.features.no_macros = self.options.no_macros; - // `bun_ast::RuntimeTranspilerCache` is the single nominal - // type on both sides; thread the pointer directly. - // Spec transpiler.zig:899/957 copies the same - // `?*RuntimeTranspilerCache` raw pointer to BOTH - // `opts.features` and the returned `ParseResult`. Derive both - // from a single reborrow so they share one provenance tag — - // re-touching the parent `&mut` after the `*mut` cast would - // pop the raw pointer off the borrow stack (Stacked Borrows). let rtc_ptr: Option> = this_parse .runtime_transpiler_cache .as_deref_mut() @@ -1686,13 +1301,6 @@ impl<'a> Transpiler<'a> { opts.features.minify_identifiers = self.options.minify_identifiers; opts.features.dead_code_elimination = self.options.dead_code_elimination; opts.features.remove_cjs_module_wrapper = this_parse.remove_cjs_module_wrapper; - // Spec transpiler.zig:925 forwards `transpiler.options - // .bundler_feature_flags`. Zig aliased a `*const StringSet`; - // `Features.bundler_feature_flags` is currently owned - // (`Option>`), so clone by value. - // TODO(refactor): change the parser-side field to - // `Option<&'a StringSet>` to avoid the clone. - // The clone drops with `opts` — no leak. opts.features.bundler_feature_flags = self .options .bundler_feature_flags @@ -1705,11 +1313,6 @@ impl<'a> Transpiler<'a> { opts.features.top_level_await = true; opts.features.is_macro_runtime = target == crate::options_impl::Target::BunMacro; - // Spec transpiler.zig:943: `opts.features.replace_exports = - // this_parse.replace_exports`. - // `bun_ast::runtime::ReplaceableExport` IS - // `js_ast::Runtime::ReplaceableExport`, so the inner - // `StringArrayHashMap` moves directly into the newtype. opts.features.replace_exports = bun_ast::runtime::ReplaceableExportMap { entries: this_parse.replace_exports, }; @@ -1718,11 +1321,6 @@ impl<'a> Transpiler<'a> { let ctx = js_ast::Macro::MacroContext::init(self); self.macro_context = Some(ctx); } - // Spec transpiler.zig:938-940: thread the caller-supplied JS - // context into the macro runtime so macros invoked during - // runtime transpilation see it (instead of null). Written on - // `self.macro_context` before reborrowing into `opts` so the - // `&mut` handed to the parser already carries the value. if target != crate::options_impl::Target::BunMacro { // SAFETY: `is_none()` check above guarantees `Some` here. self.macro_context.as_mut().unwrap().javascript_object = @@ -1748,12 +1346,6 @@ impl<'a> Transpiler<'a> { .map(|m| &mut *core::ptr::from_mut(m)); } - // PORT NOTE: spec calls `transpiler.resolver.caches.js.parse`. - // The resolver-side `cache::JavaScript` is a fieldless - // shell with no `parse` body (resolver/lib.rs:1664); - // the real `parse` lives on `crate::cache::JavaScript`. Both - // are stateless unit structs, so calling the bundler-crate one - // directly is equivalent. let parsed = match crate::cache::JavaScript::init() .parse(arena, opts, define, log, source) { @@ -1792,11 +1384,6 @@ impl<'a> Transpiler<'a> { js_ast::AlreadyBundled::BunCjs => AlreadyBundled::SourceCodeCjs, js_ast::AlreadyBundled::BytecodeCjs | js_ast::AlreadyBundled::Bytecode => 'brk: { - // Spec transpiler.zig:971-984: when the parser - // saw `// @bun @bytecode`, attempt to load the - // sidecar `.jsc` cached bytecode. Only - // fall back to re-parsing source on read - // failure / empty file. let is_cjs = matches!(already_bundled, js_ast::AlreadyBundled::BytecodeCjs); let default_value = if is_cjs { @@ -1821,13 +1408,6 @@ impl<'a> Transpiler<'a> { // `path_buf2[total] == 0` already; safe to // borrow as a NUL-terminated ZStr. let zpath = bun_core::ZStr::from_buf(&path_buf2[..], total); - // PORT NOTE: spec calls - // `bun.sys.File.toSourceAt(...)` which is - // `read_from` + wrap-in-`bun_ast::Source`. - // We only need `.contents`, so call - // `read_from` directly (the `to_source_at` - // wrapper is gated as a T1→T2 move-in, - // sys/File.rs:446). let dir = dirname_fd.unwrap_valid().unwrap_or_else(FD::cwd); match bun_sys::File::read_from(dir, zpath) { Ok(contents) if !contents.is_empty() => { @@ -1906,13 +1486,6 @@ impl<'a> Transpiler<'a> { } } -// --------------------------------------------------------------------------- -// Cold rare-loader parse paths, split out of -// `Transpiler::parse_maybe_return_file_only_allow_shared_buffer` so the -// data-format / markdown / wasm code they pull in lands in `.text.unlikely` -// instead of being interleaved (post-LTO) with the hot JS/TS parse path. -// --------------------------------------------------------------------------- - #[cold] #[inline(never)] fn parse_data_loader<'a>( @@ -1924,14 +1497,6 @@ fn parse_data_loader<'a>( log: &mut bun_ast::Log, keep_json_and_toml_as_one_statement: bool, ) -> Option> { - // PERF(port): was `inline .toml, .yaml, .json, .jsonc, .json5 - // => |kind|` — comptime monomorphization per loader; profile if it - // shows up on a hot path. - // - // PORT NOTE: `bun_parsers::*` parse into the T2 value AST - // (`bun_ast::Expr`); lift into the full T4 - // `bun_ast::Expr` via the deep-convert `From` bridge - // (Expr.rs:1265) so the StoreRef-backed accessors below work. let value_expr: bun_ast::Expr = match loader { options::Loader::Jsonc => { // We allow importing tsconfig.*.json or jsconfig.*.json with comments @@ -1966,10 +1531,6 @@ fn parse_data_loader<'a>( let mut symbols: Vec = Vec::new(); - // PORT NOTE: reshaped — Zig `arena.alloc(Part, 1)` returned - // an arena slice, but `Ast::from_parts` takes `Box<[Part]>` - // (Vec owns its buffer). The single-part array is built on - // the global heap; `stmts` stays arena-backed (`*mut [Stmt]`). let parts: Box<[bun_ast::Part]> = 'parts: { if keep_json_and_toml_as_one_statement { let stmt = bun_ast::Stmt::allocate( @@ -1992,15 +1553,6 @@ fn parse_data_loader<'a>( let properties: &mut [bun_ast::G::Property] = obj.properties.slice_mut(); if !properties.is_empty() { let n = properties.len(); - // PORT NOTE: Zig `expandToCapacity()` / `arena.alloc(Symbol, n)` - // leave slots uninitialized, which is inert in Zig. - // The loop below writes sparsely at index `i` and - // `continue`s on `"default"` / duplicate keys, so - // some slots are never assigned. In Rust an uninit - // live `Vec` element is UB the moment it is - // observed (truncate/into_boxed_slice/index-assign), - // so pre-fill every slot with `Default` instead of - // `set_len`. PERF(port): was `expandToCapacity()`. let mut decls: Vec = vec![bun_ast::G::Decl::default(); n]; symbols.resize_with(n, Default::default); @@ -2040,13 +1592,6 @@ fn parse_data_loader<'a>( Some(prop.value.expect("infallible: prop has value")); continue; } - // PORT NOTE: spec transpiler.zig:1030-1071 - // writes at `i` and shrinks to `count`, leaving - // holes when `"default"` / duplicates `continue` - // — a latent spec bug. Write densely at `count` - // (and store `count` in the checker) so - // `truncate(count)` / `[..count]` keep the - // actually-populated entries. *visited.value_ptr = count as u32; symbols[count] = bun_ast::Symbol { @@ -2315,31 +1860,9 @@ fn parse_unsupported_loader(loader: options::Loader, path: &bun_paths::fs::Path< )); } -// ══════════════════════════════════════════════════════════════════════════ -// `Transpiler::print` / `print_with_source_map` — final step of -// `ModuleLoader::transpile_source_code` (jsc_hooks.rs spec :525-539); the -// dispatch shim that `RuntimeTranspilerStore` / `AsyncModule` link against. -// -// PORT NOTE: `comptime format: js_printer.Format` demoted to a runtime arg — -// `bun_js_printer::Format` doesn't derive `ConstParamTy` (and can't be added -// from this crate). All callers pass a literal anyway; the inner -// `print_ast::<_, ASCII_ONLY, ENABLE_SOURCE_MAP>` keeps both real comptime -// bools, so codegen monomorphizes the printer body identically. -// PERF(port): outer `match format` is one extra branch — profile if hot. -// ══════════════════════════════════════════════════════════════════════════ - use bun_js_printer as js_printer; -// PORT NOTE: `module_info` threads the *printer's* `analyze_transpiled_module::ModuleInfo` -// (the producer), not `crate::analyze_transpiled_module::ModuleInfo` (the -// richer consumer-side mirror). The print -// path only ever fills the printer-owned one and hands its serialized bytes to -// T6, so unify on the printer type here. Spec: transpiler.zig:663. use js_printer::analyze_transpiled_module; -/// Map the bundler-local `Target` (options.rs:489) to the lower-tier -/// `bun_ast::Target` consumed by `js_printer::Options`. -/// The two enums are variant-for-variant identical but nominally distinct; -/// TODO(refactor): collapse them (see lib.rs `pub mod options` shadow note). #[inline] fn to_bundle_enums_target(t: crate::options_impl::Target) -> bun_ast::Target { use bun_ast::Target as T; @@ -2356,18 +1879,6 @@ fn to_bundle_enums_target(t: crate::options_impl::Target) -> bun_ast::Target { /// resolves once `lib.rs` `pub use transpiler::*` lands. pub use js_printer::Format as PrintFormat; -// PERF: this whole `print*` chain was generic over `W: WriterTrait`, but every -// call site in the tree (jsc_hooks.rs, RuntimeTranspilerStore.rs, AsyncModule.rs, -// JSTranspiler.rs, and the in-crate `transform()` path) passes the same concrete -// `&mut BufferPrinter`. Leaving the public entry points generic forced each -// downstream crate (bun_runtime / bun_jsc / bun_install / bun_bundler) to stamp -// out its own copy of the 109-fn `Printer` recursion tree — -// `llvm-nm --print-size` showed `bun_js_printer` .text at 1,367 KB vs 594 KB on -// the Zig build, with both the `_11bun_runtime` and `_7bun_jsc` copies of -// `print_expr<…>` live in `perf` and thrashing icache against each other -// (L1-icache-misses +5.1%, iTLB-misses +13.2%, IPC 1.40 vs 1.50). Pinning `W` -// to the one concrete type and marking the public entry points -// `#[inline(never)]` makes LTO emit exactly one copy in `bun_bundler`. impl<'a> Transpiler<'a> { fn print_with_source_map_maybe( &mut self, @@ -2380,19 +1891,6 @@ impl<'a> Transpiler<'a> { runtime_transpiler_cache: Option>, module_info: Option<*mut analyze_transpiled_module::ModuleInfo>, ) -> Result { - // TODO(port): narrow error set - // TODO(port): `bun.perf.trace("JSPrinter.printWithSourceMap")` / - // `("JSPrinter.print")` — `bun_perf::trace` now takes a `PerfEvent` - // enum and neither variant is in `generated_perf_trace_events.rs` - // yet. Re-add once `scripts/generate-perf-trace-events.sh` runs - // against the Rust tree. - - // PORT NOTE: Zig built `Symbol.NestedList.fromBorrowedSliceDangerous( - // &.{ast.symbols})` — aliased the stack-one-slice into the map. Rust - // can't borrow `ast.symbols` while moving `ast` into `print_ast`, so - // take the column out (the printer never reads `tree.symbols`; it - // walks `symbols` exclusively — `rg tree.symbols js_printer/lib.rs` is - // empty). `init_with_one_list` boxes the single inner list. let arena = *ast.symbols.allocator(); let symbols = bun_ast::symbol::Map::init_with_one_list( core::mem::replace(&mut ast.symbols, bun_alloc::ArenaVec::new_in(arena)) @@ -2400,37 +1898,8 @@ impl<'a> Transpiler<'a> { .collect(), ); - // `runtime_imports` is now forwarded — after Round-G `Ast.runtime_imports` - // is the real `parser::Runtime::Imports`, the same type - // `js_printer::Options.runtime_imports` takes (via `js_ast::runtime`), - // so the seam is gone. Spec: zig:593/619/645. - // `target` is now forwarded via `to_bundle_enums_target` below — it - // *does* affect the EsmAscii/bun-runtime path (js_printer/lib.rs:6872 - // gates the `var {require}=import.meta;` hoist on `target == Bun`; - // regression of oven-sh/bun#15738 if left at the `Browser` default). - // `runtime_transpiler_cache` is now forwarded — js_printer holds the - // `NonNull` directly. Spec: zig:601/627/662. - // `module_info` is now forwarded — this fn's parameter is the - // printer-crate `analyze_transpiled_module::ModuleInfo` (see the `use` - // above), so the seam is gone. Spec: zig:663 — EsmAscii arm only. - let exports_kind = ast.exports_kind; - // PERF: each `js_printer::print_*::` call below stamps out a full - // `__gated_printer::Printer` instantiation tree (~35 kB of - // .text per leaf method, 109 fns total). For `bun run` only the - // `EsmAscii + is_bun=true` arm executes, but rustc lays the Cjs / Esm / - // `is_bun=false` trees out adjacent in .text, so the live variant - // shares 64 kB faultaround windows with ~888 kB of dead code. Hoist the - // three cold arms behind `#[cold] #[inline(never)]` thunks so their - // instantiation trees land in `.text.unlikely` instead. - // - // `print_arena` is the same per-call arena that built `ast` (the one - // passed in `ParseOptions.arena`). Do NOT use `self.arena` here: on the - // runtime per-import path that aliases the per-VM `transpiler_arena` - // (`Arena::borrowing_default()` → `mi_heap_main()`, never freed), so the - // printer's rope/template-string flattening (`Str::resolve_rope_if_needed`) - // would strand its bytes in `mi_heap_main` on every print. match format { js_printer::Format::Cjs => self.print_cjs_cold::( print_arena, @@ -2453,10 +1922,6 @@ impl<'a> Transpiler<'a> { ), js_printer::Format::EsmAscii => { - // PORT NOTE: `switch (target.isBun()) { inline else => |is_bun| ... }` - // — runtime bool → comptime dispatch. Hoisted into the - // `print_ast_esm_ascii` helper so the const-generic IS_BUN can - // also drive `module_type`. if self.options.target.is_bun() { self.print_ast_esm_ascii::( print_arena, @@ -2507,10 +1972,6 @@ impl<'a> Transpiler<'a> { ) -> Result { js_printer::print_common_js::<_, false, ENABLE_SOURCE_MAP>( writer, - // The printer's per-call scratch arena (rope/template-string - // flattening via `Str::resolve_rope_if_needed` / `Str::slice`). - // Same arena that `ParseOptions.arena` used to build this AST — - // see `print_with_source_map_maybe`. print_arena, ast, symbols, @@ -2578,10 +2039,6 @@ impl<'a> Transpiler<'a> { ) } - // PERF: cold thunk — see `print_with_source_map_maybe` comment. Wraps the - // `IS_BUN=false` instantiation so its Printer<…> tree (which `bun run` - // never executes) is laid out in `.text.unlikely` instead of interleaved - // with the hot `IS_BUN=true` tree. #[cold] #[inline(never)] #[allow(clippy::too_many_arguments)] @@ -2657,10 +2114,6 @@ impl<'a> Transpiler<'a> { module_info, hmr_ref: ast.wrapper_ref, mangled_props: None, - // Spec transpiler.zig:664. The printer reads `opts.target` at - // js_printer/lib.rs:6872 to gate the `var {require}=import.meta;` - // hoist on `Target::Bun` — defaulting to `Browser` here regressed - // oven-sh/bun#15738. target: to_bundle_enums_target(self.options.target), ..Default::default() }; @@ -2675,15 +2128,6 @@ impl<'a> Transpiler<'a> { ) } - // PERF: `#[inline(never)]` + concrete `&mut BufferPrinter` (not - // ``) so this is compiled exactly once in `bun_bundler` - // and called by symbol from bun_runtime / bun_jsc / bun_install instead of - // each crate re-monomorphizing the entire `Printer` recursion tree. - // See the PERF block above this `impl` for the icache-thrash measurement. - /// `print_arena` is the same per-call arena that built `result.ast` (the - /// one passed in `ParseOptions.arena`) — the printer uses it for rope / - /// template-string flattening and the flattened bytes share the AST's - /// lifetime. See `print_with_source_map_maybe`. #[inline(never)] pub fn print( &mut self, @@ -2704,12 +2148,6 @@ impl<'a> Transpiler<'a> { ) } - // PERF: `#[inline(never)]` + concrete `&mut BufferPrinter` — see `print` - // above. This is the hot entry from jsc_hooks.rs / RuntimeTranspilerStore.rs - // / AsyncModule.rs; keeping it non-generic collapses the four cross-crate - // copies of `print_expr` (244 KB → ~61 KB). - /// `print_arena` is the same per-call arena that built `result.ast` — - /// see [`Self::print`]. #[inline(never)] pub fn print_with_source_map( &mut self, @@ -2749,17 +2187,6 @@ impl<'a> Transpiler<'a> { ) } - // PERF: like `print` (no `SourceMapHandler`, `ENABLE_SOURCE_MAP = false`, so - // the printer skips every per-token `add_source_mapping` / - // `update_generated_line_and_column` and never builds/flushes a VLQ chunk) - // but still threads `result.runtime_transpiler_cache` so the transpiled - // output is written to the on-disk cache. Used by the runtime module loader - // when no inspector is attached: `Bun__remapStackFramePositions` degrades - // gracefully (keeps the raw transpiled position) when a path has no entry in - // `SavedSourceMap`, so eagerly building a per-module source map nothing will - // consume is pure overhead. See jsc_hooks.rs `transpile_source_code_inner`. - /// `print_arena` is the same per-call arena that built `result.ast` — - /// see [`Self::print`]. #[inline(never)] pub fn print_skip_source_map( &mut self, @@ -2796,18 +2223,6 @@ impl<'a> Transpiler<'a> { let entry = fs.relative_to(entry); if !strings::starts_with(entry, b"./") { - // Entry point paths without a leading "./" are interpreted as package - // paths. This happens because they go through general path resolution - // like all other import paths so that plugins can run on them. Requiring - // a leading "./" for a relative path simplifies writing plugins because - // entry points aren't a special case. - // - // However, requiring a leading "./" also breaks backward compatibility - // and makes working with the CLI more difficult. So attempt to insert - // "./" automatically when needed. We don't want to unconditionally insert - // a leading "./" because the path may not be a file system path. For - // example, it may be a URL. So only insert a leading "./" when the path - // is an exact match for an existing file. let mut __entry = Vec::with_capacity(2 + entry.len()); __entry.extend_from_slice(b"./"); __entry.extend_from_slice(entry); @@ -2816,12 +2231,6 @@ impl<'a> Transpiler<'a> { crate::linker::dupe(entry) } - /// Port of `transpiler.zig:1254 enqueueEntryPoints`. - /// - /// PORT NOTE: the Zig version writes the resolved entry results into a - /// caller-provided `[]Result` slice; the only caller (`transform`) discards - /// that slice immediately, so the Rust port returns only the count and lets - /// `linker.enqueue_resolve_result` push directly onto `resolve_queue`. fn enqueue_entry_points(&mut self) -> usize { let mut entry_point_i: usize = 0; @@ -2989,17 +2398,9 @@ impl<'a> Transpiler<'a> { let Some(file_path_ref) = resolve_result.path_const() else { return Ok(None); }; - // PORT NOTE: `resolver::Result.path_pair` carries `bun_resolver::fs::Path<'_>`; - // downstream `linker.link`/`get_hashed_filename` and `OutputFile.src_path` - // expect `bun_paths::fs::Path<'_>` / `bun_paths::fs::Path<'static>`. Re-init via - // `text` (the only field both shapes share semantically). let file_path_text: &'static [u8] = crate::linker::dupe(file_path_ref.text); let file_path_ext: &'static [u8] = crate::linker::dupe(file_path_ref.name().ext); - // Step 1. Parse & scan - // Spec (transpiler.zig:397) keys the loader on the ORIGINAL resolve - // result's extension *before* the `client_entry_point` path override - // (line 400). Compute it here, then apply the override. let loader = self.options.loader(file_path_ext); // `client_entry_point_` is always `None` from the only in-tree caller; @@ -3157,12 +2558,6 @@ impl<'a> Transpiler<'a> { Ok(Some(output_file)) } - /// Cold path: `bun build` of a `.css` entry. Split out of - /// `build_with_resolve_result_eager` so the `bun_css` parser/printer code - /// it pulls in lands in `.text.unlikely` instead of being interleaved - /// (post-LTO) with the hot JS/TS transpile path. Returns `None` to mean - /// "the caller should `return Ok(None)`" -- the parse/minify/print error - /// has already been logged. #[cold] #[inline(never)] fn build_css_output( @@ -3302,10 +2697,6 @@ impl<'a> Transpiler<'a> { } } -/// Port of the `comptime Outstream: type` parameter to -/// `processResolveQueue` / `buildWithResolveResultEager` — Zig switched on -/// `bun.sys.File` vs `std.fs.Dir` at the type level; collapse to a runtime -/// enum since the only behavioural difference is unused (`_ = outstream`). #[derive(Clone, Copy)] enum TransformOutstream { Stdout, diff --git a/src/bundler_jsc/PluginRunner.rs b/src/bundler_jsc/PluginRunner.rs index fe427df7100..e3146311452 100644 --- a/src/bundler_jsc/PluginRunner.rs +++ b/src/bundler_jsc/PluginRunner.rs @@ -7,11 +7,6 @@ pub use bun_resolver::fs::Path as FsPath; /// (defined at the lowest tier that stores it, `bun_ast::Macro`). pub use bun_bundler::transpiler::MacroJSCtx as MacroJsCtx; -/// Spec `PluginRunner.zig:PluginRunner` — re-export of the concrete struct. -/// `extract_namespace` / `could_be_plugin` (pure byte parsing) live in -/// `bun_bundler`; the stateful struct + `on_resolve` body live in -/// `bun_jsc::plugin_runner`. `on_resolve_jsc` (below) is a free fn because it -/// only reads the global, not the runner record. pub use bun_jsc::plugin_runner::PluginRunner; /// Spec PluginRunner.zig:14 — re-export for callers that named this module. @@ -26,16 +21,6 @@ pub fn could_be_plugin(specifier: &[u8]) -> bool { PluginRunner::could_be_plugin(specifier) } -// `on_resolve` (the `Log`-reporting variant, PluginRunner.zig:34) lives at -// `bun_jsc::plugin_runner::PluginRunner` as the `PluginResolver` impl — -// `bun_jsc` is the lowest tier that can name `JSGlobalObject` AND is reachable -// from `Bun__onDidAppendPlugin`. - -/// Spec PluginRunner.zig:121 `onResolveJSC`. -/// LAYERING: body moved DOWN into `bun_jsc::virtual_machine` so the -/// VM's `resolve_maybe_needs_trailing_slash` can consult it without a -/// `bun_jsc → bun_bundler_jsc` cycle. Re-exported here for callers that -/// still name this module. pub use bun_jsc::virtual_machine::plugin_runner_on_resolve_jsc as on_resolve_jsc; // ported from: src/bundler_jsc/PluginRunner.zig diff --git a/src/bundler_jsc/analyze_jsc.rs b/src/bundler_jsc/analyze_jsc.rs index 831f4718f4f..1238762b546 100644 --- a/src/bundler_jsc/analyze_jsc.rs +++ b/src/bundler_jsc/analyze_jsc.rs @@ -22,16 +22,6 @@ pub(crate) extern "C" fn zig__ModuleInfoDeserialized__toJSModuleRecord( lexical_variables: &mut VariableEnvironment, res: &ModuleInfoDeserialized, ) -> *mut JSModuleRecord { - // Ownership of `res` stays with the caller; this function only reads it. - // The caller (BunAnalyzeTranspiledModule.cpp) decides whether to free - // immediately or keep it alive on the SourceProvider for the isolation - // SourceProvider cache. - - // Slice-field validity / alignment caveats are documented on the - // `ModuleInfoDeserialized` accessors. - // TODO(port): switch element reads to `read_unaligned` per the upstream - // note in `analyze_transpiled_module.rs` if a strict-alignment target is - // ever added. let strings_buf: &[u8] = res.strings_buf(); let strings_lens: &[u32] = res.strings_lens(); let requested_modules_keys: &[StringID] = res.requested_modules_keys(); diff --git a/src/bundler_jsc/lib.rs b/src/bundler_jsc/lib.rs index 1afeea21373..bba2238b91d 100644 --- a/src/bundler_jsc/lib.rs +++ b/src/bundler_jsc/lib.rs @@ -16,21 +16,5 @@ pub mod options_jsc; #[path = "PluginRunner.rs"] pub mod PluginRunner; -// LAYERING: `output_file_jsc` (port of `src/bundler_jsc/output_file_jsc.zig`) -// constructs `webcore::Blob`/`Store`, `api::BuildArtifact`, and -// `node::PathOrFileDescriptor`. Those types live in `bun_runtime`, which is -// not a dependency of this crate. The module has been moved to -// `bun_runtime::api::output_file_jsc`; nothing depends on -// `bun_bundler_jsc::output_file_jsc`, so no re-export is needed. - #[path = "analyze_jsc.rs"] pub mod analyze_jsc; - -// ────────────────────────────────────────────────────────────────────────── -// `JSBundleCompletionTask` was MOVED to `bun_runtime::api::js_bundle_completion_task` -// (layering: its fields name `bun_runtime` types — `JSBundler::Config`, -// `Plugin`, `HTMLBundle::Route` — so a lower-tier crate cannot own it without -// a cycle). The earlier draft that imported `bun_runtime` from here has been -// dissolved; `bun_runtime` now depends on this crate for the JSC-aware option -// parsers in `options_jsc` only. -// ────────────────────────────────────────────────────────────────────────── diff --git a/src/bunfig/arguments.rs b/src/bunfig/arguments.rs index b6b1af5b756..1ebd5c2ba7a 100644 --- a/src/bunfig/arguments.rs +++ b/src/bunfig/arguments.rs @@ -62,10 +62,6 @@ fn load_bunfig( bun_ast::expr::data::Store::create(); let _store_reset = bun_ast::StoreResetGuard::new(); - // PORT NOTE: reshaped for borrowck — `defer { ctx.log.level = original }` - // would capture `&mut *ctx.log` past the `Bunfig::parse(.., ctx)` reborrow. - // Route through the raw `*mut Log` (process-lifetime, set in - // `create_context_data()`); the guard restores `level` on unwind/return. let log_ptr: *mut bun_ast::Log = ctx.log; debug_assert!(!log_ptr.is_null()); // SAFETY: `ctx.log` is the process-global Log written once during @@ -202,11 +198,6 @@ pub fn load_config( ctx.args.absolute_working_dir = Some(Box::<[u8]>::from(&secondbuf[..cwd_len])); } - // PORT NOTE: reshaped for borrowck — `join_abs_string_buf` ties the - // returned slice's lifetime to both `cwd` (borrowed from `ctx.args`) - // and `config_buf`. We only need the length to NUL-terminate and - // re-wrap, so capture `joined.len()` and drop the `ctx` borrow before - // the `&mut ctx` call below. config_path_len = { let awd: &[u8] = ctx.args.absolute_working_dir.as_deref().unwrap(); let parts: [&[u8]; 2] = [awd, config_path_]; diff --git a/src/bunfig/bunfig.rs b/src/bunfig/bunfig.rs index 8ed18c0faf5..6f98de1333a 100644 --- a/src/bunfig/bunfig.rs +++ b/src/bunfig/bunfig.rs @@ -40,13 +40,6 @@ fn estring_to_owned(s: &E::EString, bump: &Bump) -> Box<[u8]> { Box::<[u8]>::from(s.string(bump).expect("OOM")) } -/// Port of `resolver/package_json.zig` `PackageJSON.parseMacrosJSON`. -/// -/// Re-ported here against the value-shaped `bun_ast::Expr` (the -/// tree produced by the TOML/JSON parsers) and returning the -/// `bun_options_types::context::MacroMap` shape so the result slots directly -/// into `ctx.debug.macros` without crossing the `bun_ast::Expr` / -/// `StringArrayHashMap` newtype boundary that `bun_resolver`'s copy uses. fn parse_macros_json( macros: &Expr, log: &mut bun_ast::Log, @@ -348,11 +341,6 @@ impl<'a> Parser<'a> { Ok(api::StringMap { keys, values }) } - // PORT NOTE: `comptime cmd: Command.Tag` demoted to a runtime arg — - // `bun_options_types::command_tag::Tag` does not derive `ConstParamTy` (it - // already derives `enum_map::Enum`, which conflicts). The Zig original - // monomorphised over `cmd` purely to dead-code-eliminate untaken arms; the - // runtime branches below are equivalent and the few hot fields are tiny. pub(crate) fn parse(&mut self, cmd: CommandTag) -> Result<(), bun_core::Error> { bun_analytics::features::bunfig.fetch_add(1, Ordering::Relaxed); @@ -1120,12 +1108,6 @@ impl Bunfig { let log: &mut bun_ast::Log = unsafe { &mut *log_ptr }; let log_count = log.errors + log.warnings; - // Zig passes `bun.default_allocator` here — no side `mi_heap`. The Rust - // port previously called `Arena::new()` (= `mi_heap_new` + - // `mi_heap_destroy` on drop), which perf attributed ~1.6% of - // `bun -e ''` startup to. Borrow the process default heap instead so - // TOML/JSON parse allocations route through plain `mi_malloc`, matching - // Zig. Parsed config lives for the process lifetime either way. let bump = Bump::borrowing_default(); let ext = source.path.name().ext; @@ -1185,11 +1167,6 @@ impl Bunfig { } } -// ───────────────────────────────────────────────────────────────────────────── -// `[install]` / `[install.scopes]` registry parsing and `[serve.static]`. -// Split into a second `impl` block purely to keep `parse(cmd)` readable. -// ───────────────────────────────────────────────────────────────────────────── - impl<'a> Parser<'a> { fn parse_registry_url_string( &mut self, @@ -1259,12 +1236,6 @@ impl<'a> Parser<'a> { } fn parse_install(&mut self, install_obj: &Expr) -> Result<(), bun_core::Error> { - // PORT NOTE: Zig held `*BunInstall` and `*Parser` simultaneously. - // The helper methods (`expect*`, `add_error`, `parse_registry`) take - // `&mut self`, which under Stacked Borrows would invalidate any - // long-lived `&mut` derived from `self.ctx.install`. Move the box - // out so the install borrow is provably disjoint from `self`, then - // restore it on every exit path. let mut install = self.ctx.install.take().expect("install slot primed"); let result = self.parse_install_inner(&mut install, install_obj); self.ctx.install = Some(install); diff --git a/src/cares_sys/c_ares.rs b/src/cares_sys/c_ares.rs index 06d933ddff8..d2ebd51c7ce 100644 --- a/src/cares_sys/c_ares.rs +++ b/src/cares_sys/c_ares.rs @@ -305,19 +305,6 @@ pub struct struct_hostent { pub h_addr_list: *mut *mut c_char, // NUL-terminated array } -// ─── callback-wrapper reshaping note ────────────────────────────────────── -// Zig: each reply type defines -// pub fn Callback(comptime Type) type = fn(*Type, ?Error, i32, ?*Reply) void -// pub fn callbackWrapper(comptime lookup_name, comptime Type, comptime fn) ares_callback -// which monomorphizes a unique `extern "C"` thunk per (Type, fn) pair via the -// anonymous-struct trick. Rust cannot take a fn pointer as a const generic on -// stable, so the wrappers below are reshaped to a trait: the implementing -// type provides the callback as a trait method, and the `extern "C"` thunk is -// monomorphized per `T: Trait`. -// TODO(port): a proc-macro may be cleaner if many callsites need distinct -// callbacks on the same `Type`. -// ────────────────────────────────────────────────────────────────────────── - pub trait HostentHandler: Sized { fn on_hostent(&mut self, status: Option, timeouts: i32, results: *mut struct_hostent); } @@ -574,11 +561,6 @@ impl Drop for hostent_with_ttls { } } -// Per-record-type newtype aliases. Zig instantiated the resolve machinery over -// the same `struct_hostent` / `hostent_with_ttls` with a comptime `type_name` -// string; Rust callers (`dns.rs`) need distinct type names to monomorphise the -// `CAresRecordType` cache-field constant per record. For now these are plain -// aliases — the trait impls live downstream. pub type NsHostent = struct_hostent; pub type PtrHostent = struct_hostent; pub type CnameHostent = struct_hostent; @@ -677,10 +659,6 @@ impl AddrInfo { #[inline] pub fn cnames(&self) -> &[AddrInfo_node] { - // TODO(port): Zig used `bun.span` on a [*c]AddrInfo_cname (sentinel- - // terminated linked list), returning `[]const AddrInfo_node` — note - // the type mismatch (cname vs node) in the original. This appears - // unused; preserving the empty-slice fast path only. if self.cnames_.is_null() { return &[]; } @@ -734,28 +712,13 @@ bun_opaque::opaque_ffi! { /// mutates the channel on every dispatch/process call). pub struct Channel; } -// Load-bearing: `ares_cancel`/`ares_process_fd` are declared `safe fn(&mut Channel)` -// on the basis that re-entrant callbacks re-deriving `&mut Channel` from a raw -// pointer cannot conflict because `Channel` claims zero bytes. If this type ever -// gains a non-ZST field, those signatures must revert to `unsafe fn(*mut Channel)`. const _: () = assert!(core::mem::size_of::() == 0); -/// Implemented by the type that owns a `*mut Channel` and receives socket- -/// state callbacks. Zig: `Container.onDNSSocketState` + `this.channel = ch`. -/// -/// R-2: methods take `&self`. The c-ares `sock_state_cb` re-enters the -/// container while a `&self` borrow may already be live in `on_dns_poll`; -/// the implementor routes mutation through interior mutability. pub trait ChannelContainer: Sized { fn on_dns_socket_state(&self, socket: ares_socket_t, readable: bool, writable: bool); fn set_channel(&self, channel: *mut Channel); } -/// Trait for `Channel::resolve`: ties a lookup-name string to its NSType and -/// the `extern "C"` parse-thunk used as the ares_callback. -/// TODO(port): Zig dispatched via `@field(NSType, "ns_t_" ++ lookup_name)` and -/// `cares_type.callbackWrapper(lookup_name, Type, callback)`. This trait is the -/// Rust-side reshaping; the dns_jsc consumer impls it per (T, record-type). pub trait ResolveHandler: Sized { const LOOKUP_NAME: &'static [u8]; const NS_TYPE: NSType; @@ -770,11 +733,6 @@ pub trait ResolveHandler: Sized { ); } -/// Copy `src` into the caller-owned stack `buf`, NUL-terminate, and return a -/// `*const c_char` suitable for c-ares FFI. Truncates silently at -/// `buf.len() - 1`; callers that must reject overlong input do so before -/// calling. The buffer lives in the caller's frame so the returned pointer is -/// valid for the FFI call that follows. #[inline] fn copy_nul_terminated(buf: &mut [u8], src: &[u8]) -> *const c_char { let len = src.len().min(buf.len() - 1); @@ -802,12 +760,6 @@ impl Channel { } let mut opts = Options { - // Android note: c-ares can't auto-discover servers (no /etc/resolv.conf, - // no JNI), so it falls back to 127.0.0.1 and queries time out. We do - // NOT set ARES_FLAG_NO_DFLT_SVR here — that makes init fail with - // ENOSERVER, which breaks dns.setServers() (it needs an initialized - // channel to call ares_set_servers_ports). Letting the 127.0.0.1 - // default stand means setServers() works as the documented workaround. flags: ARES_FLAG_NOCHECKRESP, sock_state_cb: Some(on_sock_state::), // R-2: `*mut` spelling is signature-only (c-ares stores a `void*`); the @@ -926,10 +878,6 @@ impl Channel { copy_nul_terminated(&mut addr_buf, ip_addr) }; - // https://c-ares.org/ares_inet_pton.html - // https://github.com/c-ares/c-ares/blob/7f3262312f246556d8c1bdd8ccc1844847f42787/src/lib/ares_gethostbyaddr.c#L71-L72 - // `ares_inet_pton` allows passing raw bytes as `dst`, - // which can avoid the use of `struct_in_addr` to reduce extra bytes. let mut addr = [0u8; 16]; if !addr_ptr.is_null() { // SAFETY: c-ares FFI; addr_ptr is a NUL-terminated stack buffer, addr is 16-byte stack scratch. @@ -1065,13 +1013,6 @@ unsafe extern "C" { pub fn ares_destroy_options(options: *mut Options); pub fn ares_dup(dest: *mut Channel, src: *mut Channel) -> c_int; pub fn ares_destroy(channel: *mut Channel); - // Opaque handle by exclusive reference only — `Channel` is `!Freeze`/`!Sync` - // (UnsafeCell + PhantomData<*mut u8>). Note: `ares_cancel`/`ares_process_fd` - // synchronously invoke stored completion callbacks which may re-enter the - // resolver and re-derive a `&mut Channel` from a raw pointer; this is sound - // because `Channel` is a ZST (`UnsafeCell<[u8;0]>`), so `&mut Channel` - // claims zero bytes and overlapping `&mut` do not conflict under Stacked - // Borrows — the borrow checker does NOT gate the raw-pointer callbacks. pub safe fn ares_cancel(channel: &mut Channel); pub safe fn ares_set_local_ip4(channel: &mut Channel, local_ip: c_uint); pub fn ares_set_local_ip6(channel: *mut Channel, local_ip6: *const u8); @@ -1267,14 +1208,6 @@ impl Default for struct_ares_addr6ttl { } } -// ────────────────────────────────────────────────────────────────────────── -// Generic reply-record plumbing. The six `struct_ares_{caa,srv,mx,txt,naptr,soa}_reply` -// types share an identical c-ares callback thunk modulo (reply type, parse fn), -// and all six `ares_parse_*_reply` externs share the signature -// `(abuf *const u8, alen c_int, out *mut *mut R) -> c_int`. Collapsed from six -// copy-pasted `callback_wrapper` bodies + six per-type handler traits. -// ────────────────────────────────────────────────────────────────────────── - /// A c-ares reply record whose parser has the canonical 3-arg signature. pub trait AresReply: Sized { /// SAFETY: thin forward to the matching `ares_parse_*_reply` extern. @@ -1796,10 +1729,6 @@ pub struct struct_ares_addr_port_node { unsafe impl bun_core::ffi::Zeroable for struct_ares_addr_port_node {} impl struct_ares_addr_port_node { - /// Type-erased pointer to the in_addr/in6_addr union, for `ares_inet_ntop`. - /// The union field stays private (active arm depends on `family`), but - /// callers need its address to round-trip through c-ares' presentation - /// converters. #[inline] pub fn addr_ptr(&self) -> *const c_void { ptr::addr_of!(self.addr).cast::() @@ -1834,12 +1763,6 @@ unsafe extern "C" { dst: *mut u8, size: ares_socklen_t, ) -> *const c_char; - /// https://c-ares.org/docs/ares_inet_pton.html - /// - /// ## Returns - /// - `1` if `src` was valid for the specified address family - /// - `0` if `src` was not parseable in the specified address family - /// - `-1` if some system error occurred. `errno` will have been set. pub fn ares_inet_pton(af: c_int, src: *const c_char, dst: *mut c_void) -> c_int; } @@ -2167,16 +2090,6 @@ pub type ares_addr_port_node = struct_ares_addr_port_node; // Bun__canonicalizeIP_ host fn: see bun_runtime::dns_jsc::cares_jsc -/// Creates a sockaddr structure from an address, port. -/// -/// # Parameters -/// - `addr`: A byte slice representing the IP address. -/// - `port`: A 16-bit unsigned integer representing the port number. -/// - `sa`: A pointer to a sockaddr structure where the result will be stored. -/// -/// # Returns -/// -/// This function returns 0 on success. pub fn get_sockaddr(addr: &[u8], port: u16, sa: &mut sockaddr) -> c_int { const BUF_SIZE: usize = 128; diff --git a/src/cares_sys/lib.rs b/src/cares_sys/lib.rs index c3fdfecfacd..f6b12352200 100644 --- a/src/cares_sys/lib.rs +++ b/src/cares_sys/lib.rs @@ -29,12 +29,6 @@ pub mod winsock { } } -/// The full c-ares FFI module. The temporary inline scaffold that previously -/// duplicated `ares_socklen_t` / `AddrInfo_hints` / `ares_inet_*` here has been -/// collapsed to a re-export of the canonical `c_ares.rs` module now that it is -/// un-gated. `c_ares` and `c_ares_draft` resolve to the SAME module, so the two -/// `AddrInfo_hints` definitions are now nominally identical (previously a latent -/// type-mismatch footgun for callers mixing the two paths). pub use c_ares_draft as c_ares; // Crate-root re-exports for callers that reference `bun_cares_sys::ares_inet_*` diff --git a/src/clap/args.rs b/src/clap/args.rs index a99fd46e0df..7fd6fe37028 100644 --- a/src/clap/args.rs +++ b/src/clap/args.rs @@ -101,19 +101,6 @@ impl ArgIter<'static> for OsIterator { } } -/// Process argv as a `&'static` slice of `&'static [u8]`. -/// -/// Zig: `bun.argv: [][:0]const u8` — the process-global view that includes -/// `BUN_OPTIONS` injection. -/// -/// This used to project `&ZStr → &[u8]` through a `OnceLock>`, -/// which (a) allocated a Vec on the `--version` startup path and (b) emitted a -/// distinct `OnceLock>::initialize` / `Once::call_once_force` -/// monomorphisation that perf showed faulting in its own 4 KB `.text` page. -/// `bun_core::ZStr` is `#[repr(transparent)]` over `[u8]`, so `&ZStr` and -/// `&[u8]` are layout-identical fat pointers — reinterpret the process-static -/// `[&ZStr]` view in place: zero alloc, zero lazy-init shim, zero extra -/// `.text`. #[inline] fn os_argv() -> &'static [&'static [u8]] { let z: &'static [&'static bun_core::ZStr] = bun_core::argv().as_slice(); @@ -206,10 +193,6 @@ impl<'a> ShellIterator<'a> { return res; } - // Slicing is not possible if a quote starts while parsing none - // quoted args. - // Example: - // ab'cd' -> abcd b'\'' => { list.extend_from_slice(&s[start..i]); start = i + 1; @@ -232,11 +215,6 @@ impl<'a> ShellIterator<'a> { _ => {} }, - // We're in this state after having parsed the quoted part of an - // argument. This state works mostly the same as .no_quote, but - // is aware, that the last character seen was a quote, which should - // not be part of the argument. This is why you will see `i - 1` here - // instead of just `i` when `iter.str` is sliced. State::AfterQuote => match c { b' ' | b'\t' | b'\n' => { let res = Self::result(s, start, i - 1, list); @@ -265,10 +243,6 @@ impl<'a> ShellIterator<'a> { } }, - // The states that parse the quoted part of arguments. The only differnece - // between single and double quoted arguments is that single quoted - // arguments ignore escape sequences, while double quoted arguments - // does escaping. State::SingleQuote => match c { b'\'' => state = State::AfterQuote, _ => {} @@ -283,11 +257,6 @@ impl<'a> ShellIterator<'a> { _ => {} }, - // The state we end up when after the escape character (`\`). All these - // states do is transition back into the previous state. - // TODO: Are there any escape sequences that does transform the second - // character into something else? For example, in Zig, `\n` is - // transformed into the line feed ascii character. State::NoQuoteEscape => { state = State::NoQuote; } diff --git a/src/clap/comptime.rs b/src/clap/comptime.rs index 66f8257ff0b..0a53f21ea40 100644 --- a/src/clap/comptime.rs +++ b/src/clap/comptime.rs @@ -6,41 +6,6 @@ use crate::args::ArgIter; use crate::streaming::{self, StreamingClap}; use crate::{Names, Param, ParseOptions, Values}; -// ───────────────────────────────────────────────────────────────────────────── -// Compile-time conversion (Zig parity) -// -// Zig's `ComptimeClap(Id, params)` is a comptime type-generator: it iterates -// `params` *at comptime* to (a) re-index every param's `id` to its slot within -// its category (flag / single / multi) and (b) emit a struct with fixed-size -// array fields. `findParam` is `inline for`, so every `args.flag("--foo")` -// compiles to a constant index — zero runtime cost. -// -// An earlier draft of this port did all of this at runtime: `convert_params` heap-allocated -// a `Vec>` on every CLI start, and `find_param` linear-scanned it -// for every `flag()`/`option()` lookup (~190 lookups × ~100 params on the -// `bun run` path). perf put this at ~0.25 % of `bun --version` cycles vs ~0 % -// in Zig. -// -// This module restores the comptime semantics on stable Rust: -// -// * `count_flags` / `count_single` / `count_multi` / `convert_params_array` -// / `find_param_index` are all `const fn`, so a param table declared with -// `concat_params!` can be converted to a `static [Param; N]` and a -// name lookup can be folded to a constant via `const { find_param_index(..) }`. -// -// * `comptime_table!` (in `lib.rs`) packages the const-fn output as a -// `&'static ConvertedTable` — the Rust analogue of the Zig comptime -// `converted_params` const baked into the generated type. -// -// * Every per-subcommand table in `runtime/cli/Arguments.rs` is now a -// `pub static *_TABLE: &ConvertedTable = comptime_table!(*_PARAMS)`, and -// `Arguments::parse` enters via `clap::parse_with_table`, so the startup -// hot path (`bun --version`, `bun run …`) never touches the heap-backed -// `for_params` / `build` / `Mutex` registry below. That path is retained -// only for the cold non-startup callers (`bun create`, `bun install`) -// that still pass a raw `&'static [Param]`. -// ───────────────────────────────────────────────────────────────────────────── - use bun_core::strings::const_bytes_eq as bytes_eq; #[inline] @@ -137,16 +102,6 @@ pub const fn convert_params_array(params: &[Param]) -> [ out } -/// Compile-time name → converted-param index. This is the Rust analogue of -/// Zig's `inline for` `findParam` and is intended to be called inside a -/// `const { }` block so the loop folds to a literal: -/// -/// ```ignore -/// const IDX: usize = find_param_index(TABLE.converted, b"--help"); -/// ``` -/// -/// Panics (at const-eval, i.e. a build error) if `name` is not in `converted` -/// — matching Zig's `@compileError("no param '…'")`. pub(crate) const fn find_param_index(converted: &[Param], name: &[u8]) -> usize { if name.len() > 2 && name[0] == b'-' && name[1] == b'-' { let (_, key) = name.split_at(2); @@ -197,11 +152,6 @@ pub const fn count_long_entries(params: &[Param]) -> usize { n } -/// Build the sorted-by-hash long-name → param-index lookup at compile time. -/// `M` must equal [`count_long_entries`]`(params)`. Index `i` corresponds 1:1 -/// with `convert_params_array(params)[i]` (both preserve input order). Uses -/// insertion sort — `M` is bounded by the number of CLI flags (~120) and this -/// runs at const-eval, never at runtime. pub const fn build_long_index(params: &[Param]) -> [LongEntry; M] { let mut out = [LongEntry { hash: 0, idx: 0 }; M]; let mut w = 0; @@ -280,10 +230,6 @@ pub struct LongEntry { idx: u16, } -/// Pre-converted param table. Either fully `const`-built via -/// [`comptime_table!`](crate::comptime_table) (rodata, zero runtime cost) or -/// lazily built once per unique input slice via [`ConvertedTable::for_params`] -/// and interned for the process lifetime. pub struct ConvertedTable { pub converted: &'static [Param], pub n_flags: usize, @@ -297,11 +243,6 @@ pub struct ConvertedTable { } impl ConvertedTable { - /// Build a table entirely at compile time. All four arguments come from - /// the `const fn`s above via [`comptime_table!`](crate::comptime_table), - /// so the converted param array, category counts, sorted long-name hash - /// index, and short-name direct index all land in rodata — full Zig - /// `ComptimeClap` parity with zero runtime work. pub const fn from_const( converted: &'static [Param], n_flags: usize, @@ -319,12 +260,6 @@ impl ConvertedTable { } } - /// Look up (or build + intern) the converted table for a `'static` param - /// slice. **Cold path** — the startup hot set (`Arguments::parse`) goes - /// through [`comptime_table!`](crate::comptime_table) + - /// [`ComptimeClap::parse_with_table`] and never reaches this. Kept for the - /// handful of non-startup callers (`bun create`, `bun install`) that still - /// hand a raw `&'static [Param]` to `clap::parse`. #[cold] pub fn for_params(params: &'static [Param]) -> &'static ConvertedTable { let key = (params.as_ptr() as usize, params.len()); @@ -399,12 +334,6 @@ impl ConvertedTable { }); } } - // Insertion sort by hash (matches the const-eval `build_long_index` - // path). `long.len()` is bounded by the per-command flag count (~tens), - // and this is a one-shot cold operation, so an O(n²) insertion sort is - // free — and it avoids dragging the generic `slice::sort_unstable` - // (pdqsort) instantiation onto the `bun install` / `bun create` arg - // path, which is the only place this `#[cold]` builder runs. { let mut j = 1; while j < long.len() { @@ -430,10 +359,6 @@ impl ConvertedTable { })) } - /// Runtime name resolution. O(1) for shorts, O(log n) for longs (with a - /// final byte-compare to reject hash collisions). Const-built tables now - /// carry a rodata `long_index` too, so the linear-scan fallback is only - /// reachable from a hand-rolled `from_const(.., &[])`. #[inline] fn find(&self, name: &[u8]) -> &'static Param { if name.len() == 2 && name[0] == b'-' { @@ -517,28 +442,11 @@ pub struct ComptimeClap { pub pos: Box<[&'static [u8]]>, pub passthrough_positionals: Box<[&'static [u8]]>, // `mem.Allocator param` field deleted — global mimalloc (see PORTING.md §Allocators). - - // Zig captures `converted_params` as a comptime const on the returned type. Rust - // carries it as a `&'static` table — either rodata (`comptime_table!`) or - // interned-once via the ptr-keyed registry — so `flag`/`option` resolve via - // hashed lookup instead of an O(n) scan, and no per-parse `Vec` is allocated. table: &'static ConvertedTable, _id: PhantomData, } impl ComptimeClap { - /// `iter` must yield `&'static [u8]` (process-lifetime args, e.g. `OsIterator`) - /// because parsed values are stored by reference. - /// - /// `params` must be `'static` (every in-tree table is a `static`/`const` - /// item); the converted form is interned once per unique slice. - /// - /// **Cold path** — the startup hot set goes through - /// [`comptime_table!`](crate::comptime_table) + [`parse_with_table`] - /// (`bun --version`, `bun run …`). Only non-startup callers that still hand a - /// raw `&'static [Param]` (`bun install`, `bun create`) reach this, so - /// it's marked `#[cold]` to keep the runtime-conversion machinery - /// (`for_params` registry, `build`) out of the startup hot cluster. #[cold] pub fn parse( params: &'static [Param], @@ -636,11 +544,6 @@ impl ComptimeClap { }) } - // Zig `deinit` only freed `multi_options[*]` and `pos` (not `passthrough_positionals` — - // likely a leak in the deprecated Zig). All are owned here, so `Drop` handles it; - // body deleted per PORTING.md §Idiom map (`pub fn deinit` → `impl Drop`, empty body - // when it only frees owned fields). - #[inline] pub fn flag(&self, name: &[u8]) -> bool { let param = self.table.find(name); diff --git a/src/clap/lib.rs b/src/clap/lib.rs index 5682f644186..53cf2c03c84 100644 --- a/src/clap/lib.rs +++ b/src/clap/lib.rs @@ -18,12 +18,6 @@ pub use streaming::StreamingClap; #[doc(hidden)] pub use bun_clap_macros::{__parse_param_impl, __parse_params_impl}; -/// Parse a single param spec string (e.g. `"-h, --help Display this help"`) -/// into a const `Param` literal at compile time. This is the Rust -/// equivalent of Zig's comptime `clap.parseParam(...) catch unreachable`. -/// -/// The argument **must** be a string literal; parse errors surface as compile -/// errors at the call site. #[macro_export] macro_rules! parse_param { ($lit:literal $(,)?) => { @@ -40,12 +34,6 @@ macro_rules! param { }; } -/// Const-time `Param` slice concatenation — the Rust analogue of Zig's -/// comptime `a ++ b ++ c` over param tables. Produces a `&'static [Param]` -/// baked into rodata; no `LazyLock`, no heap, no init closure in `.text`. -/// -/// Every `$part` must be a `const`-evaluable `&[Param]` (a `const` item or -/// `&[literal, …]`); referencing a `static` is rejected by const-eval (E0013). #[macro_export] macro_rules! concat_params { ($($part:expr),* $(,)?) => {{ @@ -56,43 +44,8 @@ macro_rules! concat_params { }}; } -/// Build a `&'static ConvertedTable` from a const-evaluable -/// `&[Param]` at compile time — the Rust analogue of Zig's -/// `ComptimeClap(Id, params)` type-generator. The converted `[Param; N]` -/// array, the three category counts, the short-name index, *and* the sorted -/// long-name hash index all land in rodata, so [`parse_with_table`] does no -/// heap allocation, no sorting, no locking, and `args.flag(b"--foo")` -/// resolves via O(log n) binary search at runtime (or O(1) when paired with -/// [`comptime::find_param_index`] inside `const { }`). -/// -/// ```ignore -/// pub const AUTO_PARAMS: &[Param] = concat_params!(...); -/// pub static AUTO_TABLE: &ConvertedTable = comptime_table!(AUTO_PARAMS); -/// ``` -/// -/// Pass `, cold` for every table off the trivial-script / `bun --version` -/// cold-start path (`bun run` / `bun build` / `bun test` / `bun install` / -/// `bun pm` / `bun x` / …) so it stays in plain `.rodata` instead of padding -/// the contiguous `.rodata.startup` run (see the per-arm notes below): -/// -/// ```ignore -/// pub static RUN_TABLE: &ConvertedTable = comptime_table!(RUN_PARAMS, cold); -/// ``` #[macro_export] macro_rules! comptime_table { - // Hot table — the default-command param table that `bun ` and - // `bun --version` dereference on cold start. Cluster every nested `__CONV` / - // `__LONG` / `__TABLE` static into `.rodata.startup`: with - // `-Zfunction-sections` each `static` otherwise lands in its own - // `.rodata.` input section that fat-LTO emits in crate-alphabetical - // order — one minor fault per scattered table on first touch. Pinning them - // adjacent lets the trivial-script cold path fault the converted arrays, - // long-name index, and `ConvertedTable` header in with one shared - // fault-around window. Non-PIE `bun` has zero runtime relocations, so these - // stay in plain rodata even with the `&'static [u8]` help strings they point - // at. Linux-only: the section-name syntax is ELF-specific (mirrors - // `bun_core::err!`'s `.bun_err` clustering). Use sparingly — only - // `AUTO_TABLE` should take this arm; everything else passes `, cold`. ($params:expr) => { $crate::comptime_table!( @build @@ -100,12 +53,6 @@ macro_rules! comptime_table { $params ) }; - // Cold tables — `bun run` / `bun build` / `bun test` / `bun install` / - // `bun pm` / `bun x` and friends. `.rodata.startup` is deliberately one - // contiguous block faulted in with a single read-around on every cold start - // (including `bun --version`); padding it with param tables those paths - // never touch only grows that run. Leave these in plain `.rodata` — - // `src/startup.order` still clusters the ones a sampled cold path hits. ($params:expr, cold) => { $crate::comptime_table!(@build { } $params) }; @@ -176,15 +123,6 @@ pub const fn __param_slices_concat(parts: &[&[Param]]) -> out } -/// Parse a `;`-separated list of param spec strings into a -/// `&'static [Param]` at compile time. -/// -/// ```ignore -/// static PARAMS: &[Param] = parse_params! { -/// "-h, --help Display this help"; -/// "-v, --version Print the version"; -/// }; -/// ``` #[macro_export] macro_rules! parse_params { ($($lit:literal);* $(;)?) => { @@ -268,27 +206,6 @@ pub enum Values { OneOptional, } -/// Represents a parameter for the command line. -/// Parameters come in three kinds: -/// * Short ("-a"): Should be used for the most commonly used parameters in your program. -/// * They can take a value three different ways. -/// * "-a value" -/// * "-a=value" -/// * "-avalue" -/// * They chain if they don't take values: "-abc". -/// * The last given parameter can take a value in the same way that a single parameter can: -/// * "-abc value" -/// * "-abc=value" -/// * "-abcvalue" -/// * Long ("--long-param"): Should be used for less common parameters, or when no single -/// character can describe the paramter. -/// * They can take a value two different ways. -/// * "--long-param value" -/// * "--long-param=value" -/// * Positional: Should be used as the primary parameter of the program, like a filename or -/// an expression to parse. -/// * Positional parameters have both names.long and names.short == None. -/// * Positional parameters must take a value. #[derive(Clone, Copy)] pub struct Param { pub id: Id, @@ -308,12 +225,6 @@ impl Default for Param { } } -// NOTE: the runtime spec parser (`fn parse_param` / `parse_long_names` / -// `parse_param_rest` / `ParseParamError` / `TokenizeAny`) was removed — the -// canonical implementation lives in `bun_clap_macros` and runs at compile time -// via `parse_param!` / `param!` / `parse_params!` above. All param specs are -// string literals, so there is no runtime caller. - #[cfg(test)] fn expect_param(expect: Param, actual: Param) { assert_eq!(expect.id.msg, actual.id.msg); @@ -327,11 +238,6 @@ fn expect_param(expect: Param, actual: Param) { } } -/// Optional diagnostics used for reporting useful errors -// PORT NOTE: Zig `Diagnostic` borrows `arg`/`name.long` from the arg iterator. Rust -// can't tie that lifetime through `&mut Diagnostic` without invariance headaches, and -// this is an error-path-only struct, so it owns its bytes instead. The `name: Names` -// field is flattened to `short`/`long` because `Names.long` is `&'static`. #[derive(Default)] pub struct Diagnostic { pub arg: Vec, @@ -340,12 +246,6 @@ pub struct Diagnostic { } impl Diagnostic { - /// Default diagnostics reporter when all you want is English with no colors. - /// Use this as a reference for implementing your own if needed. - /// - /// Error path only — never reached on the trivial-script / `bun -p` cold - /// start. `#[cold]` + `#[inline(never)]` so the formatting machinery lands - /// in `.text.unlikely`, away from the cold-start working set. #[cold] #[inline(never)] pub fn report(&self, _stream: W, err: bun_core::Error) -> Result<(), bun_core::Error> { @@ -396,15 +296,7 @@ impl Diagnostic { #[derive(Clone, Copy)] pub struct Help { - /// The description text exactly as written in the param spec — may still - /// contain `` colour markup. Used by [`help`]/[`help_ex`], which (like - /// Zig's `clap.help`) emit it verbatim, and as the source for the ANSI form - /// built lazily by [`pretty_help_desc`] on the `bun --help` colour path. pub msg: &'static [u8], - /// `msg` with `` colour markup stripped — the non-TTY / piped help form. - /// Precomputed at compile time by `parse_param!` (the strip is the only - /// transform that needs to be ready without a TTY); the ANSI-coloured form is - /// derived from `msg` on demand instead of being baked into rodata. pub msg_plain: &'static [u8], pub value: &'static [u8], } @@ -428,29 +320,12 @@ pub struct ParseOptions<'a> { pub stop_after_positional_at: usize, } -// Help/usage/error rendering — none of this is on the cold-start hot chain -// (`bun -p '1+1'` / `bun --version` / plain `bun ` never call into the -// help or diagnostics path). Mark every entry point `#[cold]` + `#[inline(never)]` -// so rustc emits them in `.text.unlikely.*` sections that the linker clusters -// together, keeping them out of the pages faulted in on a normal invocation. #[cold] #[inline(never)] fn get_help_simple(param: &Param) -> &'static [u8] { param.id.msg } -/// The param description with `` colour markup resolved — ANSI escapes when -/// stdout is a colour-capable TTY, stripped otherwise. -/// -/// The tag-stripped form ([`Help::msg_plain`]) is precomputed in rodata; the -/// ANSI form is *not* — it is rewritten from [`Help::msg`] here, only when colour -/// output is actually requested. That ``→ANSI rewrite only ever runs on -/// `bun --help` / `bun run --help`; `--print` and ordinary runs never reach this -/// path, so they pay neither the per-invocation reparse nor the extra rodata a -/// baked-in `msg_ansi` array would cost on every flag and subcommand. (Zig did -/// the rewrite at `comptime` via `Output.prettyFmt` inside -/// `clap.simpleHelpBunTopLevel`; the colour case is rare enough that doing it -/// lazily at runtime is the better trade for binary size.) #[cold] #[inline(never)] fn pretty_help_desc(param: &Param) -> std::borrow::Cow<'static, [u8]> { @@ -504,12 +379,6 @@ impl Args { } } -/// Same as `parse_ex` but uses the `args::OsIterator` by default. -/// -/// **Cold path** — the startup hot set uses [`parse_with_table`] against a -/// rodata [`comptime_table!`]. This entry point performs a runtime conversion -/// (`ConvertedTable::for_params`) and is only used by non-startup commands -/// (`bun install`, `bun create`), so it's `#[cold]` + `#[inline(never)]`. #[cold] #[inline(never)] pub fn parse( @@ -554,10 +423,6 @@ pub fn parse_with_table( Ok(Args { clap, exe_arg }) } -/// Parses the command line arguments passed into the program based on an -/// array of `Param`s. -/// -/// **Cold path** — see [`parse`]; the startup hot path is [`parse_with_table`]. #[cold] #[inline(never)] pub fn parse_ex( @@ -572,12 +437,6 @@ where ComptimeClap::::parse(params, iter, opt) } -/// Will print a help message in the following format: -/// -s, --long helpText -/// -s, helpText -/// -s helpText -/// --long helpText -/// --long helpText #[cold] #[inline(never)] pub fn help_full( @@ -823,12 +682,6 @@ pub fn simple_help(params: &[Param]) { let spaces_after = vec![b' '; num_spaces_after]; simple_print_param(param).expect("unreachable"); - // Zig's `Output.pretty(" {s} {s}", …)` (clap.zig:567) only runs prettyFmt - // over the comptime template, so `` markers inside `desc_text` leak - // through verbatim there. That is observably wrong (`bun run --help` prints - // literal `$cwd`); `pretty_help_desc` resolves the `` markup - // (ANSI on a colour TTY, stripped otherwise) so `--help` output is - // tag-clean regardless of which helper a command uses. let desc = pretty_help_desc(param); Output::pretty(format_args!( " {} {}", @@ -841,10 +694,6 @@ pub fn simple_help(params: &[Param]) { #[cold] #[inline(never)] pub fn simple_help_bun_top_level(params: &[Param]) { - // TODO(port): Zig evaluates `computed_max_spacing` at `comptime` and emits - // `@compileError` on overflow, plus uses `inline for` + comptime string - // concat (`space_buf[..n] ++ desc_text`). None of that is const-evaluable - // in Rust over a slice param. Runtime equivalent below; could macro-gen. const MAX_SPACING: usize = 30; const SPACE_BUF: &[u8; MAX_SPACING] = b" "; @@ -866,10 +715,6 @@ pub fn simple_help_bun_top_level(params: &[Param]) { let total_len = param_display_width(param); let num_spaces_after = MAX_SPACING - total_len; - // Zig: Output.pretty(space_buf[0..n] ++ desc_text, .{}) — the concat - // is the *format string*, so `` markers inside `desc_text` are - // rewritten at `comptime`. Mirror that via `pretty_help_desc`, which - // resolves the markup (ANSI on a colour TTY, stripped otherwise). let desc = pretty_help_desc(param); Output::pretty(format_args!( "{}{}", @@ -888,11 +733,6 @@ pub fn help(stream: &mut W, params: &[Param]) -> Result<(), help_ex(stream, params, get_help_simple, get_value_simple) } -/// Will print a usage message in the following format: -/// [-abc] [--longa] [-d ] [--longb ] -/// -/// First all none value taking parameters, which have a short name are -/// printed, then non positional parameters and finally the positinal. #[cold] #[inline(never)] pub fn usage_full( diff --git a/src/clap/streaming.rs b/src/clap/streaming.rs index 4a29582339b..67f9194b3da 100644 --- a/src/clap/streaming.rs +++ b/src/clap/streaming.rs @@ -51,13 +51,6 @@ struct ArgInfo<'a> { kind: ArgKind, } -/// A command line argument parser which, given an ArgIterator, will parse arguments according -/// to the params. StreamingClap parses in an iterating manner, so you have to use a loop together with -/// StreamingClap.next to parse all the arguments of your program. -/// -/// `'p` is the borrow lifetime (params/iter/diagnostic); `'a` is the arg-data lifetime -/// yielded by `ArgIterator` (e.g. `'static` for `OsIterator`). Splitting them lets -/// callers use a locally-borrowed param table with process-lifetime argv. pub struct StreamingClap<'p, 'a, Id, ArgIterator> { pub params: &'p [clap::Param], pub iter: &'p mut ArgIterator, diff --git a/src/clap_macros/lib.rs b/src/clap_macros/lib.rs index ba1390de1d7..3a9476cc733 100644 --- a/src/clap_macros/lib.rs +++ b/src/clap_macros/lib.rs @@ -15,13 +15,6 @@ use syn::parse::{Parse, ParseStream}; use syn::punctuated::Punctuated; use syn::{LitByteStr, LitStr, Path, Token, parse_macro_input}; -// ───────────────────────────────────────────────────────────────────────────── -// Parsed representation — build-time IR; the public `Param` / `Names` / -// `Values` / `Help` shapes live in `bun_clap` (this proc-macro crate cannot -// depend on it without a cycle, and the public types borrow `&'static` data -// that does not exist at macro-expansion time). -// ───────────────────────────────────────────────────────────────────────────── - #[derive(Clone, Copy, PartialEq, Eq)] enum Values { None, @@ -264,15 +257,6 @@ fn byte_str_b(s: &[u8]) -> LitByteStr { LitByteStr::new(s, Span::call_site()) } -/// 1:1 port of `bun_core::output::pretty_fmt_runtime` — rewrites Bun's `` -/// colour markup to ANSI escape sequences when `is_enabled`, or strips it when -/// not. Run here at macro-expansion time with `is_enabled = false` so each param -/// description's tag-stripped form (`Help::msg_plain`) is a `const` byte literal -/// in rodata. The ANSI form is *not* baked in — it is rare (only `bun --help` on -/// a colour TTY) and would otherwise roughly triple the help-string rodata, so -/// `bun_clap::pretty_help_desc` derives it from `Help::msg` on demand instead. -/// (Zig did the equivalent rewrite at `comptime` via `Output.prettyFmt` inside -/// `clap.simpleHelpBunTopLevel`.) fn pretty_rewrite(fmt: &[u8], is_enabled: bool) -> Vec { use bun_output_tags::{RESET, color_for_bytes}; let mut out: Vec = Vec::with_capacity(fmt.len() * 2); @@ -321,12 +305,6 @@ fn pretty_rewrite(fmt: &[u8], is_enabled: bool) -> Vec { is_reset = true; "" } else { - // Unknown tag: Zig's comptime `prettyFmt` would `@compileError` - // here, but `pretty_fmt_runtime` (the path this replaces) drops - // it silently and so does Zig's actual `clap.simpleHelp`. Match - // the lenient runtime behaviour — a compile error would be - // stricter than what shipped, and param specs don't carry - // unknown tags anyway. "" }; if is_enabled { diff --git a/src/collections/StaticHashMap.rs b/src/collections/StaticHashMap.rs index c37c17d0553..04c47c1dfa4 100644 --- a/src/collections/StaticHashMap.rs +++ b/src/collections/StaticHashMap.rs @@ -47,12 +47,6 @@ impl Entry { K: Copy + Default, V: Copy + Default, { - // PORT NOTE: Zig used `std.mem.zeroes(K)` / `undefined` — key/value of - // an empty entry (hash == EMPTY_HASH) are never read. Rust cannot use - // `mem::zeroed()` here: K may be `&[u8]` (or any `Copy` type with a - // niche), for which all-zero bytes violate the validity invariant - // regardless of whether the value is later read. Use `Default` for the - // unread placeholder instead. Self { hash: EMPTY_HASH, key: K::default(), @@ -108,10 +102,6 @@ pub const fn static_slots(capacity: usize) -> usize { // StaticHashMap // ────────────────────────────────────────────────────────────────────────── -// PORT NOTE: the inline `[Entry; CAPACITY + overflow]` array length depends on -// a const fn of `CAPACITY`, which requires nightly `feature(generic_const_exprs)`. -// Stable workaround (same as ArrayBitSet): callers pass `SLOTS = static_slots(CAPACITY)` -// as a second const param; a const-assert in `Default::default()` checks they match. pub struct StaticHashMap< K, V, diff --git a/src/collections/array_hash_map.rs b/src/collections/array_hash_map.rs index 00084593249..8a3a970494d 100644 --- a/src/collections/array_hash_map.rs +++ b/src/collections/array_hash_map.rs @@ -70,11 +70,6 @@ pub struct AutoContext; impl ArrayHashContext for AutoContext { #[inline] fn hash(&self, key: &K) -> u32 { - // Keys here are small POD (`Ref`, `u32`, indices). FxHash is a single - // mul+rotate per word — measurably cheaper than wyhash's `mum` fold for - // 8-byte keys, and what rustc uses for the same workload shape. The Zig - // port used wyhash only because that's what `std.array_hash_map` defaults - // to; nothing persists these hashes across runs. use core::hash::Hasher; let mut h = rustc_hash::FxHasher::default(); key.hash(&mut h); @@ -143,11 +138,6 @@ impl<'a> CaseInsensitiveAsciiPrehashed<'a> { } } -/// Lifts an `ArrayHashContext<[u8]>` to operate on `Box<[u8]>` keys by -/// delegating to the underlying byte slice. Used as the inner context for -/// `StringArrayHashMap` so methods reached via `Deref` (e.g. `put_no_clobber`, -/// `remove`, `entry`) compute the *same* u32 hash as the wrapper's -/// `&[u8]`-taking methods — otherwise the two paths disagree and lookups miss. #[derive(Clone, Copy)] pub struct BoxedSliceContext(C); @@ -186,10 +176,6 @@ impl ArrayHashContext<[u8]> for CaseInsensitiveAsciiStringContext { // GetOrPutResult / Entry / Iterator // ────────────────────────────────────────────────────────────────────────── -/// Result of `get_or_put*`. When `found_existing == false`, `*value_ptr` is a -/// freshly-defaulted slot the caller is expected to overwrite (Zig leaves it -/// `undefined`; Rust cannot, so the value type carries a `Default` bound on the -/// inserting paths). pub struct GetOrPutResult<'a, K, V> { pub found_existing: bool, pub index: usize, @@ -263,42 +249,14 @@ pub trait ArrayHashMapExt { // ArrayHashMap // ────────────────────────────────────────────────────────────────────────── -/// Zig `index_header` threshold: at or below this many entries the -/// hash-prefiltered linear scan over `hashes` wins (the whole `Vec` fits -/// in one cache line); above it we build/maintain the SwissTable index. Same -/// `linear_scan_max` cut-off as `std/array_hash_map.zig`. const INDEX_THRESHOLD: usize = 8; -/// Widen the cached `u32` entry hash to the `u64` hashbrown probes with. The -/// SwissTable control byte is `h2 = top 7 bits of the 64-bit hash`; if we fed -/// the raw `u32` zero-extended, every entry would land in the same h2 group -/// and probing would degrade to a scan. Splitting the low/high halves into -/// both lanes keeps h2 well-distributed without rehashing the key. #[inline(always)] const fn spread_hash(h: u32) -> u64 { let h = h as u64; h | (h.wrapping_mul(0x9E37_79B9).wrapping_shl(32)) } -// ── Non-generic index-accelerator helpers ──────────────────────────────────── -// -// The `hashbrown::HashTable` index is keyed purely by `self.hashes` -// (a `&[u32]`); nothing about it depends on `K`, `V`, `C`, or `A`. The -// previous shape — inline `|&j| spread_hash(self.hashes[j as usize])` closures -// inside the generic `push_entry` / `rebuild_index` — meant -// `hashbrown::raw::RawTable::reserve_rehash` (which is monomorphic over -// the closure type) was re-instantiated in every downstream crate that touched -// an `ArrayHashMap`: bun_css, bun_dotenv, bun_md, bun_install each emitted -// their own physically-distinct copy, ICF could not fold them (per-CGU codegen -// differs in size), and the linker scattered those hot copies into cold 64 KB -// code pages (encoding_rs / bun_css `process_doc` / bun_sql_jsc), pulling -// ~320 KB of otherwise-cold code resident on startup. -// -// Hoisting the closure into one named return-position `impl Fn` and routing -// every insert/rebuild through `#[inline(never)]` free fns collapses all of -// that to a single `reserve_rehash` monomorph emitted once in this crate's -// CGU, where `src/startup.order` can place it. - /// The one rehasher closure type for the `HashTable` accelerator. /// Return-position `impl Fn` names a single concrete type, so every /// `insert_unique`/`reserve` call below shares one `RawTable` grow path. @@ -307,12 +265,6 @@ fn index_rehasher(hashes: &[u32]) -> impl Fn(&u32) -> u64 + '_ { move |&j| spread_hash(hashes[j as usize]) } -/// Append entry index `i` (cached hash `h`) to the accelerator. Outlined so the -/// `RawTable::{insert_unique, reserve_rehash}` it monomorphizes live in -/// `bun_collections` instead of being cloned into every generic -/// `push_entry::` instantiation downstream. The extra call frame is -/// ~5 cycles against a ~30-cycle SwissTable insert; the win is one hot copy -/// the linker can order instead of N scattered ones. #[inline(never)] fn index_insert_unique( index: &mut hashbrown::HashTable>, @@ -323,14 +275,6 @@ fn index_insert_unique( index.insert_unique(spread_hash(h), i, index_rehasher(hashes)); } -/// Grow a live accelerator so it can hold `target` entries without a further -/// `RawTable` rehash. Outlined for the same reason as -/// [`index_insert_unique`] — keep the `reserve_rehash` monomorph in this crate -/// rather than re-emitting it per `` instantiation. Called from the -/// `reserve` / `ensure_*_capacity` paths so a caller that pre-sizes the map -/// (the Zig originals' `ensureTotalCapacityContext`, which also sizes the index -/// header) pays the SwissTable grow once instead of `O(log n)` times across the -/// following `push_entry` loop. #[inline(never)] fn index_reserve( index: &mut hashbrown::HashTable>, @@ -343,19 +287,6 @@ fn index_reserve( } } -/// Build a fresh `hash → entry index` accelerator from a cached-hash column, -/// pre-sized to `capacity` (clamped up to the number of entries already -/// present). Passing the owning map's *column capacity* here — not just -/// `hashes.len()` — means a map that was `reserve()`d up front gets an index -/// big enough for its final size the moment it first crosses -/// [`INDEX_THRESHOLD`], so the per-`push_entry` SwissTable grow path never -/// runs again. -/// -/// Free fn (no `K`/`V`/`C` in scope) + `#[inline(never)]` so this — and the -/// `HashTable::with_capacity` / grow path inside it — is one symbol shared by -/// every `ArrayHashMap` instantiation that uses the same `A` (only two ZST -/// allocators exist today: `DefaultAlloc` and `AstAlloc`). Boxed so the caller -/// can store it as `Option>` (8 B header vs the 32 B inline `HashTable`). #[cold] #[inline(never)] fn rebuild_index_from_hashes( @@ -372,24 +303,9 @@ fn rebuild_index_from_hashes( Box::new_in(table, A::default()) } -/// Shorthand for the allocator bound every `ArrayHashMap`/`StringArrayHashMap` -/// `impl` block needs: `core::alloc::Allocator` for the `Vec` -/// columns and the per-key `Box<[u8], A>`; `Clone` so `Vec`/`Box` can clone -/// their allocator on resize/clone; `Default` so constructors don't need an -/// `*_in(alloc: A)` variant — all current `A` (`Global`, `AstAlloc`) are ZST. pub trait MapAllocator: Allocator + Clone + Default {} impl MapAllocator for A {} -/// Bridges any `core::alloc::Allocator` `A` to the `allocator_api2` polyfill -/// trait that `hashbrown::HashTable<_, A>` is bounded on, so the index -/// accelerator's bucket array can route through `A` without `MapAllocator` -/// itself requiring `HashbrownAllocator` (orphan rules block bridging -/// `std::alloc::Global`, the default `A`, directly). -/// -/// This makes an `ArrayHashMap<_, _, _, AstAlloc>` fully arena-backed: when -/// its `Drop` never runs (the AST `MultiArrayList` slab-only-drop pattern, -/// e.g. `BundledAst` columns), nothing — columns, key boxes, index header, -/// *or* index buckets — is stranded on the global heap. #[derive(Clone, Copy, Default)] struct IndexAlloc(A); @@ -417,43 +333,12 @@ unsafe impl allocator_api2::alloc::Allocator for IndexAlloc { } } -/// Insertion-ordered hash map with contiguous key / value storage. -/// -/// `A` routes the three column `Vec`s and (for `StringArrayHashMap`) the -/// per-key `Box<[u8], A>` through the same allocator, so an -/// `ArrayHashMap<_, _, _, AstAlloc>` is bulk-freed by the AST arena's -/// `mi_heap_destroy` instead of leaking on the global heap when its owning AST -/// node never has `Drop` run (the `BabyList` pattern — same motivation as -/// `Vec` for `G::DeclList`/`PropertyList`, and -/// `StringHashMap` for `Scope::members`). The `hashbrown` index -/// accelerator stays on the global allocator; see [`MapAllocator`]. pub struct ArrayHashMap { keys: Vec, values: Vec, hashes: Vec, - /// `hash → entry index` accelerator. `None` below [`INDEX_THRESHOLD`] - /// entries. Stores `u32` indices; the table is hashed by [`spread_hash`] - /// of `self.hashes[i]` so lookups never re-hash `K`. Kept in sync with - /// the column vecs by every mutation path (patched on point removal, - /// rebuilt on permutation). Both the `Box` and the table's bucket array - /// route through `A` (via [`IndexAlloc`]), so an - /// `ArrayHashMap<_, _, _, AstAlloc>` whose `Drop` never runs (arena - /// bulk-free) strands nothing on the global heap. - /// - /// Boxed so the per-map header cost is 8 B (`Option` uses the - /// `NonNull` niche) instead of the 32 B inline `HashTable` — `Part` - /// embeds two `ArrayHashMap`s, so the inline shape alone added +48 B to - /// every `Part` and doubled the `Vec` grow `memmove`s the bundler - /// page-faults on. The box is allocated once, lazily, at the - /// `INDEX_THRESHOLD` crossover. index: Option>, A>>, ctx: C, - // Zig `pointer_stability: std.debug.SafetyLock` — debug-only re-entrancy - // guard around operations that may invalidate entry pointers. `AtomicBool` - // (not `Cell`) so the field doesn't strip `Sync` off the map in - // debug builds — a debug-only diagnostic must not change the type's - // auto-trait surface vs release (callers store maps in `static LazyLock`, - // e.g. `bundler::options::DEFAULT_LOADERS_BUN`). #[cfg(debug_assertions)] pointer_stability: core::sync::atomic::AtomicBool, } @@ -579,11 +464,6 @@ impl ArrayHashMap { self.drop_index(); } - /// Zig `ensureTotalCapacityContext`: same as `ensure_total_capacity` but - /// takes an explicit `ctx` for the stored key type. This port maintains no - /// separate index header (lookup scans the cached `hashes` vec), so the - /// context is accepted and ignored — capacity reservation is purely a Vec - /// operation here. #[inline] pub fn ensure_total_capacity_context( &mut self, @@ -593,12 +473,6 @@ impl ArrayHashMap { self.ensure_total_capacity(n) } - /// Zig `putAssumeCapacityContext`: insert/replace using an externally-supplied - /// hash/eql context instead of the stored `C`. Used when `C = AutoContext` - /// can't satisfy `K: Hash` (e.g. `bun_semver::String`, whose hash needs the - /// owning `arg_buf`/`existing_buf`). Takes closures rather than an - /// `ArrayHashAdapter` so callers with inherent-method contexts (no trait - /// impl, by-value receivers) don't need a wrapper struct. pub fn put_assume_capacity_context( &mut self, key: K, @@ -633,12 +507,6 @@ impl ArrayHashMap { self.reserve_index_to_capacity(); } - /// If the accelerator is already live, grow it to the current column - /// capacity so a `reserve()` / `ensure_*_capacity()` call also right-sizes - /// the index — keeping its SwissTable grow path off the subsequent - /// `push_entry` loop. No-op when the index hasn't materialised yet (it will - /// be built at the right size by [`rebuild_index`] when the map first - /// crosses [`INDEX_THRESHOLD`], since that reads `self.keys.capacity()`). #[inline] fn reserve_index_to_capacity(&mut self) { let cap = self.keys.capacity(); @@ -731,11 +599,6 @@ impl ArrayHashMap { self.keys.clear(); self.values.clear(); self.hashes.clear(); - // Drop (not clear) the accelerator: post-clear `len == 0` is below - // `INDEX_THRESHOLD`, so per the threshold invariant `self.index` must be - // `None` — otherwise the next few `push_entry` calls would maintain a - // hashbrown probe for a 1–8-entry map that the linear scan handles in - // one cache line. self.index = None; } @@ -747,19 +610,11 @@ impl ArrayHashMap { self.clear_retaining_capacity(); } - /// std-HashMap-compat: shared iteration over `(key, value)` pairs in - /// insertion order. Distinct from [`iterator`](Self::iterator) which yields - /// mutable `Entry { key_ptr, value_ptr }` (Zig shape) and requires - /// `&mut self`. #[inline] pub fn iter(&self) -> core::iter::Zip, core::slice::Iter<'_, V>> { self.keys.iter().zip(self.values.iter()) } - /// Zig `getIndexContext` for callers whose context is an inherent-method - /// struct (no `ArrayHashAdapter` impl). Takes the precomputed `u32` hash - /// plus an `eql` closure so e.g. `bun_semver::String::ArrayHashContext` - /// (which needs `arg_buf`/`existing_buf`) can drive a `&self` lookup. #[inline] pub fn get_index_adapted_raw bool>(&self, h: u32, eq: F) -> Option { self.find_hash(h, eq) @@ -818,16 +673,6 @@ impl ArrayHashMap { i } - /// Rebuild the `hash → index` accelerator from `self.hashes`. Called when - /// the entry count first crosses [`INDEX_THRESHOLD`]. Thin wrapper over - /// the non-generic [`rebuild_index_from_hashes`] free fn — the body has no - /// dependence on `K`/`V`/`C`/`A`, so keep it out of the generic impl to - /// avoid one monomorph per instantiating crate. - /// - /// `#[cold]` (not `#[inline]`): this fires exactly once per map lifetime — - /// the threshold-crossing transition — so weighting its arm in `push_entry` - /// as unlikely keeps the hot `Some(index)` / `None => {}` arms' codegen - /// tight and out of the boot-path `.text` working set. #[cold] fn rebuild_index(&mut self) { self.index = Some(rebuild_index_from_hashes( @@ -836,12 +681,6 @@ impl ArrayHashMap { )); } - /// Invalidate the accelerator. Called by operations that permute entry - /// indices wholesale (`sort`, `re_index`, bulk `set_entries_len`); paired - /// with an immediate `rebuild_index()` when the map is past the threshold - /// so subsequent lookups never silently fall back to O(n) linear scan. - /// Point removals (`pop`/`swap_remove`) instead patch the index in place - /// — see [`index_remove_tail`]/[`index_swap_remove`]. #[inline] fn drop_index(&mut self) { self.index = None; @@ -859,10 +698,6 @@ impl ArrayHashMap { } } - /// Patch the index after a `Vec::swap_remove(removed)`: drop the slot for - /// `removed`, then retarget the slot that still says `old_last` (the - /// pre-swap tail index, == `self.keys.len()` post-swap) to `removed`. - /// O(1); mirrors Zig `removeFromIndexByIndex` + `updateEntryIndex`. #[inline] fn index_swap_remove(&mut self, removed: usize, removed_hash: u32) { let Some(index) = self.index.as_deref_mut() else { @@ -882,10 +717,6 @@ impl ArrayHashMap { } } - /// Zig `ArrayHashMap.sort` — stable in-place sort of keys/values/hashes by - /// a caller-supplied index comparator. The closure receives borrows of the - /// key and value slices so it can compare on either without re-borrowing - /// `self`. pub fn sort(&mut self, mut less_than: impl FnMut(&[K], &[V], usize, usize) -> bool) { let len = self.keys.len(); if len < 2 { @@ -1123,10 +954,6 @@ impl, A: MapAllocator> ArrayHashMap { let i = self.get_index(key)?; self.keys.remove(i); self.hashes.remove(i); - // Ordered remove shifts every index ≥ i; rebuild rather than patching - // each slot. Immediate rebuild keeps subsequent lookups O(1) (Zig - // patches in place; this is the simpler-correct equivalent for the - // rare ordered path). self.drop_index(); if self.keys.len() > INDEX_THRESHOLD { self.rebuild_index(); @@ -1332,12 +1159,6 @@ impl ArrayHashMapExt for ArrayHashMap { // StringArrayHashMap — `[]const u8`-keyed wrapper // ────────────────────────────────────────────────────────────────────────── -/// `std.StringArrayHashMap(V)` / `bun.CaseInsensitiveASCIIStringArrayHashMap(V)`. -/// -/// Newtype (not an alias) so `get_or_put` / `get` / `put` can take `&[u8]` -/// borrows — the Zig API stores `[]const u8` keys and lets the caller decide -/// whether to dupe them; here keys are `Box<[u8]>` and the borrowing methods -/// box on insert. pub struct StringArrayHashMap { inner: ArrayHashMap, V, BoxedSliceContext, A>, // The string context is consulted for hash/eql on `[u8]` borrows. The inner @@ -1527,26 +1348,6 @@ impl ArrayHashMapExt for StringArrayHashMap { // StringHashMap — unordered `[]const u8`-keyed map // ────────────────────────────────────────────────────────────────────────── -/// `std.StringHashMap(V)`. Thin newtype over `hashbrown::HashMap` that adds -/// the Zig `getOrPut` / `getOrPutValue` entry points while keeping the -/// `hashbrown` surface (`.get`, `.contains_key`, `.reserve`, `.insert`, …) -/// reachable via `Deref`. -/// -/// Allocator-generic so AST containers (`Scope::members` &c.) can route both -/// the table *and* the owned-key boxes through `bun_alloc::AstAlloc`, -/// matching Zig's `Unmanaged` semantics where the map's backing store lives -/// in the same arena as the AST nodes that hold it. The `A = Global` default -/// keeps every existing `StringHashMap` site source-compatible. -// Hashed with seed-0 wyhash (matches Zig's `std.hash_map.StringContext`) — -// deterministic across runs and ~3-5× faster than `RandomState`/SipHash on -// the short identifier keys the parser/printer/renamer churn. -// -// The `A: Default` bound is the substitute for Zig's per-call `Allocator` -// parameter: hashbrown's `HashMap<_, _, _, A>` stores the allocator by value, -// and every key `Box<[u8], A>` needs its own `A` too. For zero-sized -// allocators (`Global`, `AstAlloc`) `A::default()` is a no-op constant; if a -// stateful allocator is ever needed, add `*_in(alloc: A)` constructors and -// loosen the bound there. #[derive(Clone)] pub struct StringHashMap { inner: hashbrown::HashMap, V, bun_wyhash::BuildHasher, A>, @@ -1557,41 +1358,8 @@ pub struct StringHashMap = hashbrown::HashMap, V, bun_wyhash::BuildHasher, A>; -/// Key stored in `StringHashMap`. Either an owned heap copy (`Owned`, the -/// default produced by `put`/`get_or_put`) or a borrowed `&'static [u8]` -/// (`Static`, produced by `put_static_key`). -/// -/// Zig's `std.StringHashMap` always *borrows* the caller's `[]const u8` key — -/// the map never copies it. The Rust port originally heap-boxed every key on -/// `put` for safety, which profiling showed as the dominant cost of -/// `DirEntry::add_entry` (the resolver's per-file hot path): the key bytes -/// there already live in the process-static `FilenameStore`/`EntryStore`, so -/// the `Box<[u8]>` was a redundant second copy. The `Static` variant lets such -/// callers store the existing slice directly, matching Zig's zero-copy -/// behaviour without giving up owned-key safety for everyone else. -/// -/// `Deref` + `Borrow<[u8]>` keep `.get(&[u8])`, -/// `.contains_key(&[u8])`, and `&**key` working unchanged at every call site, -/// so this is a drop-in replacement for the previous `Box<[u8], A>` alias. -/// -/// ## Layout -/// Packed `(ptr, len | OWNED_BIT)` instead of a 2-variant enum. The enum had -/// no usable niche (both `Box<[u8]>` and `&[u8]` start with a non-null -/// pointer), so it was 24 B; folding the owned/borrowed discriminant into the -/// top bit of `len` brings it to 16 B — same as Zig's `[]const u8`. For -/// `Scope::members` (`hashbrown::RawTable<(StringHashMapKey, Member)>`) that -/// shrinks the stored tuple 40 B → 32 B, cutting the module-scope table's -/// page footprint (and `reserve_rehash` `memcpy` traffic) by ~20 %. pub struct StringHashMapKey { - /// First byte of the key. Never null — empty borrowed keys use the slice's - /// own (dangling-but-non-null) pointer; empty owned keys use whatever - /// `Box::<[u8], A>::into_raw` returned, which round-trips through - /// `Box::from_raw_in` in `Drop`. ptr: core::ptr::NonNull, - /// Low `usize::BITS - 1` bits: byte length. Top bit: set ⇔ owned (heap - /// allocation made via `A`, freed in `Drop`); clear ⇔ borrowed (`'static` - /// or arena-lifetime, never freed). Slices cannot exceed `isize::MAX` - /// bytes, so the top bit is always free. len_tag: usize, _alloc: PhantomData>, } @@ -1645,10 +1413,6 @@ impl StringHashMapKey { len & SHMK_OWNED_BIT == 0, "slice len cannot exceed isize::MAX" ); - // Discard the stored `A` — for every `A` in use (`Global`, - // `DefaultAlloc`, `AstAlloc`) it is a ZST, so `A::default()` in `Drop` - // is the same instance. `into_raw_with_allocator` because on current - // nightly `Box::into_raw` is restricted to `Box`. let (raw, _alloc) = Box::into_raw_with_allocator(b); // SAFETY: `Box::into_raw_with_allocator` never returns null. let ptr = unsafe { core::ptr::NonNull::new_unchecked(raw.cast::()) }; @@ -1743,12 +1507,6 @@ impl From> for StringHashMapKey { } impl From<&'static [u8]> for StringHashMapKey { - /// Zero-copy: the slice is stored by reference. This is the conversion - /// `hashbrown::VacantEntryRef::insert` calls on miss in the - /// [`StringHashMap::put_borrowed`] / [`StringHashMap::get_or_put_borrowed`] - /// fast paths, so it must NOT allocate (the `'static` here is the - /// caller-asserted lifetime erasure, not a literal program-lifetime - /// requirement — see those methods' safety docs). #[inline] fn from(s: &'static [u8]) -> Self { Self::borrowed(s) @@ -1779,11 +1537,6 @@ impl DerefMut for String } } -/// Clone `key` into a `Box<[u8], A>` using `A::default()`. The previous -/// `Box::from(key)` spelling is `Global`-only; this is the allocator-generic -/// equivalent. For `AstAlloc` the buffer lands in the thread-local AST -/// `mi_heap` so the key is reclaimed by the same `mi_heap_destroy` that frees -/// the table and the AST node holding the map. #[inline] fn box_key(key: &[u8]) -> Box<[u8], A> { let mut v = Vec::with_capacity_in(key.len(), A::default()); @@ -1800,13 +1553,6 @@ fn owned_key(key: &[u8]) -> StringHashMapKey { } impl StringHashMap { - /// `const` constructor — empty map, no heap touch. Exists so aggregates - /// that embed a `StringHashMap` (e.g. `js_ast::Scope::EMPTY`) can be - /// spelled as a `const` and used with struct-update syntax in hot - /// allocation paths, instead of calling the `Default` chain at runtime - /// for every field. `hashbrown::HashMap::with_hasher_in` and - /// `BuildHasherDefault::new` are both `const fn`, so this is a true - /// compile-time value (all-zeros for ZST `A`). #[inline] pub const fn new_in(alloc: A) -> Self { Self { @@ -1863,27 +1609,12 @@ impl StringHashMap Ok(()) } - /// Insert `value` under `key` **without copying the key bytes**. This is - /// the zero-copy path that matches Zig's `StringHashMap.put` (which always - /// borrows). `key` is stored as `StringHashMapKey::Static`, so the caller - /// must guarantee the bytes genuinely live for `'static` — in practice - /// that means slices into a process-lifetime arena (`FilenameStore`, - /// `EntryStore`, AST heap) where the `'static` was minted via an explicit - /// `unsafe` lifetime widen at the call site. #[inline] pub fn put_static_key(&mut self, key: &'static [u8], value: V) -> Result<(), AllocError> { self.inner.insert(StringHashMapKey::borrowed(key), value); Ok(()) } - /// The hash this map's `BuildHasher` assigns `key` — exactly what - /// `get`/`insert`/&c. compute internally for a `[u8]` lookup. Exposed - /// so a caller that already has the key bytes in hand (and will probe *and* - /// then insert the same key) can hash once and feed the result to - /// [`get_hashed`] / [`put_static_key_hashed`] instead of re-deriving it on - /// each call. The resolver's `DirEntry::add_entry` does precisely this: one - /// case-insensitive probe against the previous-generation directory map, - /// one insert into the new one, same (lowercased) basename bytes. #[inline] pub fn hash_key(&self, key: &[u8]) -> u64 { use core::hash::BuildHasher; @@ -1900,10 +1631,6 @@ impl StringHashMap .map(|(_, v)| v) } - /// [`put_static_key`] with a caller-supplied hash. `hash` MUST equal - /// `self.hash_key(key)` (see [`hash_key`]); the insert trusts it without - /// recomputing. Same zero-copy / `'static`-key contract as [`put_static_key`]: - /// overwrites the value if the key is already present. #[inline] pub fn put_static_key_hashed( &mut self, @@ -1975,13 +1702,6 @@ impl StringHashMap Ok(()) } - /// Zig `getAdapted` — look up by `key` using `adapter` for hash/eql. - /// - /// The adapter's precomputed hash is ignored; the lookup falls back to the - /// normal `get(key)` path (correctness is preserved — `adapter.eql` is byte - /// equality for all current adapters). Callers that already hold a hash - /// computed with [`hash_key`] should use [`get_hashed`] instead to skip the - /// rehash. #[inline] pub fn get_adapted(&self, key: &[u8], _adapter: &C) -> Option<&V> { self.inner.get(key) @@ -1994,23 +1714,9 @@ impl StringHashMap } } -/// `StringHashMap::get_or_put` result — `std::HashMap` cannot hand out -/// `&mut K`, so this result omits `key_ptr` (unlike `GetOrPutResult` for the -/// array-backed maps). Callers that need to overwrite the stored key must use -/// `StringArrayHashMap` instead. pub use crate::hash_map::GetOrPutResult as StringHashMapGetOrPut; impl StringHashMap { - /// PERF(port): the previous shape (`contains_key` + `entry(Box::from(key))`) - /// hashed `key` twice and unconditionally heap-allocated the `Box` even on - /// hit. `Scope::members` calls this once per declared identifier during - /// parse, so on three.js that was ~thousands of redundant `Box` - /// allocations + double-hashes per file. Route through a single `entry()` - /// match; the `Box` is still allocated upfront (std `HashMap::entry` - /// requires the owned key) but on hit it is dropped without a second - /// probe. Full prehash reuse needs a `raw_entry`-style API; see - /// [`get_hashed`] / [`put_static_key_hashed`] for the existing single-hash - /// entry points. pub fn get_or_put(&mut self, key: &[u8]) -> Result, AllocError> { Ok(self.get_or_put_context_adapted(key, ())) } @@ -2076,14 +1782,6 @@ impl StringHash // StringHashMapContext + Prehashed adapters (src/bun.zig) // ────────────────────────────────────────────────────────────────────────── -/// `bun.StringHashMapContext` — wyhash(seed=0) over byte slices, full 64-bit. -/// This is the *unordered* map context (vs. `StringContext` above which -/// truncates to u32 for `ArrayHashMap`). -/// -/// PORT NOTE: spelled as a module rather than a unit struct so callers can -/// path-access the nested `Prehashed` / `PrehashedCaseInsensitive` types -/// (`StringHashMapContext::Prehashed::…`) on stable Rust, which forbids -/// inherent associated types. #[allow(non_snake_case)] pub mod StringHashMapContext { #[inline] @@ -2252,10 +1950,6 @@ impl StringSet { // StringHashMapUnowned (src/bun.zig) — pre-hashed string key // ────────────────────────────────────────────────────────────────────────── -/// `bun.StringHashMapUnowned.Key` — a string identity reduced to `(hash, len)` -/// so the map never stores the string bytes. Collisions on both fields are -/// treated as equal (matches the Zig — used for side-effects globs where a -/// false positive is acceptable). #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct StringHashMapUnownedKey { pub hash: u64, diff --git a/src/collections/array_list.rs b/src/collections/array_list.rs index 4446c5afa8d..fb9a12b69d1 100644 --- a/src/collections/array_list.rs +++ b/src/collections/array_list.rs @@ -1,16 +1,4 @@ #![forbid(unsafe_code)] -//! Managed `ArrayList` wrappers. -//! -//! PORT NOTE: The Zig original wraps `std.ArrayListAlignedUnmanaged` to add two things: -//! 1. A stored allocator (managed vs unmanaged split). -//! 2. "Deep" semantics — `deinit`/`clear`/`shrink`/`replaceRange` call `deinit` on each -//! removed item, with `*Shallow` variants that skip that. -//! -//! In Rust, (1) disappears entirely — `Vec` uses the global mimalloc allocator and the -//! `Allocator` type parameter is dropped per §Allocators in PORTING.md. (2) is the *default* -//! behavior of `Vec`: removing/dropping elements runs their `Drop`. So the "deep" methods -//! map to ordinary `Vec` operations; the `*Shallow` variants had no in-tree callers and are -//! not ported. use core::mem; @@ -18,32 +6,12 @@ use bun_alloc::AllocError; use super::vec_ext::VecExt; -/// Managed `ArrayList` using an arbitrary allocator. -/// Prefer using a concrete type, like `ArrayListDefault`. -/// -/// NOTE: Unlike Zig's `std.ArrayList`, dropping this type runs `Drop` on each of the items. -// PORT NOTE: `std.mem.Allocator` type param dropped — global mimalloc (non-AST crate). pub type ArrayList = ArrayListAlignedIn; -/// Managed `ArrayList` using the default allocator. No overhead compared to an unmanaged -/// `ArrayList`. -/// -/// NOTE: Unlike Zig's `std.ArrayList`, dropping this type runs `Drop` on each of the items. -// PORT NOTE: `bun.DefaultAllocator` type param dropped — global mimalloc. pub type ArrayListDefault = ArrayListAlignedIn; -/// Managed `ArrayList` using a specific kind of allocator. -/// -/// NOTE: Unlike Zig's `std.ArrayList`, dropping this type runs `Drop` on each of the items. -// PORT NOTE: `Allocator` type param dropped — global mimalloc. pub type ArrayListIn = ArrayListAlignedIn; -/// Managed `ArrayListAligned` using an arbitrary allocator. -/// -/// NOTE: Unlike Zig's `std.ArrayList`, dropping this type runs `Drop` on each of the items. -// TODO(port): const-generic alignment param. Rust `Vec` uses `align_of::()` and has no -// over-alignment knob; if any caller passes a non-null `alignment`, that call site needs a -// `#[repr(align(N))]` newtype wrapper around `T` instead. pub type ArrayListAligned = ArrayListAlignedIn; /// Managed `ArrayListAligned` using the default allocator. @@ -51,11 +19,6 @@ pub type ArrayListAligned = ArrayListAlignedIn; /// NOTE: Unlike Zig's `std.ArrayList`, dropping this type runs `Drop` on each of the items. pub type ArrayListAlignedDefault = ArrayListAlignedIn; -/// Managed `ArrayListAligned` using a specific kind of allocator. -/// -/// NOTE: Unlike Zig's `std.ArrayList`, dropping this type runs `Drop` on each of the items. -// PORT NOTE: Zig's `fn(...) type` factory → generic struct (PORTING.md §Idiom map). -// Allocator type param dropped; alignment param dropped (see ArrayListAligned TODO above). #[derive(Default)] pub struct ArrayListAlignedIn { /// Zig: `#unmanaged: Unmanaged = .empty` @@ -159,11 +122,6 @@ impl ArrayListAlignedIn { Ok(self.unmanaged.into_boxed_slice()) } - /// Creates a copy of this `ArrayList` with copies of its items. - /// - /// PORT NOTE: Zig makes *bitwise* (shallow) copies regardless of whether `T` has a - /// `deinit`. Rust cannot bit-copy a non-`Copy` `T` safely. This is bound on `T: Clone`; - /// callers that relied on shallow-copy-then-`deinitShallow` need a redesign. pub fn clone(&self) -> Result where T: Clone, @@ -337,18 +295,10 @@ impl ArrayListAlignedIn { self.unmanaged.extend(core::iter::repeat_n(value, n)); } - /// If `new_len` is less than the current length, this method will `Drop` the removed items. - /// - /// If `new_len` is greater than the current length, note that this creates copies of - /// `init_value`. pub fn resize(&mut self, init_value: T, new_len: usize) -> Result<(), AllocError> where T: Clone, { - // PORT NOTE: Zig calls `resizeWithoutDeinit` first, *then* deinits the tail via a raw - // pointer past `len` (`items().ptr[new_len..len]`). That ordering is to avoid a failed - // realloc leaving already-deinited items in the list. `Vec::resize` already drops the - // truncated tail in the shrink case and never fails, so the ordering concern vanishes. self.unmanaged.resize(new_len, init_value); Ok(()) } @@ -450,10 +400,4 @@ impl ArrayListAlignedIn { // Zig `getStdAllocator` — dropped (no allocator field). } -// PORT NOTE: Zig `pub fn deinit` → `impl Drop`. The Zig body is -// `bun.memory.deinit(self.items()); self.deinitShallow();` -// i.e. drop every item, then free the backing buffer. `Vec`'s own `Drop` does both, so per -// PORTING.md ("If the body only frees/deinits owned fields, delete the body entirely") no -// explicit `impl Drop for ArrayListAlignedIn` is written. - // ported from: src/collections/array_list.zig diff --git a/src/collections/bit_set.rs b/src/collections/bit_set.rs index 28ca40fd3d0..a6136ed861f 100644 --- a/src/collections/bit_set.rs +++ b/src/collections/bit_set.rs @@ -112,29 +112,10 @@ fn set_range_value_masks(masks: &mut [usize], range: Range, value: bool) { // ───────────────────────────── StaticBitSet ───────────────────────────── -/// Returns the optimal static bit set type for the specified number -/// of elements. The returned type will perform no allocations, -/// can be copied by value, and does not require deinitialization. -/// Both possible implementations fulfill the same interface. -/// -// TODO(port): Zig's `StaticBitSet(size)` returns `IntegerBitSet(size)` when -// `size <= @bitSizeOf(usize)` and `ArrayBitSet(usize, size)` otherwise. Stable -// Rust cannot select a struct definition from a const generic. Callers should -// pick `IntegerBitSet` or `ArrayBitSet` directly; this alias resolves to -// the array form (always correct, possibly one word larger than needed for -// N <= 64). pub type StaticBitSet = IntegerBitSet; // TODO(b2): callers needing >64 bits use ArrayBitSet directly // ───────────────────────────── IntegerBitSet ───────────────────────────── -/// A bit set with static size, which is backed by a single integer. -/// This set is good for sets with a small size, but may generate -/// inefficient code for larger sets, especially in debug mode. -/// -// TODO(port): Zig uses `std.meta.Int(.unsigned, size)` for an exact-width -// backing integer (u0..u65535). Rust has no arbitrary-width ints; we back with -// `usize` and rely on `SIZE <= usize::BITS`. Phase B may swap to a trait that -// picks u8/u16/u32/u64/u128. #[repr(transparent)] #[derive(Clone, Copy, PartialEq, Eq, Hash)] pub struct IntegerBitSet { @@ -381,10 +362,6 @@ impl IntegerBitSet { result } - /// Iterates through the items in the set, according to the options. - /// The default options (.{}) will iterate indices of set bits in - /// ascending order. Modifications to the underlying bit set may - /// or may not be observed by the iterator. pub fn iterator( self, ) -> SingleWordIterator { @@ -448,15 +425,6 @@ pub const fn num_masks_for(bit_length: usize) -> usize { bit_length.div_ceil(usize::BITS as usize) } -/// A bit set with static size, which is backed by an array of usize. -/// This set is good for sets with a larger size, but may use -/// more bytes than necessary if your set is small. -/// -// TODO(port): Zig is generic over `MaskIntType`; every in-tree caller uses -// `usize`. Dropped the type parameter. Phase B can re-generify if needed. -// TODO(port): `[usize; NUM_MASKS]` requires -// `#![feature(generic_const_exprs)]`. Phase B may instead take NUM_MASKS as a -// second const generic and assert `NUM_MASKS == num_masks_for(SIZE)`. #[repr(C)] #[derive(Clone, Copy)] pub struct ArrayBitSet { @@ -749,10 +717,6 @@ impl ArrayBitSet { result } - /// Iterates through the items in the set, according to the options. - /// The default options (.{}) will iterate indices of set bits in - /// ascending order. Modifications to the underlying bit set may - /// or may not be observed by the iterator. pub fn iterator( &self, ) -> BitSetIterator<'_, KIND_SET, DIR_FWD> { @@ -768,15 +732,6 @@ impl ArrayBitSet { // ──────────────────────── DynamicBitSetUnmanaged ──────────────────────── -/// A bit set with runtime-known size, backed by an allocated slice -/// of usize. The allocator must be tracked externally by the user. -/// -// TODO(port): the Zig type stores `masks: [*]MaskInt` where `masks[-1]` holds -// the true allocation length (needed because Zig's allocator API requires the -// original length on free). The Rust port keeps the same layout because -// `List` constructs borrowed views into a shared buffer that must look like -// freestanding `DynamicBitSetUnmanaged`s. Phase B may refactor to `Vec` -// once `List` is reworked. pub struct DynamicBitSetUnmanaged { /// The number of valid items in this bit set pub bit_length: usize, @@ -784,22 +739,10 @@ pub struct DynamicBitSetUnmanaged { /// The bit masks, ordered with lower indices first. /// Padding bits at the end must be zeroed. pub masks: *mut usize, - // This pointer is one usize after the actual allocation. - // That slot holds the size of the true allocation, which - // is needed by Zig's allocator interface in case a shrink - // fails. } const DYN_MASK_BITS: u32 = usize::BITS; -// Never modified — the Zig comment about needing `static mut` was a Zig -// limitation (no const-ptr → mut-ptr cast at comptime). All writes through -// `self.masks` are guarded by `num_masks() > 0`, which is false for the empty -// sentinel (bit_length == 0). Kept in a `RacyCell` (not `.rodata`) so that -// forming a `*mut usize` to it remains a legally-mutable pointer target — -// writing through a pointer derived from an immutable `static` would be UB -// even if it never happens at runtime, and it lets `masks_slice_mut` form a -// zero-length `&mut [usize]` without provenance hazards. static EMPTY_MASKS_DATA: bun_core::RacyCell<[usize; 2]> = bun_core::RacyCell::new([0, 0]); #[inline(always)] @@ -840,11 +783,6 @@ impl DynamicBitSetUnmanaged { unsafe { slice::from_raw_parts(self.masks, n) } } - /// Borrow the mask words as an exclusive slice of length `num_masks(bit_length)`. - /// - /// Note: two `DynamicBitSetUnmanaged` values may share storage (see - /// `DynamicBitSetList::at`). Callers must not hold a `masks_slice_mut()` - /// borrow on one view while another aliasing view is read or written. #[inline(always)] pub fn masks_slice_mut(&mut self) -> &mut [usize] { let n = Self::num_masks(self.bit_length); @@ -854,23 +792,11 @@ impl DynamicBitSetUnmanaged { unsafe { slice::from_raw_parts_mut(self.masks, n) } } - /// Raw pointer to the mask words. Use this (not `masks_slice{,_mut}`) when - /// `self` and another `DynamicBitSetUnmanaged` may point at the same - /// storage and both are accessed in the same operation — forming - /// overlapping `&mut [usize]` / `&[usize]` would be UB. #[inline(always)] pub fn masks_ptr(&self) -> *mut usize { self.masks } - /// `self.masks[i] = f(self.masks[i], other.masks[i])` for every mask word. - /// Centralises the binary set-op loop (`set_union` / `set_intersection` / - /// `set_exclude` / `toggle_set` / `copy_into`) behind a single audited - /// raw-pointer access. Raw pointers — not `masks_slice{,_mut}` — because - /// `other.masks` may alias `self.masks` when both are views from the same - /// `DynamicBitSetList`; forming overlapping `&mut [usize]` / `&[usize]` - /// would be UB. `f` receives copied `usize` values, so the per-index read - /// happens-before the write even when `src == dst`. #[inline(always)] fn zip_masks_raw(&mut self, other: &Self, mut f: impl FnMut(usize, usize) -> usize) { let num_masks = Self::num_masks(self.bit_length); @@ -1162,19 +1088,11 @@ impl DynamicBitSetUnmanaged { self.masks_slice_mut()[num_masks - 1] &= last_item_mask; } - /// Performs a union of two bit sets, and stores the - /// result in the first one. Bits in the result are - /// set if the corresponding bits were set in either input. - /// The two sets must both be the same bit_length. pub fn set_union(&mut self, other: &Self) { debug_assert!(other.bit_length == self.bit_length); self.zip_masks_raw(other, |a, b| a | b); } - /// Performs an intersection of two bit sets, and stores - /// the result in the first one. Bits in the result are - /// set if the corresponding bits were set in both inputs. - /// The two sets must both be the same bit_length. pub fn set_intersection(&mut self, other: &Self) { debug_assert!(other.bit_length == self.bit_length); self.zip_masks_raw(other, |a, b| a & b); @@ -1259,11 +1177,6 @@ impl DynamicBitSetUnmanaged { true } - /// Iterates through the items in the set, according to the options. - /// The default options (.{}) will iterate indices of set bits in - /// ascending order. Modifications to the underlying bit set may - /// or may not be observed by the iterator. Resizing the underlying - /// bit set invalidates the iterator. pub fn iterator( &self, ) -> BitSetIterator<'_, KIND_SET, DIR_FWD> { @@ -1280,21 +1193,6 @@ impl DynamicBitSetUnmanaged { } } -/// Do not resize the bitsets! -/// -/// Single buffer for multiple bitsets of equal length. Does not -/// implement all methods of DynamicBitSetUnmanaged and should -/// be used carefully. -/// -/// `buf` is a raw heap allocation rather than `Box<[usize]>` because `at()` / -/// `set()` / `set_union()` hand out and write through `*mut usize` views while -/// only holding `&self`. With `Box<[usize]>`, the only way to reach the data -/// from `&self` is `Deref` → `&[usize]` → `as_ptr()`, which yields a pointer -/// with shared-read-only provenance — writing through it (as the old code did -/// via `.cast_mut()`) is UB under Stacked Borrows. Owning the allocation as a -/// raw pointer means the heap words are never covered by a `&`/`&mut` -/// reference, so reads and writes through `at()`-derived pointers carry the -/// original allocation's full read-write provenance. pub struct DynamicBitSetList { buf: ptr::NonNull, buf_len: usize, @@ -1336,15 +1234,6 @@ impl DynamicBitSetList { }) } - /// Borrow the `i`th bitset as a non-owning `DynamicBitSetUnmanaged` view. - /// - /// The returned view's `masks` pointer aliases `self.buf`. It is a raw - /// pointer with no lifetime, so the borrow checker will **not** prevent - /// use-after-free: the caller must ensure `self` is not dropped (and `buf` - /// not reallocated — impossible today, no resize API) while the view is - /// live. All current callers (`hoisted_install`, `isolated_install`, - /// `PackageInstaller::can_run_scripts`) satisfy this by keeping the list - /// alive for the view's entire use. The view must not be `deinit`ed. pub fn at(&self, i: usize) -> core::mem::ManuallyDrop { debug_assert!(i < self.n, "DynamicBitSetList::at index out of bounds"); let num_masks = DynamicBitSetUnmanaged::num_masks(self.bit_length); @@ -1443,13 +1332,6 @@ pub enum AutoBitSet { Dynamic(DynamicBitSetUnmanaged), } -// ─── two-arm forward helper ──────────────────────────────────────────── -// Zig had `switch (this.*) { inline else => |*b| b.method() }` for the -// symmetric arms (setAll/count/findFirstSet/Iterator.next). The Rust port -// regressed those to open-coded matches; this macro restores the collapse -// and is applied to every method whose Static/Dynamic arms are textually -// identical. Asymmetric arms (clone, raw_bytes, has_intersection, Drop) -// stay open-coded — they genuinely differ. macro_rules! auto_forward { ($self:expr, |$b:ident| $body:expr) => { match $self { @@ -1549,10 +1431,6 @@ impl AutoBitSet { } } -// Both enum arms already produce the SAME concrete `BitSetIterator<'a,K,D>` -// (see ArrayBitSet::iterator / DynamicBitSetUnmanaged::iterator), so the -// wrapper enum was a no-op layer of indirection. Keep the public name as a -// type alias for any external callers. pub(crate) type AutoBitSetIterator<'a, const KIND_SET: bool, const DIR_FWD: bool> = BitSetIterator<'a, KIND_SET, DIR_FWD>; @@ -1567,13 +1445,6 @@ impl Drop for AutoBitSet { // ───────────────────────────── DynamicBitSet ───────────────────────────── -/// A bit set with runtime-known size, backed by an allocated slice -/// of usize. Thin wrapper around DynamicBitSetUnmanaged which keeps -/// track of the allocator instance. -/// -// TODO(port): in Rust the managed/unmanaged split disappears (global -// allocator). This wrapper is kept for diff parity; Phase B may collapse it -// into `DynamicBitSetUnmanaged` and re-export under both names. #[derive(Default)] pub struct DynamicBitSet { /// The number of valid items in this bit set @@ -1688,18 +1559,10 @@ impl DynamicBitSet { self.unmanaged.toggle_all(); } - /// Performs a union of two bit sets, and stores the - /// result in the first one. Bits in the result are - /// set if the corresponding bits were set in either input. - /// The two sets must both be the same bit_length. pub fn set_union(&mut self, other: &Self) { self.unmanaged.set_union(&other.unmanaged); } - /// Performs an intersection of two bit sets, and stores - /// the result in the first one. Bits in the result are - /// set if the corresponding bits were set in both inputs. - /// The two sets must both be the same bit_length. pub fn set_intersection(&mut self, other: &Self) { self.unmanaged.set_intersection(&other.unmanaged); } @@ -1722,11 +1585,6 @@ impl DynamicBitSet { self.unmanaged.eql(&other.unmanaged) } - /// Iterates through the items in the set, according to the options. - /// The default options (.{}) will iterate indices of set bits in - /// ascending order. Modifications to the underlying bit set may - /// or may not be observed by the iterator. Resizing the underlying - /// bit set invalidates the iterator. pub fn iterator( &self, ) -> BitSetIterator<'_, KIND_SET, DIR_FWD> { @@ -1736,10 +1594,6 @@ impl DynamicBitSet { // ───────────────────────────── IteratorOptions ───────────────────────────── -/// Options for configuring an iterator over a bit set -// TODO(port): Zig passes a `comptime options: IteratorOptions` struct. Stable -// Rust adt_const_params is unstable; split into two const-generic enum params -// (`KIND`, `DIRECTION`) at every callsite. #[derive(Clone, Copy, Default)] pub struct IteratorOptions { /// determines which bits should be visited @@ -1835,10 +1689,6 @@ impl<'a, const KIND_SET: bool, const DIR_FWD: bool> BitSetIterator<'a, KIND_SET, } } - // Load the next word. Don't call this if there - // isn't a next word. If the next word is the - // last word, mask off the padding bits so we - // don't visit them. #[inline(always)] fn next_word(&mut self) { let mut word = if DIR_FWD { diff --git a/src/collections/comptime_string_map.rs b/src/collections/comptime_string_map.rs index 35bd2b40005..8b195238e51 100644 --- a/src/collections/comptime_string_map.rs +++ b/src/collections/comptime_string_map.rs @@ -6,18 +6,6 @@ //! where `.0` is the `&[u8]` key and `.1` is the associated value of type `V`. // TODO: https://github.com/ziglang/zig/issues/4335 -// PORT NOTE: The Zig original is a `fn(comptime KeyType, comptime V, comptime kvs_list) type` -// that does heavy comptime work: sorts kvs by (len, bytes), builds a `len_indexes` table, -// then every lookup is an `inline while` over lengths × `inline for` over same-length keys -// with `eqlComptime` (length-known SIMD compare). Rust cannot replicate the per-callsite -// monomorphization without a proc-macro, so this models it as a const-constructed struct -// holding the precomputed tables plus a `comptime_string_map!` macro that callers use. -// Runtime loops replace `inline for`/`inline while`; each is tagged `PERF(port)`. -// -// Per PORTING.md crate-map, downstream callers of `bun.ComptimeStringMap(V, .{...})` should -// prefer `phf::phf_map!` directly when they only need `.get()`/`.has()`. This struct exists -// for the call sites that need `get_with_eql` / `get_any_case` / `index_of` / `get_key`. - // TODO(port): `strings` arrives in bun_core via move-in (was bun_core::strings — same-tier cycle). use bun_core::strings; @@ -82,12 +70,6 @@ where K: Copy + Eq + Ord + 'static, V: Copy + 'static, { - /// Builds the precomputed tables. Called by the `comptime_string_map!` macro. - /// - /// Mirrors the `comptime blk:` in the Zig: sort by (len asc, bytes asc), then - /// fill `len_indexes[len]` = first index whose key.len >= len. - // TODO(port): make this a `const fn` once const-sort is stable, or move to build.rs. - // PERF(port): Zig did this at comptime (zero runtime cost); this runs once at init. pub fn new(mut sorted_kvs: [KV; N]) -> Self { // lenAsc comparator sorted_kvs.sort_by(|a, b| { @@ -136,11 +118,6 @@ where self.get(str).is_some() } - /// Contiguous range in `kvs` whose keys have exactly `len`. - /// - /// PORT NOTE: the .zig spec open-coded this at every lookup site because `len` was - /// `comptime` there and each needed its own `comptime brk:` block. In the Rust port - /// `len` is runtime, so the duplication is vestigial — extract once and inline. #[inline(always)] fn len_bucket(&self, len: usize) -> core::ops::Range { let start = self.len_indexes[len]; @@ -219,11 +196,6 @@ where (start..end).find(|&i| str == self.kvs[i].key) } - // TODO(port): move to *_jsc — `fromJS` / `fromJSCaseInsensitive` were thin shims to - // `jsc/comptime_string_map_jsc.zig`. In Rust these become extension-trait methods in - // `bun_jsc` (e.g. `impl ComptimeStringMapJsc for ComptimeStringMap`). - // The base `bun_collections` crate has no JSC dependency. - pub fn get_with_eql(&self, input: I, eql: impl Fn(I, &'static [K]) -> bool) -> Option where I: Copy + HasLength, @@ -291,19 +263,6 @@ where } } -/// Build a `ComptimeStringMap` from `(key, value)` pairs. -/// -/// ```ignore -/// static MAP: ComptimeStringMap = comptime_string_map!(TestEnum, [ -/// (b"these", TestEnum::D), -/// (b"have", TestEnum::A), -/// ... -/// ]); -/// ``` -// TODO(port): proc-macro — Zig sorted + built len_indexes at comptime. A `macro_rules!` -// cannot sort; either (a) require callers pre-sort and compute LEN_TABLE, (b) use a -// proc-macro, or (c) lazy-init via `once_cell::Lazy` + `ComptimeStringMapWithKeyType::new`. -// Currently uses (c) for correctness; could upgrade to a proc-macro for true const. #[macro_export] macro_rules! comptime_string_map { ($V:ty, [ $( ($key:expr, $val:expr) ),* $(,)? ]) => {{ diff --git a/src/collections/hive_array.rs b/src/collections/hive_array.rs index 20761f9a912..55a780be986 100644 --- a/src/collections/hive_array.rs +++ b/src/collections/hive_array.rs @@ -6,24 +6,6 @@ use core::ptr::NonNull; use bun_core::asan; -/// Fixed-width occupancy bitset for [`HiveArray`]. -/// -/// PORT NOTE: Zig's `std.bit_set.IntegerBitSet(N)` is backed by an exact-width -/// `uN` integer (`u128`, `u256`, `u2048`, …). The Rust port's -/// [`IntegerBitSet`](crate::bit_set::IntegerBitSet) is backed by a single -/// `usize`, so for `N > 64` it silently held only 64 usable bits — every -/// `HiveArray<_, 128/256/2048>` pool degraded to 64 effective slots and spilled -/// to the heap fallback on the 65th in-flight item. Under HTTP load (the -/// `Body::Value` 256-slot pool, the `RequestContext` 2048-slot pool) this turned -/// every request into a `Box::new`. -/// -/// We can't spell `[usize; (CAPACITY+63)/64]` without `generic_const_exprs` -/// (which would virally add `where` bounds on every `HiveArray` consumer), so -/// this uses a fixed `[Cell; 32]` backing array — 2048 bits, which is the -/// largest in-tree `HiveArray` capacity. Only the first `ceil(CAPACITY/64)` -/// words are touched, so smaller pools pay 256 B of dead storage (negligible -/// next to `buffer`). The words are `Cell` so the bitset can be mutated through -/// a `&self` pool, matching `HiveArray`'s interior-mutability model. #[repr(C)] pub struct HiveBitSet { masks: [Cell; HIVE_BITSET_WORDS], @@ -65,10 +47,6 @@ impl HiveBitSet { (self.masks[index / WORD_BITS].get() >> (index % WORD_BITS)) & 1 != 0 } - /// `pub(crate)` — toggling occupancy from outside `HiveArray` while a - /// `HiveSlot`/`HiveBox` for the same index is alive would let a - /// re-`claim()` alias it. Use [`HiveArray::claim`]/[`alloc`](HiveArray::alloc)/ - /// [`put`](HiveArray::put)/[`box_at`](HiveArray::box_at). #[inline] pub(crate) fn set(&self, index: usize) { debug_assert!(index < CAPACITY); @@ -121,10 +99,6 @@ impl HiveBitSet { self.iterator::() } - /// Signature mirrors `IntegerBitSet::iterator` so existing - /// `hive.used.iterator::()` callers compile unchanged. Only - /// the `` combination is implemented (the - /// only one used in-tree); other params assert. #[inline] pub fn iterator(&self) -> HiveBitSetIter { const { @@ -164,15 +138,6 @@ impl HiveBitSetIter { } } -/// An array that efficiently tracks which elements are in use. -/// The pointers are intended to be stable -/// Sorta related to https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0447r15.html -/// -/// All slot operations take `&self` and the buffer is `UnsafeCell` — slot -/// pointers come from `UnsafeCell::get()` and so survive `&self` reborrows of -/// the pool (the `bumpalo` / `typed-arena` shape). `HiveArray` is `!Sync`. -// PORT NOTE: Zig's `capacity: u16` is widened to `usize` here because Rust array -// lengths require a `usize` const generic on stable. pub struct HiveArray { buffer: UnsafeCell<[MaybeUninit; CAPACITY]>, pub used: HiveBitSet, @@ -277,15 +242,6 @@ impl HiveArray { }) } - /// Claim a slot and return a raw pointer to its **uninitialized** storage. - /// - /// Prefer [`get_init`](Self::get_init) / [`emplace`](Self::emplace) / - /// [`claim`](Self::claim), which encode the "a `used` slot is always - /// fully initialized" invariant in the type system. This entry point - /// hands out `*mut T` to garbage; forming `&mut T` over it is instant UB - /// when `T` has niche-bearing fields, and an early return between `get()` - /// and the caller's `ptr::write` leaves the slot claimed-but-uninit so a - /// later [`put`](Self::put) drops garbage. #[deprecated = "returns *mut T to uninitialized memory; use get_init / emplace / claim"] pub fn get(&self) -> Option<*mut T> { let index = self.used.find_first_unset()?; @@ -295,19 +251,11 @@ impl HiveArray { Some(ret) } - /// One-shot claim + write. Preferred entry point — no uninit window. - /// - /// Returns `None` (and does **not** consume `value`'s slot) if the hive - /// is full; on `None` the caller still owns `value` and must drop it. #[inline] pub fn get_init(&self, value: T) -> Option> { Some(self.claim()?.write(value)) } - /// Claim + write where `T` is self-referential on its own slot address - /// (e.g. a struct that registers itself as a uws/libuv user-data pointer - /// inside its own constructor). `init` receives the slot's stable address - /// and must return the value to be stored there. #[inline] pub fn emplace(&self, init: impl FnOnce(NonNull) -> T) -> Option> { let slot = self.claim()?; @@ -315,10 +263,6 @@ impl HiveArray { Some(slot.write(init(addr))) } - /// Low-level reservation. Only when [`get_init`](Self::get_init) / - /// [`emplace`](Self::emplace) are insufficient — typically when the caller - /// must interleave fallible work between claim and commit, or perform - /// `repr(C)` placement-new via [`HiveSlot::as_uninit`]. pub fn claim(&self) -> Option> { let index = self.used.find_first_unset()?; self.used.set(index); @@ -423,25 +367,6 @@ impl HiveArray { // HiveSlot // ────────────────────────────────────────────────────────────────────────── -/// Linear reservation token for a claimed-but-uninitialized hive slot. -/// -/// `HiveArray` slots are `[MaybeUninit; CAP]`. The legacy [`HiveArray::get`] -/// contract was two-phase — claim a `*mut T` to garbage, then `ptr::write` it -/// — which opened three UB hazards in the gap: (H1) early-return / `?` / panic -/// leaves the slot claimed-uninit so a later `put()` drops garbage; (H2) -/// `&mut *p` over uninit `T` is instant validity UB when `T` has niches; (H3) -/// partial field-write then `assume_init_ref` on the whole slot. -/// -/// `HiveSlot` encodes the invariant **"a `used` slot is always fully -/// initialized"** in the type system: you cannot obtain the stable -/// initialized `*mut T` without going through [`write`](Self::write) (or the -/// `unsafe` [`assume_init`](Self::assume_init) escape hatch). If the token is -/// dropped (early return, `?`, panic) the slot is released **without** running -/// `T::drop` — it was never written. -/// -/// Two-pointer-sized; `owner` discriminates release behavior: -/// - non-null ⇒ `*const HiveArray` (release = unset `used` bit + poison), -/// - null ⇒ heap `Box>` (release = dealloc, no `T::drop`). #[must_use = "claimed hive slot is leaked if neither written nor dropped"] pub struct HiveSlot<'h, T, const CAPACITY: usize> { slot: NonNull>, @@ -528,13 +453,6 @@ impl Drop for HiveSlot<'_, T, CAPACITY> { } } -/// Single-owner handle to an initialized [`HiveArray`] slot. `Box`-shaped: -/// `Drop` returns the slot to the pool, [`into_inner`](Self::into_inner) -/// extracts the value. Single-owner (no `Clone`), so [`DerefMut`] is sound. -/// -/// For pools whose tokens cross an opaque round-trip as a slot *index* (e.g. -/// the c-ares callback context in `dns_jsc`), store [`index()`](Self::index) -/// and recover via [`HiveArray::box_at`]. pub struct HiveBox<'a, T, const CAPACITY: usize> { slot: NonNull, owner: &'a HiveArray, @@ -589,12 +507,6 @@ impl Drop for HiveBox<'_, T, CAPACITY> { } } -// PORT NOTE: In Zig this was the nested type `HiveArray(T, capacity).Fallback`. -// Rust cannot nest a generic struct that captures outer generics, so it lives at -// module scope with the same parameters. The Zig field -// `hive: if (capacity > 0) Self else void` is always materialized here; the -// `CAPACITY > 0` checks below preserve the original gating. -// PERF(port): zero-capacity case carried a zero-size hive in Zig — profile in Phase B. pub struct Fallback { pub hive: HiveArray, // PORT NOTE: `std.mem.Allocator param` dropped — global mimalloc. @@ -620,22 +532,6 @@ impl Fallback { unsafe { HiveArray::::init_in_place(core::ptr::addr_of_mut!((*out).hive)) }; } - /// Heap-allocate an empty `Fallback` without materializing it on the - /// stack first. - /// - /// `Box::new(Self::init())` is the obvious spelling, but Rust has no - /// guaranteed result-location semantics: for the 2048-slot - /// `RequestContext` pool (`sizeof ≈ 816 KB`) LLVM emits the bitset - /// zeros into a stack temporary and then `memcpy`s the **full** 816 KB - /// into the heap allocation, committing both ~812 KB of stack pages and - /// ~812 KB of heap pages that are never read. This entry point allocates - /// raw heap storage and writes only the 256-byte `used` bitset via - /// [`init_in_place`](Self::init_in_place); the `[MaybeUninit; CAPACITY]` - /// buffer is left untouched (uninitialized bytes are a valid bit-pattern - /// for `MaybeUninit`). - /// - /// The returned allocation is leaked — callers stash it in a per-thread - /// static for the process lifetime (Zig: `threadlocal var pool`). #[inline] pub fn new_boxed() -> NonNull { let mut boxed = Box::::new_uninit(); @@ -764,25 +660,6 @@ impl Fallback { } } -// ────────────────────────────────────────────────────────────────────────── -// HiveRef -// ────────────────────────────────────────────────────────────────────────── -// -// PORT NOTE: ground truth is `bun.HiveRef` in src/bun.zig. It lives here (not -// in the `bun` crate) because every consumer names it through -// `bun_collections::HiveRef`, and its only collaborator is `Fallback` above. -// -// Zig defines `const HiveAllocator = HiveArray(@This(), capacity).Fallback` -// inside the returned struct; Rust spells the self-referential pool type out -// as `Fallback, CAPACITY>`. CAPACITY is `usize` (widened -// from Zig's `u16`) to line up with `HiveArray`/`Fallback`'s const generic. - -/// Intrusive ref-counted slot allocated from a `HiveArray::Fallback` pool. -/// `pool` is a BACKREF (LIFETIMES.tsv class) — the pool strictly outlives -/// every `HiveRef` it hands out, so a raw pointer is the honest mapping. -/// -/// Prefer [`HiveRefHandle`] in new code; the raw `init`/`ref_`/`unref` family -/// remains for FFI ingress points that hold the slot as a `*mut HiveRef`. #[repr(C)] pub struct HiveRef { pub ref_count: Cell, @@ -892,10 +769,6 @@ impl HiveRefHandle { self.ptr.as_ptr() } - /// Exclusive access to the payload. `None` if there are other live handles - /// — same shape as [`Rc::get_mut`](std::rc::Rc::get_mut). A blanket - /// `DerefMut` would be unsound: `Clone` takes `&self`, so a second handle - /// could be made while a `&mut T` from `deref_mut()` is outstanding. #[inline] pub fn get_mut(&mut self) -> Option<&mut T> { if self.slot().ref_count.get() != 1 { diff --git a/src/collections/identity_context.rs b/src/collections/identity_context.rs index 0bfd37eac63..6d1e37ec4f5 100644 --- a/src/collections/identity_context.rs +++ b/src/collections/identity_context.rs @@ -63,10 +63,6 @@ impl ArrayIdentityContextU64 { // so expose as a free path alias instead. Callers: `identity_context::U64`. pub type U64 = ArrayIdentityContextU64; -// ArrayHashMap requires `C: ArrayHashContext`, so wire the inherent impls -// above into the trait. Kept as separate inherent + trait impls so direct -// `ArrayIdentityContext::hash(...)` calls (which predate the trait) still -// resolve without ambiguity. impl crate::array_hash_map::ArrayHashContext for ArrayIdentityContext { #[inline] fn hash(&self, key: &u32) -> u32 { diff --git a/src/collections/lib.rs b/src/collections/lib.rs index 10e8ad091f9..d9fde69d182 100644 --- a/src/collections/lib.rs +++ b/src/collections/lib.rs @@ -16,10 +16,6 @@ pub mod hive_array; pub mod multi_array_list; pub mod vec_ext; -// `bounded_array` moved down to `bun_core` (cycle-break for the -// `bun_string → bun_core` merge — `bun_core::string::immutable` needs it). -// Re-exported here unchanged so existing `bun_collections::BoundedArray` / -// `bun_collections::bounded_array::*` paths keep resolving. pub use bun_core::bounded_array; pub mod identity_context; pub mod linear_fifo; @@ -61,11 +57,6 @@ pub mod dynamic_bit_set { pub use super::bit_set::DynamicBitSetList as List; } -// ────────────────────────────────────────────────────────────────────────── -// `PriorityQueue` — port of `std.PriorityQueue(T, Context, lessThan)`. -// Min-heap backed by a `Vec`; the comparator context is held by value so -// callers can rebind it (Zig stores `context: Context` directly on the queue). -// ────────────────────────────────────────────────────────────────────────── pub trait PriorityCompare { fn compare(&self, a: &T, b: &T) -> core::cmp::Ordering; } @@ -168,13 +159,6 @@ pub use array_hash_map::{ StringHashMapContext, StringHashMapInner, StringHashMapKey, StringHashMapUnownedKey, StringSet, VacantEntry, string_hash_map, }; -/// Downstream crates name hashbrown's iterator/entry types in struct fields -/// (e.g. `bun_resolver::DirEntryDirIter`). `StringHashMap` `Deref`s to a -/// `hashbrown::HashMap`, so those iterators are the API surface; re-export -/// the crate so callers don't grow their own direct dep just to spell the -/// type. (A type alias per iterator would work too, but every `.iter()` / -/// `.values()` / `.entry()` returns a distinct hashbrown type — re-exporting -/// the crate is the smaller surface.) pub use hashbrown; pub mod string_map; @@ -183,26 +167,8 @@ pub use string_map::StringMap; // Re-export from bun_ptr so callers can name it as `bun_collections::TaggedPtrUnion` // (PORTING.md groups it under Collections; the impl lives in src/ptr/). pub use bun_ptr::tagged_pointer::{TaggedPtr as TaggedPointer, TaggedPtrUnion}; -// Lifetime-erasure helpers (RUST_PATTERNS.md §6/§18) — re-exported here so -// crates that already depend on `bun_collections` (logger, css, js_parser, -// crash_handler, watcher, http_types) can route the borrowck-dodge through -// one centralised `unsafe fn` instead of open-coding the lifetime cast. pub use bun_ptr::{RawSlice, detach_lifetime, detach_ref}; -// ────────────────────────────────────────────────────────────────────────── -// SmallList — `bun.SmallList(T, N)` (Zig: src/css/small_list.zig). -// -// Thin `#[repr(transparent)]` newtype over `smallvec::SmallVec<[T; N]>` that -// preserves the Zig-named API surface (`append`, `slice`, `at`, `len()->u32`, -// `init_capacity`, …) so the ~300 CSS-parser call sites stay untouched. -// Replaces the bespoke ~800-line `Data`/`HeapData` union + raw-ptr container -// that previously lived in `bun_css::small_list` (which was itself a port of -// servo/rust-smallvec — this closes the loop back onto the upstream crate). -// -// `const_generics` feature is required so `[T; N]` satisfies `smallvec::Array` -// for an arbitrary `const N: usize` (callers use N ∈ {1,2,3,4,5,6}). -// ────────────────────────────────────────────────────────────────────────── - pub use smallvec; #[repr(transparent)] @@ -309,15 +275,6 @@ impl SmallList { debug_assert!(values.len() <= N); Self(smallvec::SmallVec::from_slice(values)) } - /// Build a `SmallList` whose heap spill (if any) is allocated from `arena` - /// instead of the global allocator. - /// - /// Use this when the list is stored in an arena-owned, never-`Drop`'d - /// structure (forgotten/`set_len(0)`-cleared on teardown). The returned - /// list **must not** be dropped or grown past its initial length: when it - /// spills, the backing storage is arena memory that the global allocator - /// does not own. The arena reclaims the slab on reset; running - /// `SmallVec::drop` would call `dealloc` on a pointer it never handed out. #[cfg_attr(bun_asan, inline(never))] #[cfg_attr(not(bun_asan), inline)] pub fn from_arena_iter(arena: &bun_alloc::Arena, iter: I) -> Self @@ -344,10 +301,6 @@ impl SmallList { Self(smallvec::SmallVec::from_vec(list)) } - // ── access ───────────────────────────────────────────────────────────── - /// Zig `len()` returns `u32` (not `usize`); preserved so the ~300 call-site - /// integer arithmetic in `bun_css` stays unchanged. Inherent shadows the - /// `[T]::len()->usize` reachable via `Deref`. #[inline] pub fn len(&self) -> u32 { self.0.len() as u32 @@ -500,16 +453,6 @@ impl SmallList { } } -// ────────────────────────────────────────────────────────────────────────── -// HashMap — `std.AutoHashMap(K, V)` / `std.HashMap(K, V, Ctx, max_load)`. -// -// Ported linear-probe layout (open-addressing, tombstones, power-of-two cap, -// 80% load) so iteration order matches Zig exactly — required by callers that -// snapshot the iteration sequence (lockfile debug stringify, etc.). The `Ctx` -// type parameter is now load-bearing: `AutoHashContext` wyhashes the key, -// `IdentityContext` uses `k as u64` so pre-hashed keys aren't re-hashed. -// ────────────────────────────────────────────────────────────────────────── - pub mod zig_hash_map; pub use zig_hash_map::{AutoHashContext, HashContext, HashMap}; diff --git a/src/collections/linear_fifo.rs b/src/collections/linear_fifo.rs index 70149dde41e..d03e8d5092f 100644 --- a/src/collections/linear_fifo.rs +++ b/src/collections/linear_fifo.rs @@ -14,12 +14,6 @@ use bun_alloc::AllocError; // conservative minimum on every platform Bun ships on. const PAGE_SIZE_MIN: usize = 4096; -/// Mirrors Zig's `LinearFifoBufferType = union(enum)`. -/// -/// In the Zig original this is a *comptime* value that selects a struct layout -/// (`buf: [N]T` vs `buf: []T`, `std.mem.Allocator` param vs `void`). Rust cannot -/// branch struct layout on a const-generic enum payload, so dispatch is done -/// via the [`LinearFifoBuffer`] trait below; this enum is kept for API parity. pub enum LinearFifoBufferType { /// The buffer is internal to the fifo; it is of the specified size. Static(usize), @@ -29,12 +23,6 @@ pub enum LinearFifoBufferType { Dynamic, } -/// Backing-storage abstraction replacing Zig's `comptime buffer_type` switch. -/// `POWERS_OF_TWO` mirrors the Zig `powers_of_two` const inside the returned -/// struct; `DYNAMIC` mirrors `buffer_type == .Dynamic`. -// TODO(port): the Zig fn returns structurally different layouts per variant; -// trait+assoc-consts is the closest stable-Rust encoding. Phase B: confirm all -// in-tree callers are covered by the three impls below. pub trait LinearFifoBuffer { const POWERS_OF_TWO: bool; const DYNAMIC: bool; @@ -59,11 +47,6 @@ pub trait LinearFifoBuffer { } } -/// Reinterpret `&[MaybeUninit]` as `&[T]`. `MaybeUninit` has identical -/// layout to `T`; exposing uninitialized bytes as `T` is sound only when any -/// bit pattern is a valid `T` (in-tree LinearFifo users are byte buffers — -/// see the `StaticBuffer` TODO below). Centralises the four per-buffer-kind -/// casts behind one audited block. #[inline(always)] fn assume_init_slice(s: &[MaybeUninit]) -> &[T] { // SAFETY: see fn doc. @@ -79,11 +62,6 @@ fn assume_init_slice_mut(s: &mut [MaybeUninit]) -> &mut [T] { unsafe { &mut *(ptr::from_mut::<[MaybeUninit]>(s) as *mut [T]) } } -/// Shift `slice[1..]` down to `slice[0..len-1]` (memmove). Used by -/// `ordered_remove_item` for the four wrap/non-wrap segment shifts. Not -/// `slice::copy_within` because that requires `T: Copy`; this fifo permits -/// move-only `T` (the duplicated tail slot is logically discarded by the -/// subsequent `count -= 1`). #[inline(always)] fn shift_down_one(slice: &mut [T]) { if slice.len() <= 1 { @@ -111,11 +89,6 @@ fn poison(slice: &mut [T], n: usize) { // ── .Static ─────────────────────────────────────────────────────────────────── -/// `buffer_type == .Static` — inline `[T; N]` storage. -// TODO(port): Zig leaves the array `undefined`; we use MaybeUninit and expose -// it as &[T] via pointer cast. Sound only for `T` whose any-bit-pattern is -// valid (in-tree users are byte buffers). Phase B: bound `T: Copy` or rework -// accessors to MaybeUninit if a non-POD T appears. pub struct StaticBuffer([MaybeUninit; N]); impl LinearFifoBuffer for StaticBuffer { @@ -153,10 +126,6 @@ impl<'a, T> LinearFifoBuffer for SliceBuffer<'a, T> { // ── .Dynamic ────────────────────────────────────────────────────────────────── -/// `buffer_type == .Dynamic` — heap-allocated, growable. -/// -/// Zig stores `std.mem.Allocator` param + `buf: []T`. Per §Allocators (non-AST -/// crate) the allocator param is dropped and global mimalloc backs `Box`. pub struct DynamicBuffer(Box<[MaybeUninit]>); impl LinearFifoBuffer for DynamicBuffer { @@ -202,11 +171,6 @@ pub struct LinearFifo> { // returning a slice into a by-value `Self` would dangle when buf is inline. In // Rust every accessor takes `&self`/`&mut self`, so the distinction disappears. -// TODO(port): Reader/Writer std.Io adapters. Zig exposes -// `pub const Reader = std.Io.GenericReader(*Self, error{}, readFn)` and a -// matching Writer. Phase B: impl `bun_io::Read`/`bun_io::Write` (and -// `core::fmt::Write`) on `LinearFifo`. - impl LinearFifo> { /// `init` for `.Static`. pub fn init() -> Self { @@ -253,20 +217,11 @@ impl> LinearFifo { self.buf.len() } - /// Allocated capacity of the backing buffer (Zig: `fifo.buf.len`). - /// Distinct from [`readable_length`] (live items) and - /// [`writable_length`] (free slots) — `capacity == readable + writable`. - /// Used by GC `memoryCost` reporting where the *allocation* size, not the - /// occupancy, is what matters. #[inline] pub fn capacity(&self) -> usize { self.buf.len() } - /// Rewind `head` to 0 when the queue is empty so the next `write` can use - /// the full contiguous buffer without wrapping. Perf-only micro-opt; a - /// no-op when items remain. Mirrors the `head = 0` post-drain idiom in - /// `src/jsc/Task.zig` `tickQueueWithCount`. #[inline] pub fn reset_head_if_empty(&mut self) { if self.count == 0 { @@ -285,18 +240,6 @@ impl> LinearFifo { unsafe { ptr::copy(buf.as_ptr().add(head), buf.as_mut_ptr(), count) }; self.head = 0; } else { - // Zig: `var tmp: [page_size_min / 2 / @sizeOf(T)]T = undefined;` - // Stable Rust cannot size a stack array by `size_of::()`, so use - // a fixed byte scratch and compute the element count at runtime. - // PERF(port): was stack array sized by page_size/2/sizeof(T) — same - // byte footprint here, no heap. - // - // The scratch is a `[MaybeUninit; _]` (alignment 1). Reading or - // writing through it as `*mut T` would violate - // `ptr::copy_nonoverlapping`'s alignment precondition for any - // `align_of::() > 1`, so the tmp↔buf transfers are done at byte - // granularity instead — `*mut u8` only requires 1-byte alignment, - // which both the scratch and `buf` (cast down from `*T`) satisfy. let mut tmp_bytes = [MaybeUninit::::uninit(); PAGE_SIZE_MIN / 2]; let tmp_ptr: *mut u8 = tmp_bytes.as_mut_ptr().cast::(); let t_size = mem::size_of::(); @@ -710,10 +653,6 @@ impl> LinearFifo { } else { index %= buf_len; } - // Length of the wrapped prefix `buf[0..wrap_len)`. The readable - // region is split into the tail `buf[head..buf_len)` and this - // prefix; `wrap_len <= head` (since `count <= buf_len`) so the - // prefix never overlaps the tail. let wrap_len = head + count - buf_len; let buf = self.buf.as_mut_slice(); if index < head { @@ -734,12 +673,6 @@ impl> LinearFifo { self.count -= 1; } - /// Pump data from a reader into a writer - /// stops when reader returns 0 bytes (EOF) - /// Buffer size must be set before calling; a buffer length of 0 is invalid. - // TODO(port): `src_reader: anytype, dest_writer: *std.Io.Writer`. Phase B: - // bind to `bun_io::Read`/`bun_io::Write` (or whatever the byte-stream traits - // land as). Stubbed with generic bounds matching the called methods. pub fn pump(&mut self, mut src_reader: R, dest_writer: &mut W) -> Result<(), E> where R: FnMut(&mut [T]) -> Result, @@ -893,10 +826,6 @@ mod tests { } } - // 16-slot static buffer: `POWERS_OF_TWO` is true, matching the in-tree - // `weak_refs` FIFO in the dev server's source-map store (cap 16), the one - // real caller of `ordered_remove_item`. `i32` elements make every shift - // observable (distinct values), unlike a buffer of repeated bytes. type WrapFifo = LinearFifo>; /// Drains the FIFO into a `Vec` without mutating it, preserving FIFO order. @@ -906,12 +835,6 @@ mod tests { .collect() } - // Regression for the wrapped-branch bounds bug: `ordered_remove_item` used - // `count - head` / `head - count` for the wrapped-prefix length instead of - // the correct `head + count - buf_len`. In wrapped layouts that panics with - // an out-of-range slice index (and in narrow cases silently corrupts - // contents). The two sub-branches are `index < head` (item in the wrapped - // prefix) and `index >= head` (item in the tail segment). #[test] fn ordered_remove_item_wrapped_tail_branch_head_lt_count() { // write 12, read 8, write 10 -> head=8, count=14, buf_len=16. diff --git a/src/collections/multi_array_list.rs b/src/collections/multi_array_list.rs index 43061c51d03..5b93daf11d5 100644 --- a/src/collections/multi_array_list.rs +++ b/src/collections/multi_array_list.rs @@ -57,23 +57,6 @@ use std::alloc::{Allocator, Global}; use bun_alloc::AllocError; -/// Declares typed column-accessor extension traits for a `MultiArrayList<$T>` -/// element struct, mirroring Zig's `list.items(.field)` calling convention. -/// -/// ```ignore -/// multi_array_columns! { -/// pub trait FooColumns for Foo { -/// a: u32, -/// b: Bar, -/// } -/// } -/// // → list.items_a(): &[u32], list.items_a_mut(): &mut [u32], … -/// // on both MultiArrayList and Slice. -/// ``` -/// -/// Each generated method calls `items::<"field", $ty>()`, so the field name -/// and type are checked against `$T`'s reflected layout at compile time — -/// a typo or type mismatch is a const-eval error, not UB. #[macro_export] macro_rules! multi_array_columns { // Non-generic form. @@ -110,13 +93,6 @@ macro_rules! multi_array_columns { $( $field:ident : $ty:ty, )* }) => { $crate::__mal_paste! { - /// Simultaneous `&mut` view of every column. Returned by - /// [`split_mut`]($trait::split_mut); columns are physically - /// disjoint (SoA layout — each occupies a distinct - /// `[COLUMN_OFFSET_PER_CAP[i]*cap ..)` byte range in the single - /// backing allocation), so holding all of them mutably at once is - /// sound. This is the safe replacement for the `items_raw` + - /// per-site `unsafe { &mut * }` pattern. #[allow(dead_code, non_snake_case)] $vis struct [<$trait Mut>] <'__mal, $($decl)*> { $( pub $field: &'__mal mut [$ty], )* @@ -124,16 +100,6 @@ macro_rules! multi_array_columns { pub __mal: ::core::marker::PhantomData<&'__mal mut $elem>, } - /// Raw `*mut [T]` view of every column. Returned by - /// [`split_raw`]($trait::split_raw). Unlike [`split_mut`], the - /// pointers are derived directly from the SoA buffer's raw `bytes` - /// base (root/SharedRW provenance) with **no `&mut` intermediate**, - /// so they remain valid under Stacked Borrows even when interleaved - /// with other column accessors on the same list — the use case - /// `split_mut` cannot serve. Dereferencing is the caller's - /// responsibility (per-site `unsafe`); columns are physically - /// disjoint by `COLUMN_OFFSET_PER_CAP`, so distinct-column derefs - /// never alias. Invalidated by any reallocation of the list. #[allow(dead_code, non_snake_case)] $vis struct [<$trait Raw>] <$($decl)*> { $( pub $field: *mut [$ty], )* @@ -242,10 +208,6 @@ macro_rules! __mal_column_impl { }; } -/// Upper bound on struct field count. The reflected per-field metadata is -/// cached in fixed-size `[_; MAX_FIELDS]` arrays so `Slice` can be a plain -/// value type without a `where [(); field_count::()]:` bound propagating to -/// every caller. pub(crate) const MAX_FIELDS: usize = 32; // ──────────────────────── const-eval reflection helpers ─────────────────── @@ -274,24 +236,6 @@ pub(crate) const fn field_count() -> usize { fields_of::().len() } -/// Column-layout sort key for a field of `size` bytes within a struct of -/// alignment `struct_align`. -/// -/// The reflection API does not expose `align`, and recursing through nested -/// `TypeId::info()` to reconstruct it ICEs on types containing -/// const-expression array lengths (rustc `type_info` MVP limitation). Instead -/// we compute a key with these properties: -/// -/// * `key` is a power of two, -/// * `key` divides `size` (since size is a multiple of true alignment, the -/// largest power-of-two factor of `size` is ≥ true alignment), -/// * `key ≤ struct_align` (a field's alignment never exceeds its parent's), -/// * therefore `true_align ≤ key`. -/// -/// Sorting columns by `key` descending then packs them as `Σ size[j] * cap`; -/// because each `size[j]` is a multiple of `key[j] ≥ key[k]`, every column -/// start is a multiple of `key[k] ≥ true_align[k]`, so all columns are -/// correctly aligned without knowing their exact alignment. const fn align_sort_key(size: usize, struct_align: usize) -> usize { if size == 0 { return 1; @@ -436,14 +380,6 @@ impl Reflected { panic!("MultiArrayList: no such field"); } - /// Const-panics unless field `NAME` exists and has type `F`. - /// - /// The type check is `TypeId` equality with a fallback to size equality: - /// the experimental reflection intrinsic occasionally produces a distinct - /// `TypeId` for the same nominal type when reached through an inherent - /// associated type alias (e.g. `EntryPoint::Kind` vs `entry_point::Kind`), - /// so a size match is accepted when ids differ. Size mismatch is always - /// rejected. const fn check() -> usize { let fields = fields_of::(); let mut i = 0; @@ -466,13 +402,6 @@ impl Reflected { // ───────────────────────── column primitives ───────────────────────── -/// Base pointer of column `fi` within a buffer of `cap` elements. -/// -/// **Module invariant** (`INVARIANT:column_base`): `bytes` is either -/// `Reflected::::DANGLING` with `cap == 0`, or the start of a live -/// allocation of `ELEM_BYTES * cap` bytes at `align_of::()` alignment. -/// Under that invariant the result is `T`-aligned for `cap == 0` and aligned -/// to field `fi`'s true alignment for `cap > 0` (see [`align_sort_key`]). #[inline(always)] fn column_base(bytes: NonNull, cap: usize, fi: usize) -> NonNull { debug_assert!(fi < Reflected::::COUNT); @@ -493,10 +422,6 @@ struct Col<'a, F> { } impl<'a, F> Col<'a, F> { - /// **Module invariant** (`INVARIANT:col`): only fed `(ptr, len)` where - /// `ptr` is non-null, aligned for `F`, and either dangling with `len == 0` - /// or pointing into a column holding `≥ len` initialized `F`s valid for - /// `'a`. Upheld by every internal caller; not exposed. #[inline(always)] fn new(ptr: NonNull, len: usize) -> Self { Self { @@ -555,21 +480,6 @@ pub struct MultiArrayList { // SAFETY: `bytes` is uniquely owned; the only shared state is the allocator. unsafe impl Send for MultiArrayList {} -// NOTE: deliberately not `Sync`. `slice(&self)` hands out an owned, `Copy` -// `Slice` whose safe `items_mut`/`set` mutate the shared backing buffer, so -// two threads holding `&MultiArrayList` could race through `slice()`. Revisit -// once `Slice` no longer exposes mutation from a shared-derived handle. - -/// A `MultiArrayList::Slice` contains cached start pointers for each field in -/// the list. These pointers are not normally stored to reduce the size of the -/// list in memory. If you are accessing multiple fields, call `slice()` first -/// to compute the pointers, and then get the field arrays from the slice. -/// -/// **Known soundness gap**: `Slice: Copy` lets a caller hold two copies and -/// call `items_mut` / `set` on both, aliasing `&mut`. Removing `Copy` breaks a -/// large number of `.slice()` snapshot sites that intentionally exploit it for -/// borrowck (see `LinkerGraph::load`, `bundle_v2`). Tracked separately; treat -/// `Slice` as a raw-pointer set and avoid overlapping mutable views. pub struct Slice { /// Indexed by declaration-order field index. ptrs: [NonNull; MAX_FIELDS], @@ -622,11 +532,6 @@ impl Slice { self.len == 0 } - /// Typed column base for field `fi`. Substitutes a properly-aligned - /// dangling pointer when `F` is a ZST (the computed column offset is not - /// guaranteed `align_of::()`-aligned for over-aligned ZSTs). For - /// `cap == 0` no substitution is needed: `ptrs[fi]` is - /// `Reflected::::DANGLING`, which is already aligned for every field. #[inline(always)] fn col_ptr(&self, fi: usize) -> NonNull { if core::mem::size_of::() == 0 { @@ -635,10 +540,6 @@ impl Slice { self.ptrs[fi].cast::() } - /// Returns the column slice for field `NAME` typed as `&[F]`. - /// - /// Compile-time checked: a const-eval assertion verifies that `T` has a - /// field named `NAME` and that its type is exactly `F`. #[inline] pub fn items(&self) -> &[F] { let fi = const { Reflected::::check::() }; @@ -652,15 +553,6 @@ impl Slice { ColMut::new(self.col_ptr::(fi), self.len).as_mut_slice() } - /// Raw column pointer for callers that need simultaneous mutable access to - /// multiple distinct columns (which `items_mut`'s `&mut self` borrow would - /// otherwise forbid). Compile-time type-checked like `items`. - /// - /// Obtaining the pointer is always sound — it is computed by raw `add` on - /// the heap buffer base with no `&`/`&mut` intermediate, so it carries the - /// allocation's root provenance. The returned pointer is valid for - /// `self.len()` reads/writes; the caller must not create overlapping - /// `&mut` references to the same column when *dereferencing* it. #[inline] pub fn items_raw(&self) -> *mut F { let fi = const { Reflected::::check::() }; @@ -710,13 +602,6 @@ impl Slice { self.scatter(index, elem); } - /// Gather a `T` by per-field `ptr::read` from each column. - /// - /// The returned value is a **bitwise copy** — the SoA storage retains - /// ownership of every field. Dropping the gathered struct would free - /// columns the storage still owns (double-free on next `get` / `Drop`), - /// so it is wrapped in `ManuallyDrop`. Zig has no destructors so the - /// by-value copy is harmless there. pub fn get(&self, index: usize) -> ManuallyDrop { assert!( index < self.len, @@ -939,10 +824,6 @@ impl MultiArrayList { column_base::(self.bytes, self.capacity, fi).cast::() } - /// Get the shared slice of values for field `NAME`. - /// - /// Compile-time checked: const-eval verifies `NAME` is a field of `T` and - /// `F` is exactly its type. #[inline] pub fn items(&self) -> &[F] { let fi = const { Reflected::::check::() }; @@ -970,10 +851,6 @@ impl MultiArrayList { s.set(index, elem); } - /// Obtain all the data for one array element. - /// - /// Returns `ManuallyDrop` because the gathered struct is a bitwise - /// copy of column storage that the list still owns; see [`Slice::get`]. pub fn get(&self, index: usize) -> ManuallyDrop { self.slice().get(index) } @@ -1030,10 +907,6 @@ impl MultiArrayList { Some(ManuallyDrop::into_inner(val)) } - /// Inserts an item into an ordered list. Shifts all elements - /// after and including the specified index back by one and - /// sets the given index to the specified element. May reallocate - /// and invalidate iterators. pub fn insert(&mut self, index: usize, elem: T) -> Result<(), AllocError> { self.ensure_unused_capacity(1)?; self.insert_assume_capacity(index, elem); @@ -1123,18 +996,6 @@ impl MultiArrayList { self.capacity = 0; } - /// Run every element's destructor, then reset to empty (`len = 0`, - /// capacity retained). - /// - /// `Drop` for this type is **slab-only** — it frees the SoA backing buffer - /// but never runs column destructors (see the `Drop` impl for why: bitwise - /// [`clone`] aliasing). When `T` has fields that own global-heap resources - /// and the list is the unique owner, call this before the list goes out of - /// scope or those payloads leak. No-op when `!needs_drop::()`. - /// - /// Elements are dropped by gathering each row's column bytes back into a - /// stack `T` (the inverse of [`scatter`]) and letting it drop, so every - /// field — not just one named column — is destructed. pub fn drop_elements(&mut self) { if core::mem::needs_drop::() && self.len != 0 { let s = self.slice(); @@ -1248,12 +1109,6 @@ impl MultiArrayList { } } - /// Free the current backing allocation (if any) and reset `capacity` so a - /// repeat call is a no-op. Safe: `bytes`/`capacity` are private and every - /// constructor/mutator upholds the invariant that when - /// `layout_for::(self.capacity)` is `Some(layout)`, `self.bytes` is a - /// live allocation from `self.alloc` with exactly `layout` (see - /// [`aligned_alloc`] / [`set_capacity`]). fn free_allocated_bytes(&mut self) { if let Some(layout) = layout_for::(self.capacity) { // SAFETY: type invariant above — `self.bytes` was allocated by @@ -1278,18 +1133,6 @@ impl MultiArrayList { impl Drop for MultiArrayList { fn drop(&mut self) { - // Zig `deinit(self, gpa)`: `gpa.free(self.allocatedBytes())` — slab - // only, no per-element destructors. This is **intentionally preserved**: - // [`clone`] is a bitwise SoA memcpy (Zig semantics), so two live lists - // can alias the same column heap pointers — see `bundle_v2.rs` - // `clone_ast` / `deinit_without_freeing_arena`, which drains exactly - // one alias and relies on the other dropping slab-only. Running - // element destructors here would double-free that side. - // - // For lists that *do* uniquely own heap-backed columns (e.g. - // `LineOffsetTable.columns_for_non_ascii: Box<[i32]>`), call - // [`MultiArrayList::drop_elements`] before letting this run, or the - // column payloads leak. self.free_allocated_bytes(); } } diff --git a/src/collections/pool.rs b/src/collections/pool.rs index f23dde33c12..bc192d7568a 100644 --- a/src/collections/pool.rs +++ b/src/collections/pool.rs @@ -5,32 +5,11 @@ use core::ptr; use bun_core::Error; -// ────────────────────────────────────────────────────────────────────────── -// SinglyLinkedList -// ────────────────────────────────────────────────────────────────────────── -// -// PORT NOTE: Zig's `SinglyLinkedList(comptime T: type, comptime Parent: type)` -// threads `Parent` only so that `Node.release()` can call `Parent.release(node)`. -// In Rust the only `Parent` is `ObjectPool`, so `Node::release` is provided as -// an inherent method on `ObjectPool` instead and the `Parent` type param is -// dropped here. Diff readers: `node.release()` call sites become -// `ObjectPool::<..>::release(node)`. - /// Node inside the linked list wrapping the actual data. #[repr(C)] pub struct Node { // INTRUSIVE: pool.zig:7 — next link in singly-linked free list pub next: *mut Node, - // PORT NOTE: Zig stored `std.mem.Allocator param` here so `destroyNode` - // could free via the originating allocator. In Rust the global mimalloc - // allocator owns every `Box>`, so the field is dropped and - // `destroy_node` uses `heap::take`. - // - // PORT NOTE: `MaybeUninit` not `T` — Zig's `else undefined` (pool.zig:203) - // is well-defined-until-read, but Rust's `assume_init()` on uninit bytes is - // immediate UB for any `T` with validity invariants. Callers that use - // `INIT == None` write `data` before reading, so we keep the bytes - // uninitialized and only `assume_init_*` at access sites. pub data: MaybeUninit, } @@ -38,10 +17,6 @@ pub struct Node { // callers can write `T` directly. impl Node { - /// Read `(*p).next` for a known-non-null, live node pointer. Centralises - /// the `unsafe { (*p).next }` walk that appears throughout this module's - /// list traversals. Caller contract: `p` points at a live `Node` (never - /// null — debug-asserted). #[inline(always)] fn next_of(p: *const Node) -> *mut Node { debug_assert!(!p.is_null()); @@ -70,21 +45,11 @@ impl Node { unsafe { self.data.assume_init_mut() } } - /// Insert a new node after the current one. - /// - /// Arguments: - /// new_node: Pointer to the new node to insert. pub fn insert_after(&mut self, new_node: &mut Node) { new_node.next = self.next; self.next = std::ptr::from_mut::>(new_node); } - /// Remove a node from the list. - /// - /// Arguments: - /// node: Pointer to the node to be removed. - /// Returns: - /// node removed pub fn remove_next(&mut self) -> Option<*mut Node> { let next_node = if self.next.is_null() { return None; @@ -187,10 +152,6 @@ impl SinglyLinkedList { } } - /// Remove and return the first node in the list. - /// - /// Returns: - /// A pointer to the first node in the list. pub fn pop_first(&mut self) -> Option<*mut Node> { let first = if self.first.is_null() { return None; @@ -237,10 +198,6 @@ pub trait ObjectPoolType: Sized { pub struct DataStruct { pub list: SinglyLinkedList, pub loaded: bool, - // PORT NOTE: Zig used `MaxCountInt = std.math.IntFittingRange(0, max_count)`. - // Rust const generics cannot pick an integer type from a const value; use - // `usize` and accept the few extra bytes. - // PERF(port): was IntFittingRange — narrow if it shows up on a hot path. pub count: usize, } @@ -255,12 +212,6 @@ impl Default for DataStruct { } } -/// Object pool with a singly-linked free list. -/// -/// `THREADSAFE == true` ⇒ storage is thread-local (one free list per thread). -/// `THREADSAFE == false` ⇒ storage is a single process-wide static. -/// -/// `S` supplies the per-monomorphization static storage; see `object_pool!`. pub struct ObjectPool< T: ObjectPoolType, const THREADSAFE: bool, @@ -272,10 +223,6 @@ pub struct ObjectPool< // inherent assoc types are nightly-only; callers write `SinglyLinkedList` / // `Node` directly. -/// Per-monomorphization storage hook. Generic statics are not expressible in -/// Rust, so each `(T, THREADSAFE, MAX_COUNT)` instantiation must provide its -/// own `thread_local!` / `static` via this trait — typically generated by -/// `object_pool!`. pub trait PoolStorage: 'static { /// Run `f` with a borrow of this monomorphization's `DataStruct`. fn with(f: impl FnOnce(&RefCell>) -> R) -> R; @@ -361,13 +308,6 @@ impl, { - // We want this to be global - // but we don't want to create 3 global variables per pool - // instead, we create one global variable per pool - // - // PORT NOTE: Rust cannot place a `static` / `thread_local!` inside a - // generic `impl`; storage is supplied via the `S: PoolStorage` type - // parameter (see `object_pool!` for the usual declaration). #[inline] pub(crate) fn data(f: impl FnOnce(&RefCell>) -> R) -> R { S::with(f) @@ -424,11 +364,6 @@ where unsafe { (*Self::get_node()).data.as_mut_ptr() } } - /// Zig `get()` — pop a node from the free list or allocate a fresh one. - /// - /// When `T::INIT == None` and a fresh node is allocated, the returned - /// node's `data` is **uninitialized**; the caller must write a valid `T` - /// to it before reading it or passing the node to [`Self::release`]. pub fn get_node() -> *mut Node { let reused = Self::data(|cell| { let mut d = cell.borrow_mut(); @@ -455,10 +390,6 @@ where // TODO(port): log "Allocate {type_name} - {size} bytes" } - // Matches Zig's `data = if (Init) |i| i(..) else undefined` (pool.zig:203). - // For `INIT == None` the bytes stay uninitialized; the caller MUST write - // `data` before any read (and before `release()`, since `destroy_node` - // assumes it is initialized when dropping). let data = match T::INIT { Some(init_) => MaybeUninit::new(init_().expect("unreachable")), None => MaybeUninit::uninit(), @@ -571,20 +502,6 @@ where // object_pool! — declare per-instantiation storage // ────────────────────────────────────────────────────────────────────────── -/// Declare an `ObjectPool` alias plus its backing static/thread-local storage. -/// -/// ```ignore -/// // thread-local free list, capped at 32 nodes -/// object_pool!(pub StringVoidMapPool: StringVoidMap, threadsafe, 32); -/// // process-wide free list, uncapped -/// object_pool!(BufferPool: Vec, global, 0); -/// ``` -/// -/// Expands to a private storage struct implementing `PoolStorage` and a -/// `pub type $Name = ObjectPool<$T, .., $Storage>` alias. `threadsafe` ⇒ -/// `thread_local!` (one free list per thread); `global` ⇒ a single -/// process-wide `RefCell` (caller is responsible for not touching it from -/// multiple threads — matches the Zig `threadsafe = false` mode). #[macro_export] macro_rules! object_pool { ($vis:vis $name:ident : $ty:ty, threadsafe, $max:expr) => { @@ -637,11 +554,6 @@ macro_rules! __object_pool_storage { fn with( f: impl FnOnce(&::core::cell::RefCell<$crate::pool::DataStruct<$ty>>) -> R, ) -> R { - // PORT NOTE: Zig's `threadsafe = false` used a plain global - // `var data`; Rust forbids non-`Sync` statics, so this still - // expands to a thread-local. Single-threaded callers see the - // same one cell; cross-thread callers get per-thread pools - // (a slight behaviour difference, but safe). ::std::thread_local! { static __OBJECT_POOL_DATA: ::core::cell::RefCell< $crate::pool::DataStruct<$ty> @@ -653,10 +565,6 @@ macro_rules! __object_pool_storage { }; } -/// Internal: name of the storage struct generated by `__object_pool_storage!`. -/// Kept simple (no `paste!` dep) by using a fixed name; callers must therefore -/// declare at most one pool per containing module. If that proves too -/// restrictive, swap this for `paste::paste!`. #[doc(hidden)] #[macro_export] macro_rules! __paste_storage { @@ -665,12 +573,6 @@ macro_rules! __paste_storage { }; } -// ────────────────────────────────────────────────────────────────────────── -// `ObjectPoolType` impls for `bun_core` types live here (trait owner) to -// avoid a `bun_core → bun_collections` dep cycle now that `MutableString` -// is in `bun_core` (post `bun_string` merge). -// ────────────────────────────────────────────────────────────────────────── - /// Zig: `Npm.Registry.BodyPool = ObjectPool(MutableString, MutableString.init2048, true, 8)` /// (src/install/npm.zig). Init = `init2048`; reuse = `.reset()`. impl ObjectPoolType for bun_core::MutableString { diff --git a/src/collections/string_map.rs b/src/collections/string_map.rs index 9f29691412d..4db6fde3f8f 100644 --- a/src/collections/string_map.rs +++ b/src/collections/string_map.rs @@ -49,10 +49,6 @@ impl StringMap { self.map.count() } - /// Zig `insert` / `put`: dupe `value`; dupe `key` only when `dupe_keys` - /// and the key is new. (When `dupe_keys == false` Zig stored a borrowed - /// slice; here `Box<[u8]>` forces a copy regardless — the flag is kept for - /// API parity and to skip the redundant second copy.) pub fn insert(&mut self, key: &[u8], value: &[u8]) -> Result<(), AllocError> { let entry = self.map.get_or_put(key)?; // get_or_put already boxed `key` on miss; the Zig `dupe_keys` branch diff --git a/src/collections/vec_ext.rs b/src/collections/vec_ext.rs index bd7328df8c3..255fd129fd6 100644 --- a/src/collections/vec_ext.rs +++ b/src/collections/vec_ext.rs @@ -44,17 +44,6 @@ pub trait VecExt: Sized { /// a double-drop (PTR_AUDIT.md class #1: bitwise-copy of Drop-carrying /// type while source is still live). unsafe fn from_bump_slice(items: &mut [T]) -> Self; - /// Safe sibling of [`from_bump_slice`] for `T: Copy` — the - /// "source must never be element-dropped again" precondition holds - /// vacuously (`Copy` ⇒ no `Drop`), so the bitwise move degenerates to a - /// plain copy and needs no `unsafe` at the call site. Takes `&[T]` - /// (read-only) since nothing is logically moved out. - /// - /// Covers the dominant js_parser pattern - /// `arena.alloc_slice_copy(&[a, b]) → unsafe { from_bump_slice(..) }` - /// (invariant: bump arena outlives the AST). Callers may pass the bump - /// slice directly, or skip the intermediate bump alloc entirely and pass - /// the stack array — both compile to one memcpy into the global heap. fn from_arena_slice(items: &[T]) -> Self where T: Copy; @@ -73,10 +62,6 @@ pub trait VecExt: Sized { /// Arena pre-reservation: `Vec` cannot allocate from a bump arena, so this /// becomes a global-allocator `with_capacity`. The arena is ignored. fn init_capacity_in(_arena: &bun_alloc::Arena, cap: usize) -> Self; - /// Wrap a borrowed slice as a `Vec` that **must not be dropped or - /// grown**. Same hazard as the original — callers wrap in `ManuallyDrop`. - /// Kept only for the `StreamResult::Temporary*` pattern; new code should - /// take `&[T]` instead. unsafe fn from_borrowed_slice_dangerous(items: &[T]) -> ManuallyDrop; // ── accessors ───────────────────────────────────────────────────────── @@ -139,11 +124,6 @@ pub trait VecExt: Sized { /// to *exactly* `len + additional`. Use when the buffer is the final /// single-shot blob (sourcemap finalize, etc.). unsafe fn writable_slice_exact(&mut self, additional: usize) -> &mut [T]; - /// Reserves `additional` and returns the first `additional` slots of - /// spare capacity as `MaybeUninit`. Safe sibling of [`writable_slice`]: - /// caller writes some prefix then calls `set_len` (or [`uv_commit`] for - /// `Vec`) to commit. Unlike `spare_capacity_mut()` the returned slice - /// is exactly `additional` long, not `capacity - len`. fn reserve_spare(&mut self, additional: usize) -> &mut [core::mem::MaybeUninit]; /// `reserve(additional)` then [`expand_to_capacity`], returning the /// freshly-exposed tail as a raw `(ptr, len)` pair — i.e. @@ -189,11 +169,6 @@ pub trait VecExt: Sized { E: From; } -// Generic over `A` so the impl serves both `Vec` (Global) and -// `Vec` (AST-arena lists — `ExprNodeList`/`DeclList`/ -// `PropertyList`). `A: Default` lets every constructor produce the right -// allocator without a value in hand; both `Global` and `AstAlloc` are ZSTs -// with `Default`, so `A::default()` is free. impl VecExt for Vec { #[inline] fn init_capacity(n: usize) -> Self { @@ -468,11 +443,6 @@ impl VecExt for Vec { #[inline] fn move_to_list(&mut self) -> Vec { let taken = core::mem::replace(self, Vec::new_in(A::default())); - // Fast path: `Vec` → `Vec` is a pointer move, not a - // realloc+memcpy. Restores zero-copy behavior on the HTTP streaming - // paths (`RequestContext::response_buf`, `ByteStream`); the copying - // path is still required for `AstAlloc` etc. where the buffer must - // migrate heaps. if core::any::TypeId::of::() == core::any::TypeId::of::() { let mut taken = core::mem::ManuallyDrop::new(taken); // SAFETY: `A == Global`, so `Vec` and `Vec` have the @@ -576,12 +546,6 @@ pub trait ByteVecExt { fn write_utf16(&mut self, str: &[u16]) -> Result; fn write_type_as_bytes_assume_capacity(&mut self, int: Int); - /// libuv `uv_alloc_cb`-style: ensure **at least** `suggested` bytes of - /// spare capacity past `len()`, then return the *full* spare-capacity - /// slice (`len == capacity - len()`, which may exceed `suggested`). - /// - /// Callers that must hand libuv exactly `suggested` bytes slice the - /// result themselves: `&mut v.uv_alloc_spare(n)[..n]`. fn uv_alloc_spare(&mut self, suggested: usize) -> &mut [core::mem::MaybeUninit]; /// As [`uv_alloc_spare`] but typed `&mut [u8]` so the result can be used /// directly as a `uv_buf_t` / `read(2)` target without a per-site cast. @@ -719,20 +683,6 @@ impl OffsetByteList { } } -/// Bitwise-move every element of `src` to the **front** of `dst`, shifting -/// `dst`'s existing contents right by `src.len()`. `src` is left empty -/// (capacity retained). This is the mirror of std [`Vec::append`], which -/// moves to the back. -/// -/// Free function (not a `VecExt` method) so it is generic over *any* -/// `A: Allocator` — the `VecExt` blanket impl carries an -/// `A: Default + 'static` bound that `&'a MimallocArena` (i.e. -/// [`bun_alloc::ArenaVec`]) does not satisfy. `src` and `dst` may use -/// distinct allocators. -/// -/// Ports the open-coded `reserve → ptr::copy(shift) → copy_nonoverlapping → -/// set_len` pattern that translated Zig's `bun.copy`/`@memcpy` splice for -/// non-`Copy` element types. pub fn prepend_from(dst: &mut Vec, src: &mut Vec) { let src_len = src.len(); if src_len == 0 { diff --git a/src/collections/zig_hash_map.rs b/src/collections/zig_hash_map.rs index f8753b433a8..d85144ed107 100644 --- a/src/collections/zig_hash_map.rs +++ b/src/collections/zig_hash_map.rs @@ -61,12 +61,6 @@ fn capacity_for_size(size: u32) -> u32 { new_cap.next_power_of_two() } -// ─── HashContext ─────────────────────────────────────────────────────────── -// Zig threads a `Context` value with `hash(K) -> u64` / `eql(K, K) -> bool`. -// All Bun contexts are zero-sized, so model as a stateless trait keyed on the -// marker type. `AutoHashContext` covers `std.AutoHashMap`; `IdentityContext` -// covers the `hash(k) == k` case used for pre-hashed keys. - /// Hash/eql strategy for [`HashMap`]. Implement on a zero-sized marker type. pub trait HashContext { fn ctx_hash(key: &K) -> u64; @@ -80,10 +74,6 @@ pub struct AutoHashContext; impl HashContext for AutoHashContext { #[inline] fn ctx_hash(key: &K) -> u64 { - // Zig autoHash for unique-repr types is `Wyhash.hash(0, asBytes(&key))`. - // Routing through `core::hash::Hash` + wyhash is the closest stable - // approximation without per-type byte-layout plumbing; exact AutoContext - // bucket order isn't relied on by any test today. bun_wyhash::auto_hash(key) } #[inline] @@ -428,10 +418,6 @@ impl> HashMap { (idx, false) } - /// Zig `getOrPut`: single-probe insert-or-lookup. On miss the value slot is - /// left "undefined" in Zig; Rust cannot expose uninit through a `&mut V`, so - /// `V: Default` and the slot is default-initialised — callers overwrite - /// `*value_ptr` when `!found_existing`. pub fn get_or_put( &mut self, key: K, diff --git a/src/crash_handler/CPUFeatures.rs b/src/crash_handler/CPUFeatures.rs index c9deab1ab63..a5dc4f7c471 100644 --- a/src/crash_handler/CPUFeatures.rs +++ b/src/crash_handler/CPUFeatures.rs @@ -11,12 +11,6 @@ pub(crate) struct CPUFeatures { pub flags: Flags, } -// Zig: `packed struct(u8)` per-arch. All semantic fields are `bool`; the trailing -// `padding: uN = 0` is unused bits. bitflags! models this directly (unknown bits -// = padding). Bit order matches Zig packed-struct LSB-first layout. -// PORT NOTE: guide says "bitflags! if every field is bool" — padding is uN, but -// it is pure padding, so bitflags is the correct shape here. - #[cfg(target_arch = "x86_64")] bitflags::bitflags! { #[repr(transparent)] diff --git a/src/crash_handler/handle_oom.rs b/src/crash_handler/handle_oom.rs index 406d975831f..cd6d84404b4 100644 --- a/src/crash_handler/handle_oom.rs +++ b/src/crash_handler/handle_oom.rs @@ -1,49 +1,10 @@ use bun_alloc::AllocError; use bun_core::Error; -// fn isOomOnlyError(comptime ErrorUnionOrSet: type) bool -// -// Zig's `isOomOnlyError` is pure comptime `@typeInfo` reflection over an -// error set: it iterates the set's members and checks every name == "OutOfMemory". -// Rust has no error-set reflection. The equivalent is encoded structurally in -// the `HandleOom` trait impls below — the `AllocError` impls ARE the -// "OOM-only" arm (Output = T / Output = !), and the `bun_core::Error` impls -// ARE the "other errors possible" arm (Output = Result / Output = E). -// -// TODO(port): @typeInfo reflection — no direct Rust equivalent; encoded as trait impls. - -/// If `error_union_or_set` is `error.OutOfMemory`, calls `bun.outOfMemory`. Otherwise: -/// -/// * If that was the only possible error, returns the non-error payload for error unions, or -/// `noreturn` for error sets. -/// * If other errors are possible, returns the same error union or set, but without -/// `error.OutOfMemory` in the error set. -/// -/// Prefer this method over `catch bun.outOfMemory()`, since that could mistakenly catch -/// non-OOM-related errors. -/// -/// There are two ways to use this function: -/// -/// ```ignore -/// // option 1: -/// let thing = bun::handle_oom(allocate_thing()); -/// // option 2: -/// let thing = match allocate_thing() { Ok(v) => v, Err(err) => bun::handle_oom(err) }; -/// ``` -/// -/// PORT NOTE: In Rust, `Vec`/`Box` allocation already aborts on OOM via the -/// global allocator's `handle_alloc_error`. Per PORTING.md §Allocators, -/// callsites of `bun.handleOom(expr)` translate to bare `expr`. This function -/// remains for the residual cases where a `Result` is threaded -/// explicitly. pub fn handle_oom(error_union_or_set: A) -> A::Output { error_union_or_set.handle_oom() } -/// Encodes Zig's comptime return-type block (`return_type: { ... }`) of -/// `handleOom`. The Zig branched on `@typeInfo(ArgType)` (error_union vs -/// error_set) and on `isOomOnlyError(ArgType)`; each impl below is one arm of -/// that comptime switch. pub trait HandleOom { type Output; fn handle_oom(self) -> Self::Output; @@ -70,13 +31,6 @@ impl HandleOom for AllocError { } } -// ── .error_union, mixed error set → same union with OOM subtracted ─────── -// Zig computed the narrowed type via -// `@TypeOf(switch (err) { error.OutOfMemory => unreachable, else => |e| e })`. -// Rust error enums are nominal, not sets — there is no set subtraction. For -// the catch-all `bun_core::Error` we compare against the interned tag and -// return the same type. Per-crate `thiserror` enums that carry an -// `OutOfMemory` variant should add their own `HandleOom` impl. impl HandleOom for Result { type Output = Result; fn handle_oom(self) -> Result { diff --git a/src/crash_handler/lib.rs b/src/crash_handler/lib.rs index 8d79544897d..14daae84ede 100644 --- a/src/crash_handler/lib.rs +++ b/src/crash_handler/lib.rs @@ -18,10 +18,6 @@ //! A lot of this handler is based on the Zig Standard Library implementation //! for std.debug.panicImpl and their code for gathering backtraces. -// The cfg is the union of the two intrinsic call sites: `abort()` on the -// non-Windows crash path (all profiles) and `breakpoint()` on Windows debug -// builds only. Declaring the feature where neither is compiled (Windows -// release) trips `unused_features`. #![cfg_attr(any(not(windows), debug_assertions), feature(core_intrinsics))] #![allow(internal_features)] #![allow(nonstandard_style, static_mut_refs, unexpected_cfgs)] @@ -32,12 +28,6 @@ pub mod cpu_features; #[path = "handle_oom.rs"] pub mod handle_oom; -/// Link-time target for `bun_alloc::out_of_memory()` — declared -/// `extern "Rust"` in `bun_alloc` (which is below this crate in the dep graph) -/// and defined here. Mirrors `src/bun.zig:outOfMemory()` → -/// `crash_handler.crashHandler(.out_of_memory, null, @returnAddress())`. -/// `pub(crate)` so external callers route through the T0 `bun_alloc` entry -/// rather than bypassing it. #[cold] #[inline(never)] pub(crate) fn out_of_memory() -> ! { @@ -68,12 +58,6 @@ pub(crate) extern "Rust" fn __bun_crash_handler_dump_stack_trace( pub use draft::*; -// ────────────────────────────────────────────────────────────────────────── -// Local shim for `bun_debug` (no such crate exists yet). These are -// std.debug.* placeholders the Zig side leaned on; the Rust port will replace -// them with a real debug-info backend in a later pass. -// TODO(port): bun_debug::SelfInfo / SourceLocation / TtyConfig / capture_stack_trace -// ────────────────────────────────────────────────────────────────────────── pub mod debug { use super::draft::StackTrace; @@ -100,11 +84,6 @@ pub mod debug { pub(crate) const HAVE_ERROR_RETURN_TRACING: bool = false; pub(crate) const STRIP_DEBUG_INFO: bool = !cfg!(debug_assertions); - // ── SelfInfo (vendor/zig/lib/std/debug/SelfInfo.zig) ───────────────── - // D104: canonical home for the dladdr-backed `std.debug.SelfInfo` shim. - // Previously lived in `bun_jsc::btjs::zig_std_debug`; relocated here so the - // crash handler (lower-tier crate) gets real symbol names in debug builds - // and `btjs` re-exports from this module. #[cfg(not(windows))] use bun_collections::HashMap; use bun_core::{Error, err}; @@ -118,14 +97,6 @@ pub mod debug { address_map: HashMap>, } - /// Port of `SelfInfo.Module`. On Linux Zig uses `Dwarf.ElfModule`; on Darwin a - /// MachO symbol table reader. Both ultimately resolve `address → {name, CU, - /// source_location}`. The DWARF/MachO parsers are not ported; `dladdr(3)` - /// provides the symbol-name half (which is what `btjs` actually consumes for - /// its `__`/`_llint_call_javascript` prefix checks). `source_location` is left - /// `None`, which `print_line_info` already handles. - // PORT NOTE: full `readElfDebugInfo`/`readMachODebugInfo` (~2k LOC of DWARF) not - // ported — `dladdr` is the libc-level equivalent for symbol-name resolution. pub struct Module { base_address: usize, name: Box<[u8]>, @@ -253,15 +224,6 @@ pub mod debug { /// Port of `Module.getSymbolAtAddress`. #[cfg(windows)] pub fn get_symbol_at_address(&mut self, address: usize) -> Result { - // TODO(port-windows): SPEC DIVERGENCE — Zig's `std.debug.SelfInfo` - // resolves symbols on Windows via the loaded PE's PDB - // (`dbghelp.dll` `SymFromAddr`). That path is not yet ported, so - // every Windows backtrace currently prints bare addresses even - // when a PDB is shipped. This is NOT equivalent to the Zig spec - // for symbol-bearing builds; return the default-initialized - // `Symbol` (`name = "???"`) so the caller still prints the - // address line, but the dbghelp lookup must be implemented - // before Windows crash reports are usable. let _ = (address, self.base_address); Ok(SymbolInfo { name: b"???".to_vec().into_boxed_slice(), @@ -327,11 +289,6 @@ pub mod debug { Some(bun_paths::basename(name).to_vec().into_boxed_slice()) } - // ── std.debug.getSelfDebugInfo ─────────────────────────────────────── - // PORTING.md §Global mutable state: lazy debug-only singleton. RacyCell — - // only called from a stopped/crashing process (lldb or the crash handler - // after `panicking` has serialized), so no concurrent access; callers - // reborrow the returned `*mut` per-access. static SELF_DEBUG_INFO: bun_core::RacyCell> = bun_core::RacyCell::new(None); /// Port of `std.debug.getSelfDebugInfo`. NOT thread-safe (the Zig original @@ -357,10 +314,6 @@ pub mod debug { TtyConfig::NoColor } } - /// Port of `std.io.tty.Config` (vendor/zig/lib/std/Io/tty.zig). The - /// `windows_api` variant is omitted: every consumer here writes into an - /// in-memory buffer or raw fd 2, never the live `CONSOLE_SCREEN_BUFFER`, so - /// `SetConsoleTextAttribute` would colour the wrong stream. #[derive(Clone, Copy, PartialEq, Eq)] pub enum TtyConfig { NoColor, @@ -400,21 +353,8 @@ pub mod debug { } } -// ────────────────────────────────────────────────────────────────────────── -// Byte-writer trait — D101: deduped to canonical `bun_io::Write`. -// The local stub (TODO(port)) predated `bun_io` compiling; it carried a -// `core::fmt::Write` supertrait so `write!(…)` returned `fmt::Result`. The -// canonical trait instead provides its own `write_fmt` returning -// `Result<(), bun_core::Error>`, so `write!` on `impl Write` now yields the -// crate-native error directly (the `fmt_err` shim below became identity). -// `BoundedArray` and `FmtAdapter` impls live in `bun_io` (orphan rules). -// ────────────────────────────────────────────────────────────────────────── pub use bun_io::{FmtAdapter, Write}; -/// Raw, unbuffered stderr writer for the crash path. Stand-in for -/// `bun_sys::stderr_writer()` (not yet exposed by T1). -/// Only impls `bun_io::Write` — `write!` resolves to `bun_io::Write::write_fmt` -/// (alloc-free stack `Bridge`, async-signal-safe). pub(crate) struct StderrWriter; pub(crate) fn stderr_writer() -> StderrWriter { StderrWriter @@ -423,15 +363,6 @@ impl Write for StderrWriter { fn write_all(&mut self, bytes: &[u8]) -> Result<(), bun_core::Error> { #[cfg(windows)] { - // Zig spec: `std.fs.File.stderr().writerStreaming(&.{})` — on - // Windows that is `GetStdHandle(STD_ERROR_HANDLE)` + kernel32 - // `WriteFile`, NOT the CRT. Routing through MSVCRT `_write(2,…)` - // would (1) text-mode-translate `\n`→`\r\n` and (2) take the CRT - // per-fd lock, which can self-deadlock when the VEH crash handler - // fires on a thread that faulted *inside* CRT stdio. WriteFile is - // lock-free at the kernel32 layer. - // `WriteFile` is declared locally because `bun_windows_sys:: - // kernel32` does not (yet) export it (cf. src/sys/lib.rs). #[link(name = "kernel32")] unsafe extern "system" { fn WriteFile( @@ -491,10 +422,6 @@ mod draft { use super::{FmtAdapter, Write, debug, stderr_writer}; - /// D101: now identity. Pre-dedup `Write` had a `core::fmt::Write` supertrait so - /// `write!` returned `fmt::Result` and needed remapping. With canonical - /// `bun_io::Write::write_fmt` the error type is already `bun_core::Error`; this - /// stays as a no-op so the ~22 `.map_err(fmt_err)?` sites don't churn. #[inline(always)] fn fmt_err(e: bun_core::Error) -> bun_core::Error { e @@ -504,11 +431,6 @@ mod draft { /// `AtomicBool` static. Re-exported here so call sites read like the Zig. use bun_core::output::enable_ansi_colors_stderr; - /// Zig: `std.posix.abort()`. On POSIX this is `libc::abort()` (async-signal-safe). - /// On Windows, Zig's `std.posix.abort()` is *not* MSVCRT `abort()` — it is - /// `if (Debug) @breakpoint(); kernel32.ExitProcess(3);`. UCRT `abort()` would - /// raise SIGABRT, may print `R6010 - abort() has been called` to stderr, and - /// can pop a Watson/WER dialog — none of which the Zig spec does. #[inline(always)] fn abort() -> ! { #[cfg(windows)] @@ -541,10 +463,6 @@ mod draft { Ok(()) } - // TODO(port): `Cli` arrives from move-in (MOVE_DOWN bun_runtime::cli::Cli → crash_handler). - // Only the two bits the crash handler needs — main-thread check and the - // one-byte command tag for the trace URL — land here as plain globals that - // `bun_runtime` populates at startup. pub mod cli_state { use core::sync::atomic::{AtomicU8, AtomicU64, Ordering}; @@ -586,12 +504,6 @@ mod draft { /// The counter is incremented/decremented atomically. /// PORT NOTE: shared with bun_core::PANICKING so T0 callers see the same state. use bun_core::PANICKING; - // D131: dedup — these read the shared `PANICKING` atomic and were byte-identical - // to the bun_core (T0) copies. Re-export so `bun_crash_handler::{is_panicking, - // sleep_forever_if_another_thread_is_crashing}` keeps resolving for any - // out-of-tree callers. `dump_current_stack_trace` is intentionally NOT deduped: - // the bun_core version is an `extern "Rust"` dispatch shim, this crate's is the - // real impl (linked via `__bun_crash_handler_dump_stack_trace`). pub use bun_core::{is_panicking, sleep_forever_if_another_thread_is_crashing}; // Locked to avoid interleaving panic messages from multiple threads. @@ -607,20 +519,9 @@ mod draft { static INSIDE_NATIVE_PLUGIN: Cell> = const { Cell::new(None) }; static UNSUPPORTED_UV_FUNCTION: Cell> = const { Cell::new(None) }; - /// This can be set by various parts of the codebase to indicate a broader - /// action being taken. It is printed when a crash happens, which can help - /// narrow down what the bug is. Example: "Crashed while parsing /path/to/file.js" - /// - /// Some of these are enabled in release builds, which may encourage users to - /// attach the affected files to crash report. Others, which may have low crash - /// rate or only crash due to assertion failures, are debug-only. See `Action`. pub static CURRENT_ACTION: Cell> = const { Cell::new(None) }; } - // PORTING.md §Concurrency: `bun_threading::Guarded>` instead of bare Mutex + global Vec. - // Stores a boxed type-erased closure (not a bare fn pointer) so that - // `append_pre_crash_handler` can monomorphize a wrapper that actually invokes the - // caller's typed handler — mirroring Zig's `comptime handler` trampoline. struct CrashHandlerEntry(*mut c_void, Box); // SAFETY: only accessed under the mutex; the opaque ptr is never dereferenced // except by the registered callback on the crash thread. @@ -685,11 +586,6 @@ mod draft { } } - /// bun.bundle_v2.LinkerContext.generateCompileResultForJSChunk - /// - /// The bundler types (`LinkerContext` / `Chunk` / `PartRange`) live in a - /// higher-tier crate; `chunk`/`part_range` stay erased and are reinterpreted by - /// the `Linker` impl in `bun_bundler::LinkerContext`. #[cfg(feature = "show_crash_trace")] #[derive(Clone, Copy)] pub struct BundleGenerateChunk { @@ -789,10 +685,6 @@ mod draft { } } - /// Scoped `CURRENT_ACTION = action`. Snapshots the previous value, installs - /// `action`, and returns an [`ActionGuard`] that restores the previous value - /// on drop. Zig: `const old = current_action; defer current_action = old; - /// current_action = ...;`. #[inline] #[must_use] pub fn scoped_action(action: Action) -> ActionGuard { @@ -801,13 +693,6 @@ mod draft { ActionGuard(prev) } - /// Scoped `CURRENT_ACTION = .resolver{...}`. Zig (resolver.zig:672-679) sets - /// this only under `Environment.show_crash_trace` because module resolution is - /// extremely hot and has a low crash rate; the cfg-gate here mirrors that. - /// - /// `source_dir`/`import_path` are caller-interned (DirnameStore / source text) - /// and outlive the guard; the `&'static` lifetime erasure matches the existing - /// `Action::Parse`/`Visit`/`Print` slice fields (see TODO(port) above). #[inline] pub fn set_current_action_resolver( source_dir: &[u8], @@ -875,22 +760,8 @@ mod draft { { let _panic_guard = PANIC_MUTEX.lock(); - // Use an raw unbuffered writer to stderr to avoid losing information on - // panic in a panic. There is also a possibility that `Output` related code - // is not configured correctly, so that would also mask the message. - // - // Output.errorWriter() is not used here because it may not be configured - // if the program crashes immediately at startup. - // TODO(port): std.fs.File.stderr().writerStreaming — local raw StderrWriter (bun_sys - // FileWriter only impls std::io::Write, not the local byte-Write trait) let writer = &mut stderr_writer(); - // The format of the panic trace is slightly different in debug - // builds. Mainly, we demangle the backtrace immediately instead - // of using a trace string. - // - // To make the release-mode behavior easier to demo, debug mode - // checks for this CLI flag. let debug_trace = Environment::SHOW_CRASH_TRACE && 'check_flag: { for arg in bun_core::argv() { @@ -1055,11 +926,6 @@ mod draft { if write!(writer, "({})", bun_fmt::utf16(span)).is_err() { abort(); } - // NOTE: `GetThreadDescription` heap-allocates `name` and the - // caller is meant to `LocalFree` it. The Zig spec leaks it - // identically (crash_handler.zig:316-322) — this runs on a - // `noreturn` crash path immediately before `ExitProcess(3)`, - // so the leak is intentional. } else { if write!( writer, @@ -1111,13 +977,6 @@ mod draft { let trace: &StackTrace = 'blk: { let idx: usize = match seed { TraceSeed::ErrorReturn(ert) => break 'blk ert, - // For an actual fault the signal/exception handler hands - // us the saved register context. Seeding the walk from - // the fault `pc`/`fp` is the only reliable way to recover - // the faulting stack: the POSIX handler runs on an - // `SA_ONSTACK` altstack, so its own frame chain is - // disjoint from the faulting thread's, and release builds - // strip the unwind tables a CFI-based capture would need. TraceSeed::Fault { pc, fp } => { bun_core::debug::capture_from_context(pc, fp, &mut addr_buf) } @@ -1273,13 +1132,6 @@ mod draft { report(trace_str_buf.const_slice()); - // At this point, the crash handler has performed it's job. Reset the segfault handler - // so that a crash will actually crash. We need this because we want the process to - // exit with a signal, and allow tools to be able to gather core dumps. - // - // This is done so late (in comparison to the Zig Standard Library's panic handler) - // because if multiple threads segfault (more often the case on Windows), we don't - // want another thread to interrupt the crashing of the first one. reset_segfault_handler(); if bun_core::auto_reload_on_crash() @@ -1601,10 +1453,6 @@ mod draft { }, ); - /// Extract `(pc, fp)` from the `ucontext_t` the kernel hands the signal - /// handler. Seeds the frame-pointer walk from the faulting frame. Returns - /// `None` on arch/OS combos we don't have register offsets for (the caller - /// then falls back to a current-stack capture). #[cfg(unix)] fn fault_context_from_ucontext(ctx: *mut c_void) -> Option<(usize, usize)> { debug_assert!(!ctx.is_null()); @@ -1720,13 +1568,6 @@ mod draft { Ok(()) } - // Windows VEH handle storage lives at T0 (`bun_core::WINDOWS_SEGFAULT_HANDLE`, - // `AtomicPtr`) so `bun_core::raise_ignoring_panic_handler` can remove - // it before re-raising without an upward dep. Single source of truth — this - // crate reads/writes/swaps that same atomic; no local mirror (a second copy - // would go stale after T0's swap-to-null and trip the `debug_assert!(rc != 0)` - // in `reset_segfault_handler` on a double-remove). - #[cfg(unix)] pub fn reset_on_posix() { if Environment::ENABLE_ASAN { @@ -1787,22 +1628,9 @@ mod draft { /// `raise_ignoring_panic_handler` does the SIG_DFL reset itself with libc. pub(crate) fn install_hooks() { bun_core::CRASH_HANDLER_INSTALLED.store(true, Ordering::Relaxed); - // T0 `bun_alloc::out_of_memory()` and `bun_core::dump_current_stack_trace()` - // reach this crate via link-time `extern "Rust"` symbols - // (`__bun_crash_handler_out_of_memory` / `__bun_crash_handler_dump_stack_trace`) - // — no runtime registration needed. - // - // Route Rust `panic!()` through the trace-string + report path. Zig wires - // `pub const panic = bun.crash_handler.panic` at the root so every - // `@panic()` reports; the Rust port's bare `panic!` was printing the std - // default hook + unwinding with no trace string and no upload. std::panic::set_hook(Box::new(rust_panic_hook)); } - /// `std::panic` hook: emit the same trace-string + auto-report as the fatal - /// `crash_handler()` path, then **abort** (matches Zig's `noreturn` panic). - /// With `panic = "abort"` no unwind starts after this hook returns, so there - /// are no `catch_unwind` boundaries to reach. #[cold] #[inline(never)] fn rust_panic_hook(info: &std::panic::PanicHookInfo<'_>) { @@ -1821,10 +1649,6 @@ mod draft { } PANIC_STAGE.with(|s| s.set(1)); - // Just the panic message — no `(file:line:col)` suffix. The call site is - // captured in the backtrace and symbolized there (matching Zig, which - // never appended a location to the message). With `-Zlocation-detail=none` - // in release the location would be `:0:0` anyway. let mut msg_buf = BoundedArray::::default(); { let payload = info.payload(); @@ -1934,23 +1758,9 @@ mod draft { report(trace_str_buf.const_slice()); - // A Rust `panic!` is a bug. The process must not continue — with - // `panic = "abort"` no unwind starts, so `catch_unwind` boundaries are - // unreachable for Rust panics. This matches Zig's - // `pub const panic = bun.crash_handler.panic` (which is `noreturn`). crash(); } - /// Adapter for non-fatal `bun_core::dump_current_stack_trace` callers - /// (fd.rs EBADF debug-warn, ref_count leak reports). Zig routes these through - /// `dumpStackTrace` which on Linux debug spawns `llvm-symbolizer` — but the - /// Rust debug binary's .debug_info is large enough that the symbolizer parse - /// alone costs ~5s, which is unacceptable on a hot non-fatal path - /// (`closeSync(EBADF)` was timing out fs.test.ts at the 5s budget). For these - /// advisory dumps we honour `frame_count` and use WTF's dladdr-based printer - /// (sub-ms, function names only). The full `dump_stack_trace` (with - /// llvm-symbolizer source lines) is kept for actual crash/panic paths, which - /// call it directly. pub(crate) fn dump_current_stack_trace_from_core( first_address: Option, limits: bun_core::DumpStackTraceOptions, @@ -2047,10 +1857,6 @@ mod draft { } bun_sys::windows::EXCEPTION_STACK_OVERFLOW => CrashReason::StackOverflow, - // exception used for thread naming - // https://learn.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2017/debugger/how-to-set-a-thread-name-in-native-code?view=vs-2017#set-a-thread-name-by-throwing-an-exception - // related commit - // https://github.com/go-delve/delve/pull/1384 bun_sys::windows::MS_VC_EXCEPTION => { return bun_sys::windows::EXCEPTION_CONTINUE_EXECUTION; } @@ -2301,25 +2107,12 @@ mod draft { } } - /// Each platform is encoded as a single character. It is placed right after the - /// slash after the version, so someone just reading the trace string can tell - /// what platform it came from. L, M, and W are for Linux, macOS, and Windows, - /// with capital letters indicating aarch64, lowercase indicating x86_64. - /// - /// eg: 'https://bun.report/1.1.3/we04c... - /// ^ this tells you it is windows x86_64 - /// - /// Baseline gets a weirder encoding of a mix of b and e. struct Platform; impl Platform { // TODO(port): Zig builds this via @tagName(os) ++ "_" ++ @tagName(arch) ++ baseline. // Rust cannot concat ident names at const time without a proc-macro; spell out the cfg matrix. const CURRENT: u8 = { - // Android folds into the Linux variants — Zig's `@tagName(Environment.os)` - // (crash_handler.zig:1153) yields `"linux"` for Android because Zig keeps - // it under `os.tag == .linux`. bun.report decodes the same single-char - // codes; introducing new ones would break older decoders. #[cfg(all( any(target_os = "linux", target_os = "android"), target_arch = "x86_64", @@ -2386,11 +2179,6 @@ mod draft { }; } - /// Note to the decoder on how to process this string. This ensures backwards - /// compatibility with older versions of the tracestring. - /// - /// '1' - original. uses 7 char hash with VLQ encoded stack-frames - /// '2' - same as '1' but this build is known to be a canary build const VERSION_CHAR: &str = if Environment::IS_CANARY { "2" } else { "1" }; // Zig: `if (git_sha.len > 0) git_sha[0..7] else "unknown"` — the v1/v2 trace-string @@ -2433,12 +2221,6 @@ mod draft { let image_path = bun_sys::windows::exe_path_w(); return Some(StackLine { - // To remap this, `pdb-addr2line --exe bun.pdb 0x123456` - // Zig: `@intCast(addr - base_address)` — unchecked in ReleaseFast. - // Use a wrapping cast so an oversize/underflowed module offset - // produces a junk frame instead of panicking *inside* the crash - // handler (which would escalate to a double-panic and lose the - // entire report). address: addr.wrapping_sub(base_address) as i32, object: if name != image_path.as_slice() { @@ -2514,10 +2296,6 @@ mod draft { return None; } - // To remap this, you have to add the offset (which is going to be 0x100000000), - // and then you can run it through `llvm-symbolizer --obj bun-with-symbols 0x123456` - // The reason we are subtracting this known offset is mostly just so that we can - // fit it within a signed 32-bit integer. The VLQs will be shorter too. return Some(StackLine { object: None, address: i32::try_from(image_relative_address) @@ -2778,11 +2556,6 @@ mod draft { false } - /// Bun automatically reports crashes on Windows and macOS - /// - /// These URLs contain no source code or personally-identifiable - /// information (PII). The stackframes point to Bun's open-source native code - /// (not user code), and are safe to share publicly and with the Bun team. fn report(url: &[u8]) { if !is_reporting_enabled() { return; @@ -2822,13 +2595,6 @@ mod draft { )); // PERF(port): was assume_capacity { - // `unused_capacity_slice` is `&mut [MaybeUninit]`; - // `from_raw_parts_mut::` over that storage would assert the - // bytes are already initialized (library-UB even though - // `convert_utf8_to_utf16_in_buffer` only writes). Zero-fill the - // spare slots first so the `&mut [u16]` we hand to simdutf is over - // initialized memory. Cold crash-reporter path — the extra memset - // is irrelevant. let spare = cmd_line.unused_capacity_slice(); for slot in spare.iter_mut() { slot.write(0); @@ -2872,12 +2638,6 @@ mod draft { ) }; - // we don't care what happens with the process - // NOTE: on success `CreateProcessW` returns two open kernel handles in - // `process.hProcess` / `process.hThread` that the caller is meant to - // `CloseHandle`. The Zig spec leaks them identically (crash_handler.zig: - // 1545-1546 `_ = spawn_result;`); `report()` runs immediately before - // `crash()` → `ExitProcess(3)`, so the kernel reclaims them anyway. let _ = spawn_result; let _ = url; } @@ -2978,22 +2738,10 @@ mod draft { libc::sigaction(sig, &raw const sigact, core::ptr::null_mut()); } } - // Zig: `@trap()` — emits ud2 (x86_64 → SIGILL) / brk (aarch64 → SIGTRAP). - // `core::intrinsics::abort()` lowers to the same trap instruction, preserving - // the Zig exit signal. Do NOT use `libc::abort()` here — that raises SIGABRT - // (exit 134), which is the *Windows* path's behaviour. core::intrinsics::abort(); } #[cfg(windows)] { - // Node.js exits with code 134 (128 + SIGABRT) instead. We use abort() as it - // includes a breakpoint which makes crashes easier to debug. - // - // Zig spec (crash_handler.zig:1592): the `.windows` arm is literally - // `std.posix.abort();` — i.e. our same-module `abort()` helper, which on - // Windows is `@breakpoint()` (Debug only) then `kernel32.ExitProcess(3)`. - // Do NOT call MSVCRT `libc::abort()` here — that raises SIGABRT, may print - // the CRT `abort() has been called` message, and can invoke WER. abort() } } @@ -3009,12 +2757,6 @@ mod draft { // TODO(port): std.meta.Int(.unsigned, @bitSizeOf(anyerror)) — bun_core::Error is errno-based let err = bun_core::Error::from_errno(err_int_workaround_for_zig_ccall_bug as i32); - // The format of the panic trace is slightly different in debug - // builds Mainly, we demangle the backtrace immediately instead - // of using a trace string. - // - // To make the release-mode behavior easier to demo, debug mode - // checks for this CLI flag. let is_debug = cfg!(debug_assertions) && 'check_flag: { for arg in Output::argv() { @@ -3080,13 +2822,6 @@ mod draft { } } - /// In many places we catch errors, the trace for them is absorbed and only a - /// single line (the error name) is printed. When this is set, we will print - /// trace strings for those errors (or full stacks in debug builds). - /// - /// This can be enabled by passing `--verbose-error-trace` to the CLI. - /// In release builds with error return tracing enabled, this is also exposed. - /// You can test if this feature is available by checking `bun --help` for the flag. #[inline] pub fn handle_error_return_trace(err: bun_core::Error, maybe_trace: Option<&StackTrace>) { handle_error_return_trace_extra::(err, maybe_trace); @@ -3208,11 +2943,6 @@ mod draft { ] }; for &program in programs { - // PERF(port): was arena bulk-free + StackFallbackAllocator — using global allocator here. - // Only stop once a symbolizer actually ran and exited 0. Any failure - // (not found, spawn error, or non-zero exit) tries the next program and - // ultimately falls through to the WTF fallback below — a found-but-broken - // symbolizer must not leave the crash report with no trace at all. match spawn_symbolizer(program, trace) { Ok(()) => return, Err(_) => continue, @@ -3323,29 +3053,16 @@ mod draft { } } - /// From now on, prevent crashes from being reported to bun.report or the URL overridden in - /// BUN_CRASH_REPORT_URL. Should only be used for tests that are going to intentionally crash, - /// so that they do not fail CI due to having a crash reported. And those cases should guard behind - /// a feature flag and call right before the crash, in order to make sure that crashes other than - /// the expected one are not suppressed. pub fn suppress_reporting() { suppress_core_dumps_if_necessary(); SUPPRESS_REPORTING.store(true, Ordering::Relaxed); } - // src/ptr/ref_count.rs:16). Re-export so `bun_crash_handler::StoredTrace` paths - // keep compiling. NOTE: if `debug::return_address()` is ever wired to a real - // `@returnAddress()` intrinsic, apply that improvement in bun_core's - // `StoredTrace::capture()` instead — this crate no longer owns the type. pub use bun_core::StoredTrace; // TODO(port): move to *_jsc — `pub const js_bindings = @import("../runtime/api/crash_handler_jsc.zig").js_bindings;` // Per PORTING.md this *_jsc alias is deleted; the bindings live as an extension trait in bun_runtime. - /// For large codebases such as bun.bake.DevServer, it may be helpful - /// to dump a large amount of state to a file to aid debugging a crash. - /// - /// Pre-crash handlers are likely, but not guaranteed to call. Errors are ignored. pub fn append_pre_crash_handler( ptr: *mut T, handler: fn(&mut T) -> Result<(), bun_core::Error>, @@ -3394,10 +3111,6 @@ mod draft { // `bun_crash_handler::WriteStackTraceLimits` importers keep compiling. pub use bun_core::DumpStackTraceOptions as WriteStackTraceLimits; - /// Clone of `debug.writeStackTrace`, but can be configured to stop at either a - /// frame count, or when hitting jsc LLInt Additionally, the printing function - /// does not print the `^`, instead it highlights the word at the column. This - /// Makes each frame take up two lines instead of three. pub fn write_stack_trace( stack_trace: &StackTrace, out_stream: &mut impl Write, @@ -3548,10 +3261,6 @@ mod draft { compile_unit_name: &[u8], tty_config: TtyConfig, ) -> Result<(), bun_core::Error> { - // Zig: `Environment.base_path ++ std.fs.path.sep_str` (comptime concat). - // `Environment::BASE_PATH` is `&[u8]`, which `const_format::concatcp!` cannot - // ingest. The constant is tiny and this path is debug-only — build it once - // at runtime in a stack BoundedArray (no heap, async-signal-safe). let mut base_path_buf = BoundedArray::::default(); let _ = base_path_buf.append_slice(Environment::BASE_PATH); let _ = base_path_buf.append_slice(bun_paths::SEP_STR.as_bytes()); @@ -3615,10 +3324,6 @@ mod draft { Ok(()) } - /// Modified version of `debug.printLineFromFileAnyOs` that uses two passes. - /// - Record the whole slice into a buffer - /// - Locate the column, expand a highlight to one word. - /// - Print the line, with the highlight. fn print_line_from_file_any_os( out_stream: &mut impl Write, tty_config: TtyConfig, diff --git a/src/csrf/lib.rs b/src/csrf/lib.rs index 77285aedeae..79d87040cca 100644 --- a/src/csrf/lib.rs +++ b/src/csrf/lib.rs @@ -81,13 +81,6 @@ impl TokenFormat { } } -/// Generate a new CSRF token -/// -/// Parameters: -/// - options: Configuration for token generation -/// - out_buffer: caller-provided buffer for the raw token bytes -/// -/// Returns: A slice into `out_buffer` containing the raw token pub fn generate<'a>( options: &GenerateOptions<'_>, out_buffer: &'a mut [u8; 512], @@ -144,12 +137,6 @@ pub fn generate<'a>( Ok(&mut out_buffer[0..len]) } -/// Validate a CSRF token -/// -/// Parameters: -/// - options: Configuration for token validation -/// -/// Returns: true if valid, false if invalid pub fn verify(options: &VerifyOptions<'_>) -> bool { // Detect the encoding format let encoding: TokenFormat = options.encoding; diff --git a/src/css/context.rs b/src/css/context.rs index fe7ed8ece08..f042be36733 100644 --- a/src/css/context.rs +++ b/src/css/context.rs @@ -96,13 +96,6 @@ impl<'a> PropertyHandlerContext<'a> { } } -// ─── heavy rule-building helpers (gated) ────────────────────────────────── -// blocked_on: css_rules::{CssRule,CssRuleList,StyleRule,SupportsRule,media}, -// selectors::parser::{Direction,Component,PseudoClass}, DeclarationBlock -// construction with bump-allocated lists, properties/custom::UnparsedProperty. -// These build whole rule subtrees and are only called from the (still-gated) -// minify path; un-gate alongside `rules/style.rs`. - impl<'a> PropertyHandlerContext<'a> { /// `'static`-erased arena handle for building `DeclarationBlock<'static>` / /// `DeclarationList<'static>` (see rules/mod.rs `decl_block_static`). diff --git a/src/css/css_modules.rs b/src/css/css_modules.rs index 64bcf8eb737..54a98bf72a7 100644 --- a/src/css/css_modules.rs +++ b/src/css/css_modules.rs @@ -8,10 +8,6 @@ use crate as css; // TODO(port): narrow error set pub use crate::Error; -// ───────────────────────────────────────────────────────────────────────── -// `reference_dashed`'s `dest.importRecord()` lookup is hoisted to the caller (see PORT NOTE on -// the method) to satisfy Rust borrowck (caller holds `&mut dest.css_module`). -// ───────────────────────────────────────────────────────────────────────── pub struct CssModule<'a> { pub config: &'a Config, pub sources: &'a Vec>, @@ -77,10 +73,6 @@ impl<'a> CssModule<'a> { // PORT NOTE: `deinit` was a no-op (`// TODO: deinit`); Drop is implicit. No `impl Drop` needed. pub fn get_reference(&mut self, bump: &'a Bump, name: &'a [u8], source_index: u32) { - // PORT NOTE: Zig `getOrPut` returns an uninitialized value slot; - // bun_collections::ArrayHashMap::get_or_put requires `V: Default` - // (CssModuleExport can't be Default — BumpVec field). Reshaped to the - // entry()-API instead. use bun_collections::array_hash_map::MapEntry; match self.exports_by_source_index[source_index as usize].entry(name) { MapEntry::Occupied(mut o) => { @@ -102,14 +94,6 @@ impl<'a> CssModule<'a> { } } - // PORT NOTE: Zig `referenceDashed` took `*Printer` so it could read - // `dest.arena` and call `dest.importRecord(idx)`. In Rust the only - // caller (`DashedIdentReference::to_css`) already holds a `&mut` borrow of - // `dest.css_module` (which *is* `self`), so threading `&mut Printer` in - // here would alias. The caller pre-resolves the import-record path and - // hands it down as `specifier_path`; the fallible `importRecord` lookup - // therefore lives at the call site, which is why this no longer returns - // `Result<_, PrintErr>`. pub fn reference_dashed( &mut self, bump: &'a Bump, @@ -133,10 +117,6 @@ impl<'a> CssModule<'a> { ) } None => { - // Local export. Mark as used. - // PORT NOTE: Zig `getOrPut` returns an uninitialized value - // slot; `CssModuleExport` cannot be `Default` (BumpVec field), - // so reshape to the `entry()` API like `get_reference` above. use bun_collections::array_hash_map::MapEntry; match self.exports_by_source_index[source_index as usize].entry(name) { MapEntry::Occupied(mut o) => { @@ -475,11 +455,6 @@ impl<'a> CssModuleReference<'a> { } } -/// LAYERING: canonical implementation lives in `bun_base64::wyhash_url_safe` -/// (a leaf crate) so `bun_bundler::LinkerContext::mangle_local_css` can call -/// the *same* hasher without depending on `bun_css`. Re-export here so -/// in-crate callers (`dependencies.rs`, `rules/import.rs`) keep the -/// `css_modules::hash` path from the Zig spec. #[inline] pub fn hash<'a>(bump: &'a Bump, args: Arguments<'_>, at_start: bool) -> &'a [u8] { bun_base64::wyhash_url_safe(bump, args, at_start) diff --git a/src/css/css_parser.rs b/src/css/css_parser.rs index 18b0f031a65..94b8fea387e 100644 --- a/src/css/css_parser.rs +++ b/src/css/css_parser.rs @@ -13,11 +13,6 @@ use bun_collections::bit_set::{ArrayBitSet, num_masks_for}; use bun_collections::{ArrayHashMap, MapEntry, VecExt}; use bun_core::strings; -// ───────────────────────────── re-exports ───────────────────────────── -// -// The Zig css_parser hub re-exports the entire crate surface. The Rust port -// keeps that shape: cross-module re-exports + parser core live here. - /// `bun.ast.Index` — bundler source-file index. Hoisted into /// `bun_options_types` to keep css below the parser tier. use bun_ast::Index as SrcIndex; @@ -50,12 +45,6 @@ pub use crate::values::{ ident::{CustomIdent, CustomIdentList, DashedIdent, Ident}, }; -// ── cross-module re-exports ────────────────────────────────────────────── -// Re-export the hub surfaces (rules/, selectors/, media_query, declaration, -// context, properties) so the rule-parser layer below can name -// `CssRule`/`SelectorList`/`DeclarationBlock` directly. `gated_shims` below -// carries the handful of types `AtRulePrelude` references that those hubs don't yet -// expose. pub use crate::context::PropertyHandlerContext; pub use crate::declaration::{self, DeclarationBlock, DeclarationHandler, DeclarationList}; pub use crate::media_query::{self, MediaFeatureType, MediaList}; @@ -90,32 +79,14 @@ pub use crate::values::{ pub use gated_shims::*; -/// Minimal stand-ins for types that live in still-gated sibling *leaf* modules -/// (rules/{keyframes,page,container,...}, values::{number,string}). The hub -/// modules above are real; only the per-rule payload types `AtRulePrelude` -/// reaches into by name remain shimmed here. When a leaf un-gates, delete the -/// matching shim. mod gated_shims { - // ── rules/ leaf-module payload re-exports ──────────────────────────── - // The leaf modules are un-gated; re-export the real prelude payload types - // `AtRulePrelude` carries so the rule-parser impl bodies type-check - // against the same structs `CssRule` stores. pub use crate::rules::container::{ContainerCondition, ContainerName}; pub use crate::rules::keyframes::KeyframesName; pub use crate::rules::page::PageSelector; - // ── ast crate-tier shims ───────────────────────────────────────────── - /// `bun.ast.Ref` / `bun.ast.MangledProps` were re-exported via - /// `bun_js_parser`; css sits below that tier. The real types were - /// MOVE_DOWN'd into `bun_logger` (see logger/lib.rs:216). pub mod ast { pub use bun_ast::{Ref, RefTag}; - // Value type MUST match `bun_js_printer::MangledProps` exactly so the - // bundler can pass `&LinkerContext.mangled_props` straight through — - // the previous `*const [u8]` shim forced a `repr(Rust)` generic - // type-pun (`ArrayHashMap<_, Box<[u8]>>` → `ArrayHashMap<_, *const [u8]>`) - // whose layout equivalence the language does not guarantee. pub type MangledProps = bun_collections::ArrayHashMap>; /// `bun.fs.Path` — `ImportRecord.path` field type. The /// real `bun.fs.Path` was MOVE_DOWN'd into `bun_paths::fs`. @@ -206,10 +177,6 @@ impl SourceLocation { } } - // PORT NOTE: Zig used `anytype` + `@TypeOf` to dispatch on - // `ParserError | BasicParseError | SelectorParseErrorKind`. In Rust this - // becomes a trait `IntoParserError` implemented by each live variant - // (the `BasicParseError` arm is dead/ill-typed in Zig — see note below). pub fn new_custom_error(self, err: impl IntoParserError) -> ParseError { ParseError { kind: errors_::ParserErrorKind::custom(err.into_parser_error()), @@ -229,13 +196,6 @@ impl IntoParserError for ParserError { self } } -// PORT NOTE: Zig's `newCustomError` had a third `@TypeOf` arm for -// `BasicParseError`, but that arm is dead and ill-typed — it wraps -// `BasicParseError.intoDefaultParseError(err)` (a `ParseError(ParserError)`) -// in `.custom`, which expects a `ParserError`. No caller ever passes -// `BasicParseError`, so Zig's lazy comptime never instantiates it. We -// intentionally do NOT impl `IntoParserError` for `BasicParseError` here. -// `SelectorParseErrorKind` is impl'd in `selectors/parser.rs`. pub type Error = Err; @@ -261,56 +221,17 @@ pub fn void_wrap( move |(), p| parsefn(p) } -// ───────────────────────── Derive*-style comptime helpers ───────────────────────── -// -// The Zig file defines `DefineListShorthand`, `DefineShorthand`, -// `DefineRectShorthand`, `DefineSizeShorthand`, `DeriveParse`, `DeriveToCss`, -// `DefineEnumProperty`, `DeriveValueType` — all of which use `@typeInfo` / -// `@field` comptime reflection to generate `parse`/`toCss`/etc. for arbitrary -// types. PORTING.md §Comptime reflection: the protocol becomes a trait -// (`ToCss`, `Parse`, `EnumProperty`, ...) and per-type impls are generated by -// a `#[derive(...)]` proc-macro. We declare the traits here and stub the -// helper bodies that callers in other files reference. - -/// Shorthand longhand-reconstruction helpers. -/// -/// PORT NOTE: Zig's `DefineShorthand` bodies are `@compileError(todo_stuff.depth)` -/// — i.e. instantiating the comptime fn and reaching any method is a compile -/// error. The faithful Rust mapping is a trait with **no default bodies**: any -/// `impl DefineShorthand for T` that omits a method fails at compile time, same -/// as the Zig. Per-type bodies are emitted by `#[derive(DefineShorthand)]` -/// using the (currently commented-out) `PropertyFieldMap`/`VendorPrefixMap` -/// reflection algorithm in `css_parser.zig` lines 316–500. pub trait DefineShorthand: Sized { /// The shorthand's own `PropertyIdTag` (Zig: `comptime property_name`). const PROPERTY_NAME: PropertyIdTag; - /// Returns a shorthand from the longhand properties defined in the given - /// declaration block, plus whether all matched longhands were `!important`. - /// - /// Derive walks `decls.declarations` then `decls.important_declarations`; - /// for each property, matches its `PropertyIdTag` against each field's - /// mapped tag (and vendor prefix where applicable), deep-clones the value - /// into the corresponding field, and tracks a per-field set bitmask. If any - /// field's prefix mismatches, returns `None`. If `important_count > 0 && - /// important_count != count`, returns `None`. Returns `Some((self, important))` - /// only when every field was set. fn from_longhands( decls: &DeclarationBlock, vendor_prefix: VendorPrefix, ) -> Option<(Self, bool)>; - /// Returns the longhand `PropertyId`s this shorthand expands to, in field - /// declaration order. Derive emits a `const` array of - /// `PropertyId::{ vendor_prefix }` (prefix only for fields present - /// in `VendorPrefixMap`). fn longhands(vendor_prefix: VendorPrefix) -> &'static [PropertyId]; - /// Returns a single longhand `Property` for this shorthand, given its id. - /// Derive matches `property_id`'s tag against each field's mapped tag, - /// deep-clones the field value, and wraps it in the corresponding - /// `Property::` variant (paired with the prefix when vendor-mapped). - /// Returns `None` if no field matches. fn longhand(&self, property_id: &PropertyId) -> Option; /// Updates this shorthand from a longhand property. Derive matches @@ -319,22 +240,10 @@ pub trait DefineShorthand: Sized { fn set_longhand(&mut self, property: &Property) -> bool; } -// PORT NOTE: Zig's `DefineListShorthand` / `DefineRectShorthand` / -// `DefineSizeShorthand` / `DeriveParse` / `DeriveToCss` comptime fns became -// proc-macros (`bun_css_derive::*`, re-exported below) plus the -// `impl_rect_shorthand!` / `impl_size_shorthand!` macros in -// `properties/margin_padding.rs`. The placeholder trait stubs that previously -// mirrored their `parse`/`to_css` signatures were dead (zero impls/bounds) and -// duplicated `generics::{Parse, ToCss}`, so they were removed. - /// `enum_property_util` — generic `parse`/`toCss`/`asStr` for plain enums. pub mod enum_property_util { use super::*; - // TODO(port): `as_str` / `parse` / `to_css` here used Zig - // `bun.ComptimeEnumMap` + `@tagName`. In Rust this is - // `strum::IntoStaticStr` + `strum::EnumString` (case-insensitive). Callers - // should `#[derive(EnumProperty)]` and use the trait below. pub fn as_str + Copy>(this: &T) -> &'static str { (*this).into() } @@ -531,10 +440,6 @@ fn parse_custom_at_rule_body( let result = match T::parse_block(at_rule_parser, prelude, start, input, options, is_nested) { Ok(vv) => vv, Err(_e) => { - // match &err.kind { - // ParseErrorKind::Basic(kind) => ParseError { ... }, - // _ => input.new_error(BasicParseErrorKind::at_rule_body_invalid), - // } todo("This part here"); } }; @@ -563,25 +468,12 @@ fn parse_until_before( parse_fn: impl FnOnce(C, &mut Parser) -> CssResult, ) -> CssResult { let delimiters = parser.stop_before | delimiters_; - // PORT NOTE: reshaped for borrowck — Zig held `parser.input` aliased - // between the outer Parser and a stack-local "delimited" Parser. In Rust - // `&'a mut ParserInput<'a>` is invariant and cannot be reborrowed into a - // second `Parser<'a>` while the first lives. We instead temporarily swap - // `stop_before` on the *same* Parser, run the inner parse, and restore. - // `at_start_of` is *moved into* the inner parse (Zig moved it into the - // delimited Parser and left the outer null) — since we reuse the same - // Parser it carries through unchanged, and is consumed/cleared below - // rather than restored. let saved_stop_before = parser.stop_before; parser.stop_before = delimiters; let result = { let result = parser.parse_entirely(closure, parse_fn); if matches!(error_behavior, ParseUntilErrorBehavior::Stop) && result.is_err() { parser.stop_before = saved_stop_before; - // Match Zig: the delimited parser *moved* `at_start_of` out of the - // outer parser (`parser.at_start_of = null;`). Since we reuse the - // same Parser, explicitly clear it so the caller doesn't observe a - // stale block-start left behind by the failing inner parse. parser.at_start_of = None; return result; } @@ -672,13 +564,6 @@ fn parse_nested_block( }); let start_position = parser.input.tokenizer.get_position(); - // If a block at or before this position already failed to parse and was - // found to be unclosed at the end of input, this block lies inside that - // truncated suffix and extends to the end of input as well. Re-parsing it - // can only fail again, so skip straight to the end of input. Without this, - // backtracking callers (e.g. `Calc::parse` followed by `V::parse`, or the - // token-list color fallbacks) re-parse the unclosed suffix once per - // alternative per nesting level, which is exponential in the nesting depth. if let Some(unclosed) = parser.input.unclosed_block_at_eof { if start_position >= unclosed.start_position { parser.input.tokenizer.reset(&unclosed.eof_state); @@ -721,11 +606,6 @@ fn parse_nested_block( result } -// ───────────────────────── parser-protocol traits ───────────────────────── -// -// Zig used `ValidQualifiedRuleParser(T)` etc. as comptime duck-type checks -// (`@hasDecl`). PORTING.md: trait bounds ARE that check. - /// Qualified rules are rules that apply styles to elements in a document. pub trait QualifiedRuleParser { /// The intermediate representation of a qualified rule prelude. @@ -798,11 +678,6 @@ pub trait CustomAtRuleParser { fn reset_enclosing_layer(this: &mut Self, len: u32); fn bump_anon_layer_count(this: &mut Self, amount: i32); - /// Move the registered `@layer` names accumulated via `on_layer_rule` out - /// of the parser. The Zig spec only populates `StyleSheet.layer_names` - /// when `P == BundlerAtRuleParser` (css_parser.zig:3324); Rust can't - /// type-specialize at the call site, so this is a trait hook with a - /// default no-op for parsers that don't track layers. fn take_layer_names(_this: &mut Self) -> Vec { Vec::new() } @@ -883,12 +758,6 @@ pub type BundlerAtRule = DefaultAtRule; pub struct BundlerAtRuleParser<'a> { pub arena: &'a Bump, - /// Raw pointer aliasing the same `Vec` that `Parser.import_records` - /// points to (Zig passes one `*Vec` to both — see `parseBundler`, - /// css_parser.zig:3245). Both views are raw pointers sharing a single - /// SharedRW provenance (see `parse_bundler`); each materialises a - /// short-lived `&mut` only at the point of use, so accesses interleave - /// soundly under Stacked Borrows. pub import_records: *mut Vec, pub layer_names: Vec, pub options: &'a ParserOptions<'a>, @@ -1024,12 +893,6 @@ impl<'a> CustomAtRuleParser for BundlerAtRuleParser<'a> { } } -// ───────────────────────────── AtRulePrelude ───────────────────────────── -// -// The few leaf-module payload types not yet exposed by `rules/mod.rs` -// (KeyframesName, PageSelector, ContainerName, ContainerCondition) come from -// `gated_shims` above. - pub enum AtRulePrelude { FontFace, FontFeatureValues, @@ -1116,10 +979,6 @@ pub enum TopLevelState { } pub struct TopLevelRuleParser<'a, AtRuleParserT: CustomAtRuleParser> { - // PORT NOTE: Zig threaded `input.arena()` at every call site; the Rust - // `DeclarationList = bumpalo::Vec<'bump, Property>` needs the arena up - // front, so cache it here (same `'static`-erased borrow `DeclarationBlock` - // already uses crate-wide). pub arena: &'a Bump, pub options: &'a ParserOptions<'a>, pub state: TopLevelState, @@ -1204,10 +1063,6 @@ pub struct NestedRuleParser<'a, T: CustomAtRuleParser> { pub arena: &'a Bump, pub options: &'a ParserOptions<'a>, pub at_rule_parser: &'a mut T, - // todo_stuff.think_mem_mgmt - // PORT NOTE: `DeclarationList<'bump>` borrows the parser arena. Threading - // `'bump` here cascades into every rule type; deferred (matches - // `StyleRule`'s `'static` erasure in rules/style.rs). pub declarations: DeclarationList<'static>, // todo_stuff.think_mem_mgmt pub important_declarations: DeclarationList<'static>, @@ -1320,24 +1175,10 @@ where } } -// ───────────────────── rule_parsers (heavy impl bodies) ────────────────────── -// Un-gated: `declaration::parse_declaration_impl` + `selectors::parser` are -// real, so the `QualifiedRuleParser`/`DeclarationParser`/`RuleBodyItemParser` -// surface and `parse_nested`/`parse_style_block` compile end-to-end. The -// at-rule arms now call the leaf-module parse fns directly (`LayerName`, -// `SupportsCondition`, `KeyframesName`, `PageSelector`, `ContainerName`, -// `ContainerCondition`, `FontPaletteValuesRule`, `PageRule`, `PropertyRule` -// have un-gated). Only `@font-face`/`@keyframes` block bodies remain -// inline-``-gated on their `RuleBodyItemParser` trait impls. mod rule_parsers { use super::*; use crate::selectors::parser as selector_parser; - // PORT NOTE: Zig threaded `composes_ctx: anytype` (pointer to the - // `NestedRuleParser`) directly into `parse_declaration`. Rust's borrow checker - // forbids passing `&mut *this` while also borrowing `this.declarations` / - // `this.important_declarations`, so split-borrow the three composes fields - // into a small adaptor that implements the `ComposesCtx` dispatch trait. struct NestedComposesCtx<'a> { state: ComposesState, arena: &'a Bump, @@ -1428,10 +1269,6 @@ mod rule_parsers { return Ok(AtRulePrelude::Namespace { prefix, url: namespace }); }, b"charset" => { - // @charset is removed by rust-cssparser if it's the first rule in - // the stylesheet. Anything left is technically invalid, however, - // users often concatenate CSS files together, so we are more - // lenient and simply ignore @charset rules in the middle of a file. input.expect_string()?; return Ok(AtRulePrelude::Charset); }, @@ -1755,10 +1592,6 @@ mod rule_parsers { break 'brk AtRulePrelude::Keyframes { name: keyframes_name, prefix }; }, b"page" => { - // Zig: tryParse(parseCommaSeparated(PageSelector.parse)) → on - // .err returns empty list. EOF inside `PageSelector::parse` - // (e.g. `@page foo` with nothing after) propagates here and is - // swallowed by `try_parse` — matches css_parser.zig:2073. let selectors: Vec = input .try_parse(|input2| { input2.parse_comma_separated(css_rules::page::PageSelector::parse) @@ -2017,12 +1850,6 @@ mod rule_parsers { Ok(()) } AtRulePrelude::Layer(mut layer) => { - // PORT NOTE (css_parser.zig:2393): Zig reads - // `prelude.layer.at(0).*` — a struct copy that leaves the list - // intact — *then* calls `onLayerRule(&prelude.layer)` so the - // hook still observes the 1-element list. Mirror that: clone - // slot 0 for the rule's `name`, fire `on_layer_rule`, then - // drain the original into `push_to_enclosing_layer`. let name = if layer.len() == 0 { None } else if layer.len() == 1 { @@ -2196,15 +2023,6 @@ mod rule_parsers { input: &mut Parser, ) -> CssResult<()> { let loc = this.get_loc(start); - // PORT NOTE: Zig `defer this.composes_refs.clearRetainingCapacity();`. - // `composes_refs` is `&mut SmallList<..>` borrowed from the parent - // `TopLevelRuleParser`, so dropping `NestedRuleParser` on an error path - // does NOT clear the underlying storage. A safe `scopeguard::guard` - // over `&mut *this.composes_refs` would hold that borrow across - // `this.parse_nested(&mut self, …)` and trip borrowck, so capture the - // raw pointer instead — the guard fires at scope exit after all body - // borrows of `this` are released, and the pointee (owned by the parent - // `TopLevelRuleParser`) strictly outlives this frame. let composes_refs_ptr: *mut SmallList = &raw mut *this.composes_refs; scopeguard::defer! { // SAFETY: see PORT NOTE above — no aliasing borrow live at drop. @@ -2337,11 +2155,6 @@ mod rule_parsers { } } - /// `MediaList::parse` thunk. The body lives in `media_query.rs` in Zig; the - /// Rust port hasn't landed it yet. Kept local so the rule-parser arms above - /// type-check; becomes a one-line `MediaList::parse(input, options)` forwarder - /// once `media_query::MediaList::parse` un-gates. - // blocked_on: media_query::{MediaList,MediaQuery}::parse #[inline] fn parse_media_list(input: &mut Parser, options: &ParserOptions) -> CssResult { MediaList::parse(input, options) @@ -2353,10 +2166,6 @@ mod rule_parsers { pub struct ToCssResult { /// Serialized CSS code. pub code: Vec, - /// A map of CSS module exports, if the `css_modules` option was enabled - /// during parsing. - // TODO(port): arena lifetime — CssModuleExports/References borrow the - // parser arena. `'static` placeholder until `<'bump>` threads. pub exports: Option>, /// A map of CSS module references, if the `css_modules` config had /// `dashed_idents` enabled. @@ -2472,10 +2281,6 @@ pub struct LocalEntry { pub loc: bun_ast::Loc, } -/// If css modules is enabled, this maps locally scoped class names to their -/// ref. We use this ref as a layer of indirection during the bundling stage -/// because we don't know the final generated class names for local scope -/// until print time. pub type LocalScope = ArrayHashMap, LocalEntry>; /// Local symbol renaming results go here pub type LocalsResultsMap = ast::MangledProps; @@ -2558,12 +2363,6 @@ pub fn fill_property_bit_set( } } -// ───────────────────────────── StyleSheet ───────────────────────────── -// -// `CssRuleList`/`LayerName`/`ParserOptions` carry the type surface; the -// behavior surface (`parse`/`minify`/`to_css`/`pluck_imports`) lives in -// `stylesheet_impl` below. - pub struct StyleSheet { /// A list of top-level rules within the style sheet. pub rules: CssRuleList, @@ -2611,11 +2410,6 @@ mod stylesheet_impl { use super::*; impl StyleSheet { - /// Minify and transform the style sheet for the provided browser targets. - /// - /// PORT NOTE: `arena` is the arena that owns this stylesheet's AST - /// (Zig: `arena: Allocator`). It is threaded into `MinifyContext` so - /// downstream `deep_clone` calls allocate alongside the existing tree. pub fn minify( &mut self, arena: &Bump, @@ -2805,11 +2599,6 @@ mod stylesheet_impl { local_names: Option<&'a LocalsResultsMap>, symbols: &'a bun_ast::symbol::Map, ) -> PrintResult { - // TODO: this is not necessary - // Make sure we always have capacity > 0: https://github.com/napi-rs/napi-rs/issues/1124. - // TODO(port): writer adapter — Zig used std.Io.Writer.Allocating; here we - // route through bun_io::Write over Vec until 'bump dest threads. - // blocked_on: bun_io::Write impl for Vec / dest ownership reshape. let mut dest: Vec = Vec::with_capacity(1); let result = self.to_css_with_writer( arena, @@ -2834,10 +2623,6 @@ mod stylesheet_impl { import_records: Option<&mut Vec>, source_index: SrcIndex, ) -> Maybe<(StyleSheet, StylesheetExtra), Err> { - // PORT NOTE: Zig instantiated `StyleSheet(DefaultAtRule).parse`; Rust - // cannot vary `Self`'s `AtRule` param against `DefaultAtRuleParser`, so - // this returns the concrete `StyleSheet`. Callers that - // need a custom at-rule call `parse_with` directly. let mut default_at_rule_parser = DefaultAtRuleParser; StyleSheet::::parse_with( arena, @@ -2849,10 +2634,6 @@ mod stylesheet_impl { ) } - /// Parse a style sheet from a string. - // TODO(port): `ParserOptions<'static>` matches the `StyleSheet.options` - // field's `'static` erasure; re-threads to `<'bump>` alongside the rest of - // the crate. pub fn parse_with>( arena: &'static Bump, code: &[u8], @@ -2861,13 +2642,6 @@ mod stylesheet_impl { import_records: Option>>, source_index: SrcIndex, ) -> Maybe<(Self, StylesheetExtra), Err> { - // TODO(port): 'bump lifetime threading — every arena-backed slice the - // parser hands back is currently detached to `'static` (matching the - // crate-wide erasure on `DeclarationBlock<'static>`/`Token` payloads). - // The caller owns the arena (matching Zig's `arena: Allocator` - // parameter) so the storage outlives the returned `StyleSheet`. - // TODO(refactor): re-thread the lifetime through `CssRuleList<'bump, R>` - // and drop the `'static` bound on `arena`. let mut composes = ComposesMap::default(); let mut parser_extra = ParserExtra { local_scope: LocalScope::default(), @@ -2934,11 +2708,6 @@ mod stylesheet_impl { let source_map_urls: Vec>> = vec![parser.current_source_map_url().map(Box::<[u8]>::from)]; - // Spec: `.layer_names = if (comptime P == BundlerAtRuleParser) - // at_rule_parser.layer_names else .{}` (css_parser.zig:3324). Rust - // dispatches through the `CustomAtRuleParser::take_layer_names` hook - // (default = empty; `BundlerAtRuleParser` overrides to move its list - // out) so the accumulated layer ordering isn't silently dropped. let layer_names = P::take_layer_names(at_rule_parser); Ok(( @@ -3011,22 +2780,11 @@ mod stylesheet_impl { } } - /// *NOTE*: Used for Tailwind stylesheets only. - /// - /// This plucks out the import rules from the Tailwind stylesheet into a - /// separate rule list, replacing them with `.ignored` rules. pub fn pluck_imports( &mut self, out: &mut CssRuleList, new_import_records: &mut Vec, ) { - // PORT NOTE: the Zig fn takes `*const @This()` but writes - // `rule.* = .ignored;` through it (Zig has no const-transitivity). - // Writing through a `*const`-derived pointer is UB in Rust, so the - // receiver is reshaped to `&mut self`. The sole caller (Tailwind - // bundling) owns the stylesheet exclusively at this point. - // - // Zig used a comptime two-pass `inline for` (count, exec). Unroll. let mut count: u32 = 0; { let mut saw_imports = false; @@ -3081,10 +2839,6 @@ mod stylesheet_impl { original_path: b"", flags: Default::default(), }); - // PORT NOTE: reshaped for borrowck — Zig did - // `out.v.appendAssumeCapacity(rule.*)` (bitwise copy) then - // `rule.* = .ignored`. Rust moves the rule out via - // `mem::replace` (no `Clone` bound needed) and pushes that. let old = core::mem::replace(rule, CssRule::Ignored); // PERF(port): was appendAssumeCapacity out.v.push(old); @@ -3146,12 +2900,6 @@ mod stylesheet_impl { options: &PrinterOptions<'a>, import_info: Option>, ) -> Result { - // #[cfg(feature = "sourcemap")] - // assert!( - // options.source_map.is_none(), - // "Source maps are not supported for style attributes" - // ); - let symbols = bun_ast::symbol::Map::init_list(Default::default()); // TODO(port): writer adapter — Zig used std.Io.Writer.Allocating; route // through bun_io::Write over Vec until 'bump dest threads. @@ -3188,21 +2936,6 @@ mod stylesheet_impl { import_records: &mut Vec, source_index: SrcIndex, ) -> Maybe<(Self, StylesheetExtra), Err> { - // PORT NOTE: Zig aliased `import_records` into both `BundlerAtRuleParser` - // *and* the inner `Parser` (css_parser.zig:3245), and aliased `&options` - // into the at-rule parser while also passing `options` by value (struct - // copy) to `parseWith`. Rust forbids both overlaps directly: - // - `import_records`: derive a single raw `NonNull` from the unique - // borrow; both the at-rule parser and `Parser::new` store copies of - // that raw pointer (matching Zig's `?*Vec`). Neither holds a - // long-lived `&mut`, so interleaved writes from `on_import_rule` and - // `add_import_record`/`state`/`reset` each create a fresh short-lived - // `&mut` from the shared SharedRW provenance — sound under SB. - // - `options`: bitwise-duplicate via `ptr::read` (mirroring Zig's - // by-value struct copy) and wrap the original in `ManuallyDrop` so - // only the moved copy drops — `ParserOptions` transitively owns a - // `SmallList` (via `css_modules::Config::pattern`) which has a real - // `Drop`, so both copies must not run their destructors. let options = core::mem::ManuallyDrop::new(options); // SAFETY: original is `ManuallyDrop`; only `options_for_parse` drops. let options_for_parse = unsafe { core::ptr::read(&raw const *options) }; @@ -3243,11 +2976,6 @@ impl StyleAttribute { } } -// ───────────────────────────── RuleBodyParser ───────────────────────────── -// -// `RuleBodyItemParser`/`DeclarationParser` traits are hoisted above; this is -// pure trait-generic over `P`. - pub struct RuleBodyParser<'i, 't, P: RuleBodyItemParser> { pub input: &'i mut Parser<'t>, pub parser: &'i mut P, @@ -3356,14 +3084,6 @@ pub struct ParserOptions<'a> { pub source_index: u32, /// Whether to ignore invalid rules and declarations rather than erroring. pub error_recovery: bool, - /// A list that will be appended to when a warning occurs. - /// - /// Stored as a raw `NonNull` (mirrors Zig's `*Log`) so `warn(&self)` - /// can soundly write through it. Deriving `&mut Log` from a `&self`-reachable - /// `&'a mut Log` (the previous representation) is UB under Stacked Borrows - /// — see PORTING.md §Forbidden patterns. The caller that constructs - /// `ParserOptions` guarantees the pointee outlives `'a` and is not aliased - /// for the duration of parsing. pub logger: Option>, /// Feature flags to enable. pub flags: ParserFlags, @@ -3488,12 +3208,6 @@ pub struct Parser<'a> { pub at_start_of: Option, pub stop_before: Delimiters, pub flags: ParserOpts, - /// Stored as a raw `NonNull` (mirrors Zig's `?*Vec(ImportRecord)`, - /// css_parser.zig:3808) because `BundlerAtRuleParser` holds an aliasing - /// raw pointer to the same list. Keeping a long-lived `&'a mut` here would - /// be invalidated under Stacked Borrows the moment `on_import_rule` - /// derives its own `&mut` from the sibling raw pointer. Each access site - /// materialises a fresh short-lived `&mut` instead. pub import_records: Option>>, pub extra: Option<&'a mut ParserExtra>, } @@ -3602,11 +3316,6 @@ impl<'a> Parser<'a> { self.input.tokenizer.arena } - /// Create a new Parser. - /// - /// Pass in `import_records` to track imports (`@import` rules, `url()` - /// tokens). If this is `None`, calling `Parser::add_import_record` will - /// error. pub fn new( input: &'a mut ParserInput<'a>, import_records: Option>>, @@ -3693,11 +3402,6 @@ impl<'a> Parser<'a> { mut parse_one: impl FnMut((), &mut Parser) -> CssResult, ignore_errors: bool, ) -> CssResult> { - // Vec grows from 0 to 4 by default on first push(). So allocate with - // capacity 1, so in the somewhat common case of only one item we don't - // way overallocate. Note that we always push at least one item if - // parsing succeeds. - // PERF(port): was stack-fallback let mut values: Vec = Vec::with_capacity(1); loop { @@ -3983,20 +3687,6 @@ impl<'a> Parser<'a> { Err(start_location.new_unexpected_token_error(tok)) } - // ────────────────────────────────────────────────────────────────────── - // `*_cloned` helpers — C-7 in PORT_NOTES_PLAN. - // - // These wrap `expect_*` / `slice_from` and return the slice with its - // lifetime detached from `&mut self` (to `'static`, matching `Token`'s - // current `&'static [u8]` payload). All `unsafe { src_str(..) }` call - // sites in the CSS parser route through here instead of laundering the - // lifetime locally. - // - // Once C-9 threads `'i` through `Token<'i>`, these become safe - // `-> CssResult<&'i [u8]>` and the body drops the `unsafe` — no caller - // changes needed. - // ────────────────────────────────────────────────────────────────────── - /// `expect_ident` with the borrow detached from `&mut self` so the parser /// is reusable while the slice is held (and the slice fits `Token::Ident`). #[inline] @@ -4289,14 +3979,6 @@ pub struct ParserInput<'a> { pub tokenizer: Tokenizer<'a>, pub cached_token: Option, pub nesting_depth: u32, - /// Set once a nested block fails to parse and the end of input is reached - /// without ever finding its closing token, i.e. the stylesheet is - /// truncated somewhere inside that block. Everything from - /// `start_position` to the end of input is inside the unclosed block, so - /// re-parsing any block in that range can only fail the same way again. - /// `parse_nested_block` uses this to fail such attempts immediately - /// instead of re-scanning (and re-recursing through) the truncated - /// suffix once per backtracking alternative per nesting level. unclosed_block_at_eof: Option, math_fn_parse_failures: u64, } @@ -4312,14 +3994,6 @@ struct UnclosedBlockAtEof { } impl<'a> ParserInput<'a> { - /// Create a `ParserInput` borrowing `code` and an arena for unescaped - /// strings. Matches Zig `ParserInput.new` (css_parser.zig:4549) which - /// takes an `Allocator` parameter — the caller owns the arena and it must - /// outlive every `Token` produced from this input. - /// - /// PORTING.md §Forbidden: do not fabricate `&'a Bump` from a boxed field - /// via raw-pointer cast; the previous self-referential `owned_arena` hack - /// was removed. Callers now pass `&'a Bump` explicitly. pub fn new(code: &'a [u8], arena: &'a Bump) -> ParserInput<'a> { ParserInput { tokenizer: Tokenizer::init_with_arena(code, arena), @@ -4570,13 +4244,6 @@ pub struct Tokenizer<'a> { const FORM_FEED_BYTE: u8 = 0x0C; const REPLACEMENT_CHAR: u32 = 0xFFFD; const REPLACEMENT_CHAR_UNICODE: [u8; 3] = [0xEF, 0xBF, 0xBD]; -/// UTF-8 encoding of U+0FFD — used by `serializer` where Zig called -/// `bun.strings.encodeUTF8Comptime(0xFFD)` (css_parser.zig:6747, :6937). The -/// Zig literal is `0xFFD` (sic — likely a typo for `0xFFFD`), but the spec is -/// ground truth: encode 0x0FFD → [0xE0, 0xBF, 0xBD] to byte-match. -/// TODO(port): confirm whether the spec itself needs fixing to 0xFFFD. -// TODO(port): verify upstream — Zig wrote 0xFFD, comment says "replacement -// character" which is U+FFFD. Byte-matching the spec (0x0FFD) for now. const REPLACEMENT_CHAR_UTF8: &[u8] = &[0xE0, 0xBF, 0xBD]; const MAX_ONE_B: u32 = 0x80; const MAX_TWO_B: u32 = 0x800; @@ -5755,11 +5422,6 @@ impl TokenKind { } } -// Data layout hoisted at crate root (lib.rs) so error.rs can name `Token` -// without the parser hub. Behavior impls (kind/is_parse_error/to_css_generic) -// live here. TODO: make strings be allocated in string pool. -// TODO(port): lifetime — every &[u8] payload borrows the arena/source. Uses -// `&'static [u8]` placeholder; thread `<'a>` once payload lifetimes settle. pub use crate::Token; impl Token { @@ -6015,19 +5677,6 @@ impl Token { } } -// `impl Display for Token` lives at crate root (lib.rs) — minimal rendering -// for error messages. The full Zig `Token.format` (CSS-serialization-correct) -// is `Token::to_css_generic` above; switch lib.rs's impl to delegate once -// dependents stop relying on the simple form. -// TODO(port): Zig `format` had subtle differences from `to_css_generic` -// (quoted_string→serialize_string, idhash→serialize_identifier). Specialize -// if it matters for diagnostics. - -/// Byte-writer trait for `serializer` and `to_css_generic` (replaces Zig -/// `anytype` writer). Aliased to the canonical `bun_io::Write`; the associated -/// `type Error` is dropped — every `Result<(), W::Error>` becomes -/// `bun_io::Result<()>`. `Vec` / `ArenaVec<'_, u8>` / `Printer` all -/// implement it upstream. pub use bun_io::Write as WriteAll; // Num/Dimension data layouts hoisted at crate root (lib.rs). @@ -6578,10 +6227,6 @@ pub mod serializer { pub mod parse_utility { use super::*; - /// Parse a value from a string. - /// - /// NOTE: `input` should live as long as the returned value. Otherwise, - /// strings in the returned parsed value will point to undefined memory. pub fn parse_string( arena: &Bump, input: &[u8], @@ -6873,10 +6518,6 @@ pub fn fract(val: f32) -> f32 { pub fn f32_length_with_5_digits(n_input: f32) -> usize { let mut n = (n_input * 100000.0).round(); - // Huge values (>= ~3.4e33) overflow to infinity when scaled, and infinity - // never drops below 1.0 no matter how many times it is divided by 10, so - // the loop below would spin forever. Treat non-finite values as longer - // than any finite representation. if !n.is_finite() { return usize::MAX; } diff --git a/src/css/declaration.rs b/src/css/declaration.rs index a537474326a..012659af1c6 100644 --- a/src/css/declaration.rs +++ b/src/css/declaration.rs @@ -4,10 +4,6 @@ use bun_alloc::ArenaVecExt as _; pub use css::Error; use css::{CssResult as Result, PrintErr, Printer}; -// PORT NOTE: every leaf property module is currently a `handler_stub!` ZST in -// properties/mod.rs (no-op `handle_property`/`finalize`). The real handler -// bodies un-gate per-module as the values/ calc lattice lands; this file -// composes over whichever surface is live. use crate::css_properties::align::AlignHandler; use crate::css_properties::background::BackgroundHandler; use crate::css_properties::border::BorderHandler; @@ -28,14 +24,6 @@ use crate::css_properties::ui::ColorSchemeHandler; pub type DeclarationList<'bump> = bun_alloc::ArenaVec<'bump, css::Property>; -/// A CSS declaration block. -/// -/// Properties are separated into a list of `!important` declararations, -/// and a list of normal declarations. This reduces memory usage compared -/// with storing a boolean along with each property. -/// -/// TODO: multiarraylist will probably be faster here, as it makes one allocation -/// instead of two. pub struct DeclarationBlock<'bump> { /// A list of `!important` declarations in the block. pub important_declarations: DeclarationList<'bump>, @@ -124,10 +112,6 @@ impl<'bump> DeclarationBlock<'bump> { let handled = hndlr.handle_property(prop, ctx); if !handled { - // Zig: `hndlr.decls.append(prop.*); prop.* = .{ .all = .@"revert-layer" }` - // — move the value out and overwrite the slot with a - // non-allocating placeholder so the source list's drop is a - // no-op. hndlr .decls .push(core::mem::replace(prop, placeholder_property())); @@ -222,16 +206,6 @@ impl<'bump> DeclarationBlock<'bump> { } } -// ─── parse ──────────────────────────────────────────────────────────────── -// -// PORT NOTE: every consumer (`StyleRule`, `Keyframe`, `PageRule`, -// `StyleAttribute`, `NestedRuleParser`) stores `DeclarationBlock<'static>` — -// the crate-wide `'bump`-erasure placeholder until `'bump` threads through -// `CssRule`. `parse()` therefore lives on the `'static` instantiation and -// erases the parser arena's lifetime at the boundary; this collapses together -// with the lifetime cast in `rules/style.rs::minify` when `CssRule<'bump, R>` -// lands. - impl DeclarationBlock<'static> { pub fn parse( input: &mut css::Parser, @@ -256,10 +230,6 @@ impl DeclarationBlock<'static> { options.warn(&e); continue; } - // errdefer doesn't fire on `return .{ .err = ... }` — Result(T) is a tagged - // union, not an error union. Free any declarations accumulated so far. - // PORT NOTE: in Rust, `declarations`/`important_declarations` are bumpalo - // Vec and drop on early return; deepDeinit is implicit via Drop. return Err(e); } } @@ -271,13 +241,6 @@ impl DeclarationBlock<'static> { } } -// ─── hash / eql / deep_clone (gated) ────────────────────────────────────── -// blocked_on: properties_generated — `Property` lacks `DeepClone`/`CssEql` -// derives and `PropertyId` lacks a `hash(&mut Wyhash)` method. The bodies -// below are the real manual unrolls of Zig's comptime-reflection helpers -// (`implementEql`/`implementDeepClone`); they un-gate the moment the -// per-variant trait impls land in `properties_generated.rs`. - impl<'bump> DeclarationBlock<'bump> { pub fn hash_property_ids(&self, hasher: &mut bun_wyhash::Wyhash) { use std::hash::Hash; @@ -432,11 +395,6 @@ pub fn parse_declaration<'bump>( ) } -// PORT NOTE: Zig `composes_ctx: anytype` — branches on -// `comptime @TypeOf(composes_ctx) != void`. The Rust shape is a `ComposesCtx` -// trait (defined in `css_parser.rs`); `NoComposesCtx` returns -// `DisallowEntirely` so the `void` fast-path collapses into the match's -// no-op arm. pub fn parse_declaration_impl<'bump, C>( name: &[u8], input: &mut css::Parser, @@ -490,12 +448,6 @@ where ); } css::ComposesState::DisallowNotSingleClass(info) => { - // blocked_on: ParserOptions::warn_fmt_with_notes - // (`bun_ast::Log` notes-ownership API). Until that - // lands the note ("The parent selector is not a single - // class selector because of the syntax here:" at - // `info.to_logger_location(options.filename)`) is dropped; - // the primary warning still fires at the right location. let _ = info; options.warn_fmt( format_args!("\"composes\" only works inside single class selectors"), @@ -515,11 +467,6 @@ where Ok(()) } -/// Per-shorthand-group handler state used by `DeclarationBlock::minify`. -/// -/// PORT NOTE: each `*Handler` is a `handler_stub!` ZST until its leaf module -/// un-gates; `Direction` is the data-only `properties::text` enum. The struct -/// shape is the real Zig layout — only the handler *bodies* are deferred. pub struct DeclarationHandler<'bump> { pub background: BackgroundHandler, pub border: BorderHandler, @@ -545,10 +492,6 @@ impl<'bump> DeclarationHandler<'bump> { if let Some(direction) = self.direction.take() { self.decls.push(css::Property::Direction(direction)); } - // if (this.unicode_bidi) |unicode_bidi| { - // this.unicode_bidi = null; - // this.decls.append(context.arena, css.Property{ .unicode_bidi = unicode_bidi }) catch |err| bun.handleOom(err); - // } self.background.finalize(&mut self.decls, context); self.border.finalize(&mut self.decls, context); diff --git a/src/css/generics.rs b/src/css/generics.rs index 080a7cd4376..c426acdbba2 100644 --- a/src/css/generics.rs +++ b/src/css/generics.rs @@ -15,12 +15,6 @@ use core::cmp::Ordering; use bun_alloc::Arena; // bumpalo::Bump re-export use bun_collections::VecExt; -// Zig `std.hash.Wyhash` (iterative) → `bun_wyhash::Wyhash` (the final4 variant -// matching upstream `std.hash.Wyhash`; NOT `Wyhash11`, which is a legacy v0.11 -// variant kept only for on-disk lockfile compat — different digest). -// Re-exported `pub` so `#[derive(CssHash)]` (in `bun_css_derive`) can name the -// hasher type as `::bun_css::generics::Wyhash` without depending on `bun_wyhash` -// directly. pub use bun_wyhash::Wyhash; use crate::SmallList; @@ -45,20 +39,10 @@ pub type ArrayList<'bump, T> = bun_alloc::ArenaVec<'bump, T>; // DeepClone // ─────────────────────────────────────────────────────────────────────────────── -/// Arena-aware deep clone. Equivalent of Zig's `deepClone(T, *const T, Allocator) T`. -/// -/// Per-struct/-enum impls come from `#[derive(DeepClone)]`; -/// the Zig `implementDeepClone` body is the spec for that derive (field-wise / -/// variant-wise recursion). pub trait DeepClone<'bump>: Sized { fn deep_clone(&self, bump: &'bump Arena) -> Self; } -/// `#[derive(DeepClone)]` — field-wise / variant-wise port of Zig's -/// `css.implementDeepClone`. See `src/css_derive/lib.rs` for the expansion -/// rules. Re-exported here so `use crate::generics::DeepClone;` brings both -/// the trait and the derive into scope (same-name trait+derive is the std -/// idiom, cf. `Clone`). pub use bun_css_derive::DeepClone; #[inline] @@ -69,12 +53,6 @@ pub fn implement_deep_clone<'bump, T: DeepClone<'bump>>(this: &T, bump: &'bump A this.deep_clone(bump) } -// Alias: in Zig `deepClone` (structural type-dispatch entry) and -// `implementDeepClone` (field-reflection body) are distinct, but in Rust both -// collapse to `T::deep_clone` because the structural dispatch lives in the -// blanket impls below and the field-reflection lives in `#[derive(DeepClone)]`. -// Kept as a re-export so generated code (`properties_generated.rs`) and -// hand-written callers can use either name. pub use implement_deep_clone as deep_clone; // Blanket impls covering the structural cases the Zig switch handled inline. @@ -145,10 +123,6 @@ macro_rules! deep_clone_copy { } )*}; } -// `u8` is intentionally omitted: a `DeepClone for u8` impl would make the -// generic `&'bump [T]` impl below overlap the explicit `&'bump [u8]` impl -// (Rust has no stable specialization). Bytes only appear as `[u8]` slices in -// the CSS AST, never as standalone values. deep_clone_copy!(f32, f64, i32, u32, i64, u64, usize, isize, u16, bool); impl<'bump> DeepClone<'bump> for &'bump [u8] { @@ -186,18 +160,10 @@ impl<'bump> DeepClone<'bump> for bun_ast::Loc { // Eql // ─────────────────────────────────────────────────────────────────────────────── -/// `lhs.eql(&rhs)` for CSS types. This is the equivalent of doing -/// `#[derive(PartialEq)]` in Rust — and most impls could be exactly that. -/// Kept as a separate trait because some CSS types want structural -/// equality that differs from `PartialEq` (e.g. `VendorPrefix`, idents). pub trait CssEql { fn eql(&self, other: &Self) -> bool; } -/// `#[derive(CssEql)]` — field-wise / variant-wise port of Zig's -/// `css.implementEql`. See `src/css_derive/lib.rs` for the expansion rules. -/// Re-exported here so `use crate::generics::CssEql;` brings both trait and -/// derive into scope (same-name idiom, cf. `Clone`). pub use bun_css_derive::CssEql; #[inline] @@ -291,21 +257,6 @@ macro_rules! eql_simple { // `u8` omitted to avoid `[T]`/`[u8]` overlap — see deep_clone_copy! note. eql_simple!(f32, f64, i32, u32, i64, u64, usize, isize, u16, bool); -/// Stamp `impl CssEql for $T` forwarding to `PartialEq::eq`. -/// -/// Exported sibling of `eql_simple!` for crate-defined types whose -/// inherent `pub fn eql(&self, other) { self == other }` was a pure `PartialEq` -/// forwarder (Zig `css.implementEql(@This())` leakage). -/// -/// Unlike `#[derive(CssEql)]` (field-wise `.eql()` walk), this does **not** -/// require every field type to itself impl `CssEql`; it bridges -/// `PartialEq` → `CssEql` wholesale. Prefer `#[derive(CssEql)]` only when a -/// field's `CssEql` is intentionally *different* from its `PartialEq` (e.g. -/// `Ident`, `Url`). -/// -/// REJECTED alternative: `impl CssEql for T {}` blanket — would -/// overlap the existing `Option`/`Vec`/`[T]`/`Box` blanket impls -/// above (coherence). #[macro_export] macro_rules! css_eql_partialeq { ($($t:ty),+ $(,)?) => {$( @@ -394,16 +345,6 @@ mod ident_eql { ident_eql_impl!(CustomIdent, DashedIdent, Ident); } -// ─────────────────────────────────────────────────────────────────────────────── -// Bridge inherent eql/hash/deep_clone → trait impls -// -// Many CSS value types carry hand-rolled inherent `eql`/`hash`/`deep_clone` -// (ported verbatim from the Zig `implementEql`/`implementHash`/ -// `implementDeepClone` bodies — usually because a field is a raw `*const [u8]` -// arena slice that the derive can't see through). The `#[derive(CssEql/…)]` -// expansion on *containing* types dispatches via UFCS trait paths, so those -// inherent methods alone don't satisfy the bound. These thin forwarding impls -// close the gap without duplicating logic. mod inherent_bridge { use super::{Arena, CssEql, CssHash, DeepClone, Wyhash}; @@ -482,12 +423,6 @@ mod inherent_bridge { bridge_hash!(ViewTransitionPartName); bridge_deep_clone_copy!(WebKitScrollbarPseudoElement, ViewTransitionPartName); - // ─────────────────────────────────────────────────────────────────────── - // Property value-type bridges — `Property::deep_clone`/`eql` dispatch via - // `css::generic::deep_clone`/`eql` (trait bounds), but most leaf types - // only carry inherent methods or `derive(Clone, PartialEq)`. Bridge them. - // ─────────────────────────────────────────────────────────────────────── - /// Forward `CssEql` to `PartialEq`. macro_rules! bridge_eql_partialeq { ($($t:ty),* $(,)?) => {$( @@ -885,10 +820,6 @@ pub trait CssHash { fn hash(&self, hasher: &mut Wyhash); } -/// `#[derive(CssHash)]` — field-wise / variant-wise port of Zig's -/// `css.implementHash`. See `src/css_derive/lib.rs` for the expansion rules. -/// Re-exported here so `use crate::generics::CssHash;` brings both trait and -/// derive into scope. pub use bun_css_derive::CssHash; #[inline] @@ -950,11 +881,6 @@ impl CssHash for [T] { impl CssHash for [T; N] { fn hash(&self, hasher: &mut Wyhash) { - // Zig: `bun.writeAnyToHasher(hasher, list.len)` — feeds the raw bytes - // of `usize` into the hasher. `bun_core::write_any_to_hasher` exists - // but is `H: Hasher`-generic and routes through `Hasher::write`, which - // for `Wyhash11` calls `update` — so inlining the `usize` byte-feed - // here is byte-identical and avoids the trait hop. hasher.update(&self.len().to_ne_bytes()); for item in self { item.hash(hasher); @@ -1086,11 +1012,6 @@ pub trait IsCompatible { fn is_compatible(&self, browsers: &crate::targets::Browsers) -> bool; } -/// `#[derive(IsCompatible)]` — field-wise / variant-wise port of the -/// hand-written `isCompatible` pattern (struct → AND of fields, enum → unit -/// variants `true` / payload variants delegate). See `src/css_derive/lib.rs`. -/// Re-exported here so `use crate::generics::IsCompatible;` brings both trait -/// and derive into scope. pub use bun_css_derive::IsCompatible; #[inline] @@ -1151,10 +1072,6 @@ impl IsCompatible for Vec { } } -// The Zig original blanket-impls over "any list container". A Rust blanket -// `impl` conflicts with the `&T` impl above (coherence can't -// prove `&T` never impls `ListContainer`), so spell out the three concrete -// container types instead. macro_rules! is_compatible_container { ($(($($gen:tt)*) $ty:ty),* $(,)?) => {$( impl<$($gen)*> IsCompatible for $ty @@ -1177,35 +1094,11 @@ is_compatible_container!( (T, const N: usize) SmallList, ); -// ─────────────────────────────────────────────────────────────────────────────── -// Parse / ParseWithOptions -// ─────────────────────────────────────────────────────────────────────────────── -// Zig's `generic.parse(T, input)` / `generic.parseWithOptions(T, input, opts)` -// dispatch via `@hasDecl(T, "parse"[WithOptions])`. In Rust each leaf value -// type either hand-writes an inherent `parse(&mut Parser) -> CssResult` -// or derives one via `#[derive(Parse)]` / `#[derive(DefineEnumProperty)]`; the -// trait below is the uniform bound that `Property::parse` and the container -// blanket impls (`SmallList`/`Vec`/`Option`/`Size2D`/`Rect`) need. -// -// `Parse` is intentionally lifetime-free: every value-type parser takes -// `&mut Parser<'_>` (the borrowed source slice) and returns an owned value. -// TODO(refactor): `'bump` arena threading is a follow-up; until then the parser -// holds the arena and arena-backed lists go through `from_list(Vec)`. - /// `T::parse(&mut Parser) -> CssResult`. pub trait Parse: Sized { fn parse(input: &mut Parser) -> CssResult; } -/// `T::parse_with_options(&mut Parser, &ParserOptions) -> CssResult`. -/// -/// Zig falls through to `parse` when a type has no `parseWithOptions` decl. -/// PORT NOTE: Rust can't express that as a `where Self: Parse` default method -/// — the bound becomes part of the *method signature*, so the free -/// `parse_with_options::` below would require `T: Parse` even for impls -/// that override the body. Instead the fallthrough lives in -/// `impl_pwo_via_parse!`/`impl_parse_tocss_via_inherent!` and the container -/// impls; every `ParseWithOptions` impl provides the method explicitly. pub trait ParseWithOptions: Sized { fn parse_with_options(input: &mut Parser, options: &ParserOptions) -> CssResult; } @@ -1446,20 +1339,6 @@ impl ToCss for Ident { } } -// ── leaf-type forwarding macros ────────────────────────────────────────────── -// Every CSS leaf value type carries inherent `parse` / `to_css` (hand-written -// or derived). `Property::{parse,value_to_css}` dispatch through the -// `generic::{Parse,ToCss,ParseWithOptions}` *traits*, so each leaf must impl -// them. -// -// Two sources of the trait impl: -// 1. `#[derive(ToCss/Parse/DefineEnumProperty)]` -// (bun_css_derive) — emits the trait impl directly. -// 2. Hand-written leaves — list them under `impl_parse_tocss_via_inherent!` -// to forward the trait to the inherent. -// -// A type must use exactly one of the two; listing a derive-carrying type in -// the macro is an E0119 coherence conflict. #[macro_export] macro_rules! impl_parse_tocss_via_inherent { ($($ty:ty),+ $(,)?) => {$( @@ -1610,12 +1489,6 @@ impl PartialCmp for CSSInteger { } } -// ─────────────────────────────────────────────────────────────────────────────── -// Zero / MulF32 / TryAdd — numeric protocol traits used by `DimensionPercentage` -// and the `CalcValue` supertrait set. Formerly duplicated in `values::protocol`; -// that module now re-exports from here. -// ─────────────────────────────────────────────────────────────────────────────── - /// `D::zero()` / `d.is_zero()` — additive identity. pub trait Zero: Sized { fn zero() -> Self; diff --git a/src/css/lib.rs b/src/css/lib.rs index 101d7f2b906..1cf0f9c2dfb 100644 --- a/src/css/lib.rs +++ b/src/css/lib.rs @@ -5,28 +5,6 @@ // were translated against the crate's public surface and refer to it by name. extern crate self as bun_css; -/// Case-insensitive ASCII byte-slice dispatch — the fix for Zig's -/// `css.todo_stuff.match_ignore_ascii_case` sentinel and a drop-in port of -/// rust-cssparser's `match_ignore_ascii_case!`. -/// -/// Expands to an `if / else if / else` chain over -/// [`bun_core::strings::eql_case_insensitive_ascii_check_length`] (length-checked, -/// ASCII-fold only, byte-wise — identical to Zig's -/// `bun.strings.eqlCaseInsensitiveASCIIICheckLength`). The whole macro is an -/// expression; arms may `return`, `break 'label`, or yield a value. -/// -/// Supports `|`-alternation and Rust-style `if` guards on arms; the trailing -/// `_ =>` fallback is mandatory. -/// -/// ```ignore -/// crate::match_ignore_ascii_case! { unit, { -/// b"deg" => Ok(Angle::Deg(value)), -/// b"dppx" | b"x" => Ok(Resolution::Dppx(value)), -/// b"local" if mods.is_some() => PseudoClass::Local { .. }, -/// _ => Err(location.new_unexpected_token_error(token)), -/// }} -/// ``` -// TODO(port): swap body to phf when CI hasher lands. #[macro_export] macro_rules! match_ignore_ascii_case { ($name:expr, { $( $($lit:literal)|+ $(if $guard:expr)? => $arm:expr ,)* _ => $fallback:expr $(,)? }) => {{ @@ -93,11 +71,6 @@ pub use css_parser::{ ParserState, enum_property_util, nth, parse_utility, signfns, void_wrap, }; -// ─── selectors/ crate-root surface ──────────────────────────────────────── -// The selector grammar references these via `bun_css::*` (Zig's flat `css.*` -// namespace). `Str` is the arena-borrowed `[]const u8` slice alias; here -// it's `*const [u8]` (matches `error.rs` / `values::ident` field shape) and -// becomes `&'bump [u8]` once the arena lifetime is plumbed. pub(crate) type Str = *const [u8]; /// Dereference an arena-owned [`Str`] into a slice borrow. @@ -134,12 +107,8 @@ pub use generics as generic; pub use generics::{implement_deep_clone, implement_eql, implement_hash}; // Same-name trait + derive macro re-export so `#[derive(bun_css::DeepClone)]` // (and `use bun_css::DeepClone;` at leaf sites) brings both into scope. -pub use generics::{CssEql, DeepClone}; -// Keyword-enum / `union(enum)` derive macros (port of Zig's `DefineEnumProperty` -// / `DeriveParse` / `DeriveToCss` comptime fns). The `EnumProperty` *trait* is -// re-exported above from `css_parser`; the *derive* of the same name lives in -// the proc-macro crate. pub use bun_css_derive::{DefineEnumProperty, Parse, ToCss}; +pub use generics::{CssEql, DeepClone}; // Serializer + dtoa helpers live in the parser hub but are referenced as // `css::serializer` / `css::f32_length_with_5_digits` from value modules. pub use css_parser::{dtoa_short, f32_length_with_5_digits, serializer, to_css}; @@ -155,20 +124,12 @@ pub mod printer; #[path = "values/mod.rs"] pub mod values; -/// Data-only value-type stubs re-exported through `values::{color,ident,url}` -/// while the real `values/*.rs` files stay gated on the calc lattice. These -/// were the previous `gated_mod!(values, ...)` body — now a real module so -/// printer.rs / css_parser.rs can name the types. pub mod values_stub { /// Re-export the real `values/color.rs` surface so any remaining /// `values_stub::color::*` paths resolve to the canonical types. pub mod color { pub use crate::values::color::*; - /// `Result(CssColor)` — Zig: `pub const ParseResult = Result(CssColor);` - /// where `Result(T) = Maybe(T, ParseError(ParserError))` (css_parser.zig:278). - /// `Maybe` is now un-gated as `core::result::Result`, so this is a - /// straight type alias to the real `values::color::ParseResult`. pub type CssColorParseResult = crate::values::color::ParseResult; /// https://drafts.csswg.org/css-color/#hsl-to-rgb (`hue` is 0..1 here). @@ -177,11 +138,6 @@ pub mod values_stub { pub use crate::css_parser::color::hsl_to_rgb; } - /// Re-export of the real `values/ident.rs` — the data-only stub that used - /// to live here (so `generics::ident_eql` could compile) is obsolete: - /// `values::ident` is un-gated and `generics.rs` imports it directly. - /// The stub `IdentOrRef` had diverged (tagged enum vs packed-u128), so - /// this also removes a latent type-confusion hazard. pub mod ident { pub use crate::values::ident::*; } @@ -209,10 +165,6 @@ impl core::fmt::Display for PrintErr { } impl core::error::Error for PrintErr {} -/// `PrintErr!T` return shape (Zig: `PrintErr!void`) used by every `to_css` -/// path. Distinct from `css_parser::PrintResult = Maybe`, -/// which carries the rich `Err` — this is just the bubbled -/// signal (the *kind* lives in `Printer.error_kind`). pub(crate) type PrintResult = core::result::Result; pub use dependencies::Dependency; @@ -246,11 +198,6 @@ pub use css_parser::BundlerStyleSheet; pub use properties::PropertyIdTag; pub use rules::import::ImportConditions; -// ───────────────────────────── VendorPrefix ───────────────────────────── -// Hoisted from css_parser.rs so leaf modules (targets, prefixes) can compile -// without pulling in the 6k-line parser hub. css_parser.rs re-exports this -// when it un-gates. - bitflags::bitflags! { #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)] pub struct VendorPrefix: u8 { @@ -319,13 +266,6 @@ impl VendorPrefix { self.bits() } - /// Detects a leading vendor prefix on `name` (case-insensitive, ASCII) and - /// returns it together with the slice that follows the prefix. - /// - /// Returns `(VendorPrefix::NONE, name)` when no prefix matches. Prefix forms - /// are the canonical dash-terminated spellings (`-webkit-`, `-moz-`, `-o-`, - /// `-ms-`); callers that previously matched without the trailing dash were - /// only correct because their input domain was already constrained. #[inline] pub fn strip_from(name: &[u8]) -> (VendorPrefix, &[u8]) { use bun_core::strings::starts_with_case_insensitive_ascii as has; @@ -343,11 +283,6 @@ impl VendorPrefix { } } -// ───────────────────────── Core lexer/location types ───────────────────────── -// Hoisted from css_parser.rs / rules/mod.rs so leaf modules (error, dependencies) -// compile without the 6k-line parser hub. css_parser.rs `pub use crate::{..}`s -// these when it un-gates. - /// Line/column within a single source. Column is 1-based, line is 0-based. #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] pub struct SourceLocation { @@ -391,11 +326,6 @@ pub struct Dimension { pub unit: &'static [u8], } -/// CSS lexer token. Data-only definition hoisted out of `css_parser.rs`; the -/// `to_css*`/`eql`/`hash` impls stay in `css_parser.rs` (gated) since they -/// depend on `serializer::*` and `generics`. -// TODO(port): every &'static [u8] payload borrows the parser arena/source; -// thread `<'a>` once the bumpalo arena lifetime is plumbed. #[derive(Clone, Debug)] pub enum Token { Ident(&'static [u8]), diff --git a/src/css/media_query.rs b/src/css/media_query.rs index 328d6423a18..c15f6aed4c8 100644 --- a/src/css/media_query.rs +++ b/src/css/media_query.rs @@ -24,17 +24,6 @@ use crate::css_values::resolution::Resolution; type CSSNumber = f32; type CSSInteger = i32; -// ───────────────────────── QueryCondition trait ───────────────────────── -// Implementors: MediaCondition, StyleQuery, ContainerCondition. -// NOT SupportsCondition — its variant set {Not, And(Vec), Or(Vec), Declaration, -// Selector, Unknown} and its `needs_parens(&Self)` / `b" not "` contract are -// structurally different and must stay hand-rolled. -// -// `deep_clone` is intentionally NOT on this trait. The Zig precedent is ONE -// reflective `css.implementDeepClone` (generics.zig); the Rust equivalent is -// `#[derive(DeepClone)]` (generics.rs). The hand-expansions in callers exist -// only because of derive blockers — fix the derive, not the trait. - /// Trait modeling Zig's `ValidQueryCondition` comptime interface check. /// Any type that can appear as a node in a query-condition tree. pub trait QueryCondition: Sized + ToCss { @@ -43,11 +32,6 @@ pub trait QueryCondition: Sized + ToCss { type Feature; fn parse_feature(input: &mut Parser) -> Result; - /// `parse_feature` with `ParserOptions` threaded — needed for the - /// `env()` arm of `MediaFeatureValue::parse_unknown`. Default impl - /// drops `options` so out-of-tree implementors (e.g. - /// `rules::container::{ContainerCondition,StyleQuery}`) keep compiling - /// until they opt in. fn parse_feature_with_options( input: &mut Parser, _options: &css::ParserOptions, @@ -185,10 +169,6 @@ pub enum MediaType { Custom(*const [u8]), } -// PORT NOTE: hand-rolled — derived PartialEq on `*const [u8]` compares -// address+len, not byte content. Spec `MediaType.eql` compares slice bytes -// (via `css.implementEql`); adjacent-@media merging (rules.zig) depends on -// content equality across distinct arena offsets. impl PartialEq for MediaType { fn eq(&self, other: &Self) -> bool { match (self, other) { @@ -295,10 +275,6 @@ pub enum QueryFeature { }, } -/// Comparison operator in a range media feature. -// PORT NOTE: discriminants are power-of-two bitflags — Zig media_query.zig -// bitwise-ORs `@intFromEnum(start_operator) | @intFromEnum(end_operator)` to -// validate interval operator pairs. Do NOT use implicit 0..=4. #[repr(u8)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, strum::IntoStaticStr)] pub enum MediaFeatureComparison { @@ -314,10 +290,6 @@ pub enum MediaFeatureComparison { LessThanEqual = 16, } -/// [media feature value](https://drafts.csswg.org/mediaqueries/#typedef-mf-value). -// PORT NOTE: `Debug` hand-rolled below — `Length` (calc tree) does not derive -// `Debug`, but the `MediaCondition`/`QueryFeature` chain wants it for -// diagnostics. #[derive(Clone)] pub enum MediaFeatureValue { /// A length value. @@ -726,11 +698,6 @@ impl MediaQuery { match &self.media_type { MediaType::All => { - // We need to print "all" if there's a qualifier, or there's - // just an empty list of expressions. - // - // Otherwise, we'd serialize media queries like "(min-width: - // 40px)" in "all (min-width: 40px)", which is unexpected. if self.qualifier.is_some() || self.condition.is_none() { dest.write_str("all")?; } @@ -988,11 +955,6 @@ impl MediaFeatureName { } } - /// Parses a media feature name. Returns `(name, legacy_comparator)` — - /// `legacy_comparator` is `Some` when the ident carried a `min-`/`max-` - /// prefix (lowered to `>=`/`<=`). - /// - /// Zig: `MediaFeatureName.parse`. pub(crate) fn parse(input: &mut Parser) -> Result<(Self, Option)> { use bun_core::strings; let ident = input.expect_ident_cloned()?; @@ -1021,13 +983,6 @@ impl MediaFeatureName { None }; - // PORT NOTE: Zig `allocPrint("-webkit-{s}", .{name})` then - // `parse_utility.parseString(.., FeatureId.parse)` — the re-tokenize is - // only to feed `DefineEnumProperty.parse` an ident token. Here - // `FeatureIdTrait::from_str` does the same case-insensitive table lookup - // directly, so a stack buffer suffices and the temp string is freed - // immediately (Zig asserts `FeatureId` is an enum for the same reason). - // PERF: stack buffer here? let mut webkit_buf: [u8; 64] = [0; 64]; let final_name: &[u8] = if is_webkit { let len = 8 + name.len(); @@ -1212,10 +1167,6 @@ impl MediaFeatureValue { return Ok(MediaFeatureValue::Resolution(res)); } - // PORT NOTE: Zig `input.tryParse(EnvironmentVariable.parse, .{})` left - // `options`/`depth` undefined (tryParse builds `ArgsTuple` and only - // fills index 0) — UB. Fixed here by threading the real `ParserOptions` - // down from `QueryFeature::parse` and passing `depth = 0`. if let Ok(env) = input.try_parse(|i| EnvironmentVariable::parse(i, options, 0)) { return Ok(MediaFeatureValue::Env(env)); } @@ -1266,18 +1217,6 @@ fn write_min_max( dest.write_char(b')') } -// ───────────────────────── deep_clone ───────────────────────── -// Arena-aware `deep_clone` — port of Zig's per-type `deepClone(arena)` -// bodies. Un-gated this round so `rules::dc::{media_list,query_feature}` can -// route through real impls instead of `#[derive(Clone)]` passthroughs. -// -// PORT NOTE: written as **inherent** methods (not `#[derive(DeepClone)]`) to -// match the Zig hand-written bodies exactly: Zig copies `name`/`qualifier`/ -// `media_type`/`operator` fields by value (they are `Copy`/arena-slice types -// under the generics.zig "const strings" rule) and only recurses into the -// allocating payloads (`Vec`, `Box`, `MediaFeatureValue`). The derive would -// instead add a spurious `FeatureId: DeepClone<'bump>` where-bound. - impl MediaList { /// Zig: `MediaList.deepClone` — element-wise clone of `media_queries`. pub fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { @@ -1408,10 +1347,6 @@ impl MediaFeatureValue { pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { use MediaFeatureValue as V; match self { - // Zig: `l.deepClone(arena)` — real `values::length::Length` - // owns a calc tree. The local `value_shims::Length` stand-in is a - // unit struct, so `Clone` is faithful until the calc lattice - // un-gates and the shim is replaced. V::Length(l) => V::Length(l.clone()), V::Number(n) => V::Number(*n), V::Integer(i) => V::Integer(*i), @@ -1539,10 +1474,6 @@ impl MediaCondition { } } -/// Parse a single query condition. -/// -/// Forwarder kept for callers that don't yet thread `ParserOptions` -/// (e.g. `rules::container`); routes through `ParserOptions::default(None)`. #[inline] pub fn parse_query_condition( input: &mut Parser, @@ -1663,12 +1594,6 @@ fn parse_paren_block( } impl QueryFeature { - /// Parse a media/container feature inside `(` `)`. - /// - /// Zig: `QueryFeature.parse` (media_query.zig:945). - /// - /// Forwarder kept for callers that don't yet thread `ParserOptions` - /// (e.g. `rules::container::ContainerCondition::parse_feature`). #[inline] pub(crate) fn parse(input: &mut Parser) -> Result { Self::parse_with_options(input, &css::ParserOptions::default(None)) @@ -1737,11 +1662,6 @@ impl QueryFeature { ) -> Result { // We need to find the feature name first so we know the type. let start = input.state(); - // PORT NOTE: Zig loops `MediaFeatureName.parse` then checks - // `isExhausted()` — but `expectIdent` does not advance on error, so - // the literal Zig body would spin on a non-ident token. The intent - // (matching lightningcss) is to *skip* tokens until the name is - // found; advance one token per failed attempt. let name: MediaFeatureName = loop { if let Ok((name, legacy_op)) = input.try_parse(MediaFeatureName::::parse) { if legacy_op.is_some() { @@ -1817,10 +1737,6 @@ impl QueryFeature { } } -/// Consumes an operation or a colon, or returns an error. -/// -/// Zig: `consumeOperationOrColon` (media_query.zig:1103). Returns `Ok(None)` -/// when a colon was consumed (and `allow_colon`); `Ok(Some(op))` for `<`/`>`/`=`. fn consume_operation_or_colon( input: &mut Parser, allow_colon: bool, diff --git a/src/css/printer.rs b/src/css/printer.rs index 3655a704f66..22a48c88388 100644 --- a/src/css/printer.rs +++ b/src/css/printer.rs @@ -30,13 +30,6 @@ pub struct PrinterOptions<'a> { pub project_root: Option<&'a [u8]>, /// Targets to output the CSS for. pub targets: Targets, - /// Whether to analyze dependencies (i.e. `@import` and `url()`). - /// If true, the dependencies are returned as part of the - /// [ToCssResult](super::stylesheet::ToCssResult). - /// - /// When enabled, `@import` and `url()` dependencies - /// are replaced with hashed placeholders that can be replaced with the final - /// urls later (after bundling). pub analyze_dependencies: Option, /// A mapping of pseudo classes to replace with class names that can be applied /// from JavaScript. Useful for polyfills, for example. @@ -113,15 +106,6 @@ impl<'a> ImportInfo<'a> { } } -/// A `Printer` represents a destination to output serialized CSS, as used in -/// the [ToCss](super::traits::ToCss) trait. It can wrap any destination that -/// implements [std::fmt::Write](std::fmt::Write), such as a [String](String). -/// -/// A `Printer` keeps track of the current line and column position, and uses -/// this to generate a source map if provided in the options. -/// -/// `Printer` also includes helper functions that assist with writing output -/// that respects options such as `minify`, and `css_modules`. pub struct Printer<'a> { // #[cfg(feature = "sourcemap")] pub sources: Option<&'a Vec>>, @@ -133,12 +117,6 @@ pub struct Printer<'a> { pub minify: bool, pub targets: Targets, pub vendor_prefix: css::VendorPrefix, - /// True while nested rules are being re-serialized for a non-final vendor - /// prefix pass of an ancestor style rule (when nesting is compiled away). - /// Nested style rules that carry their own vendor prefixes override - /// `vendor_prefix`, so their output is identical in every ancestor pass; - /// they are skipped while this is set and emitted once in the final pass, - /// keeping the output linear in nesting depth instead of exponential. pub skip_prefixed_nested_rules: bool, pub in_calc: bool, pub css_module: Option>, @@ -151,12 +129,6 @@ pub struct Printer<'a> { // TODO(port): lifetime — ctx is set to a stack-local during with_context() and restored // after; `&'a StyleContext<'a>` will not borrow-check there. May need raw `*const StyleContext`. pub ctx: Option<&'a css::StyleContext<'a>>, - /// Number of parent-selector substitutions performed for `&` while - /// serializing the current rule prelude with compiled nesting (targets - /// without CSS nesting support). Reset per prelude (in - /// `StyleRule::to_css_base` and `ScopeRule::to_css`) and bounded in - /// `serialize::serialize_nesting` so deeply nested rules with multiple - /// `&` references per level cannot expand exponentially. pub nesting_expansions: u32, pub scratchbuf: BumpVec<'a, u8>, pub error_kind: Option, @@ -204,10 +176,6 @@ impl<'a> Printer<'a> { self.lookup_symbol(ident.as_ref().unwrap()) } - // Zig checked vtable identity against std.Io.Writer.Allocating and recovered the - // backing buffer length via `container_of`; in Rust the trait exposes `written_len()` - // directly (Vec / MutableString / counting sinks override it, others panic — same - // contract as Zig's `@panic("css: got bad writer type")` fallthrough). #[inline] fn get_written_amt(writer: &dyn Write) -> usize { writer.written_len() @@ -330,10 +298,6 @@ impl<'a> Printer<'a> { } } - /// Construct a `Printer` that writes into an in-memory `Vec` buffer - /// using default `PrinterOptions`. Mirrors the Zig pattern of pairing - /// `std.Io.Writer.Allocating` with `Printer.new(..., PrinterOptions.default(), ...)` - /// for sub-serialization (e.g. `PseudoClass::toCss`, `Selector` debug fmt). pub fn new_buffered( arena: &'a Bump, dest: &'a mut Vec, @@ -411,10 +375,6 @@ impl<'a> Printer<'a> { self.ctx } - /// To satisfy io.Writer interface - /// - /// NOTE: Same constraints as `write_str`, the `str` param is assumed to not - /// contain any newline characters pub fn write_all(&mut self, str_: &[u8]) -> Result<(), bun_alloc::AllocError> { self.write_str(str_).map_err(|_| bun_alloc::AllocError) } @@ -434,12 +394,6 @@ impl<'a> bun_io::Write for Printer<'a> { } impl<'a> Printer<'a> { - /// Serialize a CSS identifier through this printer. - /// - /// Thin wrapper over `css::serializer::serialize_identifier`. The - /// serializer returns `bun_io::Result<()>`; `write_str`/`write_char` have - /// already recorded `add_fmt_error()` on failure, so the error payload is - /// just remapped to `PrintErr::CSSPrintError`. #[inline] pub fn serialize_identifier(&mut self, v: &[u8]) -> PrintResult<()> { css::serializer::serialize_identifier(v, self).map_err(|_| PrintErr::CSSPrintError) @@ -475,10 +429,6 @@ impl<'a> Printer<'a> { Ok(()) } - /// Writes a raw string to the underlying destination. - /// - /// NOTE: Is is assumed that the string does not contain any newline characters. - /// If such a string is written, it will break source maps. pub fn write_str(&mut self, s: impl AsRef<[u8]>) -> PrintResult<()> { let s = s.as_ref(); #[cfg(debug_assertions)] @@ -505,10 +455,6 @@ impl<'a> Printer<'a> { Ok(()) } - /// Writes a formatted string to the underlying destination. - /// - /// NOTE: Is is assumed that the formatted string does not contain any newline characters. - /// If such a string is written, it will break source maps. pub fn write_fmt(&mut self, args: fmt::Arguments<'_>) -> PrintResult<()> { // assuming the writer comes from an ArrayList let start: usize = Self::get_written_amt(self.dest); @@ -562,10 +508,6 @@ impl<'a> Printer<'a> { pub fn write_ident(&mut self, ident: &'a [u8], handle_css_module: bool) -> PrintResult<()> { if handle_css_module { if self.css_module.is_some() { - // PORT NOTE: borrowck reshape — Zig captured `&mut self` inside the closure - // while `css_module` (a field of self) was simultaneously borrowed. We instead - // copy the `'a`-lifetime references out of `css_module` up front so the - // closure can hold the sole `&mut self`. let source_index = self.loc.source_index as usize; let arena = self.arena; let (config, hash, source): (&'a css::css_modules::Config, &'a [u8], &'a [u8]) = { @@ -724,10 +666,6 @@ impl<'a> Printer<'a> { self.whitespace() } - /// Writes a single whitespace character, unless the `minify` option is enabled. - /// - /// Use `write_char` instead if you wish to force a space character to be written, - /// regardless of the `minify` option. pub fn whitespace(&mut self) -> PrintResult<()> { if self.minify { return Ok(()); @@ -735,14 +673,6 @@ impl<'a> Printer<'a> { self.write_char(b' ') } - /// Writes a `{ ... }` block envelope: optional leading whitespace, `{`, - /// indent, the caller-supplied body, dedent, trailing newline, `}`. - /// - /// This is the shared shape used by every nested-rule at-rule printer - /// (`@media`, `@supports`, `@container`, `@layer`, `@starting-style`, - /// `@-moz-document`, unknown at-rules). The body closure is responsible - /// for its own leading `newline()` if it wants one — per-item printers - /// (e.g. `@font-face`, `@keyframes`) interleave newlines differently. pub fn block(&mut self, f: impl FnOnce(&mut Self) -> PrintResult<()>) -> PrintResult<()> { self.whitespace()?; self.write_char(b'{')?; @@ -753,16 +683,6 @@ impl<'a> Printer<'a> { self.write_char(b'}') } - /// Writes each item via `f`, calling `sep` *between* items (not before the - /// first, not after the last). All errors short-circuit via `?`. - /// - /// This is the canonical replacement for the open-coded - /// `let mut first = true; for x in iter { if !first { ? } first = false; ? }` - /// loop that pervades `to_css` impls. - /// - /// `sep` is a closure — not a `u8` — because the dominant separator in CSS - /// printing is [`delim`](Self::delim) (minify-aware whitespace around a byte), - /// and several sites need a multi-statement or `minify`-conditional separator. pub fn write_separated(&mut self, iter: I, mut sep: S, mut f: F) -> PrintResult<()> where I: IntoIterator, diff --git a/src/css/properties/align.rs b/src/css/properties/align.rs index b060d038b3f..faf37659553 100644 --- a/src/css/properties/align.rs +++ b/src/css/properties/align.rs @@ -32,11 +32,6 @@ pub enum AlignContent { ContentPosition(AlignContentContentPosition), } -// Zig: anonymous payload struct carrying `pub fn __generateToCss() void {}` — -// the marker telling `DeriveToCss` to auto-generate the field-sequence printer. -// In Rust the equivalent is `#[derive(css::ToCss)]` on the lifted named-field -// struct (see `css_derive::expand_derive_to_css` struct branch); the enum arm's -// `__inner.to_css(dest)` then resolves to this generated inherent. #[derive(Clone, PartialEq, Eq, css::ToCss)] #[css(generate_to_css)] pub struct AlignContentContentPosition { @@ -984,13 +979,6 @@ pub struct AlignHandler { pub has_any: bool, } -// ─── helper macros (Zig used `comptime prop: []const u8` + `@field` / `@unionInit`) ─── -// -// TODO(port): the Zig source threads field names as comptime strings into helper fns -// and uses @field/@unionInit for reflection. Rust cannot pass field names as values, so -// these are macro_rules! that expand at each call site; a small proc-macro could dedupe -// if maintenance burden is high. - macro_rules! handle_property_maybe_flush { ($this:expr, $dest:expr, $context:expr, $field:ident, $val:expr, $vp:expr) => {{ // If two vendor prefixes for the same property have different diff --git a/src/css/properties/animation.rs b/src/css/properties/animation.rs index 6df914f0244..9b922ec6d79 100644 --- a/src/css/properties/animation.rs +++ b/src/css/properties/animation.rs @@ -38,14 +38,6 @@ pub struct Animation { } impl Animation { - // TODO(port): PropertyFieldMap / VendorPrefixMap were comptime anonymous-struct - // metadata consumed by reflection in the shorthand codegen. Replace these with - // a derive macro (e.g. #[derive(Shorthand)]) that emits the field→PropertyIdTag - // and field→has-vendor-prefix tables. - // PORT NOTE: PropertyFieldMap dropped — `PropertyIdTag::Animation*` variants - // are not yet generated (animation longhands are unparsed-only for now), and - // the table was unread comptime metadata. Re-add when the variants land. - pub const VENDOR_PREFIX_MAP: &'static [(&'static str, bool)] = &[ ("name", true), ("duration", true), @@ -224,10 +216,6 @@ impl Animation { } } -/// A value for the [animation-name](https://drafts.csswg.org/css-animations/#animation-name) property. -// PORT NOTE: no `#[derive(PartialEq, Eq, Hash)]` — `CustomIdent`/`CSSString` -// carry raw `*const [u8]` arena pointers; derived eq/hash would compare by -// pointer. Hand-written `eql`/`hash` below compare by content. #[derive(Clone, Copy)] pub enum AnimationName { /// The `none` keyword. @@ -291,10 +279,6 @@ impl AnimationName { { return Ok(AnimationName::None); } - // PORT NOTE: `expect_string` returns a slice borrowing `&mut self`, which - // `try_parse`'s `R` type param can't carry. Erase the lifetime through a - // raw pointer inside the closure; the slice lives in the input arena and - // outlives this parse (CSSString = &'static [u8]). if let Ok(s) = input.try_parse(|i| i.expect_string().map(std::ptr::from_ref::<[u8]>)) { return Ok(AnimationName::String(s)); } @@ -479,15 +463,6 @@ pub enum AnimationTimeline { } impl AnimationTimeline { - // Port of `css.DeriveParse(@This()).parse` — void variants (`auto`, `none`) - // declared first → tried first via ident match; payloads follow in - // declaration order (`DashedIdent`, `ScrollTimeline`, `ViewTimeline`). - // Upstream `ScrollTimeline` / `ViewTimeline` carry no `parse`, so the Zig - // `DeriveParse` instantiation is dead code (`generic.parseFor` would - // `@compileError` if compiled — `Animation` is unreferenced in - // properties_generated.zig). We stop at `DashedIdent` here; if scroll()/ - // view() ever become live they need real function-syntax parsing, not the - // derived field-sequence fallback. pub fn parse(input: &mut Parser) -> css::Result { let state = input.state(); if let Ok(ident) = input.expect_ident() { @@ -510,12 +485,6 @@ impl AnimationTimeline { AnimationTimeline::Auto => dest.write_str(b"auto"), AnimationTimeline::None => dest.write_str(b"none"), AnimationTimeline::DashedIdent(d) => d.to_css(dest), - // Upstream Zig `ScrollTimeline` / `ViewTimeline` have no `toCss`; - // `DeriveToCss` would delegate to `generic.toCss` → `T.toCss` and - // `@compileError` if this arm were ever instantiated. Mirror that: - // these variants are currently unconstructible via `parse()`, and - // emitting bare space-separated fields here would be wrong CSS - // (spec syntax is `scroll(...)` / `view(...)`). AnimationTimeline::Scroll(_) | AnimationTimeline::View(_) => { unreachable!("ScrollTimeline / ViewTimeline have no toCss in spec (uninstantiated)") } diff --git a/src/css/properties/background.rs b/src/css/properties/background.rs index de6369e50a1..d4e089ffa68 100644 --- a/src/css/properties/background.rs +++ b/src/css/properties/background.rs @@ -444,11 +444,6 @@ crate::css_eql_partialeq!( BackgroundRepeat, ); -/// A [``](https://www.w3.org/TR/css-backgrounds-3/#typedef-repeat-style) value, -/// used within the `background-repeat` property to represent how a background image is repeated -/// in a single direction. -/// -/// See [BackgroundRepeat](BackgroundRepeat). #[derive(Clone, Copy, PartialEq, Eq, Hash, crate::DefineEnumProperty)] pub enum BackgroundRepeatKeyword { /// The image is repeated in this direction. @@ -606,11 +601,6 @@ pub struct BackgroundHandler { pub has_any: bool, } -// PORT NOTE: the Zig uses comptime field-name strings + @field for `flushHelper` / -// `initSmallListHelper` / `push`. Rust cannot index struct fields by string at runtime; -// these helpers are expanded into small per-field macros below. A derive macro -// could replace them. - macro_rules! init_small_list_helper { ($this:expr, $field:ident, $length:expr) => {{ let length = $length; @@ -930,11 +920,6 @@ impl BackgroundHandler { }, }); } - // Zig: defer { clearRetainingCapacity on each list } — values were moved - // by-value into `backgrounds` above, so clearing prevents double-free. - // In Rust we cloned, so the originals will Drop normally; no explicit clear - // needed for correctness. Leaving as-is. - // PERF(port): was arena bulk-free / move-then-clear — profile if hot. if self.flushed_properties.is_empty() { let mut fallbacks = diff --git a/src/css/properties/border.rs b/src/css/properties/border.rs index 2a16afa7951..c8dfa6f2560 100644 --- a/src/css/properties/border.rs +++ b/src/css/properties/border.rs @@ -165,12 +165,6 @@ where css::implement_deep_clone(self, arena) } - /// Deep-clone into a `GenericBorder` with a different const-generic - /// discriminant `Q`. The fields are identical regardless of `P`; this is - /// the Rust equivalent of Zig coercing one anonymous struct literal into - /// multiple `Border*` aliases. Needed when one logical value must be - /// emitted as two distinct physical `Property` variants (e.g. - /// inline-start → BorderLeft + BorderRight). pub(crate) fn clone_as(&self, arena: &Bump) -> GenericBorder { let cloned = self.deep_clone(arena); GenericBorder { @@ -251,12 +245,6 @@ impl BorderSideWidth { } crate::css_eql_partialeq!(BorderSideWidth); -// ────────────────────────────────────────────────────────────────────────── -// ImplFallbacks (Zig: `pub fn ImplFallbacks(comptime T: type) type`) -// ────────────────────────────────────────────────────────────────────────── -// TODO(port): Zig used `inline for (std.meta.fields(T))` reflection. We expand -// the field list at macro invocation. All fields are `CssColor`. -// Hoisted here because `macro_rules!` is order-sensitive. macro_rules! impl_fallbacks { ($T:ty; $($field:ident),+) => { impl $T { @@ -300,12 +288,6 @@ macro_rules! impl_fallbacks { }; } -// ────────────────────────────────────────────────────────────────────────── -// Rect shorthand structs (top/right/bottom/left) -// ────────────────────────────────────────────────────────────────────────── -// `define_rect_shorthand!` lives in `properties/mod.rs` (shared with -// `margin_padding.rs`). - // TODO: fallbacks define_rect_shorthand! { /// A value for the [border-color](https://drafts.csswg.org/css-backgrounds/#propdef-border-color) shorthand property. @@ -634,12 +616,6 @@ pub struct BorderHandler { mod border_handler_body { use super::*; use crate::generics::{CssEql, DeepClone}; - // ────────────────────────────────────────────────────────────────────────── - // FlushContext + flush_category! (Zig: nested struct with inline fns and - // extensive comptime string-dispatch) - // ────────────────────────────────────────────────────────────────────────── - // PORT NOTE: hoisted above `impl BorderHandler` — macro_rules! is order- - // sensitive and the flush_category!() callsites in `flush()` need these. // Route the large `Property` enum construction through a non-inlined // callee so each temporary lives in the helper's frame, not in @@ -674,11 +650,6 @@ mod border_handler_body { // PORT NOTE: `$val` is evaluated *before* reborrowing `$f` so callers may pass // expressions that read `f.arena` without tripping E0502. macro_rules! fc_logical_prop { - // PORT NOTE: the `GenericBorder` shorthand pairs carry distinct const-generic - // discriminants per side (BorderLeft = P=3, BorderRight = P=1), so a single - // `__val` cannot `deep_clone()` into both `Property` variants. Recast via - // `clone_as::()` instead. Callers always pass a `to_border()` result here, - // so the `__val` annotation drives `P` inference for that call. ($f:expr, BorderLeft, BorderRight, $val:expr) => {{ let __val: BorderLeft = $val; let f = &mut *$f; @@ -714,10 +685,6 @@ mod border_handler_body { }}; } - // `f.push(p, val)` - // PORT NOTE: Zig's `@field(BorderProperty, p)` keyed both Property and BorderProperty - // off one kebab string. Here `$p` is the PascalCase Property/PropertyIdTag variant; - // the bitflags const is derived via try_from_property_id so a single ident suffices. macro_rules! fc_push { ($f:expr, $p:ident, $val:expr) => {{ let __val = $val; diff --git a/src/css/properties/border_image.rs b/src/css/properties/border_image.rs index f6f76f85fbc..c2e817e4bea 100644 --- a/src/css/properties/border_image.rs +++ b/src/css/properties/border_image.rs @@ -34,17 +34,6 @@ pub struct BorderImage { } impl BorderImage { - // TODO(port): PropertyFieldMap / VendorPrefixMap were comptime anonymous-struct - // tables consumed via @field reflection by the shorthand codegen. Replace with - // a trait impl (e.g. `impl ShorthandProperty for BorderImage`). - // PropertyFieldMap: - // source -> PropertyIdTag::BorderImageSource - // slice -> PropertyIdTag::BorderImageSlice - // width -> PropertyIdTag::BorderImageWidth - // outset -> PropertyIdTag::BorderImageOutset - // repeat -> PropertyIdTag::BorderImageRepeat - // VendorPrefixMap: all fields = true - pub(crate) fn parse(input: &mut css::Parser) -> Result { // PORT NOTE: Zig passed `{}` ctx + a no-op callback struct; collapsed to a closure. Self::parse_with_callback(input, |_: &mut css::Parser| false) diff --git a/src/css/properties/border_radius.rs b/src/css/properties/border_radius.rs index bd664d747b8..19abcc2b12e 100644 --- a/src/css/properties/border_radius.rs +++ b/src/css/properties/border_radius.rs @@ -11,11 +11,6 @@ use crate::css_values::rect::Rect; use crate::css_values::size::Size2D; use bun_alloc::ArenaVecExt as _; -/// A value for the [border-radius](https://www.w3.org/TR/css-backgrounds-3/#border-radius) property. -// PORT NOTE: `Size2D` carries no `Clone`/`PartialEq` derives (it exposes -// inherent `deep_clone`/`eql` instead, matching the Zig protocol surface), so -// `BorderRadius` can't `#[derive]` them either. The handler below uses -// `Size2D::deep_clone`/`Size2D::eql` directly. pub struct BorderRadius { /// The x and y radius values for the top left corner. pub top_left: Size2D, @@ -100,10 +95,6 @@ impl BorderRadius { } pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { - // PORT NOTE: Zig built `Rect(*const LengthPercentage)` and reused - // `Rect.toCss`. `Rect::<&T>::to_css` would need `&T: ToCss + PartialEq`; - // inline the 4-side serialization to avoid that bound (logic is identical - // to `values::rect::Rect::to_css`). #[inline] fn write_rect( top: &LengthPercentage, @@ -178,10 +169,6 @@ pub struct BorderRadiusHandler { // token-level field/variant access. Rust has no field-by-name reflection, so these are // macro_rules! that paste the field ident and the corresponding Property variant ident. -// PORT NOTE: `Size2D::is_compatible` is bounded on `T: values::protocol::IsCompatible`, -// which `LengthPercentage` (= `DimensionPercentage`) does not yet impl. -// Hand-roll the per-component check via `LengthPercentage::is_compatible` (inherent -// method) until that protocol impl lands. #[inline] fn size2d_lp_is_compatible( val: &Size2D, diff --git a/src/css/properties/box_shadow.rs b/src/css/properties/box_shadow.rs index f2bdad7ee88..9d245524d4b 100644 --- a/src/css/properties/box_shadow.rs +++ b/src/css/properties/box_shadow.rs @@ -28,10 +28,6 @@ pub struct BoxShadow { pub inset: bool, } -// PORT NOTE: `SmallList::{deep_clone,eql,is_compatible}` are bounded on the -// `generics::{DeepClone,CssEql,IsCompatible}` traits. Wire BoxShadow into all -// three so the handler can use `SmallList` directly without -// hand-rolling per-field loops. impl<'bump> css::generic::DeepClone<'bump> for BoxShadow { #[inline] fn deep_clone(&self, bump: &'bump Arena) -> Self { @@ -277,10 +273,6 @@ impl BoxShadowHandler { fallbacks.insert(shadow.color.get_necessary_fallbacks(&context.targets)); } - // PORT NOTE: Zig used `initCapacity(len)` + `setLen(len)` + per-index field - // writes via `inline for std.meta.fields(BoxShadow)` skipping `color`. That - // pattern would observe partially-uninit `BoxShadow` values in Rust, so we - // build each fully-formed `BoxShadow` and `append`. Behavior is identical. macro_rules! build_color_fallback { ($conv:ident) => {{ let mut out: SmallList = diff --git a/src/css/properties/css_modules.rs b/src/css/properties/css_modules.rs index c3f02e4a881..6edffaf16be 100644 --- a/src/css/properties/css_modules.rs +++ b/src/css/properties/css_modules.rs @@ -126,10 +126,6 @@ pub enum Specifier { ImportRecordIndex(u32), } -// `generics::CssEql` so the `Option` blanket (used by -// `DashedIdentReference::eql` in values/ident.rs) resolves. Forwards to the -// inherent `eql` below — same shape the old data-only stub in `values/mod.rs` -// carried before this leaf un-gated. impl crate::generics::CssEql for Specifier { #[inline] fn eql(&self, other: &Self) -> bool { diff --git a/src/css/properties/custom.rs b/src/css/properties/custom.rs index c75e77d7b6d..55aa128bbdb 100644 --- a/src/css/properties/custom.rs +++ b/src/css/properties/custom.rs @@ -46,14 +46,6 @@ use bun_wyhash::Wyhash; use crate::generics::{CssEql, CssHash, DeepClone}; use bun_alloc::Arena; -// ─── External-gate shims ─────────────────────────────────────────────────── -// `TokenList::{parse,to_css}` bottom out on a handful of leaf fns that still -// carry `` in *other* files (`values/{url,ident}.rs`, -// `css_modules.rs`). Those gates are stale — every dependency they cite now -// exists — but this round's edit scope is `custom.rs` + `css_parser.rs` only. -// To un-gate the TokenList hub without touching those files, the leaf bodies -// are inlined here verbatim. Once `url.rs`/`ident.rs` un-gate, callers below -// can swap back to the canonical methods and this module drops. mod ext { use super::*; use crate::dependencies; @@ -182,14 +174,6 @@ mod ext { } } -// ─── Token protocol impls ────────────────────────────────────────────────── -// `Token` / `Num` / `Dimension` are defined data-only at crate root (lib.rs); -// their `eql`/`hash` bodies in css_parser.rs forward to `generic::implement_*` -// which bound on these traits — provide them here so the cycle closes and -// `#[derive(CssEql/CssHash/DeepClone)]` on `TokenOrValue` resolves the -// `Token(Token)` arm. Hand-written (not derived) because `Token` carries -// `&'static [u8]` payloads (arena-lifetime placeholder) and named-field -// variants whose layout lives outside this module's edit scope. impl CssEql for crate::Num { #[inline] fn eql(&self, other: &Self) -> bool { @@ -828,10 +812,6 @@ pub type TokenListFns = TokenList; pub(crate) type Fallbacks = (SupportsCondition, TokenList); -/// A color value with an unresolved alpha value (e.g. a variable). -/// These can be converted from the modern slash syntax to older comma syntax. -/// This can only be done when the only unresolved component is the alpha -/// since variables can resolve to multiple tokens. #[derive(CssEql, CssHash, DeepClone)] pub enum UnresolvedColor { /// An rgb() color. @@ -1014,11 +994,6 @@ impl UnresolvedColor { } } -// `ComponentParser::parse_relative` is generic over `C: LightDarkOwned` so the -// `from light-dark(...)` relative-color path can rebuild a `light-dark()` of -// whatever output type the caller is producing. Zig duck-types this via -// `lightDarkOwned` on both `CssColor` and `UnresolvedColor`; in Rust the trait -// lives in `values::color` and we wire `UnresolvedColor` into it here. impl css_values::color::LightDarkOwned for UnresolvedColor { #[inline] fn light_dark_owned(light: Self, dark: Self) -> Self { @@ -1190,10 +1165,6 @@ impl EnvironmentVariableName { } } -/// A UA-defined environment variable name. -// PORT NOTE: Zig `css.DefineEnumProperty(@This())` provides eql/hash/parse/ -// to_css/deep_clone via comptime reflection over @tagName. Replaced by an -// `EnumProperty` impl below (kebab-case match) — same protocol surface. #[derive(Clone, Copy, PartialEq, Eq, strum::IntoStaticStr, CssHash)] pub enum UAEnvironmentVariable { /// The safe area inset from the top of the viewport. @@ -1331,15 +1302,6 @@ impl TokenOrValue { } } -// ─── Clone / Debug shims ─────────────────────────────────────────────────── -// `selectors::parser::PseudoElement` / `PseudoClass` derive `Clone` over a -// `TokenList` payload, and `media_query::MediaFeatureValue` derives -// `Debug + Clone` over an `EnvironmentVariable` payload. The leaf value types -// (`Url`, `CustomIdent`, …) don't all `#[derive(Clone)]` yet, so hand-roll -// the structural clone here. PORT NOTE: Zig had no `Clone` distinction — -// shallow struct copy was implicit; arena-slice payloads (`*const [u8]`) are -// `Copy`, and the only owning fields are `Vec` / `Vec`. - impl Clone for TokenList { fn clone(&self) -> Self { TokenList { v: self.v.clone() } @@ -1430,11 +1392,6 @@ impl core::fmt::Debug for EnvironmentVariable { } } -/// A known property with an unparsed value. -/// -/// This type is used when the value of a known property could not -/// be parsed, e.g. in the case css `var()` references are encountered. -/// In this case, the raw tokens are stored instead. pub struct UnparsedProperty { /// The id of the property. pub property_id: css::properties::PropertyId, diff --git a/src/css/properties/flex.rs b/src/css/properties/flex.rs index 3f7a4408a13..1e87a1294b5 100644 --- a/src/css/properties/flex.rs +++ b/src/css/properties/flex.rs @@ -60,12 +60,6 @@ pub struct FlexFlow { pub wrap: FlexWrap, } -// (old using name space) css.DefineShorthand(@This(), css.PropertyIdTag.@"flex-flow", PropertyFieldMap); -// TODO(port): PropertyFieldMap / VendorPrefixMap are comptime shorthand metadata consumed by -// css::DefineShorthand reflection. Port as part of the shorthand derive. -// PropertyFieldMap = { direction: PropertyIdTag::FlexDirection, wrap: PropertyIdTag::FlexWrap } -// VendorPrefixMap = { direction: true, wrap: true } - impl FlexFlow { pub(crate) fn parse(input: &mut css::Parser) -> css::Result { let mut direction: Option = None; @@ -123,11 +117,6 @@ pub struct Flex { pub basis: LengthPercentageOrAuto, } -// (old using name space) css.DefineShorthand(@This(), css.PropertyIdTag.flex, PropertyFieldMap); -// TODO(port): PropertyFieldMap / VendorPrefixMap shorthand metadata — see FlexFlow note. -// PropertyFieldMap = { grow: PropertyIdTag::FlexGrow, shrink: PropertyIdTag::FlexShrink, basis: PropertyIdTag::FlexBasis } -// VendorPrefixMap = { grow: true, shrink: true, basis: true } - impl Flex { pub(crate) fn parse(input: &mut css::Parser) -> css::Result { if input @@ -227,10 +216,6 @@ impl Flex { } } -/// A value for the legacy (prefixed) [box-orient](https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#orientation) property. -/// Partially equivalent to `flex-direction` in the standard syntax. -/// A value for the legacy (prefixed) [box-orient](https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#orientation) property. -/// Partially equivalent to `flex-direction` in the standard syntax. #[derive(Clone, Copy, PartialEq, Eq, Hash, css::DefineEnumProperty)] pub enum BoxOrient { /// Items are laid out horizontally. @@ -255,10 +240,6 @@ pub enum BoxDirection { pub(crate) type FlexAlign = BoxAlign; -/// A value for the legacy (prefixed) [box-align](https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#alignment) property. -/// Equivalent to the `align-items` property in the standard syntax. -/// A value for the legacy (prefixed) [box-align](https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#alignment) property. -/// Equivalent to the `align-items` property in the standard syntax. #[derive(Clone, Copy, PartialEq, Eq, Hash, css::DefineEnumProperty)] pub enum BoxAlign { /// Items are aligned to the start. @@ -295,10 +276,6 @@ impl BoxAlign { } } -/// A value for the legacy (prefixed) [box-pack](https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#packing) property. -/// Equivalent to the `justify-content` property in the standard syntax. -/// A value for the legacy (prefixed) [box-pack](https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#packing) property. -/// Equivalent to the `justify-content` property in the standard syntax. #[derive(Clone, Copy, PartialEq, Eq, Hash, css::DefineEnumProperty)] pub enum BoxPack { /// Items are justified to the start. @@ -335,10 +312,6 @@ impl BoxPack { } } -/// A value for the legacy (prefixed) [box-lines](https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#multiple) property. -/// Equivalent to the `flex-wrap` property in the standard syntax. -/// A value for the legacy (prefixed) [box-lines](https://www.w3.org/TR/2009/WD-css3-flexbox-20090723/#multiple) property. -/// Equivalent to the `flex-wrap` property in the standard syntax. #[derive(Clone, Copy, PartialEq, Eq, Hash, css::DefineEnumProperty)] pub enum BoxLines { /// Items are laid out in a single line. @@ -357,11 +330,6 @@ impl BoxLines { } } -// Old flex (2012): https://www.w3.org/TR/2012/WD-css3-flexbox-20120322/ -/// A value for the legacy (prefixed) [flex-pack](https://www.w3.org/TR/2012/WD-css3-flexbox-20120322/#flex-pack) property. -/// Equivalent to the `justify-content` property in the standard syntax. -/// A value for the legacy (prefixed) [flex-pack](https://www.w3.org/TR/2012/WD-css3-flexbox-20120322/#flex-pack) property. -/// Equivalent to the `justify-content` property in the standard syntax. #[derive(Clone, Copy, PartialEq, Eq, Hash, css::DefineEnumProperty)] pub enum FlexPack { /// Items are justified to the start. @@ -403,10 +371,6 @@ impl FlexPack { } } -/// A value for the legacy (prefixed) [flex-item-align](https://www.w3.org/TR/2012/WD-css3-flexbox-20120322/#flex-align) property. -/// Equivalent to the `align-self` property in the standard syntax. -/// A value for the legacy (prefixed) [flex-item-align](https://www.w3.org/TR/2012/WD-css3-flexbox-20120322/#flex-align) property. -/// Equivalent to the `align-self` property in the standard syntax. #[derive(Clone, Copy, PartialEq, Eq, Hash, css::DefineEnumProperty)] pub enum FlexItemAlign { /// Equivalent to the value of `flex-align`. @@ -446,10 +410,6 @@ impl FlexItemAlign { } } -/// A value for the legacy (prefixed) [flex-line-pack](https://www.w3.org/TR/2012/WD-css3-flexbox-20120322/#flex-line-pack) property. -/// Equivalent to the `align-content` property in the standard syntax. -/// A value for the legacy (prefixed) [flex-line-pack](https://www.w3.org/TR/2012/WD-css3-flexbox-20120322/#flex-line-pack) property. -/// Equivalent to the `align-content` property in the standard syntax. #[derive(Clone, Copy, PartialEq, Eq, Hash, css::DefineEnumProperty)] pub enum FlexLinePack { /// Content is aligned to the start. @@ -558,10 +518,6 @@ impl FlexHandler { ($prop:ident, $val:expr, $vp:expr) => {{ maybe_flush!($prop, $val, $vp); - // Otherwise, update the value and add the prefix - // PORT NOTE: Zig threaded `context.arena` into `css.generic.deepClone`; - // every payload here is `Clone` (Copy enums / f32 / i32 / LengthPercentageOrAuto), - // so `.clone()` is the faithful equivalent. if let Some(field) = &mut self.$prop { field.0 = ($val).clone(); field.1.insert(*$vp); @@ -851,10 +807,6 @@ impl FlexHandler { } }}; } - // TODO(port): single_property! macro encodes Zig's comptime `prop_2009`/`prop_2012` branches. - // The Zig version gates the entire 2009 block on `comptime prop_2009 != null`; here the macro - // arms with `prop_2009 = None` pass a no-op closure, so the `prefix.contains(NONE)` check - // still runs but has no effect. Verify this matches behavior exactly. single_property!( FlexDirection, diff --git a/src/css/properties/font.rs b/src/css/properties/font.rs index 125b7ccead1..bcc5e2505df 100644 --- a/src/css/properties/font.rs +++ b/src/css/properties/font.rs @@ -90,10 +90,6 @@ impl FontWeight { // deepClone → derived Clone; TODO(port): arena-aware deep_clone if needed } -/// An [absolute font weight](https://www.w3.org/TR/css-fonts-4/#font-weight-absolute-values), -/// as used in the `font-weight` property. -/// -/// See [FontWeight](FontWeight). #[derive(Clone, PartialEq)] pub enum AbsoluteFontWeight { /// An explicit weight. @@ -184,10 +180,6 @@ impl FontSize { // deepClone → derived Clone } -/// An [absolute font size](https://www.w3.org/TR/css-fonts-3/#absolute-size-value), -/// as used in the `font-size` property. -/// -/// See [FontSize](FontSize). #[derive(Clone, Copy, PartialEq, Eq, Hash, css::DefineEnumProperty)] pub enum AbsoluteFontSize { /// "xx-small" @@ -217,10 +209,6 @@ impl AbsoluteFontSize { } } -/// A [relative font size](https://www.w3.org/TR/css-fonts-3/#relative-size-value), -/// as used in the `font-size` property. -/// -/// See [FontSize](FontSize). #[derive(Clone, Copy, PartialEq, Eq, Hash, css::DefineEnumProperty)] pub enum RelativeFontSize { Smaller, @@ -281,10 +269,6 @@ impl FontStretch { } } -/// A [font stretch keyword](https://www.w3.org/TR/css-fonts-4/#font-stretch-prop), -/// as used in the `font-stretch` property. -/// -/// See [FontStretch](FontStretch). #[derive(Clone, Copy, PartialEq, Eq, Hash, css::DefineEnumProperty)] pub enum FontStretchKeyword { /// 100% @@ -333,10 +317,6 @@ impl FontStretchKeyword { pub enum FontFamily { /// A generic family name. Generic(GenericFontFamily), - /// A custom family name. - // TODO(port): arena-backed slice — should be &'bump [u8] once 'bump lifetime is threaded through - // PORT NOTE: with *const [u8] derived PartialEq/Eq/Hash would compare by pointer; Zig's custom - // HashContext hashes/compares by content (Wyhash over bytes) — provide manual impls below. FamilyName(*const [u8]), } @@ -480,10 +460,6 @@ impl Clone for FontFamily { } } -/// A [generic font family](https://www.w3.org/TR/css-fonts-4/#generic-font-families) name, -/// as used in the `font-family` property. -/// -/// See [FontFamily](FontFamily). #[derive(Clone, Copy, PartialEq, Eq, Hash, css::DefineEnumProperty)] pub enum GenericFontFamily { Serif, @@ -688,11 +664,6 @@ impl LineHeight { } } -/// A value for the [font](https://www.w3.org/TR/css-fonts-4/#font-prop) shorthand property. -// PORT NOTE: Zig's `eql`/`deepClone` were reflection-based (`css.implementEql` -// / `css.implementDeepClone`); the field-wise `#[derive(DeepClone, CssEql)]` -// is the Rust equivalent — every field type carries the trait via the -// blankets/bridges in `generics.rs`. #[derive(DeepClone, CssEql)] pub struct Font { /// The font family. @@ -1162,12 +1133,6 @@ fn compatible_font_family( } if let Some(families) = family.as_mut() { - // PORT NOTE: Zig (font.zig:1029-1035) iterates `families.sliceConst()` - // by value while inserting into `families` mid-loop, then `break`s. - // In Rust the immutable slice borrow would alias the &mut needed for - // `insert` (and `insert` may reallocate, invalidating the iterator). - // Reshape: capture the system-ui index first, drop the borrow, then - // perform the inserts using the captured index. if let Some(i) = families.slice_const().iter().position(is_system_ui) { for (j, name) in DEFAULT_SYSTEM_FONTS.iter().enumerate() { // TODO(port): families.insert(arena, idx, val) — Vec::insert with arena diff --git a/src/css/properties/grid.rs b/src/css/properties/grid.rs index 7ea72033c05..0043b8c3077 100644 --- a/src/css/properties/grid.rs +++ b/src/css/properties/grid.rs @@ -17,10 +17,6 @@ pub enum TrackSizing { Tracklist(TrackList), } -/// A [``](https://drafts.csswg.org/css-grid-2/#typedef-track-list) value, -/// as used in the `grid-template-rows` and `grid-template-columns` properties. -/// -/// See [TrackSizing](TrackSizing). pub struct TrackList { /// A list of line names. pub line_names: Vec, @@ -400,11 +396,6 @@ fn parse_line_names(input: &mut Parser) -> css::Result { }) } -/// A [``](https://drafts.csswg.org/css-grid-2/#typedef-track-repeat) value, -/// used in the `repeat()` function. -/// -/// See [TrackRepeat](TrackRepeat). -// TODO(port): css.DeriveParse / css.DeriveToCss → #[derive(Parse, ToCss)] proc-macro #[derive(PartialEq, Eq)] pub enum RepeatCount { /// The number of times to repeat. @@ -472,10 +463,6 @@ impl GridTemplateAreas { let mut row: u32 = 0; let mut columns: u32 = 0; - // PORT NOTE: `expect_string` returns a slice borrowing `&mut self`, which - // `try_parse`'s `R` type param can't carry. Erase the lifetime through a - // raw pointer inside the closure; the slice lives in the input arena and - // outlives this parse. if let Ok(s) = input.try_parse(|i| i.expect_string().map(std::ptr::from_ref::<[u8]>)) { // SAFETY: `s` points to a slice returned by `expect_string`, which is backed by the // parser's input arena and remains valid for the duration of this parse. diff --git a/src/css/properties/margin_padding.rs b/src/css/properties/margin_padding.rs index d335107cb74..d54132a5fb2 100644 --- a/src/css/properties/margin_padding.rs +++ b/src/css/properties/margin_padding.rs @@ -6,14 +6,6 @@ use crate::properties::{Property, PropertyId, PropertyIdTag}; use crate::{DeclarationList, PropertyHandlerContext}; use bun_alloc::ArenaVecExt as _; -// `RectShorthand`/`SizeShorthand` mirror Zig's `css.DefineRectShorthand` / -// `css.DefineSizeShorthand` comptime mixins. The marker traits stay (some -// callers name `::Value`). The rect-shorthand structs -// below are stamped out by `define_rect_shorthand!` (struct + PROPERTY_FIELD_MAP -// + deep_clone/eql + parse/to_css + RectShorthand impl); the size-shorthand -// structs keep hand-written bodies and get parse/to_css from -// `impl_size_shorthand!`. Both macros live in the parent `properties/mod.rs` -// (shared with `border.rs`). pub trait RectShorthand { type Value; } @@ -67,25 +59,6 @@ impl_size_shorthand!( inline_end ); -// ────────────────────────────────────────────────────────────────────────── -// Shorthand value types -// ────────────────────────────────────────────────────────────────────────── -// -// Zig used `css.DefineRectShorthand(@This(), V)` / `css.DefineSizeShorthand(@This(), V)` -// as comptime mixins that inject `parse` + `toCss`. In Rust those become trait -// impls (`RectShorthand` / `SizeShorthand`) that provide default `parse`/`to_css`. -// The trait comes first (PORTING.md §Comptime reflection); a `#[derive]` could -// replace the manual impls. -// -// `implementDeepClone` / `implementEql` are field-wise reflection helpers → -// `#[derive(Clone, PartialEq)]`; the `DeepClone`/`CssEql` trait impls are -// bridged via `bridge_clone_partialeq!` in `generics.rs`. -// -// `PropertyFieldMap` (an anonymous struct mapping field-name → PropertyIdTag) -// becomes an associated const slice; consumers that did `@field(map, name)` -// will look up by name. // TODO(port): if consumers need O(1) by-field access, -// switch to per-type associated consts. - define_rect_shorthand! { /// A value for the [inset](https://drafts.csswg.org/css-logical/#propdef-inset) shorthand property. Inset, LengthPercentageOrAuto, @@ -360,26 +333,6 @@ pub type PaddingHandler = SizeHandler; pub type ScrollMarginHandler = SizeHandler; pub type InsetHandler = SizeHandler; -// ────────────────────────────────────────────────────────────────────────── -// NewSizeHandler — Zig `fn(comptime ...) type { return struct { ... } }` -// ────────────────────────────────────────────────────────────────────────── -// -// The Zig generator took 11 `comptime PropertyIdTag` parameters, a -// `comptime PropertyCategory`, and an optional `{feature, shorthand_feature}` -// pair, and used `@field` / `@tagName` / `@unionInit` to project in/out of -// the `Property` tagged union by tag name at compile time. -// -// Rust cannot reflect on enum variants by `PropertyIdTag` value, so the -// per-variant projection is moved into a `SizeHandlerSpec` trait. The -// generic body (`handle_property` / `flush` / helpers) is preserved 1:1 and -// calls through `S::*`. Each concrete handler is a zero-sized marker type -// implementing the spec. -// -// TODO(port): a `macro_rules! size_handler_spec!` could generate the four -// `SizeHandlerSpec` impls from the same 13-argument table the Zig used, -// eliminating the per-spec extract/construct boilerplate. Left explicit for -// reviewability. - /// Selector for the four physical slots on `SizeHandler` (Zig used a /// `comptime field: []const u8` and `@field(this, field)`). #[derive(Copy, Clone)] @@ -399,11 +352,6 @@ enum LogicalSlot { InlineEnd, } -/// Compile-time configuration for one `SizeHandler` instantiation. -/// -/// Replaces the 13 `comptime` parameters of Zig's `NewSizeHandler` and the -/// `@field(property, @tagName(X_prop))` / `@unionInit(Property, @tagName(X_prop), v)` -/// reflection it performed. pub trait SizeHandlerSpec { // ---- comptime tag parameters ---- const TOP: PropertyIdTag; @@ -430,12 +378,6 @@ pub trait SizeHandlerSpec { /// `shorthand_extra.?.shorthand_feature`. const SHORTHAND_FEATURE: Option; - // ---- value-type bindings (Zig: `X_prop.valueType()`) ---- - // In every instantiation in this file the longhand value type is - // `LengthPercentageOrAuto`, so the generic body below uses that - // concretely. If a future spec needs a different `valueType()`, lift it - // to an associated type here. - /// Zig: `shorthand_prop.valueType()` (the 4-field rect struct). type Shorthand; /// Zig: `block_shorthand.valueType()` (the 2-field block struct). @@ -443,13 +385,6 @@ pub trait SizeHandlerSpec { /// Zig: `inline_shorthand.valueType()` (the 2-field inline struct). type InlineShorthand; - // ---- @field / @unionInit replacements ---- - // Each pair is the Rust spelling of: - // `@field(property, @tagName(X_prop))` → extract_x - // `@unionInit(Property, @tagName(X_prop), v)` → make_x - // TODO(port): these are pure mechanical pattern-matches over `Property`; - // could generate via macro. - fn extract_top(p: &Property) -> &LengthPercentageOrAuto; fn extract_bottom(p: &Property) -> &LengthPercentageOrAuto; fn extract_left(p: &Property) -> &LengthPercentageOrAuto; @@ -574,10 +509,6 @@ impl SizeHandler { dest: &mut DeclarationList, context: &mut PropertyHandlerContext, ) -> bool { - // Zig: `switch (@as(PropertyIdTag, property.*))` — the *raw* union - // discriminant, ported as `Property::variant_tag()`. The `.unparsed` - // arm needs the inner `property_id` to decide whether the unparsed - // value belongs to this handler, so it stays a structural match. if let Property::Unparsed(unparsed) = property { let id = unparsed.property_id.tag(); if id == S::TOP @@ -830,11 +761,6 @@ impl SizeHandler { self.flush(dest, context); } - // PORT NOTE: reshaped — Zig's single `flushHelper` (generic over `comptime field: []const u8` - // via `@field(this, field)`) is split into `flush_helper_physical` + `flush_helper_logical` - // because the physical slots hold `Option` and the logical slots hold - // `Option`; Rust cannot express `@field` over heterogeneous Option payloads generically. - /// Zig `flushHelper` for the four physical slots (`top`/`bottom`/`left`/`right`). fn flush_helper_physical( &mut self, @@ -844,10 +770,6 @@ impl SizeHandler { dest: &mut DeclarationList, context: &mut PropertyHandlerContext, ) { - // PERF(port): `category` was comptime monomorphization — profile if hot. - // If the category changes betweet logical and physical, - // or if the value contains syntax that isn't supported across all targets, - // preserve the previous value as a fallback. if category != self.category || (self.physical_slot_is_some(field) && context.targets.browsers.is_some() @@ -866,10 +788,6 @@ impl SizeHandler { dest: &mut DeclarationList, context: &mut PropertyHandlerContext, ) { - // PERF(port): `category` was comptime monomorphization — profile if hot. - // If the category changes betweet logical and physical, - // or if the value contains syntax that isn't supported across all targets, - // preserve the previous value as a fallback. if category != self.category || (self.logical_slot_is_some(field) && context.targets.browsers.is_some() @@ -1111,10 +1029,6 @@ impl SizeHandler { LogicalSidePair::Inline => (S::INLINE_START, S::INLINE_END), }; - // Zig: `@as(PropertyIdTag, start.*.?) == start_prop` — raw - // discriminant. `variant_tag()` keeps `Unparsed` distinct so an - // unparsed longhand falls through to the else branch and is appended - // as-is, instead of hitting `unreachable!()` in `extract_*`. if start .as_ref() .map(|p| p.variant_tag() == start_prop) @@ -1184,16 +1098,6 @@ enum LogicalSidePair { Inline, } -// ────────────────────────────────────────────────────────────────────────── -// Spec instantiations -// ────────────────────────────────────────────────────────────────────────── -// -// PORT NOTE: the `extract_*` / `make_*` / `shorthand_*` bodies are pure -// `@field` / `@unionInit` token-pasting in Zig (`NewSizeHandler`). -// `size_handler_spec_projections!` expands them from the 11 `Property` -// variant idents + 3 shorthand value-type idents that the Zig -// `NewSizeHandler(...)` call sites passed positionally. - macro_rules! size_handler_spec_projections { ( $Top:ident, $Bottom:ident, $Left:ident, $Right:ident, diff --git a/src/css/properties/masking.rs b/src/css/properties/masking.rs index 964188074ab..7b142cef4e1 100644 --- a/src/css/properties/masking.rs +++ b/src/css/properties/masking.rs @@ -26,10 +26,6 @@ use crate::generics::{CssEql, DeepClone}; use crate::properties::PropertyId; use crate::properties::PropertyIdTag; -/// A [``](https://www.w3.org/TR/css-masking-1/#typedef-geometry-box) value -/// as used in the `mask-clip` and `clip-path` properties. -// TODO(port): css.DefineEnumProperty(@This()) — comptime-generated eql/hash/parse/toCss/deepClone. -// In Rust this becomes #[derive] of the css enum-property protocol (kebab-case serialization). #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, css::Parse, css::ToCss)] pub enum GeometryBox { /// The painted content is clipped to the content box. @@ -106,11 +102,6 @@ pub struct Ellipse { pub struct Polygon { /// The fill rule used to determine the interior of the polygon. pub fill_rule: FillRule, - /// The points of each vertex of the polygon. - // TODO(port): css is an AST crate (§Allocators) — if Polygon is arena-fed this must become - // `bun_alloc::ArenaVec<'bump, Point>` and Polygon/BasicShape/ClipPath gain `<'bump>`. - // No construction site exists in src/css/*.zig today, so provenance is unconfirmed; keeping - // plain Vec until the arena story is verified. pub points: Vec, } @@ -466,15 +457,6 @@ impl MaskBorder { // deepClone → #[derive(Clone)] } -/// A value for the [-webkit-mask-composite](https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-mask-composite) -/// property. -/// -/// See also [MaskComposite](MaskComposite). -/// A value for the [-webkit-mask-composite](https://developer.mozilla.org/en-US/docs/Web/CSS/-webkit-mask-composite) -/// property. -/// -/// See also [MaskComposite](MaskComposite). -// TODO(port): css.DefineEnumProperty(@This()) → derive css enum-property protocol #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, css::Parse, css::ToCss)] pub enum WebKitMaskComposite { #[css(name = "clear")] @@ -505,15 +487,6 @@ pub enum WebKitMaskComposite { Xor, } -/// A value for the [-webkit-mask-source-type](https://github.com/WebKit/WebKit/blob/6eece09a1c31e47489811edd003d1e36910e9fd3/Source/WebCore/css/CSSProperties.json#L6578-L6587) -/// property. -/// -/// See also [MaskMode](MaskMode). -/// A value for the [-webkit-mask-source-type](https://github.com/WebKit/WebKit/blob/6eece09a1c31e47489811edd003d1e36910e9fd3/Source/WebCore/css/CSSProperties.json#L6578-L6587) -/// property. -/// -/// See also [MaskMode](MaskMode). -// TODO(port): css.DefineEnumProperty(@This()) → derive css enum-property protocol #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, css::Parse, css::ToCss)] pub enum WebKitMaskSourceType { /// Equivalent to `match-source` in the standard `mask-mode` syntax. diff --git a/src/css/properties/mod.rs b/src/css/properties/mod.rs index 85ee46adb5f..7ed82632ec1 100644 --- a/src/css/properties/mod.rs +++ b/src/css/properties/mod.rs @@ -4,48 +4,6 @@ #![warn(unused_must_use)] -// `properties_generated.rs` carries the 249-variant `Property` / -// `PropertyId` / `PropertyIdTag` enums referenced by `declaration.rs`, -// `context.rs`, and `rules/`. Every *value type* the `Property` enum names is -// re-exposed below via `pub mod $name`. When a leaf .rs file un-gates, its -// real type replaces the stub transparently (same path, same name). -// -// `prefixes::Feature` and the entire `values/` lattice are real, so -// `PropertyId::set_prefixes_for_targets` / `from_name_and_prefix` and the -// `Property` payloads that name `css_values::*` resolve directly. - -/// Declares a property-handler ZST with the `handle_property` / `finalize` -/// surface that `DeclarationHandler` (declaration.rs) composes over. The -/// real handler bodies live in the gated leaf .rs files; until those -/// un-gate, these no-op stubs keep `DeclarationHandler` compiling against -/// the now-real `Property` enum. -/// -/// PORT NOTE: Zig handlers are plain structs with `handleProperty(*Self, -/// *const Property, *DeclarationList, *PropertyHandlerContext) bool` + -/// `finalize(*Self, *DeclarationList, *PropertyHandlerContext) void`. Same -/// shape here; lifetimes on `DeclarationList<'bump>` / context are erased -/// behind anonymous lifetimes since the stub bodies touch neither. - -// ─── Rect / Size shorthand impl + define macros ──────────────────────────── -// Shared by `border.rs` and `margin_padding.rs`. These are the Rust port of -// Zig's `css.DefineRectShorthand` / `css.DefineSizeShorthand` comptime mixins -// (src/css/css_parser.zig:502 / :532). -// -// `impl_rect_shorthand!` / `impl_size_shorthand!` stamp out the inherent -// `parse`/`to_css` pair (and the `generic::{Parse,ToCss}` forwarders) for a -// pre-existing struct. Rust has no field reflection, so the size-shorthand -// field names are passed in (`start`/`end` for border, `block_start`/… for -// margin_padding). -// -// `define_rect_shorthand!` is the full mixin: it emits the struct -// `{top,right,bottom,left}`, `PROPERTY_FIELD_MAP`, `deep_clone`/`eql`, the -// `RectShorthand` marker impl, *and* calls `impl_rect_shorthand!`. Used by -// the 8 rect-shorthand value types in border.rs / margin_padding.rs so the -// boilerplate isn't hand-copied per type. -// -// Declared here (textually before `pub mod border;` / `pub mod -// margin_padding;`) so macro_rules! scoping makes them visible in both -// submodules without `#[macro_export]`. macro_rules! impl_rect_shorthand { ($T:ident, $V:ty) => { impl $T { @@ -143,18 +101,9 @@ macro_rules! impl_size_shorthand { // (Zig: `pub const X = @import("./X.zig");`) // pub mod align; -// `animation`: un-gated — real AnimationName / Animation / AnimationIterationCount / -// AnimationDirection / AnimationPlayState / AnimationFillMode / AnimationTimeline / -// Scroller / ScrollAxis / ViewTimeline / AnimationRangeStart / AnimationRangeEnd / -// AnimationRange / TimelineRangeName / AnimationComposition / AnimationHandler -// live in `animation.rs`. pub mod animation; pub mod background; pub mod border; -// `border_image`: un-gated — real BorderImage / BorderImageSlice / -// BorderImageSideWidth / BorderImageRepeat / BorderImageHandler live in -// `border_image.rs`. parse/to_css for BorderImageSideWidth remain internally -// gated on the DeriveParse/DeriveToCss proc-macros. pub mod border_image; // `border_radius`: un-gated — real BorderRadius + BorderRadiusHandler // (handle_property/finalize bodies) live in `border_radius.rs`. @@ -165,11 +114,6 @@ pub mod box_shadow; pub mod display; pub mod effects; pub mod flex; -// `font`: un-gated — real data types (FontWeight / FontSize / FontStretch / -// FontFamily / FontStyle / FontVariantCaps / LineHeight / Font / FontHandler) -// live in `font.rs`. parse/to_css/handle_property bodies remain internally -// ``-gated there until DeriveParse/DeriveToCss proc-macros + -// EnumProperty derive land. pub mod font; pub mod grid; // `list`: un-gated — real ListStyleType / CounterStyle / Symbols / Symbol @@ -192,18 +136,8 @@ pub mod transform; pub mod transition; pub mod ui; -// `css_modules`: un-gated — real `Composes` payload (names/from/loc/ -// cssparser_loc) + `Specifier` enum (Global/ImportRecordIndex) live in -// `css_modules.rs`. `Composes::to_css` stays internally ``-gated -// on `CustomIdent::to_css` (Printer::write_ident). pub mod css_modules; -// `custom`: un-gated — real data types (TokenList / TokenOrValue / -// CustomProperty / CustomPropertyName / UnparsedProperty / EnvironmentVariable -// / Variable / Function / UnresolvedColor / UAEnvironmentVariable) live in -// `custom.rs`. parse/to_css/deep_clone/eql/hash bodies remain internally -// ``-gated there until their leaf deps (ident/url/color/ -// generics) un-gate. pub mod custom; pub mod properties_generated; @@ -214,10 +148,6 @@ mod properties_impl; pub use self::custom::CustomPropertyName; pub use self::properties_generated::{Property, PropertyId, PropertyIdTag}; -/// A [CSS-wide keyword](https://drafts.csswg.org/css-cascade-5/#defaulting-keywords). -// Zig: `css.DefineEnumProperty(@This())` provides eql/hash/parse/toCss/deepClone via -// comptime reflection over @tagName. The Rust derive emits `EnumProperty` + -// `From for &'static str` + inherent `parse`/`to_css`. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, crate::DefineEnumProperty)] pub enum CSSWideKeyword { /// The property's initial value. @@ -232,15 +162,6 @@ pub enum CSSWideKeyword { RevertLayer, } -// ─── generic::{Parse,ToCss,ParseWithOptions} leaf-type registrations ─────── -// `Property::parse` / `Property::value_to_css` (properties_generated.rs) -// dispatch through `css::generic::{parse_with_options,to_css}`, which require -// every payload type to implement the protocol traits in `crate::generics`. -// Each leaf already has inherent `parse` / `to_css` (hand-written or via -// `#[derive(Parse, ToCss)]` / `#[derive(DefineEnumProperty)]`); the -// `impl_parse_tocss_via_inherent!` macro forwards to those. Shorthand families that -// generate their own impls inside their declaring macro (border rect/size, -// margin_padding rect/size) are not re-listed here. mod generic_registrations { use super::*; use crate::css_values; @@ -296,11 +217,6 @@ mod generic_registrations { } } - // ── crate::properties::* leaves with REAL inherent parse/to_css ── - // NOTE: types deriving `css::DefineEnumProperty` / `Parse` / `ToCss` already - // get `generics::{Parse,ParseWithOptions,ToCss}` from the derive — listing - // them here would conflict (E0119). Only payloads with hand-written - // inherent `parse`/`to_css` (no derive) need the forwarding shim. impl_parse_tocss_via_inherent!( // align align::Gap, @@ -414,11 +330,4 @@ mod generic_registrations { } pub(crate) use generic_registrations::GenericBorderImpl; -// ─── Dead code (not ported) ──────────────────────────────────────────────── -// The original Zig file contains ~1800 lines of commented-out code (lines 60–1876) -// implementing the old `DefineProperties(...)` comptime-reflection approach that -// predates `properties_generated.zig`. It is dead reference material and is -// intentionally omitted here. See `src/css/properties/properties.zig` for the -// historical block; the live definitions come from `properties_generated`. - // ported from: src/css/properties/properties.zig diff --git a/src/css/properties/outline.rs b/src/css/properties/outline.rs index 9f42cdc663a..b912a9a0d2e 100644 --- a/src/css/properties/outline.rs +++ b/src/css/properties/outline.rs @@ -3,10 +3,6 @@ use super::border::{GenericBorder, LineStyle}; /// A value for the [outline](https://drafts.csswg.org/css-ui/#outline) shorthand property. pub(crate) type Outline = GenericBorder; -/// A value for the [outline-style](https://drafts.csswg.org/css-ui/#outline-style) property. -// `DeriveParse`/`DeriveToCss` in Zig are comptime-reflection helpers that iterate variants -// to implement the domain protocol — in Rust the protocol is a trait and we derive it. -// `implementEql`/`implementDeepClone` are field-iteration eq/clone → `#[derive(PartialEq, Clone)]`. #[derive(Clone, PartialEq, Eq, crate::Parse, crate::ToCss)] pub enum OutlineStyle { /// The `auto` keyword. diff --git a/src/css/properties/overflow.rs b/src/css/properties/overflow.rs index f6613e7c245..d4506cb50fa 100644 --- a/src/css/properties/overflow.rs +++ b/src/css/properties/overflow.rs @@ -27,10 +27,6 @@ impl Overflow { } } -/// An [overflow](https://www.w3.org/TR/css-overflow-3/#overflow-properties) keyword -/// as used in the `overflow-x`, `overflow-y`, and `overflow` properties. -// PORT NOTE: css.DefineEnumProperty(@This()) — comptime mixin providing -// eql/hash/parse/to_css/deep_clone from @tagName. #[derive(Clone, Copy, PartialEq, Eq, Hash, crate::DefineEnumProperty)] pub enum OverflowKeyword { /// Overflowing content is visible. diff --git a/src/css/properties/prefix_handler.rs b/src/css/properties/prefix_handler.rs index e3bebada314..1585640d19b 100644 --- a/src/css/properties/prefix_handler.rs +++ b/src/css/properties/prefix_handler.rs @@ -9,13 +9,6 @@ use bun_alloc::ArenaVecExt as _; pub struct FallbackHandler { pub color: Option, pub text_shadow: Option, - // TODO: add these back plz - // filter: Option, - // backdrop_filter: Option, - // fill: Option, - // stroke: Option, - // caret_color: Option, - // caret: Option, } impl FallbackHandler { @@ -25,22 +18,8 @@ impl FallbackHandler { dest: &mut css::DeclarationList, context: &mut css::PropertyHandlerContext, ) -> bool { - // The Zig source does `inline for (std.meta.fields(FallbackHandler))` and uses - // `@field` / `@unionInit` keyed on the field name. Rust has no field reflection, - // so we expand each (field, Property variant, has_vendor_prefix) pair via macro. - // TODO(port): proc-macro — if the field list grows, generate these arms from a - // single source of truth shared with `Property`/`PropertyIdTag`. - let arena = dest.bump(); - // PORT NOTE: Zig's `inline for` over `std.meta.fields(FallbackHandler)` dispatched - // each (field, Property variant) pair via a single generic body using `@field` / - // `@unionInit` + `css.generic.{deepClone,isCompatible,hasGetFallbacks}`. Rust has - // no field reflection and the generic-trait surface (`DeepClone`/`IsCompatible`/ - // `get_fallbacks` on `SmallList`) is still partially gated, so we - // expand each pair via a macro that takes per-type closures for those three ops. - // This keeps the *control flow* identical while letting each payload type use its - // own inherent methods until the trait lattice un-gates. macro_rules! handle_unprefixed { ( $self_field:ident, diff --git a/src/css/properties/properties_generated.rs b/src/css/properties/properties_generated.rs index 9777c271783..c041a00a1d4 100644 --- a/src/css/properties/properties_generated.rs +++ b/src/css/properties/properties_generated.rs @@ -370,10 +370,6 @@ impl PropertyIdTag { ) } - /// The caniuse `prefixes::Feature` that governs this property's vendor - /// prefixes, if one exists. Returns `None` for unprefixed properties *and* - /// for the 23 prefixed-but-unmapped legacy properties (`box-orient`, - /// `flex-pack`, `mask-box-image-*`, …) that have no `Feature` entry. pub const fn prefix_feature(self) -> Option { use PropertyIdTag as T; Some(match self { @@ -679,14 +675,6 @@ impl PropertyIdTag { } } -/// A known CSS property name + (for prefixable properties) the vendor -/// prefix it was parsed with. Variants without payload are unprefixed. -// -// PORT NOTE: do NOT `#[derive(PartialEq, Eq)]` here — the spec-correct -// equality (Zig `PropertyId.eql`, properties_generated.zig:9195) ignores the -// `Custom(CustomPropertyName)` payload and is hand-written below. A derived -// impl would (a) conflict (E0119) and (b) diverge by comparing custom-name -// bytes. #[derive(Debug, Clone, Copy)] pub enum PropertyId { BackgroundColor, @@ -940,15 +928,6 @@ pub enum PropertyId { Custom(CustomPropertyName), } -// PORT NOTE: Zig `PropertyId.eql()` (properties_generated.zig:9195) compares the -// tag, then *only* compares the payload when its type is `VendorPrefix` — for -// `.custom` (whose payload is `CustomPropertyName`) and all unit/void variants -// it returns `true` on tag match alone. A derived `PartialEq` would compare the -// `CustomPropertyName` bytes, diverging from the spec (observable in -// `rules/style.zig:isDuplicate`). `prefix()` already returns the `VendorPrefix` -// payload for the 65 prefixed variants and `VendorPrefix::empty()` for every -// other variant (including `Custom`/`All`/`Unparsed`), so `tag` + `prefix` -// equality is exactly the Zig semantics. impl PartialEq for PropertyId { #[inline] fn eq(&self, other: &Self) -> bool { @@ -1222,10 +1201,6 @@ impl PropertyId { } } - /// Returns the property name, without any vendor prefixes. - /// - /// Mirrors Zig `PropertyId.name()` (properties_generated.zig:7674), which is - /// literally `if (.custom) return custom.asStr(); return @tagName(this.*);`. pub fn name(&self) -> &[u8] { match self { PropertyId::Custom(c) => c.as_str(), @@ -3131,11 +3106,6 @@ impl PropertyId { } } -/// A parsed CSS declaration value, tagged by [`PropertyIdTag`]. Prefixed -/// properties carry `(value, VendorPrefix)`. -// PORT NOTE: no `#[derive(Clone)]` — several `css_values::*` payloads -// (Image, Size2D, Rect, SmallList) intentionally lack `Clone` and use -// `deep_clone(&Arena)` instead. `Property::deep_clone` is the public API. pub enum Property { BackgroundColor(css::css_values::color::CssColor), BackgroundImage(SmallList), @@ -6180,10 +6150,6 @@ impl Property { PropertyId::Unparsed => {} } - // If a value was unable to be parsed, treat as an unparsed property. - // This is different from a custom property, handled above, in that the property name is known - // and stored as an enum rather than a string. This lets property handlers more easily deal with it. - // Ideally we'd only do this if var() or env() references were seen, but err on the safe side for now. input.reset(&state); UnparsedProperty::parse(property_id, input, options).map(Property::Unparsed) } @@ -6192,26 +6158,9 @@ impl Property { properties_impl::property_mixin::to_css(self, dest, important) } - /// Returns the given longhand property for a shorthand. - /// - /// PORT NOTE: in Zig (`properties_generated.zig:7087-7160`) each arm - /// dispatches to `v.longhand(property_id)` where the per-type `longhand` - /// is provided by `DefineShorthand`, whose body is - /// `@compileError(todo_stuff.depth)`. Zig only instantiates - /// `Property.longhand` when referenced (it isn't), so the @compileError - /// never fires. Rust type-checks eagerly, so the per-arm dispatch is - /// routed through a no-op `lh!` that mirrors the Zig fallthrough - /// (`return null`) until the `DefineShorthand` derive is ported. There - /// are no callers. - // blocked_on: shorthand_handler_port — leaf shorthand types lack `.longhand()` (Zig body is `@compileError(todo_stuff.depth)`, .zig:7087-7160) pub fn longhand(&self, property_id: &PropertyId) -> Option { #[inline(always)] fn lh(_v: &T, _id: &PropertyId) -> Option { - // PORT NOTE: per-type `v.longhand(property_id)` is - // `@compileError(todo_stuff.depth)` in Zig and never instantiated. - // Trip in debug so callers can't accidentally rely on the - // always-`None` placeholder before `DefineShorthand::longhand` - // is ported. debug_assert!( false, "Property::longhand: per-type DefineShorthand::longhand not yet ported" @@ -7400,21 +7349,12 @@ impl Property { } } - /// We're going to have this empty for now since not every property has a deinit function. - /// It's not strictly necessary since all allocations are into an arena. - /// It's mostly intended as a performance optimization in the case where mimalloc arena is used, - /// since it can reclaim the memory and use it for subsequent allocations. - /// I haven't benchmarked that though, so I don't actually know how much faster it would actually make it. pub fn deinit(&mut self, arena: &bun_alloc::Arena) { let _ = self; let _ = arena; } } -// PORT NOTE: `declaration::placeholder_property()` (the moved-out slot -// sentinel in `DeclarationBlock::minify`) is `Property{ .all = .revert-layer }` -// in Zig. Expose it via `Default` so the un-gated stub branch in -// `declaration.rs` keeps compiling against the real enum. impl Default for Property { #[inline] fn default() -> Self { diff --git a/src/css/properties/properties_impl.rs b/src/css/properties/properties_impl.rs index aa1295dd1a5..218964123cc 100644 --- a/src/css/properties/properties_impl.rs +++ b/src/css/properties/properties_impl.rs @@ -7,19 +7,6 @@ use css::css_properties::CustomPropertyName; use css::css_properties::{Property, PropertyId, PropertyIdTag}; impl Property { - /// Returns the *raw* enum discriminant of this `Property` as a - /// [`PropertyIdTag`]. - /// - /// Unlike [`Property::property_id`], this does **not** look through - /// `Property::Unparsed` to the wrapped `UnparsedProperty::property_id` — - /// an `Unparsed` declaration always returns `PropertyIdTag::Unparsed`, and - /// a `Custom` declaration always returns `PropertyIdTag::Custom`. - /// - /// This mirrors Zig's `@as(PropertyIdTag, property.*)` (a raw union-tag - /// coercion). Handlers that switch on the discriminant to project a parsed - /// payload — e.g. `SizeHandler` in `margin_padding.rs` — must use this so - /// an unparsed `margin-top: var(--x)` does not route into the parsed - /// `MarginTop` arm and panic in `extract_top`. #[inline] pub fn variant_tag(&self) -> PropertyIdTag { match self { @@ -32,10 +19,6 @@ impl Property { } } -/// Ordered single-bit prefix flags for the `inline for (VendorPrefix.FIELDS)` -/// Zig idiom. The crate-root `VendorPrefix::FIELDS` is a `&[&str]` name list; -/// the to_css loops here need the bitflag values directly, in Zig declaration -/// order (webkit, moz, ms, o, none). pub(super) const PREFIX_FLAGS: [VendorPrefix; 5] = [ VendorPrefix::WEBKIT, VendorPrefix::MOZ, diff --git a/src/css/properties/shape.rs b/src/css/properties/shape.rs index 17615dce199..eed45cd4bd3 100644 --- a/src/css/properties/shape.rs +++ b/src/css/properties/shape.rs @@ -1,18 +1,7 @@ pub use crate::css_parser as css; -/// A [``](https://www.w3.org/TR/css-shapes-1/#typedef-fill-rule) used to -/// determine the interior of a `polygon()` shape. -/// -/// See [Polygon](Polygon). -// TODO(port): Zig source is `css.DefineEnumProperty(@compileError(css.todo_stuff.depth))` — -// a placeholder that compile-errors on use. Left as an uninhabited stub until the -// CSS shapes module is actually implemented. pub enum FillRule {} -/// A CSS [``](https://www.w3.org/TR/css-color-4/#typedef-alpha-value), -/// used to represent opacity. -/// -/// Parses either a `` or ``, but is always stored and serialized as a number. pub struct AlphaValue { pub v: f32, } diff --git a/src/css/properties/size.rs b/src/css/properties/size.rs index 70fa24529e1..f7f560557dc 100644 --- a/src/css/properties/size.rs +++ b/src/css/properties/size.rs @@ -69,10 +69,6 @@ pub enum Size { Contain, } -/// Case-insensitive keyword dispatch for `Size`/`MaxSize` parse bodies. -/// PORT NOTE: Zig used `bun.ComptimeStringMap(..).getASCIIICaseInsensitive` — -/// expanded as an `if`-chain over `eql_case_insensitive_ascii::` (≤14 keys; -/// per PORTING.md a phf table is overkill at this size). macro_rules! size_ident_match { ($ident:expr, { $($lit:literal => $val:expr,)+ } else $err:expr) => {{ let __ident: &[u8] = $ident; @@ -453,14 +449,6 @@ pub struct SizeHandler { // arena is recovered via `dest.bump()`. use css::compat::Feature; -// ─── helper macros (Zig used `inline fn` + `comptime []const u8` field names + @field/@unionInit) ─── -// -// TODO(port): the following four macros replace Zig's `propertyHelper`, `logicalUnparsedHelper`, -// `flushPrefixHelper`, `flushPropertyHelper`, `flushLogicalHelper`. The Zig code passes field -// names as comptime strings and uses @field/@unionInit/@tagName to splice them into struct/enum -// accesses. Rust has no equivalent reflection — macro_rules! is the closest 1:1 mapping. -// PERF(port): was comptime monomorphization. - macro_rules! property_helper { ($this:expr, $field:ident, $ty:ty, $value:expr, $category:expr, $dest:expr, $context:expr) => {{ // If the category changes betweet logical and physical, diff --git a/src/css/properties/transform.rs b/src/css/properties/transform.rs index 0ddb169af13..cc88d081946 100644 --- a/src/css/properties/transform.rs +++ b/src/css/properties/transform.rs @@ -12,10 +12,6 @@ use crate::{ DeclarationList, Parser, PrintErr, Printer, PropertyHandlerContext, Result, Token, VendorPrefix, }; -/// A value for the [transform](https://www.w3.org/TR/2019/CR-css-transforms-1-20190214/#propdef-transform) property. -// PORT NOTE: was `BumpVec<'bump, Transform>`; downgraded to `Vec` so the -// `Property` enum (properties_generated.rs) stays lifetime-free. Re-thread -// `'bump` through `Property<'a>` crate-wide in a later pass (see line :1096). #[derive(Clone, PartialEq, Default)] pub struct TransformList { pub v: Vec, @@ -61,12 +57,6 @@ impl TransformList { return dest.write_str("none"); } - // TODO: Re-enable with a better solution - // See: https://github.com/parcel-bundler/lightningcss/issues/288 - // PORT NOTE: Zig's minify branch built a sub-`Printer` writing into a temp - // buffer then `dest.writeStr(base)` — observably identical to writing - // directly into `dest` while `dest.minify` is set (the original - // lightningcss size-comparison was already disabled upstream). Collapsed. self.to_css_base(dest) } @@ -1002,10 +992,6 @@ impl TransformHandler { let transform_val = &val.0; let vp = val.1; - // If two vendor prefixes for the same property have different - // values, we need to flush what we have immediately to preserve order. - // PORT NOTE: reshaped for borrowck — Zig held &self.transform across - // self.flush(); compute the predicate first, then act. let needs_flush = if let Some(current) = &self.transform { current.0 != *transform_val && !current.1.contains(vp) } else { diff --git a/src/css/properties/transition.rs b/src/css/properties/transition.rs index 10e61d8d012..96806208d14 100644 --- a/src/css/properties/transition.rs +++ b/src/css/properties/transition.rs @@ -119,11 +119,6 @@ pub struct TransitionHandler { pub has_any: bool, } -// PORT NOTE: Zig's `property`/`maybeFlush` took `comptime prop: []const u8` and used -// `@field(this, prop)` + `val: anytype` for comptime field dispatch. Rust has no -// `@field`, and passing both `&mut self` and `&mut self.` to a generic fn -// trips borrowck (because `flush` needs `&mut self`). Macros expand at the call -// site exactly like the Zig comptime dispatch did. macro_rules! handler_maybe_flush { ($this:expr, $dest:expr, $context:expr, $field:ident, $val:expr, $vp:expr) => {{ // If two vendor prefixes for the same property have different @@ -483,10 +478,6 @@ mod transition_handler_body { let mut cloned = false; let prefix_to_iter = property_id.prefix().or_none(); - // Expand vendor prefixes into multiple transitions. - // PORT NOTE: Zig used `inline for (VendorPrefix.FIELDS)` over packed-struct - // bool fields. With bitflags, iterate the individual flag bits. - // PERF(port): was comptime-unrolled inline-for — profile if it shows up on a hot path. for &prefix_flag in VendorPrefix::FIELDS { if prefix_to_iter.contains(prefix_flag) { let mut t = if cloned { diff --git a/src/css/rules/container.rs b/src/css/rules/container.rs index f97c5e1d3f4..f5efcb4ba86 100644 --- a/src/css/rules/container.rs +++ b/src/css/rules/container.rs @@ -65,10 +65,6 @@ pub enum ContainerSizeFeatureId { Orientation, } -// `QueryFeature` requires `FeatureId: FeatureIdTrait` at the type -// level, so this impl must be present for `ContainerSizeFeature` to resolve. -// `value_type` inlines the Zig `DeriveValueType` reflection; `to_css`/`from_str` -// delegate to `enum_property_util` (driven by the `EnumProperty` derive). impl crate::media_query::FeatureIdTrait for ContainerSizeFeatureId { // Zig: pub const valueType = css.DeriveValueType(@This(), ValueTypeMap).valueType; // PORT NOTE: DeriveValueType is comptime reflection over ValueTypeMap; expanded inline. diff --git a/src/css/rules/document.rs b/src/css/rules/document.rs index b2949793101..fdf4d758cb8 100644 --- a/src/css/rules/document.rs +++ b/src/css/rules/document.rs @@ -1,10 +1,6 @@ use crate::css_rules::{CssRuleList, Location}; use crate::{PrintErr, Printer}; -/// A [@-moz-document](https://www.w3.org/TR/2012/WD-css3-conditional-20120911/#at-document) rule. -/// -/// Note that only the `url-prefix()` function with no arguments is supported, and only the `-moz` prefix -/// is allowed since Firefox was the only browser that ever implemented this rule. pub struct MozDocumentRule { /// Nested rules within the `@-moz-document` rule. pub rules: CssRuleList, diff --git a/src/css/rules/font_face.rs b/src/css/rules/font_face.rs index f0200635845..b3cceeda897 100644 --- a/src/css/rules/font_face.rs +++ b/src/css/rules/font_face.rs @@ -12,15 +12,6 @@ use super::ArrayList; // FontFaceProperty // ────────────────────────────────────────────────────────────────────────── -/// A property within an `@font-face` rule. -/// -/// See [FontFaceRule](FontFaceRule). -// -// blocked_on: properties::font::{FontFamily,FontWeight,FontStretch} + -// properties::custom::CustomProperty (both `gated_prop!`-stubbed in -// properties/mod.rs). The enum body un-gates with the variant payloads -// once those leaves un-gate. - pub enum FontFaceProperty { /// The `src` property. Source(ArrayList), @@ -136,11 +127,6 @@ pub struct UnicodeRange { pub end: u32, } -// blocked_on: Printer::write_fmt, Parser::{expect_ident_matching,position, -// slice_from,next_including_whitespace,state,reset, -// new_basic_unexpected_token_error}, Token shape (Dimension/Number/Delim -// payloads), bun_core::{split_first,split_first_with_expected}. - impl UnicodeRange { pub(crate) fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { // Attempt to optimize the range to use question mark syntax. @@ -192,14 +178,6 @@ impl UnicodeRange { /// https://drafts.csswg.org/css-syntax/#urange-syntax pub(crate) fn parse(input: &mut css::Parser) -> css::Result { - // = - // u '+' '?'* | - // u '?'* | - // u '?'* | - // u | - // u | - // u '+' '?'+ - input.expect_ident_matching(b"u")?; let after_u = input.position(); Self::parse_tokens(input)?; @@ -483,11 +461,6 @@ impl FontFormat { // Source / FontTechnology / UrlSource // ────────────────────────────────────────────────────────────────────────── -/// A value for the [src](https://drafts.csswg.org/css-fonts/#src-desc) -/// property in an `@font-face` rule. -// -// blocked_on: properties::font::FontFamily (gated_prop!). - pub enum Source { /// A `url()` with optional format metadata. Url(UrlSource), @@ -539,14 +512,6 @@ impl Source { #[derive(Clone, Copy, PartialEq, Eq, css::DefineEnumProperty)] pub enum FontTechnology { - /// A font format keyword in the `format()` function of the - /// [src](https://drafts.csswg.org/css-fonts/#src-desc) - /// property of an `@font-face` rule. - /// A font features tech descriptor in the `tech()`function of the - /// [src](https://drafts.csswg.org/css-fonts/#font-features-tech-values) - /// property of an `@font-face` rule. - /// Supports OpenType Features. - /// https://docs.microsoft.com/en-us/typography/opentype/spec/featurelist FeaturesOpentype, /// Supports Apple Advanced Typography Font Features. /// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM09/AppendixF.html @@ -554,10 +519,6 @@ pub enum FontTechnology { /// Supports Graphite Table Format. /// https://scripts.sil.org/cms/scripts/render_download.php?site_id=nrsi&format=file&media_id=GraphiteBinaryFormat_3_0&filename=GraphiteBinaryFormat_3_0.pdf FeaturesGraphite, - /// A color font tech descriptor in the `tech()`function of the - /// [src](https://drafts.csswg.org/css-fonts/#src-desc) - /// property of an `@font-face` rule. - /// Supports the `COLR` v0 table. ColorColrv0, /// Supports the `COLR` v1 table. ColorColrv1, @@ -589,11 +550,6 @@ pub struct UrlSource { pub tech: ArrayList, } -// blocked_on: Url::{parse,to_css}, FontFormat::{parse,to_css}, -// FontTechnology::{parse,to_css}, Parser::{try_parse_with, -// expect_function_matching,parse_nested_block,parse_list}, -// css::{void_wrap,to_css::from_list}, DeepClone. - impl UrlSource { pub(crate) fn parse(input: &mut css::Parser) -> css::Result { let url = Url::parse(input)?; @@ -700,16 +656,6 @@ impl FontFaceRule { pub(crate) struct FontFaceDeclarationParser; -// PORT NOTE: Zig modeled `AtRuleParser` / `QualifiedRuleParser` / -// `DeclarationParser` / `RuleBodyItemParser` as nested namespaces with -// associated consts + fns. In Rust these are trait impls on -// `FontFaceDeclarationParser`. -// -// blocked_on: css::{AtRuleParser,QualifiedRuleParser,DeclarationParser, -// RuleBodyItemParser} trait signatures, properties::font::* + -// properties::custom::CustomProperty, Size2D::parse, Parser surface, -// FontFaceProperty enum body. - const _: () = { use crate::css_properties::custom::{CustomProperty, CustomPropertyName}; use crate::css_properties::font::{FontFamily, FontStretch, FontWeight}; diff --git a/src/css/rules/import.rs b/src/css/rules/import.rs index d25a5e8b81a..bffbf485ef4 100644 --- a/src/css/rules/import.rs +++ b/src/css/rules/import.rs @@ -8,10 +8,6 @@ use crate::{PrintErr, Printer}; use bun_alloc::Arena; use bun_ast::ImportRecord; -/// Named replacement for the Zig anonymous `struct { v: ?LayerName }` used in -/// both `ImportConditions.layer` and `ImportRule.layer`. The two Zig anonymous -/// structs are layout-identical (the code `@ptrCast`s between the parents), so -/// we use a single Rust type for both. #[repr(C)] #[derive(Default)] pub struct Layer { @@ -92,34 +88,6 @@ impl ImportConditions { Ok(()) } - /// This code does the same thing as `deepClone` right now, but might change in the future so keeping this separate. - /// - /// So this code is used when we wrap a CSS file in import conditions in the final output chunk: - /// ```css - /// @layer foo { - /// /* css file contents */ - /// } - /// ``` - /// - /// However, the *prelude* of the condition /could/ contain a URL token: - /// ```css - /// @supports (background-image: url('example.png')) { - /// /* css file contents */ - /// } - /// ``` - /// - /// In this case, the URL token's import record actually belongs to the /parent/ of the current CSS file (the one who imported it). - /// Therefore, we need to copy this import record from the parent into the import record list of this current CSS file. - /// - /// In actuality, the css parser doesn't create an import record for URL tokens in `@supports` because that's pointless in the context of hte - /// @supports rule. - /// - /// Furthermore, a URL token is not valid in `@media` or `@layer` rules. - /// - /// But this could change in the future, so still keeping this function. - /// - // blocked_on: MediaList::clone_with_import_records (no impl yet on MediaList). - pub fn clone_with_import_records( &self, arena: &Arena, @@ -278,10 +246,6 @@ impl ImportRule { } pub fn deep_clone(&self, bump: &Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. `url: &'static [u8]` - // is an arena-owned slice → identity copy (generics.zig "const - // strings" rule); `media` routes through `dc::media_list` until - // `MediaList` gains an arena-aware `deep_clone`. Self { url: self.url, layer: self.layer.as_ref().map(|l| l.deep_clone(bump)), diff --git a/src/css/rules/keyframes.rs b/src/css/rules/keyframes.rs index 0f75617789d..5e38d46e520 100644 --- a/src/css/rules/keyframes.rs +++ b/src/css/rules/keyframes.rs @@ -12,11 +12,6 @@ use super::ArrayList; // KeyframesName // ────────────────────────────────────────────────────────────────────────── -/// ` = | ` -// PORT NOTE: Zig threaded the parser-input lifetime; this stores -// `&'static [u8]` per PORTING.md §AST crates and the rules/mod.rs -// `CssRule` lifetime-erasure note. -// TODO(refactor): re-thread `'bump` here. pub enum KeyframesName { /// `` of a `@keyframes` name. Ident(CustomIdent), @@ -181,10 +176,6 @@ impl KeyframeSelector { // blocked_on: css::derive_parse (DeriveParse comptime macro replacement). impl KeyframeSelector { - // Zig: `pub const parse = css.DeriveParse(@This()).parse;` - // PORT NOTE: `DeriveParse` is a comptime type-generator producing `parse` from - // variant introspection. Expanded by hand here: try the tuple variant - // (`Percentage`) first, then fall back to keyword idents (`from`/`to`). pub(crate) fn parse(input: &mut css::Parser) -> css::Result { if let Ok(p) = input.try_parse(Percentage::parse) { return Ok(KeyframeSelector::Percentage(p)); @@ -328,15 +319,6 @@ impl KeyframesRule { pub(crate) struct KeyframesListParser; -// PORT NOTE: in Zig these are nested `pub const DeclarationParser = struct { ... }` -// namespaces that the css parser duck-types via `@hasDecl`. In Rust they become -// trait impls on `KeyframesListParser`. -// -// blocked_on: css::{DeclarationParser, AtRuleParser, QualifiedRuleParser, -// RuleBodyItemParser} trait signatures (css_parser.rs round-5 surface), -// Parser::parse_comma_separated, DeclarationBlock::parse, ParserOptions::default -// arena threading. - const _: () = { use css::css_parser::{ AtRuleParser, DeclarationParser, QualifiedRuleParser, RuleBodyItemParser, diff --git a/src/css/rules/layer.rs b/src/css/rules/layer.rs index 023179aa803..2895c267078 100644 --- a/src/css/rules/layer.rs +++ b/src/css/rules/layer.rs @@ -12,10 +12,6 @@ use crate::{PrintErr, Printer, SmallList}; /// notation (`a.b.c`) creates sublayers. #[derive(Default)] pub struct LayerName { - // TODO(port): arena lifetime — Zig `[]const u8` segments borrow the parser - // arena. Thread `'bump` once `CssRuleList` re-gains its arena lifetime; - // until then segments are laundered through `&'static [u8]` like every - // other CSS slice in this crate. pub v: SmallList<&'static [u8], 1>, } @@ -39,10 +35,6 @@ impl PartialEq for LayerName { } impl Eq for LayerName {} -// PORT NOTE: trait `Clone` (not just inherent `deep_clone`) so the bundler's -// `Chunk::Layers::to_owned` can `deep_clone_with(|l| l.clone())`. Segments are -// arena-borrowed `&'static [u8]` (Copy), so this is the same shallow -// `SmallList` copy as `deep_clone` / `clone_with_import_records`. impl Clone for LayerName { fn clone(&self) -> Self { LayerName { v: self.v.clone() } @@ -51,15 +43,6 @@ impl Clone for LayerName { impl LayerName { pub fn clone_with_import_records(&self, bump: &Arena, _: &mut Vec) -> Self { - // `[]const u8` segments are arena-borrowed, not owned, so the Zig - // `deepClone` here was a shallow `SmallList` copy. No import records to - // rewrite — layer names contain no URLs. - // - // Allocate the segment-pointer slab from `bump` (not the global - // allocator): callers store the result in arena-owned `ImportConditions` - // / `BundlerCssRule` slabs whose elements are never `Drop`'d (the - // bundler `mem::forget`s / `set_len(0)`s them at chunk teardown), so a - // global heap spill here would leak. LayerName { v: SmallList::from_arena_iter(bump, self.v.slice().iter().copied()), } diff --git a/src/css/rules/media.rs b/src/css/rules/media.rs index 29ec17f97ae..0ba5346c51e 100644 --- a/src/css/rules/media.rs +++ b/src/css/rules/media.rs @@ -12,11 +12,6 @@ pub struct MediaRule { pub loc: Location, } -// ─── behavior bodies ────────────────────────────────────────────────────── -// PORT NOTE: `minify` lives in `rules/mod.rs` (hoisted next to `CssRuleList:: -// minify` so the dispatch can call it without re-exporting `MinifyContext` -// here). `to_css` un-gated this round — `MediaList::{always_matches,to_css}` -// and `CssRuleList::to_css` are both real now. impl MediaRule { pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { if dest.minify && self.query.always_matches() { diff --git a/src/css/rules/mod.rs b/src/css/rules/mod.rs index 5780f930222..4708bc6ea0e 100644 --- a/src/css/rules/mod.rs +++ b/src/css/rules/mod.rs @@ -31,20 +31,6 @@ pub mod tailwind; pub mod unknown; pub mod viewport; -// ─── CssRule / CssRuleList ───────────────────────────────────────────────── -// Zig: pub fn CssRule(comptime Rule: type) type { return union(enum) { ... } } -// -// PORT NOTE: the original port threaded a `'bump` arena lifetime through every -// rule (matching Zig's `ArrayListUnmanaged`-backed AST). That cascades into -// every leaf module signature; while those leaves are gated, `CssRule` is -// kept lifetime-free here (the gated bodies re-introduce `'bump` when they -// un-gate alongside `bumpalo::collections::Vec` storage). - -// ─── CssRule variant table ──────────────────────────────────────────────── -// Single source of truth for the 20 typed at-rule payloads. Adding a new -// at-rule = one line here; the enum variant + `to_css` arm + `deep_clone` -// arm are generated. `Unknown`/`Custom`/`Ignored` stay a fixed tail because -// their `to_css` arms are special-cased (see PORT NOTE on `Custom`). macro_rules! css_rule_variants { ( $( $(#[$doc:meta])* $Variant:ident($Payload:ty) ),+ $(,)? ) => { /// A single CSS rule (at-rule or style rule). @@ -63,36 +49,11 @@ macro_rules! css_rule_variants { match self { $( CssRule::$Variant(x) => x.to_css(dest), )+ CssRule::Unknown(x) => x.to_css(dest), - // Zig: `.custom => |x| x.toCss(dest) catch return dest.addFmtError()`. - // - // PORT NOTE (incomplete): the spec has TWO concrete `R` types — - // `DefaultAtRule` (whose `toCss` errors unconditionally) and - // `TailwindAtRule` (src/css/rules/tailwind.zig:14-19, used via - // `BundlerAtRule` when `ENABLE_TAILWIND_PARSING`), whose `toCss` - // SUCCEEDS and writes `@tailwind ;`. This arm therefore - // diverges from the spec for `R = TailwindAtRule`: it fails - // serialization where the spec round-trips. - // - // The correct port threads a `ToCss`-style bound (or per-`R` - // vtable) so `Custom(x)` dispatches to `x.to_css(dest)` and only - // maps the error path via `add_fmt_error()`. That bound cascades - // through every nested `CssRuleList` printer (media, supports, - // layer, document, nesting, starting_style, style, scope, - // container) — deferred to the patch that un-gates - // `BundlerAtRule = TailwindAtRule`. - // TODO(port): dispatch to `x.to_css(dest)` once `R: ToCss` (or - // equivalent) is threaded; current behavior is only spec-correct - // for `R = DefaultAtRule`. CssRule::Custom(_x) => Err(dest.add_fmt_error()), CssRule::Ignored => Ok(()), } } - /// Zig: `css.implementDeepClone(@This(), this, arena)` — variant-wise - /// dispatch to each leaf rule's `deep_clone`. Hand-written (not - /// `#[derive(DeepClone)]`) because the leaf payloads expose `deep_clone` - /// as **inherent** methods rather than `DeepClone` trait impls; - /// method-syntax dispatch here picks up either. pub fn deep_clone<'bump>(&self, bump: &'bump bun_alloc::Arena) -> Self where R: css::generics::DeepClone<'bump>, @@ -180,42 +141,9 @@ impl Default for CssRuleList { } } -// ─── leaf-rule to_css shims ──────────────────────────────────────────────── -// All leaf modules now own a real, un-gated `to_css` body; `CssRule::to_css` -// dispatches straight through. (Shim macro deleted — last entry was -// `StyleRule`, dropped once DeclarationBlock::to_css + selector serialize -// landed.) - -// ─── leaf-rule deep_clone ────────────────────────────────────────────────── -// Every leaf module now owns a real inherent `deep_clone` body — the field- -// wise / variant-wise port of `css.implementDeepClone`. `CssRule::deep_clone` -// (below) dispatches via method-syntax so it picks up the inherent impl. -// -// PORT NOTE: most leaf rules can't use `#[derive(DeepClone)]` directly yet -// because two field types still lack an arena-aware `deep_clone(&self, -// &Arena) -> Self`: `SelectorList` (selectors/parser.rs uses no-arg -// `deep_clone()`) and `Property` (properties_generated.rs — per-variant body -// gated on leaf_value_traits). `MediaList` / `QueryFeature` / -// `DeclarationBlock` now route to their real arena-aware impls. The leaf -// bodies hand-roll the field walk and route the remaining blocked fields -// through the `dc::*` passthroughs below. Once an upstream type grows its own -// `deep_clone(&self, &Arena)`, swap the `dc::foo(&x, bump)` call for -// `x.deep_clone(bump)` and delete the helper. pub(super) mod dc { use bun_alloc::Arena; - /// `DeclarationBlock::deep_clone` — real port body inlined here (the - /// canonical impl in declaration.rs is gated on `Property: DeepClone`). - /// Field-walk over both `DeclarationList`s, routing each `Property` - /// through `dc::property` so the only remaining bottleneck is the - /// per-variant `Property::deep_clone` body. - /// - /// PORT NOTE: threads the real `'bump` lifetime instead of fabricating - /// `'static` (PORTING.md §Forbidden: `unsafe { &*(p as *const _) }` to - /// extend a lifetime). Callers whose storage is still pinned to - /// `DeclarationBlock<'static>` must fix that storage type — the lie - /// belongs there, not here, and collapses when `CssRule<'bump, R>` - /// re-threads the arena lifetime. #[inline] pub(crate) fn decl_block<'bump>( this: &crate::DeclarationBlock<'bump>, @@ -261,12 +189,6 @@ pub(super) mod dc { decl_block(this, unsafe { arena_static(bump) }) } - /// Empty `DeclarationBlock<'static>` — Zig spec writes `css.DeclarationBlock{}`. - /// - /// Exists so call-sites that need an empty block (rules.zig:363 - /// `nested_rule.declarations = .{}`) route through ONE centralized - /// erasure helper. Delete with `decl_block_static` once - /// `CssRule<'bump, R>` re-threads the arena lifetime. #[inline] pub(crate) fn decl_block_empty_static(bump: &Arena) -> crate::DeclarationBlock<'static> { // SAFETY: `'bump`-erasure placeholder — see `arena_static`. @@ -336,19 +258,6 @@ pub(super) mod dc { } } -// `Location` is plain `Copy` data; the derive expands to field-wise -// `u32::deep_clone` (identity). Doubles as the in-tree smoke test that the -// `#[derive(DeepClone)]` proc-macro round-trips through a real CSS type. -// (The Zig `implementDeepClone` returns `this.*` for simple-copy types.) - -// ─── shared serialization helpers for leaf rules ────────────────────────── -// Several leaf-rule `to_css` bodies bottom out on helpers whose canonical -// homes are still ``-gated outside `rules/` (DeclarationBlock:: -// to_css_block, VendorPrefix::toCss, CustomIdent/DashedIdent ::toCss). The -// bodies are tiny and have no further blockers, so they're inlined here so the -// 12 leaf rules can serialize for real. Once the upstream gates drop, callers -// switch back and these are deleted. - /// Port of `DeclarationBlock.toCssBlock` (declaration.zig). The real impl is /// gated in `declaration.rs`; `Property::to_css` is un-gated so the body is /// trivially inlinable here. @@ -403,10 +312,6 @@ pub(super) fn vendor_prefix_to_css( } } -/// Port of `CustomIdentFns.toCss` → `Printer.writeIdent` with CSS-module -/// custom-ident scoping. Both `CustomIdent::to_css` and `Printer::write_ident` -/// are gated on the css_modules `Pattern::write` borrowck reshape; this is the -/// non-css-module tail (`serialize_identifier`) that both share. #[inline] pub(super) fn custom_ident_to_css( ident: &css::css_values::ident::CustomIdent, @@ -424,10 +329,6 @@ pub(super) fn custom_ident_to_css( dest.write_ident(v, enabled) } -/// Port of `DashedIdentFns.toCss` → `Printer.writeDashedIdent`. The real -/// printer method is gated on a borrowck reshape of the css-module pattern -/// closure; the non-css-module path (the only one any current rule reaches) -/// is `--` + `serialize_name(rest)`. #[inline] pub(super) fn dashed_ident_to_css( ident: &css::css_values::ident::DashedIdent, @@ -440,12 +341,6 @@ pub(super) fn dashed_ident_to_css( dest.serialize_name(&v[2..]) } -/// Shim: `MediaRule::minify` is gated in `media.rs` until that file's full -/// `to_css` body un-gates. Recurse into the nested list and report whether the -/// rule should be dropped. NOTE: `never_matches()` is a *drop condition*, not -/// merely an optimization — omitting it diverges output (e.g. `@media not all -/// { a{color:red} }` must be removed). `MediaList::never_matches` is un-gated, -/// so call it here to match the spec (`media.zig:19-23`). impl media::MediaRule { pub fn minify( &mut self, @@ -461,11 +356,6 @@ impl media::MediaRule { } impl CssRule { - /// Whether this rule is skipped while `Printer::skip_prefixed_nested_rules` - /// is set (a non-final vendor prefix pass of an ancestor style rule) and - /// emitted only in the ancestor's final pass: a style rule with its own - /// vendor prefixes overrides `Printer::vendor_prefix`, so its output is - /// identical in every ancestor pass. pub(crate) fn is_deferred_to_final_prefix_pass(&self) -> bool { match self { CssRule::Style(style) => !style.vendor_prefix.is_empty(), @@ -487,11 +377,6 @@ impl CssRuleList { continue; } - // While re-serializing nested rules for a non-final vendor prefix - // pass of an ancestor style rule, skip style rules that carry - // their own vendor prefixes: they override `dest.vendor_prefix`, - // so this pass would emit an exact duplicate of what the final - // pass emits. if dest.skip_prefixed_nested_rules && rule.is_deferred_to_final_prefix_pass() { continue; } @@ -553,11 +438,6 @@ impl CssRuleList { where R: for<'b> css::generics::DeepClone<'b>, { - // blocked_on (style arm only): StyleRule::{minify,is_compatible, - // update_prefix,hash_key,is_duplicate}, selector::{is_compatible, - // is_equivalent,Selector::from_component}, SelectorList::deep_clone, - // DeclarationBlock::deep_clone — all `` in their leaves. - let mut style_rules = StyleRuleKeyMap::default(); let mut rules: Vec> = Vec::new(); @@ -608,12 +488,6 @@ impl CssRuleList { } } CssRule::Container(cont) => { - // The condition-merge/dedup port is still pending, but the - // nested rules must be minified so the nesting-away - // selector expansion stays bounded by - // `MAX_SELECTOR_EXPANSION` — otherwise an at-rule between - // two nesting levels hides the inner levels from the cap - // and the printer expands them exponentially. cont.rules.minify(context, parent_is_unused)?; } CssRule::LayerBlock(lay) => { @@ -631,22 +505,14 @@ impl CssRuleList { doc.rules.minify(context, parent_is_unused)?; } CssRule::Style(_sty) => { - // The full `.style` arm (selector compat partitioning, - // merge-with-previous, logical/@supports expansion, - // dedup via StyleRuleKey, nested-rule split) bottoms - // out on the gated StyleRule behavior surface. Until - // that un-gates, fall through and keep the rule as-is. - - { - minify_style_arm( - rule, - &mut rules, - &mut style_rules, - context, - parent_is_unused, - )?; - break 'arm; - } + minify_style_arm( + rule, + &mut rules, + &mut style_rules, + context, + parent_is_unused, + )?; + break 'arm; } CssRule::CounterStyle(_) => { /* TODO(port): Zig fallthrough */ } CssRule::Scope(scpe) => { @@ -655,19 +521,6 @@ impl CssRuleList { scpe.rules.minify(context, parent_is_unused)?; } CssRule::Nesting(nst) => { - // See `Container` above. `@nest` wraps a single style - // rule whose own selectors also form a nesting level, so - // charge them against the cap and recurse into its nested - // rules with the multiplier bumped accordingly. - // - // Deliberately does NOT run `StyleRule::minify` on the - // wrapped rule: that would feed its declarations through - // the property handlers, which consume logical properties - // (staging LTR/RTL fallbacks in the handler context) that - // the `@nest` minify port does not yet drain — silently - // dropping the declaration. Leaving the declarations - // untouched preserves them verbatim, matching the - // pre-port behavior. nst.style.charge_selector_expansion(context)?; nst.style.minify_nested_rules(context, parent_is_unused)?; } @@ -684,11 +537,6 @@ impl CssRuleList { rules.push(core::mem::replace(rule, CssRule::Ignored)); moved_rule = true; - // Non-style rules act as a barrier for style-rule dedup — - // an intervening at-rule may change how declarations are - // interpreted, so identical selectors on either side aren't - // safely mergeable. - style_rules.clear(); } @@ -742,10 +590,6 @@ fn minify_style_arm css::generics::DeepClone<'b>>( && context.targets.should_compile_selectors() && !sty.is_compatible(context.targets) { - // The :is() selector accepts a forgiving selector list, so use that if possible. - // Note that :is() does not allow pseudo elements, so we need to check for that. - // In addition, :is() takes the highest specificity of its arguments, so if the selectors - // have different weights, we need to split them into separate rules as well. if context .targets .is_compatible(css::compat::Feature::IsSelector) @@ -909,20 +753,6 @@ fn minify_style_arm css::generics::DeepClone<'b>>( Ok(()) } -// ─── StyleRuleKey ────────────────────────────────────────────────────────── -/// A key to a `StyleRule` meant for use in a hash map for quickly detecting -/// duplicates. It stores an index into the live `rules` Vec plus a -/// pre-computed hash for fast lookups. -/// -/// PORT NOTE: the Zig spec (`rules.zig:StyleRuleKey`) additionally stores -/// `list: *const ArrayList(CssRule(R))` and dereferences it inside `eql()`. -/// That pattern is unsound in Rust under Stacked/Tree Borrows — keys persist -/// in the dedup map across iterations of `minify_style_arm`, and between -/// iterations the same `Vec` is written through fresh `&mut` reborrows -/// (`rules.push`, `rules[i] = Ignored`), invalidating any previously-derived -/// `*const Vec` provenance. Instead we keep only `(index, hash)` here and do -/// the equality check in `StyleRuleKeyMap::remove_duplicate`, which receives -/// the live `&[CssRule]` slice explicitly at the call site. #[derive(Clone, Copy)] pub(crate) struct StyleRuleKey { index: usize, @@ -939,13 +769,6 @@ impl StyleRuleKey { } } -/// Dedup table for [`StyleRuleKey`]s — the Rust-side equivalent of Zig's -/// `StyleRuleKey(R).HashMap(usize)`. -/// -/// Buckets keyed by the pre-computed `StyleRule::hash_key()` hold indices into -/// the caller's `rules` Vec; equality (`StyleRule::is_duplicate`) is evaluated -/// against an explicitly-passed `&[CssRule]` so we never smuggle a stale -/// raw pointer across `&mut rules` writes (see PORT NOTE on `StyleRuleKey`). #[derive(Default)] pub(crate) struct StyleRuleKeyMap { buckets: bun_collections::HashMap>, @@ -1077,30 +900,8 @@ pub struct StyleContext<'a> { pub parent: Option<&'a StyleContext<'a>>, } -/// Upper bound on the number of selectors that compiling nested rules away for -/// the configured targets may expand a stylesheet into. -/// -/// When the targets don't support CSS nesting (or a rule's selectors need to be -/// split for compatibility), every nesting level multiplies the parent -/// selector list into its nested rules. That expansion is exponential in the -/// nesting depth, so a few hundred bytes of adversarial input (e.g. 20+ levels -/// of two-selector rules) would otherwise balloon into gigabytes of cloned -/// rules and output. Real-world stylesheets stay far below this limit — 65,536 -/// expanded selectors already corresponds to megabytes of output — so exceeding -/// it is reported as a `selector_expansion_limit_exceeded` minify error -/// instead. pub const MAX_SELECTOR_EXPANSION: u32 = 65_536; -/// Per-stylesheet minification state threaded through `CssRuleList::minify` -/// and every leaf rule's `minify`. -/// -/// PORT NOTE: Zig carried `arena: std.mem.Allocator` for the AST arena; -/// here that is `&'a Arena` (bumpalo). All sub-allocations during minify go -/// through it so the whole transformed tree is bulk-freed with the arena. -// PORT NOTE: split lifetimes — `'bump` is the parser arena (long), `'a` is the -// per-minify borrow scope (short). `&'a mut DeclarationHandler<'a>` would force -// the handler borrow to outlive the arena (invariance via `bumpalo::Vec`), -// making `Stylesheet::minify`'s stack-local handlers unusable. pub struct MinifyContext<'a, 'bump> { /// Arena that owns the AST being minified (same arena it was parsed into). pub arena: &'bump bun_alloc::Arena, @@ -1108,11 +909,6 @@ pub struct MinifyContext<'a, 'bump> { pub handler: &'a mut css::DeclarationHandler<'bump>, pub important_handler: &'a mut css::DeclarationHandler<'bump>, pub handler_context: css::PropertyHandlerContext<'bump>, - /// Class/id names known to be unused (tree-shaking input). - // PORT NOTE: Zig `*const std.StringArrayHashMapUnmanaged(void)`. - // `selector::is_unused` currently borrows `&ArrayHashMap<&[u8], ()>`; the - // owning `MinifyOptions` stores `Box<[u8]>` keys — reconcile when - // `style.rs::minify` un-gates (single key type, `Borrow<[u8]>` lookup). pub unused_symbols: &'a bun_collections::ArrayHashMap, ()>, /// Pre-scanned `@custom-media` definitions, if the feature is enabled. pub custom_media: diff --git a/src/css/rules/page.rs b/src/css/rules/page.rs index 275c7e3b802..51bbce217b6 100644 --- a/src/css/rules/page.rs +++ b/src/css/rules/page.rs @@ -4,15 +4,7 @@ use crate::{DeclarationBlock, PrintErr, Printer}; use super::ArrayList; -/// A [page selector](https://www.w3.org/TR/css-page-3/#typedef-page-selector) -/// within a `@page` rule. -/// -/// Either a name or at least one pseudo class is required. pub struct PageSelector { - /// An optional named page type. - // PORT NOTE: arena-owned slice borrowed from parser input; `&'static` per - // PORTING.md §AST crates / rules/mod.rs lifetime-erasure note. - // TODO(port): re-thread `'bump`. pub name: Option<&'static [u8]>, /// A list of page pseudo classes. pub pseudo_classes: ArrayList, @@ -310,10 +302,6 @@ pub(crate) struct PageRuleParser<'a> { pub options: &'a css::ParserOptions<'a>, } -// PORT NOTE: Zig modeled DeclarationParser/AtRuleParser/QualifiedRuleParser/ -// RuleBodyItemParser as nested `pub const Foo = struct { ... }` namespaces with -// methods taking `*This`. In Rust these become trait impls on PageRuleParser; -// associated `pub const X = T` → `type X = T`. const _: () = { use css::css_parser::{ AtRuleParser, DeclarationParser, QualifiedRuleParser, RuleBodyItemParser, diff --git a/src/css/rules/property.rs b/src/css/rules/property.rs index 8388e17eb41..c5a22e9c0a5 100644 --- a/src/css/rules/property.rs +++ b/src/css/rules/property.rs @@ -61,12 +61,6 @@ impl PropertyRule { impl PropertyRule { pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: `css.implementDeepClone` field-walk. `SyntaxString` has an - // inherent `deep_clone(&self, &Arena)`. While `ParsedComponent` is - // ``-gated to `()`, `Option<()>` is `Copy` → identity; - // once it un-gates, swap to `self.initial_value.as_ref().map(|v| - // v.deep_clone(bump))` (values/syntax.rs already provides the - // inherent impl). Self { name: self.name.deep_clone(bump), syntax: self.syntax.deep_clone(bump), diff --git a/src/css/rules/scope.rs b/src/css/rules/scope.rs index 63f9fc7d35b..53d637a018a 100644 --- a/src/css/rules/scope.rs +++ b/src/css/rules/scope.rs @@ -2,11 +2,6 @@ use crate::css_rules::{CssRuleList, Location}; use crate::selectors::SelectorList; use crate::{PrintErr, Printer}; -/// A [@scope](https://drafts.csswg.org/css-cascade-6/#scope-atrule) rule. -/// -/// @scope () [to ()]? { -/// -/// } pub struct ScopeRule { /// A selector list used to identify the scoping root(s). pub scope_start: Option, diff --git a/src/css/rules/style.rs b/src/css/rules/style.rs index 2197aa8c8c2..ab630c8fe17 100644 --- a/src/css/rules/style.rs +++ b/src/css/rules/style.rs @@ -5,12 +5,6 @@ use crate::error::MinifyErr; use crate::selectors::selector; use crate::{PrintErr, Printer, VendorPrefix}; -// `fn StyleRule(comptime R: type) type { return struct {...} }` → generic struct. -// -// PORT NOTE: `DeclarationBlock<'bump>` borrows the parser arena (bumpalo Vecs). -// Threading `'bump` here cascades into `CssRule<'bump, R>` / `CssRuleList<'bump, R>` -// (rules/mod.rs PORT NOTE) which is deferred until the leaf rules un-gate -// together; for now the lifetime is erased to `'static`. pub struct StyleRule { /// The selectors for the style rule. pub selectors: selector::parser::SelectorList, @@ -39,10 +33,6 @@ impl StyleRule { // std.hash.Wyhash.init(0) — same algorithm as bun.hash let mut hasher = bun_wyhash::Wyhash::init(0); self.selectors.hash(&mut hasher); - // PORT NOTE: `DeclarationBlock::hash_property_ids` is still - // ``-gated in declaration.rs; inline its body here. The - // Zig `PropertyId.hash` is `hasher.update(asBytes(&@intFromEnum(self)))` - // — i.e. just the u16 tag bytes. for decl in self.declarations.declarations.iter() { let tag = decl.property_id().tag() as u16; hasher.update(&tag.to_ne_bytes()); @@ -96,10 +86,6 @@ impl StyleRule { dest.vendor_prefix = prefix; let (line, col) = (dest.line, dest.col); self.to_css_base(dest, remaining_prefixes.is_empty())?; - // A non-final pass emits nothing when the rule has no - // declarations of its own and all of its nested rules are - // deferred to the final pass; don't write a separator - // after such a pass. if dest.line != line || dest.col != col { first_rule = false; } @@ -163,12 +149,6 @@ impl StyleRule { } if dest.css_module.is_some() { - // PORT NOTE: reshaped for borrowck — Zig - // `if (dest.css_module) |*css_module| - // css_module.handleComposes(dest, ...)` overlaps - // `&mut dest.css_module` with `&mut *dest`. Move the - // module out for the duration of the call, then put - // it back before any `dest.new_error` early return. let mut cm = dest.css_module.take(); let err = if let Some(css_module) = &mut cm { css_module @@ -233,13 +213,6 @@ impl StyleRule { self.rules.to_css(dest)?; helpers_end(dest, has_declarations)?; } else { - // This rule is serialized once per vendor prefix, and each pass - // re-serializes the nested rules. Nested style rules that carry - // their own vendor prefixes override `dest.vendor_prefix`, so they - // produce identical output in every pass; mark non-final passes so - // they are skipped and emitted only in the final pass. Otherwise - // they would be duplicated once per ancestor prefix, which grows - // exponentially with nesting depth. let saved_skip = dest.skip_prefixed_nested_rules; let skip_prefixed_nested = saved_skip || !is_final_prefix_pass; // Whether any nested rule is emitted in this pass; if not, don't @@ -279,12 +252,6 @@ impl StyleRule { use css::context::DeclarationContext; let mut unused = false; - // TODO(port): blocked_on key-type mismatch — `selector::is_unused` takes - // `&ArrayHashMap<&[u8], ()>` but `MinifyContext.unused_symbols` is - // `&ArrayHashMap, ()>` (rules/mod.rs PORT NOTE: "reconcile when - // style.rs::minify un-gates — single key type, Borrow<[u8]> lookup"). - // The reconciliation lives in rules/mod.rs + selectors/selector.rs, not - // here; gate the body until those agree. if context.unused_symbols.count() > 0 { if selector::is_unused( @@ -305,26 +272,7 @@ impl StyleRule { self.charge_selector_expansion(context)?; - // TODO: this - // let pure_css_modules = context.pure_css_modules; - // if context.pure_css_modules { - // if !self.selectors.0.iter().all(is_pure_css_modules_selector) { - // return Err(MinifyError { - // kind: crate::error::MinifyErrorKind::ImpureCSSModuleSelector, - // loc: self.loc, - // }); - // } - // - // // Parent rule contained id or class, so child rules don't need to. - // context.pure_css_modules = false; - // } - context.handler_context.context = DeclarationContext::StyleRule; - // PORT NOTE: `DeclarationBlock<'static>` (struct PORT NOTE above) forces - // `minify` to want `DeclarationHandler<'static>`; route through the - // single centralized `'bump`-erasure helper instead of open-coding the - // lifetime cast. Collapses when `CssRule<'bump, R>` - // re-threads the arena lifetime. self.declarations.minify( super::dc::decl_handler_static(&mut *context.handler), super::dc::decl_handler_static(&mut *context.important_handler), @@ -342,14 +290,6 @@ impl StyleRule { Ok(false) } - /// Charge this rule's selectors against the selector-expansion budget. - /// - /// Compiling the enclosing nesting away for the targets repeats this rule's - /// selectors once per combination of the enclosing style rules' selectors. - /// That expansion is multiplicative across nesting levels, so bound it — - /// otherwise a few hundred bytes of deeply nested multi-selector rules - /// expand into gigabytes of cloned rules and output. See - /// [`css_rules::MAX_SELECTOR_EXPANSION`](super::MAX_SELECTOR_EXPANSION). pub(crate) fn charge_selector_expansion( &self, context: &mut MinifyContext<'_, '_>, @@ -384,18 +324,6 @@ impl StyleRule { { use css::context::{DeclarationContext, PropertyHandlerContext}; - // When the targets require compiling nesting away (or splitting this - // rule's selectors for compatibility), each of this rule's selectors - // multiplies the expansion of every nested rule. - // - // Mirrors the selector-compatibility branch in `minify_style_arm` - // (rules/mod.rs): an incompatible selector list is either collapsed - // into a single `:is()` selector (nothing cloned) or partitioned - // into one cloned rule per selector (fan-out = selector count). - // Only the partition case multiplies on its own — but the `:is()` - // wrap keeps one `&` reference per original selector, so when - // nesting is compiled away the printed output still fans out per - // selector, which is why the nesting branch bumps unconditionally. let saved_expansion_multiplier = context.selector_expansion_multiplier; let selectors_incompatible = self.selectors.v.len() > 1 && context.targets.should_compile_selectors() @@ -471,10 +399,6 @@ impl StyleRule { where R: crate::generics::DeepClone<'bump>, { - // css is an AST crate (PORTING.md §Allocators): std.mem.Allocator → &'bump Bump, threaded. - // PORT NOTE: `css.implementDeepClone` field-walk. `declarations` routes - // through `dc::decl_block` until `DeclarationBlock::deep_clone` un-gates - // (declaration.rs — bottoms out on `Property: DeepClone`). Self { selectors: self.selectors.deep_clone(), vendor_prefix: self.vendor_prefix, diff --git a/src/css/rules/supports.rs b/src/css/rules/supports.rs index c4c5c7cf50f..c9fc350630a 100644 --- a/src/css/rules/supports.rs +++ b/src/css/rules/supports.rs @@ -5,11 +5,6 @@ use crate::properties::PropertyId; use crate::{PrintErr, Printer}; use bun_alloc::ArenaPtr; -/// A [``](https://drafts.csswg.org/css-conditional-3/#typedef-supports-condition), -/// as used in the `@supports` and `@import` rules. -// PORT NOTE: Zig threaded the parser-input lifetime (`[]const u8` slices borrow -// the source). Currently uses `&'static [u8]` per PORTING.md §AST crates; -// TODO(refactor): re-thread `'i` once `PropertyId<'i>` and the parser arena are real. pub enum SupportsCondition { /// A `not` expression. Not(Box), @@ -35,10 +30,6 @@ pub enum SupportsCondition { pub struct Declaration { /// The property id for the declaration. pub property_id: PropertyId, - /// The raw value of the declaration. - /// - /// What happens if the value is a URL? A URL in this context does nothing - /// e.g. `@supports (background-image: url('example.png'))` pub value: &'static [u8], } @@ -56,10 +47,6 @@ impl Declaration { impl Declaration { pub(crate) fn eql(&self, other: &Self) -> bool { - // PORT NOTE: Zig `css.implementEql` field-walk, hand-expanded. - // `PropertyId` carries its own tag+prefix `PartialEq` (see - // properties_generated.rs `impl PartialEq for PropertyId`); `value` is - // byte-slice equality. self.property_id == other.property_id && self.value == other.value } } @@ -74,10 +61,6 @@ impl SupportsCondition { } pub fn deep_clone(&self, bump: &bun_alloc::Arena) -> SupportsCondition { - // PORT NOTE: `css.implementDeepClone` variant-walk (hand-rolled — - // `#[derive(DeepClone)]` can't be used while `Selector`/`Unknown` - // carry `&'static [u8]`; the blanket `&'bump [u8]` impl doesn't unify - // with a fresh `'__bump`). let alloc = ArenaPtr::new(bump); match self { Self::Not(c) => Self::Not(Box::new_in(c.deep_clone(bump), alloc)), @@ -101,16 +84,7 @@ impl SupportsCondition { } impl SupportsCondition { - // blocked_on: generics::CssHash for PropertyId — `#[derive(CssHash)]` / - // `implement_hash` need every field type to provide `.hash(&mut Wyhash)`. - // `PropertyId` only impls `core::hash::Hash` today. TODO(refactor): add - // `impl CssHash for PropertyId` then swap to `#[derive(CssHash)]`. - pub fn hash(&self, hasher: &mut bun_wyhash::Wyhash) { - // PORT NOTE: Zig `css.implementHash` variant-walk, hand-expanded because - // `#[derive(CssHash)]` would require `PropertyId: CssHash` (it only - // provides `core::hash::Hash`). Semantics match the Zig reflection: - // hash the discriminant, then field-wise structural hash. use core::hash::{Hash, Hasher}; core::mem::discriminant(self).hash(hasher); match self { @@ -130,10 +104,6 @@ impl SupportsCondition { } pub fn eql(&self, other: &SupportsCondition) -> bool { - // PORT NOTE: Zig `css.implementEql` variant-walk, hand-expanded because - // `#[derive(CssEql)]` would require `PropertyId: CssEql` (it only - // provides the custom tag+prefix `PartialEq`). Semantics match the Zig - // reflection: tag mismatch → false, then field-wise structural eq. match (self, other) { (Self::Not(a), Self::Not(b)) => a.eql(b), (Self::And(a), Self::And(b)) | (Self::Or(a), Self::Or(b)) => { @@ -233,11 +203,6 @@ impl SupportsCondition { } let name = property_id.name(); - // PORT NOTE: `inline for (css.VendorPrefix.FIELDS) |field| { if @field(prefix, field) ... }` - // iterates the packed-struct bool fields at comptime. VendorPrefix ports to - // bitflags!; iterate the ordered single-bit table directly (same pattern as - // rules/style.rs). The Zig also builds `var p = VendorPrefix{}; @field(p, field) = true;` - // but never reads it — dead store dropped. dest.write_separated( css::VendorPrefix::FIELDS .iter() @@ -463,14 +428,6 @@ impl SupportsRule { where R: for<'b> crate::generics::DeepClone<'b>, { - // The condition-merge/dedup port is still pending, but the nested rules - // must be minified so compiling nesting away for the targets stays - // bounded by `MAX_SELECTOR_EXPANSION`. `@supports` preserves the `&` - // resolution context at print time (`to_css` below recurses into - // `self.rules` without clearing `dest.ctx`), so style rules nested - // behind it multiply against the enclosing nesting levels exactly like - // plain nested rules — leaving them unvisited here lets the printer - // expand them exponentially. self.rules.minify(context, parent_is_unused) } } diff --git a/src/css/rules/tailwind.rs b/src/css/rules/tailwind.rs index 78686113eb4..78a3b9b1e1a 100644 --- a/src/css/rules/tailwind.rs +++ b/src/css/rules/tailwind.rs @@ -39,11 +39,6 @@ pub enum TailwindStyleName { /// This injects Tailwind's utility classes and any utility classes registered /// by plugins. Utilities, - /// Use this directive to control where Tailwind injects the hover, focus, - /// responsive, dark mode, and other variants of each class. - /// - /// If omitted, Tailwind will append these classes to the very end of - /// your stylesheet by default. Variants, } diff --git a/src/css/rules/viewport.rs b/src/css/rules/viewport.rs index 445fe43d4dc..881e123994b 100644 --- a/src/css/rules/viewport.rs +++ b/src/css/rules/viewport.rs @@ -5,10 +5,6 @@ use crate::{DeclarationBlock, PrintErr, Printer, VendorPrefix}; pub struct ViewportRule { /// The vendor prefix for this rule, e.g. `@-ms-viewport`. pub vendor_prefix: VendorPrefix, - /// The declarations within the `@viewport` rule. - // PORT NOTE: `DeclarationBlock<'bump>` borrows the parser arena; lifetime - // erased to `'static` here per the rules/mod.rs `CssRule` PORT NOTE - // (the `'bump` arena lifetime is re-threaded crate-wide in one pass). pub declarations: DeclarationBlock<'static>, /// The location of the rule in the source file. pub loc: Location, diff --git a/src/css/selectors/builder.rs b/src/css/selectors/builder.rs index 35969127a18..ce8d4865d58 100644 --- a/src/css/selectors/builder.rs +++ b/src/css/selectors/builder.rs @@ -26,22 +26,7 @@ use crate::selector::parser::{ SpecificityAndFlags, compute_specificity, }; -/// Top-level SelectorBuilder struct. This should be stack-allocated by the -/// consumer and never moved (because it contains a lot of inline data that -/// would be slow to memmov). -/// -/// After instantiation, callers may call the push_simple_selector() and -/// push_combinator() methods to append selector data as it is encountered -/// (from left to right). Once the process is complete, callers should invoke -/// build(), which transforms the contents of the SelectorBuilder into a heap- -/// allocated Selector and leaves the builder in a drained state. pub struct SelectorBuilder { - /// The entire sequence of simple selectors, from left to right, without combinators. - /// - /// We make this large because the result of parsing a selector is fed into a new - /// Arc-ed allocation, so any spilled vec would be a wasted allocation. Also, - /// Components are large enough that we don't have much cache locality benefit - /// from reserving stack space for fewer of them. simple_selectors: SmallList, 32>, /// The combinators, and the length of the compound selector to their left. @@ -129,24 +114,9 @@ impl SelectorBuilder { if parsed_part { flags |= SelectorFlags::HAS_PART; } - // `build_with_specificity_and_flags()` will - // PORT NOTE: Zig had `defer this.deinit()` here to free SmallList capacity - // after building. In Rust, `Drop` on `SelectorBuilder` handles this when the - // builder goes out of scope; the call below already drains the contents. self.build_with_specificity_and_flags(SpecificityAndFlags { specificity, flags }) } - /// Builds a selector with the given specificity and flags. - /// - /// PERF: - /// Recall that this code is ported from servo, which optimizes for matching speed, so - /// the final AST has the components of the selector stored in reverse order, which is - /// optimized for matching. - /// - /// We don't really care about matching selectors, and storing the components in reverse - /// order requires additional allocations, and undoing the reversal when serializing the - /// selector. So we could just change this code to store the components in the same order - /// as the source. pub(crate) fn build_with_specificity_and_flags( &mut self, spec: SpecificityAndFlags, @@ -201,13 +171,6 @@ impl SelectorBuilder { } } - // This function should take every component from `self.simple_selectors` - // and place it into `components` and return it. - // - // This means that we shouldn't leak any `GenericComponent`, so - // it is safe to just set the length to 0. - // - // Combinators don't need to be deinitialized because they are simple enums. self.simple_selectors.set_len(0); self.combinators.set_len(0); diff --git a/src/css/selectors/mod.rs b/src/css/selectors/mod.rs index 4f5433deff0..2360d9878e0 100644 --- a/src/css/selectors/mod.rs +++ b/src/css/selectors/mod.rs @@ -25,11 +25,6 @@ pub mod selector; pub use parser::{Component, PseudoClass, PseudoElement, Selector, SelectorList}; -/// Our implementation of the `SelectorImpl` interface — the Rust-shaped -/// equivalent of Zig's `selector.impl.Selectors`. Defined in the hub (not in -/// `selector.rs`) to break the parser↔selector dependency cycle: `parser.rs` -/// needs `impl_::Selectors` to instantiate `Component`/`Selector`/ -/// `SelectorList`, and `selector.rs` needs those instantiations. pub mod impl_ { use crate::VendorPrefix; use crate::css_values::ident::{Ident, IdentOrRef}; diff --git a/src/css/selectors/parser.rs b/src/css/selectors/parser.rs index 7a52ff92866..e38f39bb7d5 100644 --- a/src/css/selectors/parser.rs +++ b/src/css/selectors/parser.rs @@ -23,29 +23,8 @@ pub use bun_css::Printer as PrinterRe; // re-export parity (Printer/PrintErr wer /// `css::Result` — the CSS parser result type (`Ok(T)` / `Err(css::ParseError)`). type CResult = css::Result; -// TODO(port): arena lifetimes. The Zig code threads `parser.arena` / `input.arena()` -// (a bump arena) through every allocation. The Rust port uses `Vec`/`Box` and a `Str` alias for -// source-borrowed byte slices; re-thread `'bump` and switch to -// `bun_alloc::ArenaVec<'bump, T>` / `&'bump [u8]` per PORTING.md §Allocators (AST crates). -// PERF(port): was arena bulk-free — profile if it shows up on a hot path. -// -// NOTE: `Str` is `&'static [u8]` here (not `crate::Str = *const [u8]`) to match -// `crate::Token`'s payload shape (`Token::Ident(&'static [u8])` etc.) — every -// `Str` in this module originates from a token slice. The `'static` is a -// placeholder for the tokenizer source lifetime; it widens to `&'bump -// [u8]` once `Parser<'bump>` threads the arena lifetime. type Str = &'static [u8]; // arena-backed `[]const u8` source slice -// ─── Protocol traits ───────────────────────────────────────────────────────── -// Zig's `implementEql` / `implementHash` / `implementDeepClone` are comptime -// field/variant reflection over `@typeInfo(T)` — in Rust this is the body of -// `#[derive(CssEql, CssHash, DeepClone)]` (`bun_css_derive`). Non-generic -// grammar types below carry the derive directly; the ``- -// generic types hand-write bodies (the derive's `where Impl: CssEql` bound is -// useless — equality recurses on `Impl::Assoc`, not `Impl`). -// -// `deep_clone` on the grammar types drops the `&Arena` parameter: `GenericSelector.components` -// is `Vec<_, ArenaPtr>` and clones into the *source* allocator (intra-arena only). use css::generics::{CssEql, CssHash}; /// Drain a `SmallList` into a `Box<[T]>`. `SmallList` has no `into_vec`; @@ -66,14 +45,6 @@ fn small_list_into_box(mut sl: SmallList) -> Box<[T]> { v.into_boxed_slice() } -/// Allocate an ASCII-lowercased copy of `name` in the parse-session bump arena. -/// Zig used `parser.arena().alloc(u8, n)` (the bump arena owns the buffer -/// for the parse session and frees it on arena reset). Returns a raw arena -/// pointer (`*const [u8]`) — `Ident.v`'s field type — so we don't fabricate a -/// `'static` lifetime (PORTING.md §Forbidden: never `Box::leak` to satisfy -/// `&'static`). Re-threading `&'bump Bump` would widen `Ident.v` to -/// `&'bump [u8]`. -// PERF(port): was arena alloc — profile if it shows up on a hot path. #[inline] fn arena_lowercase(bump: &Bump, name: &[u8]) -> *const [u8] { let buf = bump.alloc_slice_fill_copy(name.len(), 0u8); @@ -81,11 +52,6 @@ fn arena_lowercase(bump: &Bump, name: &[u8]) -> *const [u8] { std::ptr::from_ref::<[u8]>(buf) } -// ─── selector-slice protocol helpers ───────────────────────────────────────── -// `Box<[GenericSelector]>` appears in `Component::{Negation,Where,Is, -// Any,Has}`, `NthOfSelectorData`, and (via `SelectorList.v`) at the top level. -// Hoisted as free fns so the hand-written `eql`/`hash`/`deep_clone` bodies -// below stay small. #[inline] fn eql_selector_slice( a: &[GenericSelector], @@ -127,14 +93,6 @@ pub fn valid_selector_impl() { // bound `T: SelectorImpl` is the check. } -/// The `SelectorImpl` shape (Zig validated via `ValidSelectorImpl`). Implemented -/// by `impl_::Selectors` in `bun_css::selector::impl_`. -// PORT NOTE: `PartialEq + Clone` bounds dropped — the concrete assoc types -// (`values::ident::{Ident,IdentOrRef}`, `*const [u8]`) implement structural -// equality via the `CssEql` protocol (`generics::implement_eql`), not -// `core::cmp::PartialEq`. Every `eql`/`deep_clone`/`hash` callsite in this -// module forwards through `css::implement_*` which bound on `CssEql`/ -// `DeepClone`/`CssHash`, so the std bounds were never load-bearing. pub trait SelectorImpl: Sized { type ExtraMatchingData; type AttrValue: Clone; @@ -150,14 +108,6 @@ pub trait SelectorImpl: Sized { type PseudoElement: Clone; } -/// Constrained `SelectorImpl` with the concrete assoc-type bundle Bun uses. -/// -/// PORT NOTE: in Zig the `parse_*` functions were `comptime Impl: type` generics -/// but every body assumed the concrete `selector.impl.Selectors` shapes (it was -/// the only instantiation). Rust can't see through the open `Impl::LocalName` -/// to `Ident`, so the parse functions bound on this sub-trait instead — the -/// associated-type equality clauses make `Impl::LocalName == Ident` etc. -/// visible to the body without monomorphizing the signature. pub trait BunSelectorImpl: SelectorImpl< AttrValue = css::CSSString, @@ -488,12 +438,6 @@ fn compute_simple_selector_specificity( } C::Slotted(selector) => { specificity.element_selectors += 1; - // Note that due to the way ::slotted works we only compete with - // other ::slotted rules, so the above rule doesn't really - // matter, but we do it still for consistency with other - // pseudo-elements. - // - // See: https://github.com/w3c/csswg-drafts/issues/1915 specificity.add(Specificity::from_u32(selector.specificity())); } C::Host(maybe_selector) => { @@ -518,12 +462,6 @@ fn compute_simple_selector_specificity( specificity.class_like_selectors += 1; } C::NthOf(nth_of_data) => { - // https://drafts.csswg.org/selectors/#specificity-rules: - // - // The specificity of the :nth-last-child() pseudo-class, - // like the :nth-child() pseudo-class, combines the - // specificity of a regular pseudo-class with that of its - // selector argument S. specificity.class_like_selectors += 1; let mut max: u32 = 0; for selector in nth_of_data.selectors.iter() { @@ -532,11 +470,6 @@ fn compute_simple_selector_specificity( specificity.add(Specificity::from_u32(max)); } C::Negation(_) | C::Is(_) | C::Any { .. } => { - // https://drafts.csswg.org/selectors/#specificity-rules: - // - // The specificity of an :is() pseudo-class is replaced by the - // specificity of the most specific complex selector in its - // selector list argument. let list: &[GenericSelector] = match simple_selector { C::Negation(list) => list, C::Is(list) => list, @@ -564,10 +497,6 @@ fn compute_simple_selector_specificity( } } -/// Build up a Selector. -/// selector : simple_selector_sequence [ combinator simple_selector_sequence ]* ; -/// -/// `Err` means invalid selector. fn parse_selector( parser: &mut SelectorParser, input: &mut CssParser, @@ -714,12 +643,6 @@ fn parse_selector( }) } -/// simple_selector_sequence -/// : [ type_selector | universal ] [ HASH | class | attrib | pseudo | negation ]* -/// | [ HASH | class | attrib | pseudo | negation ]+ -/// -/// `Err(())` means invalid selector. -/// `Ok(true)` is an empty selector fn parse_compound_selector( parser: &mut SelectorParser, state: &mut SelectorParsingState, @@ -753,33 +676,6 @@ fn parse_compound_selector( if empty { if let Some(url) = parser.default_namespace() { - // If there was no explicit type selector, but there is a - // default namespace, there is an implicit "|*" type - // selector. Except for :host() or :not() / :is() / :where(), - // where we ignore it. - // - // https://drafts.csswg.org/css-scoping/#host-element-in-tree: - // - // When considered within its own shadow trees, the shadow - // host is featureless. Only the :host, :host(), and - // :host-context() pseudo-classes are allowed to match it. - // - // https://drafts.csswg.org/selectors-4/#featureless: - // - // A featureless element does not match any selector at all, - // except those it is explicitly defined to match. If a - // given selector is allowed to match a featureless element, - // it must do so while ignoring the default namespace. - // - // https://drafts.csswg.org/selectors-4/#matches - // - // Default namespace declarations do not affect the compound - // selector representing the subject of any selector within - // a :is() pseudo-class, unless that compound selector - // contains an explicit universal selector or type selector. - // - // (Similar quotes for :where() / :not()) - // let ignore_default_ns = state .contains(SelectorParsingState::SKIP_DEFAULT_NAMESPACE) || matches!( @@ -890,18 +786,8 @@ pub fn valid_selector_parser() { // In Rust these are inherent methods on `SelectorParser`; nothing to validate at runtime. } -/// The [:dir()](https://drafts.csswg.org/selectors-4/#the-dir-pseudo) pseudo class. -// Re-export of the canonical `{ltr, rtl}` enum from `properties::text` — both Zig -// specs (selectors/parser.zig:700, properties/text.zig:251) define the same -// `DefineEnumProperty` shape, so the Rust port shares one definition. The -// `#[derive(DefineEnumProperty)]` on the canonical provides `parse`/`to_css`/ -// `as_str`; `CssEql`/`CssHash`/`DeepClone` come from `generics::inherent_bridge`. pub use css::css_properties::text::Direction; -/// A pseudo class. -// PORT NOTE: `PartialEq` derive dropped — `Local`/`Global` carry -// `Box` and `CustomFunction` carries `TokenList`, neither of which -// implements `PartialEq`. Equality goes through `eql()` (CssEql protocol). #[derive(Clone, CssEql, CssHash)] pub enum PseudoClass { /// https://drafts.csswg.org/selectors-4/#linguistic-pseudos @@ -1080,19 +966,9 @@ impl PseudoClass { } pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { - // PERF(alloc): I don't like making these little allocations - // PORT NOTE: Zig builds a fresh `Printer` over an allocating writer, - // calls `serialize::serializePseudoClass`, then writes the buffer to - // `dest`. The buffered indirection only matters for length-dependent - // minification decisions made by callers (none here), so write - // directly to `dest` until `Printer::new_buffered` lands. serialize::serialize_pseudo_class(self, dest, None) } - // eql / hash — provided by `#[derive(CssEql, CssHash)]` (variant-wise; the - // `Box` arms recurse via the `CssEql for GenericSelector` impl - // below). `deep_clone` is `Clone` — the selector AST is global-alloc and - // every borrowed payload (`Str`, `Ident.v`) is an arena-static identity copy. pub fn deep_clone(&self) -> Self { self.clone() } @@ -1212,10 +1088,6 @@ pub struct SelectorParser<'a> { // PERF(port): was arena bulk-free — re-thread `&'bump Bump` to restore. } -// Zig: `pub const Impl = impl_.Selectors;` lived inside the struct for -// `ValidSelectorParser`'s comptime decl-probe. Rust inherent associated types -// are unstable (rust#8995); the equivalent contract is the `BunSelectorImpl` -// blanket impl above, so expose the alias at module scope instead. pub type SelectorParserImpl = impl_::Selectors; impl<'a> SelectorParser<'a> { @@ -1226,11 +1098,6 @@ impl<'a> SelectorParser<'a> { raw: Str, loc: usize, ) -> ::LocalIdentifier { - // blocked_on: `Parser::add_symbol_for_name` (gated in css_parser.rs on - // ArrayHashMap::entry + SymbolList::push). The CSS-modules branch - // returns the symbol-table ref; until that un-gates, fall through to - // the ident arm so non-modules parsing is correct. - if input.flags.css_modules() { return ::LocalIdentifier::from_ref( input.add_symbol_for_name( @@ -1261,17 +1128,6 @@ impl<'a> SelectorParser<'a> { name: Str, input: &mut CssParser, ) -> CResult { - // Spec parity: parser.zig:1054 uses `ComptimeEnumMap.get(name)` which is - // CASE-SENSITIVE (`ComptimeStringMap.get`, not `getAnyCase`/`getASCIIICaseInsensitive`). - // `::CUE(..)` / `::View-Transition-Group(..)` therefore fall through to - // `CustomFunction` in the spec — match that here by looking up `name` - // verbatim with no case folding. - // - // PERF(port): 6 entries with near-unique lengths (3/10/19/19/21/26) — - // a length-gated `match` rejects the overwhelmingly-common miss path - // (unknown `::-webkit-foo(...)` etc.) on a single `usize` compare, - // versus phf's hash + 2 table loads + slice compare. Only len==19 has - // two candidates, disambiguated by one full slice compare each. match name.len() { 3 if name == b"cue" => { return Ok(PseudoElement::CueFunction { @@ -1317,11 +1173,6 @@ impl<'a> SelectorParser<'a> { ); } - // blocked_on: properties::custom (TokenList::parse_raw / TokenOrValue) un-gate. - // The stub `properties::custom::TokenList` is a unit struct with no `.v` - // field and no `parse_raw`; consume the function args as opaque tokens - // until the real `custom.rs` un-gates. - { let mut args: Vec = Vec::new(); TokenList::parse_raw(input, &mut args, self.options, 0)?; @@ -1388,10 +1239,6 @@ impl<'a> SelectorParser<'a> { ) -> CResult { let pseudo_class = crate::match_ignore_ascii_case! { name, { b"lang" => { - // PORT NOTE: `expect_ident_or_string` returns `&'_ [u8]` - // (lifetime-tied to `&mut self`), which can't satisfy - // `parse_comma_separated`'s HRTB. Clone the token to extract - // the underlying `&'static [u8]` payload directly. let languages = parser.parse_comma_separated(|p| -> CResult { let loc = p.current_source_location(); let tok = p.next()?.clone(); @@ -1905,21 +1752,6 @@ impl CssHash for GenericSelectorList { // GenericSelector // ───────────────────────────────────────────────────────────────────────────── -/// -- original comment from servo -- -/// A Selector stores a sequence of simple selectors and combinators. The -/// iterator classes allow callers to iterate at either the raw sequence level or -/// at the level of sequences of simple selectors separated by combinators. Most -/// callers want the higher-level iterator. -/// -/// We store compound selectors internally right-to-left (in matching order). -/// Additionally, we invert the order of top-level compound selectors so that -/// each one matches left-to-right. This is because matching namespace, local name, -/// id, and class are all relatively cheap, whereas matching pseudo-classes might -/// be expensive (depending on the pseudo-class). Since authors tend to put the -/// pseudo-classes on the right, it's faster to start matching on the left. -/// -/// This reordering doesn't change the semantics of selector matching, and we -/// handle it in to_css to make it invisible to serialization. #[derive(Clone)] pub struct GenericSelector { pub specificity_and_flags: SpecificityAndFlags, @@ -1933,10 +1765,6 @@ impl<'a, Impl: SelectorImpl> fmt::Display for SelectorDebugFmt<'a, Impl> { if !cfg!(debug_assertions) { return Ok(()); } - // TODO(port): the Zig builds a fresh `Printer` and calls - // `tocss_servo::to_css_selector` into a buffer, then writes the buffer. - // blocked_on: `Printer::new_buffered` + `SymbolMap::default` (debug- - // only path; serialization body lives in `selector::tocss_servo`). write!(f, "Selector(<{} components>)", self.0.components.len()) } } @@ -2120,10 +1948,6 @@ impl<'a, Impl: SelectorImpl> Iterator for RawParseOrderFromIter<'a, Impl> { // GenericComponent // ───────────────────────────────────────────────────────────────────────────── -/// A CSS simple selector or combinator. We store both in the same enum for -/// optimal packing and cache performance, see [1]. -/// -/// [1] https://bugzilla.mozilla.org/show_bug.cgi?id=1357973 #[derive(Clone)] pub enum GenericComponent { Combinator(Combinator), @@ -2165,43 +1989,12 @@ pub enum GenericComponent { Nth(NthSelectorData), NthOf(NthOfSelectorData), NonTsPseudoClass(Impl::NonTSPseudoClass), - /// The ::slotted() pseudo-element: - /// - /// https://drafts.csswg.org/css-scoping/#slotted-pseudo - /// - /// The selector here is a compound selector, that is, no combinators. - /// - /// NOTE(emilio): This should support a list of selectors, but as of this - /// writing no other browser does, and that allows them to put ::slotted() - /// in the rule hash, so we do that too. - /// - /// See https://github.com/w3c/csswg-drafts/issues/2158 Slotted(GenericSelector), /// The `::part` pseudo-element. /// https://drafts.csswg.org/css-shadow-parts/#part Part(Box<[Impl::Identifier]>), - /// The `:host` pseudo-class: - /// - /// https://drafts.csswg.org/css-scoping/#host-selector - /// - /// NOTE(emilio): This should support a list of selectors, but as of this - /// writing no other browser does, and that allows them to put :host() - /// in the rule hash, so we do that too. - /// - /// See https://github.com/w3c/csswg-drafts/issues/2158 Host(Option>), - /// The `:where` pseudo-class. - /// - /// https://drafts.csswg.org/selectors/#zero-matches - /// - /// The inner argument is conceptually a SelectorList, but we move the - /// selectors to the heap to keep Component small. Where(Box<[GenericSelector]>), - /// The `:is` pseudo-class. - /// - /// https://drafts.csswg.org/selectors/#matches-pseudo - /// - /// Same comment as above re. the argument. Is(Box<[GenericSelector]>), Any { vendor_prefix: Impl::VendorPrefix, @@ -2213,11 +2006,6 @@ pub enum GenericComponent { Has(Box<[GenericSelector]>), /// An implementation-dependent pseudo-element selector. PseudoElement(Impl::PseudoElement), - /// A nesting selector: - /// - /// https://drafts.csswg.org/css-nesting-1/#nest-selector - /// - /// NOTE: This is a lightningcss addition. Nesting, } @@ -2764,30 +2552,12 @@ bitflags::bitflags! { /// aren't type or universal selectors. const SKIP_DEFAULT_NAMESPACE = 1 << 0; - /// Whether we've parsed a ::slotted() pseudo-element already. - /// - /// If so, then we can only parse a subset of pseudo-elements, and - /// whatever comes after them if so. const AFTER_SLOTTED = 1 << 1; - /// Whether we've parsed a ::part() pseudo-element already. - /// - /// If so, then we can only parse a subset of pseudo-elements, and - /// whatever comes after them if so. const AFTER_PART = 1 << 2; - /// Whether we've parsed a pseudo-element (as in, an - /// `Impl::PseudoElement` thus not accounting for `::slotted` or - /// `::part`) already. - /// - /// If so, then other pseudo-elements and most other selectors are - /// disallowed. const AFTER_PSEUDO_ELEMENT = 1 << 3; - /// Whether we've parsed a non-stateful pseudo-element (again, as-in - /// `Impl::PseudoElement`) already. If so, then other pseudo-classes are - /// disallowed. If this flag is set, `AFTER_PSEUDO_ELEMENT` must be set - /// as well. const AFTER_NON_STATEFUL_PSEUDO_ELEMENT = 1 << 4; /// Whether we explicitly disallow combinators. @@ -2900,12 +2670,6 @@ pub enum Combinator { Descendant, // space NextSibling, // + LaterSibling, // ~ - /// A dummy combinator we use to the left of pseudo-elements. - /// - /// It serializes as the empty string, and acts effectively as a child - /// combinator in most cases. If we ever actually start using a child - /// combinator for this, we will need to fix up the way hashes are computed - /// for revalidation selectors. PseudoElement, /// Another combinator used for ::slotted(), which represent the jump from /// a node to its assigned slot. @@ -2918,10 +2682,6 @@ pub enum Combinator { /// Non-standard Vue >>> combinator. /// https://vue-loader.vuejs.org/guide/scoped-css.html#deep-selectors DeepDescendant, - /// Non-standard /deep/ combinator. - /// Appeared in early versions of the css-scoping-1 specification: - /// https://www.w3.org/TR/2014/WD-css-scoping-1-20140403/#deep-combinator - /// And still supported as an alias for >>> by Vue. Deep, } @@ -3304,18 +3064,6 @@ pub fn parse_type_selector( sink.push_simple_selector(GenericComponent::ExplicitNoNamespace); } QNamePrefix::ExplicitAnyNamespace => { - // Element type selectors that have no namespace - // component (no namespace separator) represent elements - // without regard to the element's namespace (equivalent - // to "*|") unless a default namespace has been declared - // for namespaced selectors (e.g. in CSS, in the style - // sheet). If a default namespace has been declared, - // such selectors will represent only elements in the - // default namespace. - // -- Selectors § 6.1.1 - // So we'll have this act the same as the - // QNamePrefix::ImplicitAnyNamespace case. - // For lightning css this logic was removed, should be handled when matching. sink.push_simple_selector(GenericComponent::ExplicitAnyNamespace); } QNamePrefix::ImplicitNoNamespace => { @@ -3343,11 +3091,6 @@ pub fn parse_type_selector( Ok(true) } -/// Parse a simple selector other than a type selector. -/// -/// * `Err(())`: Invalid selector, abort -/// * `Ok(None)`: Not a simple selector, could be something else. `input` was not consumed. -/// * `Ok(Some(_))`: Parsed a simple selector or pseudo-element pub fn parse_one_simple_selector( parser: &mut SelectorParser, input: &mut CssParser, @@ -3907,11 +3650,6 @@ where F: FnOnce(Box<[GenericSelector]>) -> GenericComponent, { debug_assert!(parser.parse_is_and_where()); - // https://drafts.csswg.org/selectors/#matches-pseudo: - // - // Pseudo-elements cannot be represented by the matches-any - // pseudo-class; they are not valid within :is(). - // let mut child_state = { let mut child_state = *state; child_state.insert(SelectorParsingState::SKIP_DEFAULT_NAMESPACE); @@ -3999,10 +3737,6 @@ pub enum QNamePrefix { ExplicitNamespace(Impl::NamespacePrefix, Impl::NamespaceUrl), // `prefix|foo` } -/// * `Err(())`: Invalid selector, abort -/// * `Ok(None(token))`: Not a simple selector, could be something else. `input` was not consumed, -/// but the token is still returned. -/// * `Ok(Some(namespace, local_name))`: `None` for the local name means a `*` universal selector pub fn parse_qualified_name( parser: &mut SelectorParser, input: &mut CssParser, @@ -4193,19 +3927,6 @@ impl AttributeFlags { } } -/// HTML attributes whose value is matched ASCII-case-insensitively when no -/// explicit `s`/`i` flag is given on the attribute selector. -/// -/// -/// PERF(port): Zig used `ComptimeEnumMap.has` (zero-cost membership at -/// comptime). An earlier `phf::Set` port paid, on every -/// `[attr=val]` selector, a 32-bit FNV-ish hash over the name plus a -/// bounds check, indirect load, and full key compare — measurable in CSS -/// bundling profiles where the dominant inputs (`class`, `href`, `data-*`, -/// `aria-*`) are *misses*. A 2-level open-coded dispatch (length → -/// first-byte → exact bytes) rejects those misses in ≤2 scalar compares and -/// resolves hits in ≤3 short slice compares; the 46-entry table is small -/// enough that LLVM unrolls each leaf into a single word/SIMD compare. #[inline] fn is_html_case_insensitive_attribute(name: &[u8]) -> bool { // 46 entries, lengths 3..=14. Buckets at len 5/7/8 are dense (11/7/8 @@ -4288,11 +4009,6 @@ pub enum ViewTransitionPartName { impl ViewTransitionPartName { pub fn to_css(&self, dest: &mut Printer) -> Result<(), PrintErr> { - // PORT NOTE: `CustomIdentFns::to_css` is ``-gated on - // `Printer::{css_module,write_ident}`; inline the - // `write_ident(v, false)` body (CSS-modules custom-ident scoping is a - // serializer concern, not a grammar concern — the gated impl just - // toggles the second arg). let write_ci = |name: &CustomIdent, dest: &mut Printer| -> Result<(), PrintErr> { dest.serialize_identifier(name.v()) }; diff --git a/src/css/selectors/selector.rs b/src/css/selectors/selector.rs index 969e5909a6c..6674fbfb5ef 100644 --- a/src/css/selectors/selector.rs +++ b/src/css/selectors/selector.rs @@ -19,11 +19,6 @@ pub use parser::PseudoElement; pub use parser::Selector; pub use parser::SelectorList; -/// Our implementation of the `SelectorImpl` interface — the trait-based -/// `impl_::Selectors` marker lives in the hub (`super::impl_`) so the -/// parser↔selector cycle has a single anchor. This module is the literal -/// Zig-shaped namespace (`selector.impl.Selectors.SelectorImpl.*` type -/// aliases) kept for diff parity with `selector.zig`. pub use super::impl_; // TODO(port): `impl` is a Rust keyword; using raw identifier `r#impl` for module name parity. pub mod r#impl { @@ -546,12 +541,6 @@ fn is_selector_unused( for component in selector.components.iter() { match component { Component::Class(ident) | Component::Id(ident) => { - // PORT NOTE: `IdentOrRef::as_original_string` is - // ``-gated (blocked_on bun_ast::symbol::List::at - // + Symbol.original_name). Inline the ident arm; the ref arm - // (CSS-modules symbol-table lookup) is unreachable until - // `Parser::add_symbol_for_name` un-gates (see - // `SelectorParser::new_local_identifier`). let actual_ident: &[u8] = match (*ident).as_ident() { // SAFETY: arena-owned slice (`'static` placeholder for the arena lifetime). Some(i) => unsafe { crate::arena_str(i.v) }, @@ -602,11 +591,6 @@ fn is_selector_unused( false } -/// The serialization module ported from lightningcss. -/// -/// Note that we have two serialization modules, one from lightningcss and one from servo. -/// -/// This is because it actually uses both implementations. This is confusing. pub mod serialize { use super::*; @@ -650,17 +634,6 @@ pub mod serialize { bun_core::scoped_log!(CSS_SELECTORS, "\n"); } - // Compound selectors invert the order of their contents, so we need to - // undo that during serialization. - // - // This two-iterator strategy involves walking over the selector twice. - // We could do something more clever, but selector serialization probably - // isn't hot enough to justify it, and the stringification likely - // dominates anyway. - // - // NB: A parse-order iterator is a Rev<>, which doesn't expose as_slice(), - // which we need for |split|. So we split by combinators on a match-order - // sequence and then reverse. let mut combinators = CombinatorIter { sel: selector, i: 0, @@ -695,16 +668,6 @@ pub mod serialize { let first_index: usize = if has_leading_nesting { 1 } else { 0 }; first = false; - // 1. If there is only one simple selector in the compound selectors - // which is a universal selector, append the result of - // serializing the universal selector to s. - // - // Check if `!compound{}` first--this can happen if we have - // something like `... > ::before`, because we store `>` and `::` - // both as combinators internally. - // - // If we are in this case, after we have serialized the universal - // selector, we skip Step 2 and continue with the algorithm. let (can_elide_namespace, first_non_namespace) = if first_index >= compound.len() { (true, first_index) } else { @@ -719,13 +682,6 @@ pub mod serialize { let mut perform_step_2 = true; let next_combinator = combinators.next(); if first_non_namespace == compound.len() - 1 { - // We have to be careful here, because if there is a - // pseudo element "combinator" there isn't really just - // the one simple selector. Technically this compound - // selector contains the pseudo element selector as well - // -- Combinator::PseudoElement, just like - // Combinator::SlotAssignment, don't exist in the - // spec. if next_combinator == Some(parser::Combinator::PseudoElement) && compound[first_non_namespace].as_combinator() == Some(parser::Combinator::SlotAssignment) @@ -760,15 +716,6 @@ pub mod serialize { } } - // 2. Otherwise, for each simple selector in the compound selectors - // that is not a universal selector of which the namespace prefix - // maps to a namespace that is not the default namespace - // serialize the simple selector and append the result to s. - // - // See https://github.com/w3c/csswg-drafts/issues/1606, which is - // proposing to change this to match up with the behavior asserted - // in cssom/serialize-namespaced-type-selectors.html, which the - // following code tries to match. if perform_step_2 { let iter = compound; let mut i: usize = 0; @@ -805,10 +752,6 @@ pub mod serialize { if i < compound.len() { for simple in &iter[i..] { if matches!(simple, Component::ExplicitUniversalType) { - // Can't have a namespace followed by a pseudo-element - // selector followed by a universal selector in the same - // compound selector, so we don't have to worry about the - // real namespace being in a different `compound`. if can_elide_namespace { continue; } @@ -818,22 +761,11 @@ pub mod serialize { } } - // 3. If this is not the last part of the chain of the selector - // append a single SPACE (U+0020), followed by the combinator - // ">", "+", "~", ">>", "||", as appropriate, followed by another - // single SPACE (U+0020) if the combinator was not whitespace, to - // s. if let Some(c) = next_combinator { serialize_combinator(c, dest)?; } else { combinators_exhausted = true; } - - // 4. If this is the last part of the chain of the selector and - // there is a pseudo-element, append "::" followed by the name of - // the pseudo-element, to s. - // - // (we handle this above) } Ok(()) } @@ -978,13 +910,6 @@ pub mod serialize { serialize_selector(selector, dest, ctx, false)?; dest.write_char(b')')?; } - // Component::Nth(nth_data) => { - // nth_data.write_start(dest, nth_data.is_function())?; - // if nth_data.is_function() { - // nth_data.write_affine(dest)?; - // dest.write_char(b')')?; - // } - // } _ => { tocss_servo::to_css_component(component, dest)?; } @@ -1231,22 +1156,6 @@ pub mod serialize { d.write_str(val) } - // switch (pseudo_element.*) { - // // CSS2 pseudo elements support a single colon syntax in addition - // // to the more correct double colon for other pseudo elements. - // // We use that here because it's supported everywhere and is shorter. - // .after => try dest.writeStr(":after"), - // .before => try dest.writeStr(":before"), - // .marker => try dest.writeStr(":first-letter"), - // .selection => |prefix| Helpers.writePrefixed(dest, prefix, "selection"), - // .cue => dest.writeStr("::cue"), - // .cue_region => dest.writeStr("::cue-region"), - // .cue_function => |v| { - // dest.writeStr("::cue("); - // try serializeSelector(v.selector, dest, context, false); - // try dest.writeChar(')'); - // }, - // } match pseudo_element { // CSS2 pseudo elements support a single colon syntax in addition // to the more correct double colon for other pseudo elements. @@ -1339,18 +1248,6 @@ pub mod serialize { Ok(()) } - /// Maximum number of parent-selector substitutions allowed while - /// serializing a single rule prelude with compiled nesting. - /// - /// When the targets don't support CSS nesting, every `&` is replaced with - /// the parent selector, which may itself contain `&` referring to the - /// grandparent, and so on. A selector with multiple `&` references per - /// nesting level therefore expands to (references per level)^depth copies - /// of its ancestors, so a few KB of deeply nested input can print - /// gigabytes of output. Real-world nesting needs at most a handful of - /// substitutions per rule; anything past this limit is a runaway - /// expansion, so bail out with an error instead of allocating without - /// bound. const MAX_NESTING_EXPANSIONS: u32 = 65_536; pub(crate) fn serialize_nesting( @@ -1366,10 +1263,6 @@ pub mod serialize { None, ); } - // If there's only one simple selector, just serialize it directly. - // Otherwise, use an :is() pseudo class. - // Type selectors are only allowed at the start of a compound selector, - // so use :is() if that is not the case. if ctx.selectors.v.len() == 1 && (first || (!has_type_selector(ctx.selectors.v.at(0)) @@ -1420,17 +1313,6 @@ pub mod tocss_servo { selector: &parser::Selector, dest: &mut Printer, ) -> Result<(), PrintErr> { - // Compound selectors invert the order of their contents, so we need to - // undo that during serialization. - // - // This two-iterator strategy involves walking over the selector twice. - // We could do something more clever, but selector serialization probably - // isn't hot enough to justify it, and the stringification likely - // dominates anyway. - // - // NB: A parse-order iterator is a Rev<>, which doesn't expose as_slice(), - // which we need for |split|. So we split by combinators on a match-order - // sequence and then reverse. let mut combinators = CombinatorIter { sel: selector, i: 0, @@ -1449,16 +1331,6 @@ pub mod tocss_servo { continue; } - // 1. If there is only one simple selector in the compound selectors - // which is a universal selector, append the result of - // serializing the universal selector to s. - // - // Check if `!compound{}` first--this can happen if we have - // something like `... > ::before`, because we store `>` and `::` - // both as combinators internally. - // - // If we are in this case, after we have serialized the universal - // selector, we skip Step 2 and continue with the algorithm. let (can_elide_namespace, first_non_namespace): (bool, usize) = if compound.is_empty() { (true, 0) } else { @@ -1473,13 +1345,6 @@ pub mod tocss_servo { let mut perform_step_2 = true; let next_combinator = combinators.next(); if first_non_namespace == compound.len() - 1 { - // We have to be careful here, because if there is a - // pseudo element "combinator" there isn't really just - // the one simple selector. Technically this compound - // selector contains the pseudo element selector as well - // -- Combinator::PseudoElement, just like - // Combinator::SlotAssignment, don't exist in the - // spec. if next_combinator == Some(parser::Combinator::PseudoElement) && compound[first_non_namespace].as_combinator() == Some(parser::Combinator::SlotAssignment) @@ -1501,22 +1366,9 @@ pub mod tocss_servo { } } - // 2. Otherwise, for each simple selector in the compound selectors - // that is not a universal selector of which the namespace prefix - // maps to a namespace that is not the default namespace - // serialize the simple selector and append the result to s. - // - // See https://github.com/w3c/csswg-drafts/issues/1606, which is - // proposing to change this to match up with the behavior asserted - // in cssom/serialize-namespaced-type-selectors.html, which the - // following code tries to match. if perform_step_2 { for simple in compound { if matches!(simple, Component::ExplicitUniversalType) { - // Can't have a namespace followed by a pseudo-element - // selector followed by a universal selector in the same - // compound selector, so we don't have to worry about the - // real namespace being in a different `compound`. if can_elide_namespace { continue; } @@ -1525,22 +1377,11 @@ pub mod tocss_servo { } } - // 3. If this is not the last part of the chain of the selector - // append a single SPACE (U+0020), followed by the combinator - // ">", "+", "~", ">>", "||", as appropriate, followed by another - // single SPACE (U+0020) if the combinator was not whitespace, to - // s. if let Some(c) = next_combinator { to_css_combinator(c, dest)?; } else { combinators_exhausted = true; } - - // 4. If this is the last part of the chain of the selector and - // there is a pseudo-element, append "::" followed by the name of - // the pseudo-element, to s. - // - // (we handle this above) } Ok(()) } @@ -1789,13 +1630,6 @@ pub(crate) struct CombinatorIter<'a> { } impl<'a> CombinatorIter<'a> { - /// Original source has this iterator defined like so: - /// ```rs - /// selector - /// .iter_raw_match_order() // just returns an iterator - /// .rev() // reverses the iterator - /// .filter_map(|x| x.as_combinator()) // returns only entries which are combinators - /// ``` pub(crate) fn next(&mut self) -> Option { while self.i < self.sel.components.len() { let idx = self.sel.components.len() - 1 - self.i; @@ -1815,37 +1649,6 @@ pub(crate) struct CompoundSelectorIter<'a> { } impl<'a> CompoundSelectorIter<'a> { - /// This iterator is basically like doing `selector.components.splitByCombinator()`. - /// - /// For example: - /// ```css - /// div > p.class - /// ``` - /// - /// The iterator would return: - /// ``` - /// First slice: - /// .{ - /// .{ .local_name = "div" } - /// } - /// - /// Second slice: - /// .{ - /// .{ .local_name = "p" }, - /// .{ .class = "class" } - /// } - /// ``` - /// - /// BUT, the selectors are stored in reverse order, so this code needs to split the components backwards. - /// - /// Original source has this iterator defined like so: - /// ```rs - /// selector - /// .iter_raw_match_order() - /// .as_slice() - /// .split(|x| x.is_combinator()) // splits the slice into subslices by elements that match over the predicate - /// .rev() // reverse - /// ``` #[inline] pub(crate) fn next(&mut self) -> Option<&'a [parser::Component]> { // Since we iterating backwards, we convert all indices into "backwards form" by doing `self.sel.components.len() - 1 - i` diff --git a/src/css/small_list.rs b/src/css/small_list.rs index f1b3c57201d..b29a3c25510 100644 --- a/src/css/small_list.rs +++ b/src/css/small_list.rs @@ -21,21 +21,6 @@ pub use bun_collections::SmallList; -// ─── CSS-domain extension trait ──────────────────────────────────────────── -// (the `SmallListCssExt` trait that lived here was a verbatim duplicate of the -// `generics::{DeepClone,CssEql,CssHash,IsCompatible,Parse,ToCss}` blanket impls -// for `SmallList` and has been removed — import the relevant `generics` -// trait at the call site instead.) - -// ─── getFallbacks ────────────────────────────────────────────────────────── -// The Zig version uses `@hasDecl(T, "getImage")` and `T == TextShadow` comptime -// dispatch with a comptime-computed return type. In Rust this becomes a trait -// with associated type for the return. - -/// Duck-typed protocol from the Zig source (`@hasDecl(T, "getImage")`): any -/// value type that carries an `Image` and can produce color/prefix fallbacks -/// of itself. Implemented by `values::image::Image` and -/// `properties::background::Background`. pub trait ImageFallback: Sized { fn get_image(&self) -> &crate::values::image::Image; fn with_image(&self, arena: &bun_alloc::Arena, image: crate::values::image::Image) -> Self; @@ -53,12 +38,6 @@ pub trait ImageFallback: Sized { // `ImageFallback for Image` is implemented alongside the type in // `crate::values::image` to avoid a duplicate impl here. -/// Port of Zig `SmallList(T, N).getFallbacks` for the `@hasDecl(T, "getImage")` -/// branch. The TextShadow branch is `get_fallbacks_text_shadow`. -/// -/// Free-standing (was an inherent on `SmallList`) so it can live in this -/// crate now that `SmallList` is foreign. The lone caller threads `self` -/// explicitly. #[inline] pub(crate) fn get_fallbacks( this: &mut SmallList, diff --git a/src/css/targets.rs b/src/css/targets.rs index 15a73a4404f..31cb086a357 100644 --- a/src/css/targets.rs +++ b/src/css/targets.rs @@ -157,21 +157,6 @@ impl Targets { } } -/// Browser versions to compile CSS for. -/// -/// Versions are represented as a single 24-bit integer, with one byte -/// per `major.minor.patch` component. -/// -/// # Example -/// -/// This example represents a target of Safari 13.2.0. -/// -/// ```ignore -/// Browsers { -/// safari: Some((13 << 16) | (2 << 8)), -/// ..Browsers::default() -/// } -/// ``` #[derive(Debug, Clone, Copy, Default, PartialEq, Eq)] pub struct Browsers { pub android: Option, @@ -213,10 +198,6 @@ impl Browsers { } let number_part = &str[2..]; - // Zig: `try std.fmt.parseInt(u16, number_part, 10)` — propagates - // error.InvalidCharacter / error.Overflow. Preserve the tag for - // @errorName snapshot compat (do NOT collapse to UnsupportedCSSTarget). - // TODO(port): narrow error set (InvalidCharacter | Overflow) let year = strings::parse_int::(number_part, 10) .ok() .ok_or_else(|| bun_core::err!("InvalidCharacter"))?; @@ -467,13 +448,6 @@ impl Default for Features { } impl Features { - /// Map a `compat::Feature` enum variant to the same-named `Features` bitflag. - /// - /// Zig did this via `@field(feature, @tagName(compat_feature)) = true` reflection - /// inside `shouldCompileSame` (a `comptime` parameter, so a non-matching variant - /// was a compile error). Rust takes the variant at runtime, so the table is - /// hand-written: every `compat::Feature` whose snake_case tag matches a - /// `Features` field gets an arm; any other variant is a programmer error. pub fn from_compat(compat_feature: css::compat::Feature) -> Features { use css::compat::Feature; match compat_feature { diff --git a/src/css/values/alpha.rs b/src/css/values/alpha.rs index 2113b195d0f..6beeb9096d0 100644 --- a/src/css/values/alpha.rs +++ b/src/css/values/alpha.rs @@ -2,10 +2,6 @@ use crate::values::number::CSSNumberFns; use crate::values::percentage::NumberOrPercentage; use crate::{Parser, PrintErr, Printer, Result}; -/// A CSS [``](https://www.w3.org/TR/css-color-4/#typedef-alpha-value), -/// used to represent opacity. -/// -/// Parses either a `` or ``, but is always stored and serialized as a number. #[derive(Clone, Copy, Debug, PartialEq)] pub struct AlphaValue { pub v: f32, diff --git a/src/css/values/angle.rs b/src/css/values/angle.rs index d200a55940f..86d2fd961d2 100644 --- a/src/css/values/angle.rs +++ b/src/css/values/angle.rs @@ -13,10 +13,6 @@ const TAG_RAD: u8 = 2; const TAG_GRAD: u8 = 4; const TAG_TURN: u8 = 8; -/// A CSS [``](https://www.w3.org/TR/css-values-4/#angles) value. -/// -/// Angles may be explicit or computed by `calc()`, but are always stored and serialized -/// as their computed value. #[repr(u8)] #[derive(Clone, Copy, PartialEq, crate::generics::CssHash, crate::generics::DeepClone)] pub enum Angle { diff --git a/src/css/values/calc.rs b/src/css/values/calc.rs index 63ae6c475c2..d2a31f96af5 100644 --- a/src/css/values/calc.rs +++ b/src/css/values/calc.rs @@ -90,10 +90,6 @@ impl CalcUnit { } } -/// A mathematical expression used within the `calc()` function. -/// -/// This type supports generic value types. Values such as `Length`, `Percentage`, -/// `Time`, and `Angle` support `calc()` expressions. pub enum Calc { /// A literal value. /// PERF: this pointer feels unnecessary if V is small @@ -114,16 +110,6 @@ pub enum Calc { Function(Box>), } -// ───────────────────────────── CalcValue trait ───────────────────────────── -// Replaces the Zig `switch (V)` / `@hasDecl(V, ...)` comptime-type dispatch. -// Every type that can appear inside `Calc` implements this. -// -// The numeric protocol (`mul_f32`/`try_sign`/`try_map`/`try_op`/`try_op_to`/ -// `partial_cmp`/`try_from_angle`/`parse`/`to_css`/`is_compatible`) lives in -// `crate::values::protocol` (re-exported from `crate::generics`); `CalcValue` -// pulls it in as supertraits so each concrete impl block only carries the -// calc-specific hooks below. - pub trait CalcValue: Sized + Clone @@ -152,10 +138,6 @@ impl Clone for Calc { } } -// Structural equality decoupled from `CalcValue` so `derive(PartialEq)` on -// `Length` / `DimensionPercentage` consumers can compare through -// `Box>` without pulling in the full behavior bound. `Calc::eql` -// (below) keeps its `V: CalcValue` bound for callers that already have it. impl PartialEq for Calc { fn eq(&self, other: &Self) -> bool { match (self, other) { @@ -338,10 +320,6 @@ impl Calc { parse_ident: F, ) -> CssResult { let location = input.current_source_location(); - // PORT NOTE: clone the token before reborrowing `input` so the function - // name slice is owned by the cloned `Token` (whose payload already - // carries the parser's arena lifetime) instead of being laundered to - // `'static` here. let tok = input.next()?.clone(); let unit = match tok { css::Token::Function(f) => match CalcUnit::get_any_case(f) { @@ -456,13 +434,7 @@ impl Calc { Self::parse_math_fn( i, (), - |_, a, b| { - // Zig `@mod(a, b)`: floored modulo, result takes sign of divisor. - // Equivalent to `a - b * floor(a / b)`. Rust `%` is truncated (sign of - // dividend) and `rem_euclid` is non-negative, so neither matches for - // negative `b` — use the explicit floor formula. - a - b * (a / b).floor() - }, + |_, a, b| a - b * (a / b).floor(), |_, a, b| MathFunction::Rem { dividend: a, divisor: b, @@ -690,12 +662,6 @@ impl Calc { other => return Ok(other), }, Err(e) => { - // A math function token can only be parsed by `Self::parse`. - // If that failed, none of the alternatives below can succeed - // either, so return the error rather than falling through: - // `V::parse` would re-enter the same nested block through - // `Calc::parse` again, which makes deeply nested invalid - // arguments exponentially slow to reject. let start = input.state(); let failed_unit = match input.next() { Ok(css::Token::Function(name)) => CalcUnit::get_any_case(name), @@ -861,12 +827,6 @@ impl Calc { } } - // blocked_on: values/length.rs un-gate — until Length is real, - // `atan2(10px, 5px)` (and any other length-dimension pair) falls - // through to the CSSNumber path below and errors with `invalid_value`, - // diverging from Zig (`Angle::Rad(atan2(10,5))`). Tracked as a known - // incompleteness; no behaviour stub is added because a partial - // dimension matcher would mis-reduce mixed-unit lengths. if let Ok(v) = try_parse_atan2_args::(input, ctx) { return Ok(v); } @@ -1102,11 +1062,6 @@ impl Calc { } } - /// PERF: - /// I don't like how this function requires allocating a second ArrayList - /// I am pretty sure we could do this reduction in place, or do it as the - /// arguments are being parsed. - // PERF(port): `args`/`reduced` were arena bulk-free (ArrayList fed input.arena()) — profile if hot fn reduce_args(args: &mut Vec, order: Ordering) { // Reduces the arguments of a min() or max() expression, combining compatible values. // e.g. min(1px, 1em, 2px, 3in) => min(1px, 1em) @@ -1188,10 +1143,6 @@ pub enum TrigFnKind { Atan, } -/// A CSS math function. -/// -/// Math functions may be used in most properties and values that accept numeric -/// values, including lengths, percentages, angles, times, etc. pub enum MathFunction { /// The `calc()` function. Calc(Calc), @@ -1612,18 +1563,6 @@ fn absf(a: f32) -> f32 { a.abs() } -// ───────────────────────── CalcValue impls ───────────────────────────────── -// One impl per concrete `V` that `Calc` is instantiated with. The -// numeric-protocol surface (`mul_f32`/`try_sign`/`try_map`/`try_op{,_to}`/ -// `partial_cmp`/`try_from_angle`/`parse`/`to_css`/`is_compatible`) is -// satisfied via `crate::values::protocol::*` supertraits; only the -// calc-specific Zig `switch (V)` arms (`intoValue` / `addValue` / `intoCalc` -// / `eql`) live here. -// -// Any type whose protocol impls don't already exist elsewhere gets them -// immediately below its `CalcValue` impl as one-line forwarders to the -// inherent method. - impl CalcValue for CSSNumber { #[inline] fn add_internal(self, rhs: Self) -> Self { @@ -1667,26 +1606,6 @@ impl CalcValue for Angle { } } -// ───────────────────────────────────────────────────────────────────────────── -// `protocol::*` forwarder stamper for `CalcValue` leaf types. -// -// Rust analogue of Zig's single comptime dispatcher in `src/css/generics.zig` -// (`tryFromAngle`/`trySign`/`tryMap`/`tryOp`/`tryOpTo`/`partialCmp`, lines -// ~489-570), which duck-types via `@hasDecl(T, "sign")` etc. Here each leaf -// opts in per-trait; only the listed arms are stamped — `Parse`/`ToCss`/ -// `IsCompatible` already supplied by `impl_parse_tocss_via_inherent!` / -// `bridge_is_compatible!` stay out of the invocation. -// -// Arm vocab (each `arm: spec,` — trailing comma required): -// mul_f32: forward -// partial_cmp: forward -// try_from_angle: forward | none -// try_sign: forward | (ident = infallible inherent → `Some`) -// try_map: forward | -// try_op: forward | |rhs, ctx, f| { } (idents = param names; -// try_op_to: |rhs, ctx, f| { } captured for hygiene) -// is_compatible: forward | always_true -// parse_to_css: forward (stamps both `Parse` + `ToCss`) macro_rules! calc_protocol_forwarders { ($T:ty { $($arms:tt)* }) => { calc_protocol_forwarders!(@ $T; $($arms)*); }; (@ $T:ty;) => {}; @@ -1736,10 +1655,6 @@ macro_rules! calc_protocol_forwarders { } calc_protocol_forwarders!(@ $T; $($r)*); }; - // Closure-ish syntax so `$this/$rhs/$ctx/$f` carry call-site hygiene into - // the param list (macro_rules! fn-params are hygienic — `self` does not - // resolve in a `:block` from the call site, so the caller binds it via - // `$this`). (@ $T:ty; try_op: |$this:ident, $rhs:ident, $ctx:ident, $f:ident| $body:block, $($r:tt)*) => { impl protocol::TryOp for $T { #[inline] fn try_op(&self, $rhs: &Self, $ctx: C, $f: impl Fn(C, f32, f32) -> f32) -> Option { @@ -1863,10 +1778,6 @@ calc_protocol_forwarders!(Length { try_sign: forward, try_map: forward, try_op: |this, rhs, ctx, f| { - // Delegate to `protocol::TryOp for LengthValue` (which inlines the - // same-unit/px-convert dispatch) rather than the inherent `Length::try_op` - // — the inherent takes a 2-arg `Fn` closure, and adapting our 3-arg `f` - // to that without `C: Copy` would only yield `FnOnce`. if let (Length::Value(a), Length::Value(b)) = (this, rhs) { return ::try_op(a, b, ctx, f).map(Length::Value); } @@ -1899,13 +1810,6 @@ impl CalcValue for Length { } } -/// `protocol::*` + `CalcValue` impls for the two concrete `DimensionPercentage` -/// instantiations that participate in `Calc`. Kept as a macro (rather than -/// a blanket `impl`) so coherence doesn't tangle with the `where Self: -/// CalcValue` bounds on the inherent methods these forward to. Trailing extra -/// arms (e.g. `parse_to_css: forward,`) are forwarded into -/// `calc_protocol_forwarders!` — `LengthPercentage` already gets `Parse`/`ToCss` -/// from `impl_parse_tocss_via_inherent!`, only `Angle` needs them stamped here. macro_rules! dim_pct_protocol { ($D:ty $(, $($extra:tt)*)?) => { calc_protocol_forwarders!(DimensionPercentage<$D> { diff --git a/src/css/values/color.rs b/src/css/values/color.rs index ba92a1ae9ec..0d4eb5c69bc 100644 --- a/src/css/values/color.rs +++ b/src/css/values/color.rs @@ -106,24 +106,6 @@ impl RGBA { } } -/// Convert a unit-interval f32 (nominally 0.0..=1.0) to a u8 in 0..=255. -/// -/// Whilst scaling by 256 and flooring would provide an equal distribution of -/// integers to percentage inputs, this is not what Gecko does so we instead -/// multiply by 255 and round (adding 0.5 and flooring is equivalent to rounding). -/// -/// Chrome does something similar for the alpha value, but not the rgb values. -/// -/// See -/// -/// Clamping to 256 and rounding after would let 1.0 map to 256, and -/// `256.0_f32 as u8` saturates (historically UB): -/// -/// -/// NaN → 0 (clamp passes NaN through; `NaN as u8` saturates to 0). -/// -/// NOTE: this *rounds*. Do **not** use for thumbhash, whose spec truncates -/// (`thumbhash.zig:256` `@intFromFloat`). #[inline] pub(crate) fn clamp_unit_f32(val: f32) -> u8 { (val * 255.0).round().clamp(0.0, 255.0) as u8 @@ -161,25 +143,6 @@ pub enum FloatColor { Hwb(HWB), } -// ────────────────────────────────────────────────────────────────────────── -// Variant dispatch — single source of truth for (Variant ↔ Payload type ↔ -// css-name ↔ hash-ordinal). Every match-over-all-variants in this file is -// driven from the three `impl_variant_dispatch!` invocations below; do NOT -// hand-roll a new copy. -// -// Mirrors Zig's `switch (color.*) { inline else => |*v| v.into(T) }` shape -// (color.zig:3213 `ColorspaceConversions`) which the original Rust port -// regressed into 14 textual copies inside `define_colorspace!`. -// -// ROW ORDER IS LOAD-BEARING: ordinals feed `CssColor::hash` (Wyhash). -// css-name is NOT derivable from the ident: `XyzD65` serializes as `"xyz"` -// (Safari-15 compat), and `FloatColor::Rgb`'s payload type is `SRGB`. -// ────────────────────────────────────────────────────────────────────────── - -/// Marker for any `T` that has the full `From<_>` lattice over every concrete -/// colorspace payload (every `define_colorspace!` type does, via the handwritten -/// + generated `From` impls). Lets `convert_to::()` dispatch `v.into()` -/// uniformly without re-stamping the match per `T`. pub trait FromAnyColorspace: From + From @@ -881,10 +844,6 @@ impl CssColor { } pub fn hash(&self, hasher: &mut bun_wyhash::Wyhash) { - // PORT NOTE: Zig `css.implementHash` — variant-tag prefix + payload fields. - // Hash the discriminant + the active variant's f32 components explicitly; - // never reinterpret a `repr(Rust)` enum as raw bytes (unspecified layout / - // padding → UB and non-deterministic hashes). #[inline] fn hash_components( hasher: &mut bun_wyhash::Wyhash, @@ -996,13 +955,6 @@ impl ColorFallbackKind { } } -// ────────────────────────────────────────────────────────────────────────── -// Colorspace traits (replaces Zig comptime mixins: DefineColorspace, -// BoundedColorGamut, UnboundedColorGamut, HslHwbColorGamut, DeriveInterpolate, -// RecangularPremultiply, PolarPremultiply, AdjustPowerlessLAB/LCH, -// ColorspaceConversions, ColorIntoMixin, ImplementIntoCssColor) -// ────────────────────────────────────────────────────────────────────────── - /// Trait every colorspace implements. The Zig used `@field(this, "x")` over the /// first three struct fields plus `alpha`; here we expose them by index. /// `// TODO(port): could be derived with a proc-macro.` @@ -1555,15 +1507,6 @@ impl FloatColor { } } -// ────────────────────────────────────────────────────────────────────────── -// Colorspace structs (LAB, SRGB, HSL, HWB, SRGBLinear, P3, A98, ProPhoto, -// Rec2020, XYZd50, XYZd65, LCH, OKLAB, OKLCH) -// -// In Zig each struct manually wires `pub const X = mixin.X` for ~12 mixin -// items. In Rust the trait impls below cover that surface; the per-type -// declarations collapse into a `define_colorspace!` macro invocation. -// ────────────────────────────────────────────────────────────────────────── - macro_rules! define_colorspace { ( $(#[$meta:meta])* @@ -2705,14 +2648,6 @@ pub fn write_predefined(predefined: &PredefinedColor, dest: &mut Printer) -> Res use bun_core::powf as bun_powf; pub(crate) fn gam_srgb(r: f32, g: f32, b: f32) -> (f32, f32, f32) { - // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L31 - // convert an array of linear-light sRGB values in the range 0.0-1.0 - // to gamma corrected form - // https://en.wikipedia.org/wiki/SRGB - // Extended transfer function: - // For negative values, linear portion extends on reflection - // of axis, then uses reflected pow below that - fn gam_srgb_component(c: f32) -> f32 { let abs = c.abs(); if abs > 0.0031308 { @@ -2734,14 +2669,6 @@ pub(crate) fn gam_srgb(r: f32, g: f32, b: f32) -> (f32, f32, f32) { } pub(crate) fn lin_srgb(r: f32, g: f32, b: f32) -> (f32, f32, f32) { - // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L11 - // convert sRGB values where in-gamut values are in the range [0 - 1] - // to linear light (un-companded) form. - // https://en.wikipedia.org/wiki/SRGB - // Extended transfer function: - // for negative values, linear portion is extended on reflection of axis, - // then reflected power function is used. - fn lin_srgb_component(c: f32) -> f32 { let abs = c.abs(); if abs < 0.04045 { @@ -2781,18 +2708,6 @@ const D50: [f32; 3] = [ ((1.0f64 - 0.3457f64 - 0.3585f64) / 0.3585f64) as f32, ]; -// ────────────────────────────────────────────────────────────────────────── -// Handwritten conversions (Zig `color_conversions` namespace). -// -// In Zig, `ColorIntoMixin(T, .Space).into(target)` looked up `intoXXX` in -// (a) handwritten `color_conversions.convert_`, then -// (b) generated `generated_color_conversions.convert_`, then -// (c) the type itself. -// -// In Rust we express each conversion as `impl From for Dst`. The -// handwritten ones are below; generated ones live in `color_generated.rs`. -// ────────────────────────────────────────────────────────────────────────── - impl From for SRGB { fn from(rgb: RGBA) -> SRGB { rgb.into_srgb() @@ -3047,10 +2962,6 @@ impl From for PredefinedColor { } impl From for XYZd65 { fn from(p3_: P3) -> XYZd65 { - // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L91 - // convert linear-light display-p3 values to CIE XYZ - // using D65 (no chromatic adaptation) - // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html const MATRIX: [f32; 9] = [ 0.4865709486482162, 0.26566769316909306, @@ -3097,13 +3008,6 @@ impl From for XYZd65 { let g = lin_a98rgb_component(a98.g); let b = lin_a98rgb_component(a98.b); - // convert an array of linear-light a98-rgb values to CIE XYZ - // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html - // has greater numerical precision than section 4.3.5.3 of - // https://www.adobe.com/digitalimag/pdfs/AdobeRGB1998.pdf - // but the values below were calculated from first principles - // from the chromaticity coordinates of R G B W - // see matrixmaker.html const MATRIX: [f32; 9] = [ 0.5766690429101305, 0.1855582379065463, @@ -3134,13 +3038,6 @@ impl From for PredefinedColor { } impl From for XYZd50 { fn from(prophoto_: ProPhoto) -> XYZd50 { - // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L118 - // convert an array of prophoto-rgb values - // where in-gamut colors are in the range [0.0 - 1.0] - // to linear light (un-companded) form. - // Transfer curve is gamma 1.8 with a small linear portion - // Extended transfer function - fn lin_pro_photo_component(c: f32) -> f32 { const ET2: f32 = 16.0 / 512.0; let abs = c.abs(); @@ -3156,10 +3053,6 @@ impl From for XYZd50 { let g = lin_pro_photo_component(prophoto.g); let b = lin_pro_photo_component(prophoto.b); - // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L155 - // convert an array of linear-light prophoto-rgb values to CIE XYZ - // using D50 (so no chromatic adaptation needed afterwards) - // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html const MATRIX: [f32; 9] = [ 0.7977604896723027, 0.13518583717574031, @@ -3190,11 +3083,6 @@ impl From for PredefinedColor { } impl From for XYZd65 { fn from(rec2020_: Rec2020) -> XYZd65 { - // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L235 - // convert an array of rec2020 RGB values in the range 0.0 - 1.0 - // to linear light (un-companded) form. - // ITU-R BT.2020-2 p.4 - fn lin_rec2020_component(c: f32) -> f32 { const A: f32 = 1.09929682680944; const B: f32 = 0.018053968510807; @@ -3213,10 +3101,6 @@ impl From for XYZd65 { let g = lin_rec2020_component(rec2020.g); let b = lin_rec2020_component(rec2020.b); - // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L276 - // convert an array of linear-light rec2020 values to CIE XYZ - // using D65 (no chromatic adaptation) - // http://www.brucelindbloom.com/index.html?Eqn_RGB_XYZ_Matrix.html const MATRIX: [f32; 9] = [ 0.6369580483012914, 0.14461690358620832, @@ -3328,11 +3212,6 @@ impl From for ProPhoto { 1.2119675456389454, ]; fn gam_pro_photo_component(c: f32) -> f32 { - // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L137 - // convert linear-light prophoto-rgb in the range 0.0-1.0 - // to gamma corrected form - // Transfer curve is gamma 1.8 with a small linear portion - // TODO for negative values, extend linear portion on reflection of axis, then add pow below that const ET: f32 = 1.0 / 512.0; let abs = c.abs(); if abs >= ET { @@ -3425,10 +3304,6 @@ impl From for A98 { ]; fn gam_a98_component(c: f32) -> f32 { - // https://github.com/w3c/csswg-drafts/blob/fba005e2ce9bcac55b49e4aa19b87208b3a0631e/css-color-4/conversions.js#L193 - // convert linear-light a98-rgb in the range 0.0-1.0 - // to gamma corrected form - // negative values are also now accepted let sign: f32 = if c < 0.0 { -1.0 } else { 1.0 }; sign * bun_powf(c.abs(), 256.0 / 563.0) } @@ -3636,16 +3511,6 @@ impl From for OKLAB { } } -// ────────────────────────────────────────────────────────────────────────── -// ConvertTo (kept for parity; in Rust the `.into()` dispatch above replaces -// `ColorIntoMixin(T, .Space).into(target)`). -// ────────────────────────────────────────────────────────────────────────── - -// PORT NOTE: `ColorIntoMixin` resolved conversions at comptime via @hasDecl -// across handwritten + generated tables. In Rust this is the union of the -// `impl From for Dst` blocks above plus `color_generated.rs`; the -// generated file fills the transitive gaps the macro requires. - crate::css_eql_partialeq!(CssColor); // ported from: src/css/values/color.zig diff --git a/src/css/values/css_string.rs b/src/css/values/css_string.rs index cbb4bed93bb..2ccce9e12d7 100644 --- a/src/css/values/css_string.rs +++ b/src/css/values/css_string.rs @@ -3,14 +3,6 @@ pub use css::CssResult as Result; pub use css::PrintErr; pub use css::Printer; -/// A quoted CSS string. -/// -/// INVARIANT: the pointee is a sub-slice of the parser source buffer / arena -/// and remains valid for the lifetime of the parse + print session (i.e. as -/// long as the originating `ParserInput`/arena lives). Stored as a raw slice -/// pointer rather than `&'static [u8]` so the arena lifetime is not laundered -/// to `'static` (see PORTING.md §Forbidden patterns). A future refactor -/// should thread an explicit `'bump` lifetime here. pub type CssString = *const [u8]; pub struct CssStringFns; diff --git a/src/css/values/easing.rs b/src/css/values/easing.rs index 30981e81a0d..4748e07ff7e 100644 --- a/src/css/values/easing.rs +++ b/src/css/values/easing.rs @@ -52,14 +52,6 @@ enum EasingKeyword { StepEnd, } -/// Zig: `Map.getASCIIICaseInsensitive(ident)`. -/// -/// PERF(port): was `phf::Map<&[u8], _>` + lowercase-into-stack-buf + `get`. -/// 7 keys with near-unique lengths (only len 8 collides: `ease-out` / -/// `step-end`), so a length-gated byte match is cheaper than phf's -/// hash+displace+verify — one `usize` compare rejects almost every miss -/// before any byte work, and hits resolve in a single slice compare. Same -/// pattern as `clap::find_param` (12577e958d71). fn easing_map_get_any_case(ident: &[u8]) -> Option { // Longest key is "ease-in-out" (11 bytes). let len = ident.len(); @@ -85,11 +77,6 @@ fn easing_map_get_any_case(ident: &[u8]) -> Option { impl EasingFunction { pub fn parse(input: &mut css::Parser) -> Result { - // PORT NOTE: reshaped for borrowck — `try_parse(|i| i.expect_ident())` - // ties the returned slice to the closure's `&mut Parser` borrow, so the - // ident can't escape. Read the next token by value (Token slices are - // `'static` placeholders for the not-yet-threaded `'bump`) and dispatch - // on Ident vs Function in one go; on any other token, error. let location = input.current_source_location(); let tok = input.next()?.clone(); if let Token::Ident(ident) = tok { @@ -258,14 +245,6 @@ enum StepPositionKeyword { JumpEnd, } -/// Zig: `Map.getASCIIICaseInsensitive(ident)` — lowercase into a stack buffer, -/// then a length-gated byte match. -/// -/// PERF(port): was `phf::Map<&[u8], _>` (6 keys). phf hashes the whole slice -/// before a single bucket compare; with 6 keys spread across 5 distinct -/// lengths (3/5/8/9/9/10), gating on `len()` rejects every miss with one -/// `usize` compare and resolves every hit with at most one slice compare -/// (two at len 9). See `clap::find_param` for the same shape. fn step_position_map_get_any_case(ident: &[u8]) -> Option { // Longest key is "jump-start" (10 bytes). let (buf, len) = bun_core::strings::ascii_lowercase_buf::<10>(ident)?; diff --git a/src/css/values/gradient.rs b/src/css/values/gradient.rs index 17631e82381..8a0b4799fed 100644 --- a/src/css/values/gradient.rs +++ b/src/css/values/gradient.rs @@ -12,19 +12,6 @@ use crate::values::position::{ use crate::{PrintErr, Printer, VendorPrefix}; use bun_alloc::Arena; -// `'bump` arena threading dropped for now: `BumpVec<'bump,_>` → -// `Vec<_>` (matches `Parser::parse_comma_separated → Vec`); re-thread once -// `Parser<'bump,'_>` two-lifetime arity lands and `arena()` returns -// `&'bump Bump`. The generic `D` bound (`LengthPercentage` / `AnglePercentage`) -// is expressed via the local `GradientPosition` trait below — replaces the -// gated `crate::generic::{Parse,ToCss,DeepClone}` set so the monomorphized -// `parse_items` / `serialize_items` / `GradientItem` bodies type- -// check against the two concrete `DimensionPercentage<_>` instantiations. - -/// Protocol for the `D` type parameter in `GradientItem` / `ColorStop` / -/// `parse_items` / `serialize_items`. Only ever instantiated at -/// `LengthPercentage` (= `DimensionPercentage`) and -/// `AnglePercentage` (= `DimensionPercentage`). pub trait GradientPosition: Sized + Clone + PartialEq { fn parse(input: &mut Parser) -> Result; fn to_css(&self, dest: &mut Printer) -> core::result::Result<(), PrintErr>; @@ -33,10 +20,6 @@ pub trait GradientPosition: Sized + Clone + PartialEq { fn is_percentage_with_value(&self, v: f32) -> bool; } -// Only two `D` instantiations exist (`LengthValue` / `Angle`); both already -// satisfy `DimensionPercentage: CalcValue` in `calc.rs`. A blanket impl -// would need to re-state that bound; concrete impls are simpler and match -// the Zig monomorphization sites exactly. macro_rules! impl_gradient_position { ($ty:ty) => { impl GradientPosition for $ty { @@ -1019,10 +1002,6 @@ impl LineDirection { } } -/// Either a color stop or interpolation hint within a gradient. -/// -/// This type is generic, and items may be either a [LengthPercentage](super::length::LengthPercentage) -/// or [Angle](super::angle::Angle) depending on what type of gradient it is within. #[derive(Clone, PartialEq)] pub enum GradientItem { /// A color stop. @@ -1255,10 +1234,6 @@ impl WebKitColorStop { } } -/// A [``](https://www.w3.org/TR/css-images-4/#color-stop-syntax) within a gradient. -/// -/// This type is generic, and may be either a [LengthPercentage](super::length::LengthPercentage) -/// or [Angle](super::angle::Angle) depending on what type of gradient it is within. #[derive(Clone, PartialEq)] pub struct ColorStop { /// The color of the color stop. diff --git a/src/css/values/ident.rs b/src/css/values/ident.rs index 21108d8cf37..63bfd721a9c 100644 --- a/src/css/values/ident.rs +++ b/src/css/values/ident.rs @@ -6,15 +6,6 @@ use bun_ast::Ref; use bun_core::strings; use bun_wyhash::Wyhash; -// ──────────────────────── arena-slice newtype boilerplate ──────────────── -// `DashedIdent` / `Ident` / `CustomIdent` are DISTINCT CSS value types per -// spec (their `parse`/`to_css` differ intentionally — `--` prefix check, -// plain ident, CSS-wide-keyword rejection respectively) but share an -// identical `*const [u8]` arena-slice newtype shell. This macro stamps out -// the struct + the byte-identical `v()`/`deep_clone`/`hash`/`as_slice` -// boilerplate; per-type `parse`/`to_css` live in separate inherent `impl` -// blocks below (Rust allows multiple inherent impls). Precedent: -// `generics.rs` `ident_eql_impl!` already macroizes the shared `CssEql`. macro_rules! arena_slice_newtype { ($(#[$meta:meta])* $name:ident) => { $(#[$meta])* @@ -26,18 +17,6 @@ macro_rules! arena_slice_newtype { } impl $name { - /// Borrow the underlying arena-owned slice. - /// - /// `v` is always a non-null fat pointer into the parser's bump arena - /// (constructed from `expect_ident()` source text or copied from - /// another instance). Arena bytes are immutable and outlive every - /// value produced from them, so handing out `&[u8]` is sound. - /// - /// NOTE: the borrow is tied to `&self`. Call sites that must return - /// the slice with the `'static` placeholder lifetime (e.g. - /// `IdentOrRef::{debug_ident,as_str,as_original_string}`, - /// `Printer::lookup_ident_or_ref`, `SelectorParser::namespace_for_prefix`) - /// still go through the raw `v` field directly until `'bump` is threaded. #[inline] pub fn v(&self) -> &[u8] { // SAFETY: arena-owned, never null, immutable for the parse session @@ -46,10 +25,6 @@ macro_rules! arena_slice_newtype { } pub fn deep_clone(&self, _bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: Zig `css.implementDeepClone` — field-wise. The - // `*const [u8]` slice is arena-owned (never mutated, freed on - // arena reset), so identity copy is correct (matches generics.zig - // "const strings" fast-path). *self } @@ -68,20 +43,6 @@ macro_rules! arena_slice_newtype { }; } -// ───────────────────────── DashedIdentReference ────────────────────────── -// `properties::css_modules::Specifier` is real (parse/to_css/eql/hash); the -// `from` field below uses it directly. `parse_with_options` honors -// `ParserOptions.css_modules.dashed_idents`. `to_css` resolves the -// import-record path up front and hands it to `CssModule::reference_dashed` -// (borrowck — see PORT NOTE on that method). - -/// A CSS [``](https://www.w3.org/TR/css-values-4/#dashed-idents) reference. -/// -/// Dashed idents are used in cases where an identifier can be either author defined _or_ CSS-defined. -/// Author defined idents must start with two dash characters ("--") or parsing will fail. -/// -/// In CSS modules, when the `dashed_idents` option is enabled, the identifier may be followed by the -/// `from` keyword and an argument indicating where the referenced identifier is declared (e.g. a filename). #[derive(Debug, Clone, Copy)] pub struct DashedIdentReference { /// The referenced identifier. @@ -149,11 +110,6 @@ impl DashedIdentReference { let ident_v = unsafe { crate::arena_str(self.ident.v) }; let source_index = dest.loc.source_index; let bump = dest.arena; - // PORT NOTE: Zig `referenceDashed` took `*Printer` and called - // `dest.importRecord()` internally. Rust borrowck forbids handing - // `dest` to a method on `dest.css_module`, so resolve the path - // here and pass the slice down. The `?` preserves the Zig - // `try dest.importRecord(...)` error path. use crate::properties::css_modules::Specifier; let specifier_path: Option<&[u8]> = match &self.from { Some(Specifier::ImportRecordIndex(idx)) => { @@ -180,19 +136,9 @@ impl DashedIdentReference { pub use DashedIdent as DashedIdentFns; arena_slice_newtype! { - /// A CSS [``](https://www.w3.org/TR/css-values-4/#dashed-idents) declaration. - /// - /// Dashed idents are used in cases where an identifier can be either author defined _or_ CSS-defined. - /// Author defined idents must start with two dash characters ("--") or parsing will fail. DashedIdent } -// TODO(port): Zig `pub fn HashMap(comptime V: type) type` returned an -// ArrayHashMapUnmanaged with a custom string-hash context. Inherent assoc -// type aliases are unstable in Rust; expose as a free type alias instead. -// bun_collections::ArrayHashMap is wyhash-keyed; verify the hasher matches -// std.array_hash_map.hashString or supply a custom Hash impl. -// blocked_on: bun_collections::ArrayHashMap surface pub type DashedIdentHashMap = bun_collections::ArrayHashMap; impl DashedIdent { @@ -234,24 +180,10 @@ impl Ident { // ───────────────────────────── IdentOrRef ──────────────────────────────── -/// Encodes an `Ident` or the bundler's `Ref` into 16 bytes. -/// -/// It uses the top bit of the pointer to denote whether it's an ident or a ref -/// -/// If it's an `Ident`, then `__ref_bit == false` and `__len` is the length of the slice. -/// -/// If it's `Ref`, then `__ref_bit == true` and `__len` is the bit pattern of the `Ref`. -/// -/// In debug mode, if it is a `Ref` we will also set the `__ptrbits` to point to the original -/// []const u8 so we can debug the string. This should be fine since we use arena #[repr(transparent)] #[derive(Clone, Copy, Default)] pub struct IdentOrRef(u128); -// Zig packed struct(u128) field layout, LSB-first: -// __ptrbits: u63 -> bits 0..63 -// __ref_bit: bool -> bit 63 -// __len: u64 -> bits 64..128 const PTRBITS_MASK: u128 = (1u128 << 63) - 1; const REF_BIT: u128 = 1u128 << 63; @@ -315,11 +247,6 @@ impl IdentOrRef { } } - // NOTE: no `#[cfg(not(debug_assertions))]` variant. Zig's `@compileError` is lazy (fires only - // if the body is analyzed); Rust's `compile_error!` fires at expansion and would break every - // release build. Omitting the fn in release yields a name-resolution error at the call site, - // which is the closest Rust equivalent. - pub fn from_ident(ident: Ident) -> Self { let s = ident.v(); let (ptr, len) = (s.as_ptr() as usize as u64, s.len() as u64); @@ -455,13 +382,6 @@ impl core::fmt::Display for IdentOrRef { pub use CustomIdent as CustomIdentFns; -/// ASCII-case-insensitive check for the words reserved from the -/// [``](https://www.w3.org/TR/css-values-4/#custom-idents) -/// production: the CSS-wide keywords + `default`. -/// -/// `default` is *not* a CSS-wide keyword (cf. [`CSSWideKeyword`]); it is -/// reserved separately by css-values-4. `none` is *not* in this set — -/// `` / `` callers check it themselves. #[inline] pub(crate) fn is_reserved_custom_ident(s: &[u8]) -> bool { strings::eql_any_case_insensitive_ascii( diff --git a/src/css/values/image.rs b/src/css/values/image.rs index 719c062717e..29ebaefb927 100644 --- a/src/css/values/image.rs +++ b/src/css/values/image.rs @@ -236,11 +236,6 @@ impl Image { } } - // TODO(port): `css.DeriveParse(@This()).parse` — hand-expanded: try each - // variant in Zig field order (none/url/gradient/image-set). - // blocked_on: `Url::parse` (gated on `Parser::add_import_record`). The - // gradient/image-set arms are real; the url arm un-gates with url.rs. - pub fn parse(input: &mut css::Parser) -> Result { if input .try_parse(|i| i.expect_ident_matching(b"none")) @@ -287,10 +282,6 @@ impl crate::small_list::ImageFallback for Image { } } -/// A CSS [`image-set()`](https://drafts.csswg.org/css-images-4/#image-set-notation) value. -/// -/// `image-set()` allows the user agent to choose between multiple versions of an image to -/// display the most appropriate resolution or file type that it supports. pub struct ImageSet { /// The image options to choose from. // PERF(port): was ArrayListUnmanaged fed arena — profile if hot @@ -398,10 +389,6 @@ impl ImageSetOption { pub(crate) fn parse(input: &mut css::Parser) -> Result { let start_position = input.input.tokenizer.get_position(); let loc = input.current_source_location(); - // PORT NOTE: `expect_url_or_string` returns a borrow of the parser, so - // it can't be used as a `try_parse` callback directly (the result type - // `R` may not borrow the closure arg). Erase the borrow via `*const` - // — token slices are arena-static (see `css_parser::src_str`). let image = if let Ok(url) = input.try_parse(|p| p.expect_url_or_string().map(std::ptr::from_ref::<[u8]>)) { diff --git a/src/css/values/length.rs b/src/css/values/length.rs index 504bf05ed39..133d7096ca5 100644 --- a/src/css/values/length.rs +++ b/src/css/values/length.rs @@ -71,20 +71,6 @@ const PX_PER_Q: f32 = PX_PER_CM / 40.0; const PX_PER_PT: f32 = PX_PER_IN / 72.0; const PX_PER_PC: f32 = PX_PER_IN / 6.0; -// ────────────────────────────────────────────────────────────────────────── -// LengthValue -// -// The Zig original is a `union(enum)` with ~50 variants, each carrying a single -// `CSSNumber` (f32). Nearly every method iterates `std.meta.fields(@This())` / -// `bun.meta.EnumFields(@This())` to dispatch by tag — Zig comptime reflection. -// -// Per PORTING.md §"Comptime reflection": >8 variants → small macro generator. -// `define_length_units!` generates the enum plus the handful of per-variant -// dispatch helpers (`value()`, `unit()`, `from_unit_ci()`, `map_value()`, -// `try_same_unit_op()`, `feature()`); all higher-level methods are then written -// in terms of those, keeping the logic 1:1 with the Zig. -// ────────────────────────────────────────────────────────────────────────── - macro_rules! define_length_units { ( $( @@ -650,12 +636,6 @@ impl Length { } } -// ─── protocol-trait impls for the calc lattice ──────────────────────────── -// These wire `LengthValue`/`Angle` into `DimensionPercentage`'s bound set -// (`protocol::{Zero,MulF32,TryAdd,TrySign,TryMap,TryOp,PartialCmp,Parse, -// ToCss,IsCompatible}`). All forward to the inherent methods above; once -// `crate::generics::parse_tocss_numeric_gated` un-gates these collapse into -// blanket impls there. impl protocol::Zero for LengthValue { #[inline] fn zero() -> Self { @@ -699,11 +679,6 @@ impl protocol::TryMap for LengthValue { impl protocol::TryOp for LengthValue { #[inline] fn try_op(&self, rhs: &Self, ctx: C, f: impl Fn(C, f32, f32) -> f32) -> Option { - // PORT NOTE: `LengthValue::try_op` takes a 2-arg closure (ctx folded - // in by caller); the `protocol::TryOp` shape passes `C` by-value with - // no `Copy` bound, so we can't capture `ctx` in an `Fn` closure that - // might be called more than once. Inline the same-unit + px-convert - // dispatch here so `f` is invoked exactly once on the chosen path. if core::mem::discriminant(self) == core::mem::discriminant(rhs) { let v = f(ctx, self.value(), rhs.value()); return Some(self.map_value(|_| v)); diff --git a/src/css/values/mod.rs b/src/css/values/mod.rs index eee401eb419..5527982ba5a 100644 --- a/src/css/values/mod.rs +++ b/src/css/values/mod.rs @@ -1,18 +1,9 @@ pub use crate::css_parser as css; pub mod css_modules { - // Back-compat re-export. Canonical home is `properties::css_modules::Specifier`; - // all in-tree callers (`values::ident`, `properties::custom`) now reference - // that path directly. Kept so out-of-tree / gated code that still spells - // `values::css_modules::Specifier` resolves to the same single type. pub use crate::properties::css_modules::Specifier; } -// Value types form a deep dependency lattice rooted at `calc.rs`: -// number→calc, angle→{calc,percentage}, alpha→percentage, time→calc, -// percentage→{calc,length}, length→{calc,percentage}, -// color→{calc,angle,percentage}, gradient→{color,angle,length,position}, -// image→{gradient,url}, ident→properties/css_modules. pub mod angle; pub mod css_string; pub mod number; @@ -26,21 +17,17 @@ pub mod time; // The `protocol` submodule below supplies the numeric protocol traits // (`Zero`/`MulF32`/`TryAdd`/`Parse`) used by `Calc` / `DimensionPercentage`. pub mod color; +#[path = "color_generated.rs"] +pub mod color_generated; pub mod easing; pub mod gradient; +pub mod ident; pub mod image; pub mod length; pub mod position; pub mod rect; pub mod size; pub mod syntax; -// `color_generated.rs` is the codegen'd named-color tables (47KB). Its parent -// in Zig was `color.zig`'s `pub usingnamespace`; here it's a sibling module -// re-exported through `color::*` so the stub-set re-export at crate root -// (`pub use values::color::{CssColor, RGBA, ...}`) keeps resolving. -#[path = "color_generated.rs"] -pub mod color_generated; -pub mod ident; pub mod url; /// Numeric protocol traits referenced by `DimensionPercentage` and the diff --git a/src/css/values/percentage.rs b/src/css/values/percentage.rs index 6011f1c2afb..999df3d603b 100644 --- a/src/css/values/percentage.rs +++ b/src/css/values/percentage.rs @@ -156,11 +156,6 @@ impl PartialEq for DimensionPercentage { } } -// `Zero`/`MulF32`/`TryAdd`/`Parse` protocol traits live in -// `crate::values::protocol` until `generics::parse_tocss_numeric_gated` -// un-gates. The bound set below mirrors the full Zig comptime-method surface -// on `D`; per-method `where` clauses narrow further so plain -// `DimensionPercentage` (no behavior) needs only `D: Clone`. impl DimensionPercentage where // TODO(port): narrow these bounds; mirroring methods called on D below. @@ -479,17 +474,6 @@ impl NumberOrPercentage { } } - // pub fn parse(input: *css.Parser) Result(NumberOrPercentage) { - // _ = input; // autofix - // @panic(css.todo_stuff.depth); - // } - - // pub fn toCss(this: *const NumberOrPercentage, dest: *css.Printer) css.PrintErr!void { - // _ = this; // autofix - // _ = dest; // autofix - // @panic(css.todo_stuff.depth); - // } - pub(crate) fn into_f32(&self) -> f32 { match self { Self::Number(n) => *n, diff --git a/src/css/values/rect.rs b/src/css/values/rect.rs index 7a8c585a8ee..2ab48bb92b3 100644 --- a/src/css/values/rect.rs +++ b/src/css/values/rect.rs @@ -3,17 +3,6 @@ use crate::css_parser::{CssResult as Result, PrintErr, Printer}; use crate::targets::Browsers; use crate::values::protocol::{IsCompatible, Parse, ToCss}; -// PORT NOTE: the Zig `needsDeinit(comptime T: type) bool` switch and the -// `deinit(this, arena)` method are dropped entirely. They existed to -// thread per-field `arena.free` through a comptime type table; in Rust, -// `T: Drop` on the four fields handles this automatically (and arena-owned -// payloads in `bun_css` are bulk-freed by the bump, never per-value). - -/// A generic value that represents a value for four sides of a box, -/// e.g. border-width, margin, padding, etc. -/// -/// When serialized, as few components as possible are written when -/// there are duplicate values. pub struct Rect { /// The top component. pub top: T, @@ -40,11 +29,6 @@ impl Rect { where T: Clone, { - // PORT NOTE: Zig branched on `comptime needs_deinit` to decide between - // bitwise copy and per-field `.deepClone(arena)`. In Rust this is - // just the `DeepClone`/`Clone` trait on `T` — the cheap-copy types - // (`f32`, `NumberOrPercentage`, `LineStyle`) impl it as a bit copy. - // TODO(port): narrow trait bound once css::generic::DeepClone lands. Self { top: self.top.clone(), right: self.right.clone(), diff --git a/src/css/values/size.rs b/src/css/values/size.rs index 6caed113cf9..f9de6b22883 100644 --- a/src/css/values/size.rs +++ b/src/css/values/size.rs @@ -10,13 +10,6 @@ pub struct Size2D { pub b: T, } -// PORT NOTE: Zig's `switch (T) { f32 => ..., LengthPercentage => ..., else => T.parse }` -// is comptime type dispatch. In Rust this is expressed via trait bounds — `f32` and -// `LengthPercentage` must impl the same `Parse`/`ToCss`/`Eql` traits as other CSS value -// types (the `f32` impls delegate to `CSSNumberFns`). The per-type `switch` arms are -// therefore collapsed into trait method calls below. -// TODO(port): confirm trait names match the crate API once `generics:: -// parse_tocss_numeric_gated` un-gates; for now bound on `values::protocol`. impl Size2D where T: Clone + PartialEq, diff --git a/src/css/values/syntax.rs b/src/css/values/syntax.rs index 864842a06cd..0b206bf67c2 100644 --- a/src/css/values/syntax.rs +++ b/src/css/values/syntax.rs @@ -18,12 +18,6 @@ use bun_core::strings; // https://drafts.csswg.org/css-syntax-3/#whitespace const SPACE_CHARACTERS: &[u8] = &[0x20, 0x09]; -/// A CSS [syntax string](https://drafts.css-houdini.org/css-properties-values-api/#syntax-strings) -/// used to define the grammar for a registered custom property. -// PORT NOTE: the Zig source comments note "Zig doesn't have lifetimes, so 'i is omitted" — -// upstream lightningcss Rust threaded `'i`, but the port uses `&'static [u8]` / -// `*const [u8]` placeholders for arena-borrowed slices (matching `Token` / -// `ident.rs`). TODO(refactor): thread `'bump` and restore the lifetime. #[derive(Debug, Clone, PartialEq, Eq)] pub enum SyntaxString { /// A list of syntax components. @@ -216,11 +210,6 @@ impl SyntaxString { } } -/// A [syntax component](https://drafts.css-houdini.org/css-properties-values-api/#syntax-component) -/// within a [SyntaxString](SyntaxString). -/// -/// A syntax component consists of a component kind an a multiplier, which indicates how the component -/// may repeat during parsing. #[derive(Debug, Clone, PartialEq, Eq)] pub struct SyntaxComponent { pub kind: SyntaxComponentKind, @@ -292,12 +281,6 @@ pub enum SyntaxComponentKind { TransformList, /// A `` component. CustomIdent, - /// A literal component. - // PORT NOTE: PORTING.md §Forbidden bans laundering a parser-borrowed slice to - // `&'static`. Zig's arena keeps the source alive for the AST's lifetime; Rust - // would need a `'bump` lifetime threaded through `SyntaxString`. The port owns - // the bytes instead — `Box<[u8]>` per §Forbidden ("the field should be - // `Box<[T]>` … not `&'static [T]`"). May swap for `&'bump [u8]` later. Literal(Box<[u8]>), } @@ -382,12 +365,6 @@ fn is_name_code_point(c: u8) -> bool { is_ident_start(c) || (c >= b'0' && c <= b'9') || c == b'-' } -// ─── ParsedComponent ────────────────────────────────────────────────────── -// `ParsedComponent` is the materialized form of a `SyntaxComponentKind` and -// carries `Image` / `CssColor` / `Transform{,List}` / `TokenList` payloads. -// PORT NOTE: no `#[derive]` — payload types lack a common Debug/Clone/PartialEq -// surface (Image: none; TokenList: Default-only; Ident/CustomIdent: no Eq; -// Transform: no Debug). Zig has only `deepClone` + `toCss`, mirrored below. pub enum ParsedComponent { /// A `` value. Length(Length), @@ -413,10 +390,6 @@ pub enum ParsedComponent { Resolution(Resolution), /// A `` value. TransformFunction(Transform), - /// A `` value. - // PORT NOTE: `TransformList<'bump>` borrows the parser arena. The port uses - // `'static` placeholders (matching `Token`/`Ident`). TODO(refactor): thread - // `'bump` through `ParsedComponent<'a>`. TransformList(TransformList), /// A `` value. CustomIdent(CustomIdent), @@ -480,11 +453,6 @@ impl ParsedComponent { } pub(crate) fn deep_clone(&self, bump: &bun_alloc::Arena) -> Self { - // PORT NOTE: hand-expanded `css.implementDeepClone` (variant-wise reflection). - // Payload signatures aren't yet uniform across the crate (some `deep_clone()` - // take no arena, some take `&Arena`, some are `Copy`), so the `#[derive(DeepClone)]` - // macro can't cover this enum until the signatures are unified. Match-arm dispatch - // mirrors the Zig comptime switch exactly. match self { ParsedComponent::Length(v) => ParsedComponent::Length(v.deep_clone()), ParsedComponent::Number(v) => ParsedComponent::Number(*v), diff --git a/src/css/values/time.rs b/src/css/values/time.rs index 329731e2b4d..a38e50bb405 100644 --- a/src/css/values/time.rs +++ b/src/css/values/time.rs @@ -4,11 +4,6 @@ use crate::values::angle::Angle; use crate::values::calc::Calc; use crate::values::number::{CSSNumber, CSSNumberFns}; -/// A CSS [`

- //

Hello {name}!

- // - //
- // - // Tools that use esbuild to compile TypeScript code inside a Svelte - // file like this only give esbuild the contents of the