mirror of
https://github.com/oven-sh/bun
synced 2026-02-28 12:31:00 +01:00
Compare commits
31 Commits
dylan/pyth
...
claude/fix
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
82a55d41fe | ||
|
|
b7475d8768 | ||
|
|
4494170f74 | ||
|
|
9484218ba4 | ||
|
|
2a5e8ef38c | ||
|
|
a84f12b816 | ||
|
|
0f43ea9bec | ||
|
|
0889897a1c | ||
|
|
68f2ea4b95 | ||
|
|
d4ebfd9771 | ||
|
|
e3c25260ed | ||
|
|
1bded85718 | ||
|
|
cf6cdbbbad | ||
|
|
89d2b1cd0b | ||
|
|
2019a1b11d | ||
|
|
6c70ce2485 | ||
|
|
0e386c4168 | ||
|
|
e5cd034e9a | ||
|
|
45b9d1baba | ||
|
|
0ad562d3bd | ||
|
|
63a323a511 | ||
|
|
af76296637 | ||
|
|
d1047c2cf1 | ||
|
|
315e822866 | ||
|
|
7f498a2e07 | ||
|
|
5d4b1821f3 | ||
|
|
41de7a3bfb | ||
|
|
d23312d3f6 | ||
|
|
de8c754c6a | ||
|
|
27e1363a66 | ||
|
|
eba4da23e6 |
@@ -1,5 +1,5 @@
|
||||
ARG LLVM_VERSION="19"
|
||||
ARG REPORTED_LLVM_VERSION="19.1.7"
|
||||
ARG LLVM_VERSION="21"
|
||||
ARG REPORTED_LLVM_VERSION="21.1.8"
|
||||
ARG OLD_BUN_VERSION="1.1.38"
|
||||
ARG BUILDKITE_AGENT_TAGS="queue=linux,os=linux,arch=${TARGETARCH}"
|
||||
|
||||
|
||||
@@ -109,12 +109,12 @@ const buildPlatforms = [
|
||||
{ os: "linux", arch: "x64", distro: "amazonlinux", release: "2023", features: ["docker"] },
|
||||
{ os: "linux", arch: "x64", baseline: true, distro: "amazonlinux", release: "2023", features: ["docker"] },
|
||||
{ os: "linux", arch: "x64", profile: "asan", distro: "amazonlinux", release: "2023", features: ["docker"] },
|
||||
{ os: "linux", arch: "aarch64", abi: "musl", distro: "alpine", release: "3.22" },
|
||||
{ os: "linux", arch: "x64", abi: "musl", distro: "alpine", release: "3.22" },
|
||||
{ os: "linux", arch: "x64", abi: "musl", baseline: true, distro: "alpine", release: "3.22" },
|
||||
{ os: "linux", arch: "aarch64", abi: "musl", distro: "alpine", release: "3.23" },
|
||||
{ os: "linux", arch: "x64", abi: "musl", distro: "alpine", release: "3.23" },
|
||||
{ os: "linux", arch: "x64", abi: "musl", baseline: true, distro: "alpine", release: "3.23" },
|
||||
{ os: "windows", arch: "x64", release: "2019" },
|
||||
{ os: "windows", arch: "x64", baseline: true, release: "2019" },
|
||||
// TODO: Enable when Windows ARM64 CI runners are ready
|
||||
// TODO: Re-enable when Windows ARM64 VS component installation is resolved on Buildkite runners
|
||||
// { os: "windows", arch: "aarch64", release: "2019" },
|
||||
];
|
||||
|
||||
@@ -133,9 +133,9 @@ const testPlatforms = [
|
||||
{ os: "linux", arch: "aarch64", distro: "ubuntu", release: "25.04", tier: "latest" },
|
||||
{ os: "linux", arch: "x64", distro: "ubuntu", release: "25.04", tier: "latest" },
|
||||
{ os: "linux", arch: "x64", baseline: true, distro: "ubuntu", release: "25.04", tier: "latest" },
|
||||
{ os: "linux", arch: "aarch64", abi: "musl", distro: "alpine", release: "3.22", tier: "latest" },
|
||||
{ os: "linux", arch: "x64", abi: "musl", distro: "alpine", release: "3.22", tier: "latest" },
|
||||
{ os: "linux", arch: "x64", abi: "musl", baseline: true, distro: "alpine", release: "3.22", tier: "latest" },
|
||||
{ os: "linux", arch: "aarch64", abi: "musl", distro: "alpine", release: "3.23", tier: "latest" },
|
||||
{ os: "linux", arch: "x64", abi: "musl", distro: "alpine", release: "3.23", tier: "latest" },
|
||||
{ os: "linux", arch: "x64", abi: "musl", baseline: true, distro: "alpine", release: "3.23", tier: "latest" },
|
||||
{ os: "windows", arch: "x64", release: "2019", tier: "oldest" },
|
||||
{ os: "windows", arch: "x64", release: "2019", baseline: true, tier: "oldest" },
|
||||
// TODO: Enable when Windows ARM64 CI runners are ready
|
||||
@@ -304,6 +304,13 @@ function getCppAgent(platform, options) {
|
||||
};
|
||||
}
|
||||
|
||||
// Cross-compile Windows ARM64 from x64 runners
|
||||
if (os === "windows" && arch === "aarch64") {
|
||||
return getEc2Agent({ ...platform, arch: "x64" }, options, {
|
||||
instanceType: "c7i.4xlarge",
|
||||
});
|
||||
}
|
||||
|
||||
return getEc2Agent(platform, options, {
|
||||
instanceType: arch === "aarch64" ? "c8g.4xlarge" : "c7i.4xlarge",
|
||||
});
|
||||
@@ -326,8 +333,10 @@ function getLinkBunAgent(platform, options) {
|
||||
}
|
||||
|
||||
if (os === "windows") {
|
||||
return getEc2Agent(platform, options, {
|
||||
instanceType: arch === "aarch64" ? "r8g.large" : "r7i.large",
|
||||
// Cross-compile Windows ARM64 from x64 runners
|
||||
const agentPlatform = arch === "aarch64" ? { ...platform, arch: "x64" } : platform;
|
||||
return getEc2Agent(agentPlatform, options, {
|
||||
instanceType: "r7i.large",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -345,7 +354,7 @@ function getZigPlatform() {
|
||||
arch: "aarch64",
|
||||
abi: "musl",
|
||||
distro: "alpine",
|
||||
release: "3.22",
|
||||
release: "3.23",
|
||||
};
|
||||
}
|
||||
|
||||
@@ -456,6 +465,17 @@ function getBuildCommand(target, options, label) {
|
||||
return `bun run build:${buildProfile}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get extra flags needed when cross-compiling Windows ARM64 from x64.
|
||||
* Applied to C++ and link steps (not Zig, which has its own toolchain handling).
|
||||
*/
|
||||
function getWindowsArm64CrossFlags(target) {
|
||||
if (target.os === "windows" && target.arch === "aarch64") {
|
||||
return " --toolchain windows-aarch64";
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Platform} platform
|
||||
* @param {PipelineOptions} options
|
||||
@@ -463,6 +483,8 @@ function getBuildCommand(target, options, label) {
|
||||
*/
|
||||
function getBuildCppStep(platform, options) {
|
||||
const command = getBuildCommand(platform, options);
|
||||
const crossFlags = getWindowsArm64CrossFlags(platform);
|
||||
|
||||
return {
|
||||
key: `${getTargetKey(platform)}-build-cpp`,
|
||||
label: `${getTargetLabel(platform)} - build-cpp`,
|
||||
@@ -476,7 +498,7 @@ function getBuildCppStep(platform, options) {
|
||||
// We used to build the C++ dependencies and bun in separate steps.
|
||||
// However, as long as the zig build takes longer than both sequentially,
|
||||
// it's cheaper to run them in the same step. Can be revisited in the future.
|
||||
command: [`${command} --target bun`, `${command} --target dependencies`],
|
||||
command: [`${command}${crossFlags} --target bun`, `${command}${crossFlags} --target dependencies`],
|
||||
};
|
||||
}
|
||||
|
||||
@@ -533,7 +555,7 @@ function getLinkBunStep(platform, options) {
|
||||
ASAN_OPTIONS: "allow_user_segv_handler=1:disable_coredump=0:detect_leaks=0",
|
||||
...getBuildEnv(platform, options),
|
||||
},
|
||||
command: `${getBuildCommand(platform, options, "build-bun")} --target bun`,
|
||||
command: `${getBuildCommand(platform, options, "build-bun")}${getWindowsArm64CrossFlags(platform)} --target bun`,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1179,6 +1201,8 @@ async function getPipeline(options = {}) {
|
||||
buildImages || publishImages
|
||||
? [...buildPlatforms, ...testPlatforms]
|
||||
.filter(({ os }) => os !== "darwin")
|
||||
// Windows ARM64 cross-compiles from x64 runners, no separate image needed
|
||||
.filter(({ os, arch }) => !(os === "windows" && arch === "aarch64"))
|
||||
.map(platform => [getImageKey(platform), platform])
|
||||
: [],
|
||||
);
|
||||
|
||||
@@ -219,9 +219,8 @@ function create_release() {
|
||||
bun-windows-x64-profile.zip
|
||||
bun-windows-x64-baseline.zip
|
||||
bun-windows-x64-baseline-profile.zip
|
||||
# TODO: Enable when Windows ARM64 CI runners are ready
|
||||
# bun-windows-aarch64.zip
|
||||
# bun-windows-aarch64-profile.zip
|
||||
bun-windows-aarch64.zip
|
||||
bun-windows-aarch64-profile.zip
|
||||
)
|
||||
|
||||
function upload_artifact() {
|
||||
|
||||
4
.github/workflows/CLAUDE.md
vendored
4
.github/workflows/CLAUDE.md
vendored
@@ -33,8 +33,8 @@ The workflow runs all three formatters simultaneously:
|
||||
|
||||
#### 3. Tool Installation
|
||||
|
||||
##### Clang-format-19
|
||||
- Installs ONLY `clang-format-19` package (not the entire LLVM toolchain)
|
||||
##### Clang-format-21
|
||||
- Installs ONLY `clang-format-21` package (not the entire LLVM toolchain)
|
||||
- Uses `--no-install-recommends --no-install-suggests` to skip unnecessary packages
|
||||
- Quiet installation with `-qq` and `-o=Dpkg::Use-Pty=0`
|
||||
|
||||
|
||||
4
.github/workflows/format.yml
vendored
4
.github/workflows/format.yml
vendored
@@ -10,8 +10,8 @@ on:
|
||||
merge_group:
|
||||
env:
|
||||
BUN_VERSION: "1.3.2"
|
||||
LLVM_VERSION: "19.1.7"
|
||||
LLVM_VERSION_MAJOR: "19"
|
||||
LLVM_VERSION: "21.1.8"
|
||||
LLVM_VERSION_MAJOR: "21"
|
||||
|
||||
jobs:
|
||||
autofix:
|
||||
|
||||
@@ -35,7 +35,7 @@ $ sudo pacman -S base-devel cmake git go libiconv libtool make ninja pkg-config
|
||||
```
|
||||
|
||||
```bash#Fedora
|
||||
$ sudo dnf install cargo clang19 llvm19 lld19 cmake git golang libtool ninja-build pkg-config rustc ruby libatomic-static libstdc++-static sed unzip which libicu-devel 'perl(Math::BigInt)'
|
||||
$ sudo dnf install cargo clang21 llvm21 lld21 cmake git golang libtool ninja-build pkg-config rustc ruby libatomic-static libstdc++-static sed unzip which libicu-devel 'perl(Math::BigInt)'
|
||||
```
|
||||
|
||||
```bash#openSUSE Tumbleweed
|
||||
@@ -90,17 +90,17 @@ Our build scripts will automatically detect and use `ccache` if available. You c
|
||||
|
||||
## Install LLVM
|
||||
|
||||
Bun requires LLVM 19 (`clang` is part of LLVM). This version requirement is to match WebKit (precompiled), as mismatching versions will cause memory allocation failures at runtime. In most cases, you can install LLVM through your system package manager:
|
||||
Bun requires LLVM 21.1.8 (`clang` is part of LLVM). This version is enforced by the build system — mismatching versions will cause memory allocation failures at runtime. In most cases, you can install LLVM through your system package manager:
|
||||
|
||||
{% codetabs group="os" %}
|
||||
|
||||
```bash#macOS (Homebrew)
|
||||
$ brew install llvm@19
|
||||
$ brew install llvm@21
|
||||
```
|
||||
|
||||
```bash#Ubuntu/Debian
|
||||
$ # LLVM has an automatic installation script that is compatible with all versions of Ubuntu
|
||||
$ wget https://apt.llvm.org/llvm.sh -O - | sudo bash -s -- 19 all
|
||||
$ wget https://apt.llvm.org/llvm.sh -O - | sudo bash -s -- 21 all
|
||||
```
|
||||
|
||||
```bash#Arch
|
||||
@@ -112,17 +112,17 @@ $ sudo dnf install llvm clang lld-devel
|
||||
```
|
||||
|
||||
```bash#openSUSE Tumbleweed
|
||||
$ sudo zypper install clang19 lld19 llvm19
|
||||
$ sudo zypper install clang21 lld21 llvm21
|
||||
```
|
||||
|
||||
{% /codetabs %}
|
||||
|
||||
If none of the above solutions apply, you will have to install it [manually](https://github.com/llvm/llvm-project/releases/tag/llvmorg-19.1.7).
|
||||
If none of the above solutions apply, you will have to install it [manually](https://github.com/llvm/llvm-project/releases/tag/llvmorg-21.1.8).
|
||||
|
||||
Make sure Clang/LLVM 19 is in your path:
|
||||
Make sure Clang/LLVM 21 is in your path:
|
||||
|
||||
```bash
|
||||
$ which clang-19
|
||||
$ which clang-21
|
||||
```
|
||||
|
||||
If not, run this to manually add it:
|
||||
@@ -131,13 +131,13 @@ If not, run this to manually add it:
|
||||
|
||||
```bash#macOS (Homebrew)
|
||||
# use fish_add_path if you're using fish
|
||||
# use path+="$(brew --prefix llvm@19)/bin" if you are using zsh
|
||||
$ export PATH="$(brew --prefix llvm@19)/bin:$PATH"
|
||||
# use path+="$(brew --prefix llvm@21)/bin" if you are using zsh
|
||||
$ export PATH="$(brew --prefix llvm@21)/bin:$PATH"
|
||||
```
|
||||
|
||||
```bash#Arch
|
||||
# use fish_add_path if you're using fish
|
||||
$ export PATH="$PATH:/usr/lib/llvm19/bin"
|
||||
$ export PATH="$PATH:/usr/lib/llvm21/bin"
|
||||
```
|
||||
|
||||
{% /codetabs %}
|
||||
@@ -299,7 +299,7 @@ The issue may manifest when initially running `bun setup` as Clang being unable
|
||||
```
|
||||
The C++ compiler
|
||||
|
||||
"/usr/bin/clang++-19"
|
||||
"/usr/bin/clang++-21"
|
||||
|
||||
is not able to compile a simple test program.
|
||||
```
|
||||
|
||||
110
bench/snippets/abort-signal.mjs
Normal file
110
bench/snippets/abort-signal.mjs
Normal file
@@ -0,0 +1,110 @@
|
||||
// Benchmark for AbortController/AbortSignal abort() performance
|
||||
// Tests the optimization of skipping Event creation when no listeners are registered
|
||||
|
||||
import { bench, group, run } from "../runner.mjs";
|
||||
|
||||
// Warmup: ensure JIT compilation
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
const controller = new AbortController();
|
||||
controller.abort();
|
||||
}
|
||||
|
||||
group("AbortController.abort()", () => {
|
||||
bench("no listener", () => {
|
||||
const controller = new AbortController();
|
||||
controller.abort();
|
||||
});
|
||||
|
||||
bench("with addEventListener", () => {
|
||||
const controller = new AbortController();
|
||||
controller.signal.addEventListener("abort", () => {});
|
||||
controller.abort();
|
||||
});
|
||||
|
||||
bench("with onabort property", () => {
|
||||
const controller = new AbortController();
|
||||
controller.signal.onabort = () => {};
|
||||
controller.abort();
|
||||
});
|
||||
|
||||
bench("with 3 listeners", () => {
|
||||
const controller = new AbortController();
|
||||
controller.signal.addEventListener("abort", () => {});
|
||||
controller.signal.addEventListener("abort", () => {});
|
||||
controller.signal.addEventListener("abort", () => {});
|
||||
controller.abort();
|
||||
});
|
||||
});
|
||||
|
||||
group("AbortSignal static methods", () => {
|
||||
bench("AbortSignal.abort() - pre-aborted", () => {
|
||||
const signal = AbortSignal.abort();
|
||||
// Signal is already aborted, no event dispatch needed
|
||||
});
|
||||
|
||||
bench("AbortSignal.any([]) - empty array", () => {
|
||||
const signal = AbortSignal.any([]);
|
||||
});
|
||||
|
||||
bench("AbortSignal.any([signal, signal]) - 2 signals", () => {
|
||||
const a = new AbortController();
|
||||
const b = new AbortController();
|
||||
const signal = AbortSignal.any([a.signal, b.signal]);
|
||||
});
|
||||
|
||||
bench("AbortSignal.any() then abort - no listener", () => {
|
||||
const a = new AbortController();
|
||||
const b = new AbortController();
|
||||
const signal = AbortSignal.any([a.signal, b.signal]);
|
||||
a.abort();
|
||||
});
|
||||
|
||||
bench("AbortSignal.any() then abort - with listener", () => {
|
||||
const a = new AbortController();
|
||||
const b = new AbortController();
|
||||
const signal = AbortSignal.any([a.signal, b.signal]);
|
||||
signal.addEventListener("abort", () => {});
|
||||
a.abort();
|
||||
});
|
||||
});
|
||||
|
||||
group("AbortController creation only", () => {
|
||||
bench("new AbortController()", () => {
|
||||
const controller = new AbortController();
|
||||
});
|
||||
|
||||
bench("new AbortController() + access signal", () => {
|
||||
const controller = new AbortController();
|
||||
const signal = controller.signal;
|
||||
});
|
||||
});
|
||||
|
||||
group("AbortSignal.timeout()", () => {
|
||||
// Note: These don't actually wait for timeout, just measure creation overhead
|
||||
bench("AbortSignal.timeout(1000) creation", () => {
|
||||
const signal = AbortSignal.timeout(1000);
|
||||
});
|
||||
|
||||
bench("AbortSignal.timeout(0) creation", () => {
|
||||
const signal = AbortSignal.timeout(0);
|
||||
});
|
||||
});
|
||||
|
||||
group("abort with reason", () => {
|
||||
bench("abort() with no reason", () => {
|
||||
const controller = new AbortController();
|
||||
controller.abort();
|
||||
});
|
||||
|
||||
bench("abort() with string reason", () => {
|
||||
const controller = new AbortController();
|
||||
controller.abort("cancelled");
|
||||
});
|
||||
|
||||
bench("abort() with Error reason", () => {
|
||||
const controller = new AbortController();
|
||||
controller.abort(new Error("cancelled"));
|
||||
});
|
||||
});
|
||||
|
||||
await run();
|
||||
38
bench/snippets/buffer-slice.mjs
Normal file
38
bench/snippets/buffer-slice.mjs
Normal file
@@ -0,0 +1,38 @@
|
||||
// @runtime bun,node
|
||||
import { bench, group, run } from "../runner.mjs";
|
||||
|
||||
const small = Buffer.alloc(64, 0x42);
|
||||
const medium = Buffer.alloc(1024, 0x42);
|
||||
const large = Buffer.alloc(1024 * 1024, 0x42);
|
||||
|
||||
group("slice - no args", () => {
|
||||
bench("Buffer(64).slice()", () => small.slice());
|
||||
bench("Buffer(1024).slice()", () => medium.slice());
|
||||
bench("Buffer(1M).slice()", () => large.slice());
|
||||
});
|
||||
|
||||
group("slice - one int arg", () => {
|
||||
bench("Buffer(64).slice(10)", () => small.slice(10));
|
||||
bench("Buffer(1024).slice(10)", () => medium.slice(10));
|
||||
bench("Buffer(1M).slice(1024)", () => large.slice(1024));
|
||||
});
|
||||
|
||||
group("slice - two int args", () => {
|
||||
bench("Buffer(64).slice(10, 50)", () => small.slice(10, 50));
|
||||
bench("Buffer(1024).slice(10, 100)", () => medium.slice(10, 100));
|
||||
bench("Buffer(1M).slice(1024, 4096)", () => large.slice(1024, 4096));
|
||||
});
|
||||
|
||||
group("slice - negative args", () => {
|
||||
bench("Buffer(64).slice(-10)", () => small.slice(-10));
|
||||
bench("Buffer(1024).slice(-100, -10)", () => medium.slice(-100, -10));
|
||||
bench("Buffer(1M).slice(-4096, -1024)", () => large.slice(-4096, -1024));
|
||||
});
|
||||
|
||||
group("subarray - two int args", () => {
|
||||
bench("Buffer(64).subarray(10, 50)", () => small.subarray(10, 50));
|
||||
bench("Buffer(1024).subarray(10, 100)", () => medium.subarray(10, 100));
|
||||
bench("Buffer(1M).subarray(1024, 4096)", () => large.subarray(1024, 4096));
|
||||
});
|
||||
|
||||
await run();
|
||||
@@ -105,6 +105,10 @@ summary(() => {
|
||||
bench(`small (${small.length} chars) - Bun.markdown.render`, () => {
|
||||
return Bun.markdown.render(small, renderCallbacks);
|
||||
});
|
||||
|
||||
bench(`small (${small.length} chars) - Bun.markdown.react`, () => {
|
||||
return Bun.markdown.react(small);
|
||||
});
|
||||
}
|
||||
|
||||
bench(`small (${small.length} chars) - marked`, () => {
|
||||
@@ -125,6 +129,10 @@ summary(() => {
|
||||
bench(`medium (${medium.length} chars) - Bun.markdown.render`, () => {
|
||||
return Bun.markdown.render(medium, renderCallbacks);
|
||||
});
|
||||
|
||||
bench(`medium (${medium.length} chars) - Bun.markdown.react`, () => {
|
||||
return Bun.markdown.react(medium);
|
||||
});
|
||||
}
|
||||
|
||||
bench(`medium (${medium.length} chars) - marked`, () => {
|
||||
@@ -145,6 +153,10 @@ summary(() => {
|
||||
bench(`large (${large.length} chars) - Bun.markdown.render`, () => {
|
||||
return Bun.markdown.render(large, renderCallbacks);
|
||||
});
|
||||
|
||||
bench(`large (${large.length} chars) - Bun.markdown.react`, () => {
|
||||
return Bun.markdown.react(large);
|
||||
});
|
||||
}
|
||||
|
||||
bench(`large (${large.length} chars) - marked`, () => {
|
||||
|
||||
@@ -33,7 +33,23 @@ var testArray = [
|
||||
|
||||
import { bench, run } from "../runner.mjs";
|
||||
|
||||
bench("structuredClone(array)", () => structuredClone(testArray));
|
||||
bench("structuredClone(nested array)", () => structuredClone(testArray));
|
||||
bench("structuredClone(123)", () => structuredClone(123));
|
||||
bench("structuredClone({a: 123})", () => structuredClone({ a: 123 }));
|
||||
|
||||
// Array fast path targets
|
||||
var numbersSmall = Array.from({ length: 10 }, (_, i) => i);
|
||||
var numbersMedium = Array.from({ length: 100 }, (_, i) => i);
|
||||
var numbersLarge = Array.from({ length: 1000 }, (_, i) => i);
|
||||
var stringsSmall = Array.from({ length: 10 }, (_, i) => `item-${i}`);
|
||||
var stringsMedium = Array.from({ length: 100 }, (_, i) => `item-${i}`);
|
||||
var mixed = [1, "hello", true, null, undefined, 3.14, "world", false, 42, "test"];
|
||||
|
||||
bench("structuredClone([10 numbers])", () => structuredClone(numbersSmall));
|
||||
bench("structuredClone([100 numbers])", () => structuredClone(numbersMedium));
|
||||
bench("structuredClone([1000 numbers])", () => structuredClone(numbersLarge));
|
||||
bench("structuredClone([10 strings])", () => structuredClone(stringsSmall));
|
||||
bench("structuredClone([100 strings])", () => structuredClone(stringsMedium));
|
||||
bench("structuredClone([10 mixed])", () => structuredClone(mixed));
|
||||
|
||||
await run();
|
||||
|
||||
@@ -7,6 +7,13 @@ register_repository(
|
||||
4f4f5ef8ebc6e23cbf393428f0ab1b526773f7ac
|
||||
)
|
||||
|
||||
set(BORINGSSL_CMAKE_ARGS -DBUILD_SHARED_LIBS=OFF)
|
||||
|
||||
# Disable ASM on Windows ARM64 to avoid mixing non-ARM object files into ARM64 libs
|
||||
if(WIN32 AND CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64|AARCH64")
|
||||
list(APPEND BORINGSSL_CMAKE_ARGS -DOPENSSL_NO_ASM=1)
|
||||
endif()
|
||||
|
||||
register_cmake_command(
|
||||
TARGET
|
||||
boringssl
|
||||
@@ -15,7 +22,7 @@ register_cmake_command(
|
||||
ssl
|
||||
decrepit
|
||||
ARGS
|
||||
-DBUILD_SHARED_LIBS=OFF
|
||||
${BORINGSSL_CMAKE_ARGS}
|
||||
INCLUDES
|
||||
include
|
||||
)
|
||||
|
||||
@@ -902,19 +902,6 @@ target_include_directories(${bun} PRIVATE
|
||||
${NODEJS_HEADERS_PATH}/include/node
|
||||
)
|
||||
|
||||
# --- Python ---
|
||||
set(PYTHON_ROOT /Users/dylan/code/bun/vendor/cpython/install)
|
||||
set(PYTHON_VERSION_MAJOR 3)
|
||||
set(PYTHON_VERSION_MINOR 13)
|
||||
set(PYTHON_VERSION "${PYTHON_VERSION_MAJOR}.${PYTHON_VERSION_MINOR}")
|
||||
target_include_directories(${bun} PRIVATE
|
||||
${PYTHON_ROOT}/include/python${PYTHON_VERSION}
|
||||
)
|
||||
# Pass PYTHON_ROOT to C++ so BunPython.cpp can use it for runtime paths
|
||||
target_compile_definitions(${bun} PRIVATE
|
||||
PYTHON_ROOT="${PYTHON_ROOT}"
|
||||
)
|
||||
|
||||
if(NOT WIN32)
|
||||
target_include_directories(${bun} PRIVATE ${CWD}/src/bun.js/bindings/libuv)
|
||||
endif()
|
||||
@@ -1029,6 +1016,7 @@ if(NOT WIN32)
|
||||
-Wno-unused-function
|
||||
-Wno-c++23-lambda-attributes
|
||||
-Wno-nullability-completeness
|
||||
-Wno-character-conversion
|
||||
-Werror
|
||||
)
|
||||
else()
|
||||
@@ -1046,6 +1034,7 @@ if(NOT WIN32)
|
||||
-Werror=sometimes-uninitialized
|
||||
-Wno-c++23-lambda-attributes
|
||||
-Wno-nullability-completeness
|
||||
-Wno-character-conversion
|
||||
-Werror
|
||||
)
|
||||
|
||||
@@ -1074,6 +1063,7 @@ else()
|
||||
-Wno-inconsistent-dllimport
|
||||
-Wno-incompatible-pointer-types
|
||||
-Wno-deprecated-declarations
|
||||
-Wno-character-conversion
|
||||
)
|
||||
endif()
|
||||
|
||||
@@ -1149,6 +1139,15 @@ if(LINUX)
|
||||
-Wl,--wrap=pow
|
||||
-Wl,--wrap=powf
|
||||
)
|
||||
|
||||
# Disable LTO for workaround-missing-symbols.cpp to prevent LLD 21 from emitting
|
||||
# glibc versioned symbol names (e.g. exp@GLIBC_2.17) from .symver directives into
|
||||
# the .lto_discard assembler directive, which fails to parse the '@' character.
|
||||
if(ENABLE_LTO)
|
||||
set_source_files_properties(${CWD}/src/bun.js/bindings/workaround-missing-symbols.cpp
|
||||
PROPERTIES COMPILE_OPTIONS "-fno-lto"
|
||||
)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT ABI STREQUAL "musl")
|
||||
@@ -1327,19 +1326,6 @@ if(APPLE)
|
||||
target_compile_definitions(${bun} PRIVATE U_DISABLE_RENAMING=1)
|
||||
endif()
|
||||
|
||||
# --- Python ---
|
||||
# Link against shared Python library so extension modules can find symbols
|
||||
if(APPLE)
|
||||
target_link_libraries(${bun} PRIVATE
|
||||
"${PYTHON_ROOT}/lib/libpython${PYTHON_VERSION}.dylib"
|
||||
"-framework CoreFoundation"
|
||||
)
|
||||
else()
|
||||
target_link_libraries(${bun} PRIVATE
|
||||
"${PYTHON_ROOT}/lib/libpython${PYTHON_VERSION}.so"
|
||||
)
|
||||
endif()
|
||||
|
||||
if(USE_STATIC_SQLITE)
|
||||
target_compile_definitions(${bun} PRIVATE LAZY_LOAD_SQLITE=0)
|
||||
else()
|
||||
@@ -1471,6 +1457,8 @@ if(NOT BUN_CPP_ONLY)
|
||||
# ==856230==See https://github.com/google/sanitizers/issues/856 for possible workarounds.
|
||||
# the linked issue refers to very old kernels but this still happens to us on modern ones.
|
||||
# disabling ASLR to run the binary works around it
|
||||
# Skip post-build test/features when cross-compiling (can't run the target binary on the host)
|
||||
if(NOT CMAKE_CROSSCOMPILING)
|
||||
set(TEST_BUN_COMMAND_BASE ${BUILD_PATH}/${bunExe} --revision)
|
||||
set(TEST_BUN_COMMAND_ENV_WRAP
|
||||
${CMAKE_COMMAND} -E env BUN_DEBUG_QUIET_LOGS=1)
|
||||
@@ -1519,6 +1507,7 @@ if(NOT BUN_CPP_ONLY)
|
||||
${BUILD_PATH}/features.json
|
||||
)
|
||||
endif()
|
||||
endif() # NOT CMAKE_CROSSCOMPILING
|
||||
|
||||
if(CMAKE_HOST_APPLE AND bunStrip)
|
||||
register_command(
|
||||
@@ -1565,7 +1554,10 @@ if(NOT BUN_CPP_ONLY)
|
||||
string(REPLACE bun ${bunTriplet} bunPath ${bun})
|
||||
endif()
|
||||
|
||||
set(bunFiles ${bunExe} features.json)
|
||||
set(bunFiles ${bunExe})
|
||||
if(NOT CMAKE_CROSSCOMPILING)
|
||||
list(APPEND bunFiles features.json)
|
||||
endif()
|
||||
if(WIN32)
|
||||
list(APPEND bunFiles ${bun}.pdb)
|
||||
elseif(APPLE)
|
||||
|
||||
@@ -26,6 +26,12 @@ if(RELEASE)
|
||||
list(APPEND LOLHTML_BUILD_ARGS --release)
|
||||
endif()
|
||||
|
||||
# Cross-compilation: tell cargo to target ARM64
|
||||
if(WIN32 AND CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64|AARCH64")
|
||||
list(APPEND LOLHTML_BUILD_ARGS --target aarch64-pc-windows-msvc)
|
||||
set(LOLHTML_LIBRARY ${LOLHTML_BUILD_PATH}/aarch64-pc-windows-msvc/${LOLHTML_BUILD_TYPE}/${CMAKE_STATIC_LIBRARY_PREFIX}lolhtml${CMAKE_STATIC_LIBRARY_SUFFIX})
|
||||
endif()
|
||||
|
||||
# Windows requires unwind tables, apparently.
|
||||
if (NOT WIN32)
|
||||
# The encoded escape sequences are intentional. They're how you delimit multiple arguments in a single environment variable.
|
||||
@@ -51,11 +57,18 @@ if(WIN32)
|
||||
if(MSVC_VERSIONS)
|
||||
list(GET MSVC_VERSIONS -1 MSVC_LATEST) # Get the latest version
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64")
|
||||
set(MSVC_LINK_PATH "${MSVC_LATEST}/bin/HostARM64/arm64/link.exe")
|
||||
# Use Hostx64/arm64 for cross-compilation from x64, fall back to native
|
||||
if(EXISTS "${MSVC_LATEST}/bin/Hostx64/arm64/link.exe")
|
||||
set(MSVC_LINK_PATH "${MSVC_LATEST}/bin/Hostx64/arm64/link.exe")
|
||||
else()
|
||||
set(MSVC_LINK_PATH "${MSVC_LATEST}/bin/HostARM64/arm64/link.exe")
|
||||
endif()
|
||||
set(CARGO_LINKER_VAR "CARGO_TARGET_AARCH64_PC_WINDOWS_MSVC_LINKER")
|
||||
set(MSVC_LIB_ARCH "arm64")
|
||||
else()
|
||||
set(MSVC_LINK_PATH "${MSVC_LATEST}/bin/Hostx64/x64/link.exe")
|
||||
set(CARGO_LINKER_VAR "CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER")
|
||||
set(MSVC_LIB_ARCH "x64")
|
||||
endif()
|
||||
if(EXISTS "${MSVC_LINK_PATH}")
|
||||
list(APPEND LOLHTML_ENV "${CARGO_LINKER_VAR}=${MSVC_LINK_PATH}")
|
||||
|
||||
@@ -4,7 +4,7 @@ register_repository(
|
||||
REPOSITORY
|
||||
oven-sh/mimalloc
|
||||
COMMIT
|
||||
ffa38ab8ac914f9eb7af75c1f8ad457643dc14f2
|
||||
1beadf9651a7bfdec6b5367c380ecc3fe1c40d1a
|
||||
)
|
||||
|
||||
set(MIMALLOC_CMAKE_ARGS
|
||||
@@ -14,7 +14,7 @@ set(MIMALLOC_CMAKE_ARGS
|
||||
-DMI_BUILD_TESTS=OFF
|
||||
-DMI_USE_CXX=ON
|
||||
-DMI_SKIP_COLLECT_ON_EXIT=ON
|
||||
|
||||
|
||||
# ```
|
||||
# ❯ mimalloc_allow_large_os_pages=0 BUN_PORT=3004 mem bun http-hello.js
|
||||
# Started development server: http://localhost:3004
|
||||
@@ -51,7 +51,7 @@ if(ENABLE_ASAN)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_DEBUG_UBSAN=ON)
|
||||
elseif(APPLE OR LINUX)
|
||||
if(APPLE)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OVERRIDE=OFF)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OVERRIDE=OFF)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OSX_ZONE=OFF)
|
||||
list(APPEND MIMALLOC_CMAKE_ARGS -DMI_OSX_INTERPOSE=OFF)
|
||||
else()
|
||||
@@ -87,9 +87,9 @@ endif()
|
||||
|
||||
if(WIN32)
|
||||
if(DEBUG)
|
||||
set(MIMALLOC_LIBRARY mimalloc-debug)
|
||||
set(MIMALLOC_LIBRARY mimalloc-static-debug)
|
||||
else()
|
||||
set(MIMALLOC_LIBRARY mimalloc)
|
||||
set(MIMALLOC_LIBRARY mimalloc-static)
|
||||
endif()
|
||||
elseif(DEBUG)
|
||||
if (ENABLE_ASAN)
|
||||
|
||||
@@ -3,18 +3,35 @@ set(CMAKE_SYSTEM_PROCESSOR aarch64)
|
||||
|
||||
set(CMAKE_C_COMPILER_WORKS ON)
|
||||
set(CMAKE_CXX_COMPILER_WORKS ON)
|
||||
set(CMAKE_CROSSCOMPILING ON)
|
||||
|
||||
# Force ARM64 architecture ID - this is what CMake uses to determine /machine: flag
|
||||
set(MSVC_C_ARCHITECTURE_ID ARM64 CACHE INTERNAL "")
|
||||
set(MSVC_CXX_ARCHITECTURE_ID ARM64 CACHE INTERNAL "")
|
||||
# The rest only applies when building on Windows (C++ and link steps).
|
||||
# The Zig step runs on Linux and only needs CMAKE_SYSTEM_NAME/PROCESSOR above.
|
||||
if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows")
|
||||
|
||||
# CMake 4.0+ policy CMP0197 controls how MSVC machine type flags are handled
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0197 NEW CACHE INTERNAL "")
|
||||
# Ensure clang/clang-cl targets Windows ARM64 (otherwise ARM64-specific flags like
|
||||
# -march=armv8-a are rejected as x86-only).
|
||||
set(CMAKE_C_COMPILER_TARGET aarch64-pc-windows-msvc CACHE STRING "" FORCE)
|
||||
set(CMAKE_CXX_COMPILER_TARGET aarch64-pc-windows-msvc CACHE STRING "" FORCE)
|
||||
|
||||
# Clear any inherited static linker flags that might have wrong machine types
|
||||
set(CMAKE_STATIC_LINKER_FLAGS "" CACHE STRING "" FORCE)
|
||||
# ARM64 has lock-free atomics (highway's FindAtomics check can't run ARM64 test binary on x64)
|
||||
set(ATOMICS_LOCK_FREE_INSTRUCTIONS TRUE CACHE BOOL "" FORCE)
|
||||
set(HAVE_CXX_ATOMICS_WITHOUT_LIB TRUE CACHE BOOL "" FORCE)
|
||||
set(HAVE_CXX_ATOMICS64_WITHOUT_LIB TRUE CACHE BOOL "" FORCE)
|
||||
|
||||
# Use wrapper script for llvm-lib that strips /machine:x64 flags
|
||||
# This works around CMake 4.1.0 bug where both ARM64 and x64 machine flags are added
|
||||
get_filename_component(_TOOLCHAIN_DIR "${CMAKE_CURRENT_LIST_DIR}" DIRECTORY)
|
||||
set(CMAKE_AR "${_TOOLCHAIN_DIR}/scripts/llvm-lib-wrapper.bat" CACHE FILEPATH "" FORCE)
|
||||
# Force ARM64 architecture ID - this is what CMake uses to determine /machine: flag
|
||||
set(MSVC_C_ARCHITECTURE_ID ARM64 CACHE INTERNAL "")
|
||||
set(MSVC_CXX_ARCHITECTURE_ID ARM64 CACHE INTERNAL "")
|
||||
|
||||
# CMake 4.0+ policy CMP0197 controls how MSVC machine type flags are handled
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0197 NEW CACHE INTERNAL "")
|
||||
|
||||
# Clear any inherited static linker flags that might have wrong machine types
|
||||
set(CMAKE_STATIC_LINKER_FLAGS "" CACHE STRING "" FORCE)
|
||||
|
||||
# Use wrapper script for llvm-lib that strips /machine:x64 flags
|
||||
# This works around CMake 4.1.0 bug where both ARM64 and x64 machine flags are added
|
||||
get_filename_component(_TOOLCHAIN_DIR "${CMAKE_CURRENT_LIST_DIR}" DIRECTORY)
|
||||
set(CMAKE_AR "${_TOOLCHAIN_DIR}/scripts/llvm-lib-wrapper.bat" CACHE FILEPATH "" FORCE)
|
||||
|
||||
endif()
|
||||
|
||||
@@ -12,13 +12,7 @@ if(NOT ENABLE_LLVM)
|
||||
return()
|
||||
endif()
|
||||
|
||||
# LLVM 21 is required for Windows ARM64 (first version with ARM64 Windows builds)
|
||||
# Other platforms use LLVM 19.1.7
|
||||
if(WIN32 AND CMAKE_SYSTEM_PROCESSOR MATCHES "ARM64|aarch64|AARCH64")
|
||||
set(DEFAULT_LLVM_VERSION "21.1.8")
|
||||
else()
|
||||
set(DEFAULT_LLVM_VERSION "19.1.7")
|
||||
endif()
|
||||
set(DEFAULT_LLVM_VERSION "21.1.8")
|
||||
|
||||
optionx(LLVM_VERSION STRING "The version of LLVM to use" DEFAULT ${DEFAULT_LLVM_VERSION})
|
||||
|
||||
@@ -27,6 +21,8 @@ if(USE_LLVM_VERSION)
|
||||
set(LLVM_VERSION_MAJOR ${CMAKE_MATCH_1})
|
||||
set(LLVM_VERSION_MINOR ${CMAKE_MATCH_2})
|
||||
set(LLVM_VERSION_PATCH ${CMAKE_MATCH_3})
|
||||
# Accept any LLVM version within the same major.minor range (e.g. Alpine 3.23 ships 21.1.2)
|
||||
set(LLVM_VERSION_RANGE ">=${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}.0 <${LLVM_VERSION_MAJOR}.${LLVM_VERSION_MINOR}.99")
|
||||
endif()
|
||||
|
||||
set(LLVM_PATHS)
|
||||
@@ -54,6 +50,11 @@ if(APPLE)
|
||||
list(APPEND LLVM_PATHS ${HOMEBREW_PREFIX}/opt/llvm/bin)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
# Prefer standalone LLVM over VS-bundled (standalone supports cross-compilation)
|
||||
list(APPEND LLVM_PATHS "C:/Program Files/LLVM/bin")
|
||||
endif()
|
||||
|
||||
if(UNIX)
|
||||
list(APPEND LLVM_PATHS /usr/lib/llvm/bin)
|
||||
|
||||
@@ -78,14 +79,12 @@ macro(find_llvm_command variable command)
|
||||
)
|
||||
endif()
|
||||
|
||||
math(EXPR LLVM_VERSION_NEXT_MAJOR "${LLVM_VERSION_MAJOR} + 1")
|
||||
|
||||
find_command(
|
||||
VARIABLE ${variable}
|
||||
VERSION_VARIABLE LLVM_VERSION
|
||||
COMMAND ${commands}
|
||||
PATHS ${LLVM_PATHS}
|
||||
VERSION ">=${LLVM_VERSION_MAJOR}.1.0 <${LLVM_VERSION_NEXT_MAJOR}.0.0"
|
||||
VERSION "${LLVM_VERSION_RANGE}"
|
||||
)
|
||||
list(APPEND CMAKE_ARGS -D${variable}=${${variable}})
|
||||
endmacro()
|
||||
|
||||
@@ -6,7 +6,7 @@ option(WEBKIT_LOCAL "If a local version of WebKit should be used instead of down
|
||||
option(WEBKIT_BUILD_TYPE "The build type for local WebKit (defaults to CMAKE_BUILD_TYPE)")
|
||||
|
||||
if(NOT WEBKIT_VERSION)
|
||||
set(WEBKIT_VERSION 515344bc5d65aa2d4f9ff277b5fb944f0e051dcd)
|
||||
set(WEBKIT_VERSION 8af7958ff0e2a4787569edf64641a1ae7cfe074a)
|
||||
endif()
|
||||
|
||||
# Use preview build URL for Windows ARM64 until the fix is merged to main
|
||||
|
||||
@@ -35,7 +35,7 @@ winget install "Visual Studio Community 2022" --override "--add Microsoft.Visual
|
||||
|
||||
After Visual Studio, you need the following:
|
||||
|
||||
- LLVM (19.1.7 for x64, 21.1.8 for ARM64)
|
||||
- LLVM 21.1.8
|
||||
- Go
|
||||
- Rust
|
||||
- NASM
|
||||
@@ -51,7 +51,7 @@ After Visual Studio, you need the following:
|
||||
irm https://get.scoop.sh | iex
|
||||
scoop install nodejs-lts go rust nasm ruby perl ccache
|
||||
# scoop seems to be buggy if you install llvm and the rest at the same time
|
||||
scoop install llvm@19.1.7
|
||||
scoop install llvm@21.1.8
|
||||
```
|
||||
|
||||
For Windows ARM64, download LLVM 21.1.8 directly from GitHub releases (first version with ARM64 Windows builds):
|
||||
|
||||
@@ -40,7 +40,7 @@ sudo pacman -S base-devel cmake git go libiconv libtool make ninja pkg-config py
|
||||
```
|
||||
|
||||
```bash Fedora
|
||||
sudo dnf install cargo clang19 llvm19 lld19 cmake git golang libtool ninja-build pkg-config rustc ruby libatomic-static libstdc++-static sed unzip which libicu-devel 'perl(Math::BigInt)'
|
||||
sudo dnf install cargo clang21 llvm21 lld21 cmake git golang libtool ninja-build pkg-config rustc ruby libatomic-static libstdc++-static sed unzip which libicu-devel 'perl(Math::BigInt)'
|
||||
```
|
||||
|
||||
```bash openSUSE Tumbleweed
|
||||
@@ -95,17 +95,17 @@ Our build scripts will automatically detect and use `ccache` if available. You c
|
||||
|
||||
## Install LLVM
|
||||
|
||||
Bun requires LLVM 19 (`clang` is part of LLVM). This version requirement is to match WebKit (precompiled), as mismatching versions will cause memory allocation failures at runtime. In most cases, you can install LLVM through your system package manager:
|
||||
Bun requires LLVM 21.1.8 (`clang` is part of LLVM). This version is enforced by the build system — mismatching versions will cause memory allocation failures at runtime. In most cases, you can install LLVM through your system package manager:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```bash macOS (Homebrew)
|
||||
brew install llvm@19
|
||||
brew install llvm@21
|
||||
```
|
||||
|
||||
```bash Ubuntu/Debian
|
||||
# LLVM has an automatic installation script that is compatible with all versions of Ubuntu
|
||||
wget https://apt.llvm.org/llvm.sh -O - | sudo bash -s -- 19 all
|
||||
wget https://apt.llvm.org/llvm.sh -O - | sudo bash -s -- 21 all
|
||||
```
|
||||
|
||||
```bash Arch
|
||||
@@ -117,17 +117,17 @@ sudo dnf install llvm clang lld-devel
|
||||
```
|
||||
|
||||
```bash openSUSE Tumbleweed
|
||||
sudo zypper install clang19 lld19 llvm19
|
||||
sudo zypper install clang21 lld21 llvm21
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
If none of the above solutions apply, you will have to install it [manually](https://github.com/llvm/llvm-project/releases/tag/llvmorg-19.1.7).
|
||||
If none of the above solutions apply, you will have to install it [manually](https://github.com/llvm/llvm-project/releases/tag/llvmorg-21.1.8).
|
||||
|
||||
Make sure Clang/LLVM 19 is in your path:
|
||||
Make sure Clang/LLVM 21 is in your path:
|
||||
|
||||
```bash
|
||||
which clang-19
|
||||
which clang-21
|
||||
```
|
||||
|
||||
If not, run this to manually add it:
|
||||
@@ -136,13 +136,13 @@ If not, run this to manually add it:
|
||||
|
||||
```bash macOS (Homebrew)
|
||||
# use fish_add_path if you're using fish
|
||||
# use path+="$(brew --prefix llvm@19)/bin" if you are using zsh
|
||||
export PATH="$(brew --prefix llvm@19)/bin:$PATH"
|
||||
# use path+="$(brew --prefix llvm@21)/bin" if you are using zsh
|
||||
export PATH="$(brew --prefix llvm@21)/bin:$PATH"
|
||||
```
|
||||
|
||||
```bash Arch
|
||||
# use fish_add_path if you're using fish
|
||||
export PATH="$PATH:/usr/lib/llvm19/bin"
|
||||
export PATH="$PATH:/usr/lib/llvm21/bin"
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
@@ -309,7 +309,7 @@ The issue may manifest when initially running `bun setup` as Clang being unable
|
||||
```txt
|
||||
The C++ compiler
|
||||
|
||||
"/usr/bin/clang++-19"
|
||||
"/usr/bin/clang++-21"
|
||||
|
||||
is not able to compile a simple test program.
|
||||
```
|
||||
|
||||
@@ -3,9 +3,9 @@ title: Markdown
|
||||
description: Parse and render Markdown with Bun's built-in Markdown API, supporting GFM extensions and custom rendering callbacks
|
||||
---
|
||||
|
||||
{% callout type="note" %}
|
||||
**Unstable API** — This API is under active development and may change in future versions of Bun.
|
||||
{% /callout %}
|
||||
<Callout type="note">
|
||||
**Unstable API** — This API is under active development and may change in future versions of Bun.
|
||||
</Callout>
|
||||
|
||||
Bun includes a fast, built-in Markdown parser written in Zig. It supports GitHub Flavored Markdown (GFM) extensions and provides three APIs:
|
||||
|
||||
|
||||
10
flake.nix
10
flake.nix
@@ -26,10 +26,10 @@
|
||||
};
|
||||
};
|
||||
|
||||
# LLVM 19 - matching the bootstrap script (targets 19.1.7, actual version from nixpkgs-unstable)
|
||||
llvm = pkgs.llvm_19;
|
||||
clang = pkgs.clang_19;
|
||||
lld = pkgs.lld_19;
|
||||
# LLVM 21 - matching the bootstrap script (targets 21.1.8, actual version from nixpkgs-unstable)
|
||||
llvm = pkgs.llvm_21;
|
||||
clang = pkgs.clang_21;
|
||||
lld = pkgs.lld_21;
|
||||
|
||||
# Node.js 24 - matching the bootstrap script (targets 24.3.0, actual version from nixpkgs-unstable)
|
||||
nodejs = pkgs.nodejs_24;
|
||||
@@ -42,7 +42,7 @@
|
||||
pkgs.pkg-config
|
||||
pkgs.ccache
|
||||
|
||||
# Compilers and toolchain - version pinned to LLVM 19
|
||||
# Compilers and toolchain - version pinned to LLVM 21
|
||||
clang
|
||||
llvm
|
||||
lld
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"private": true,
|
||||
"name": "bun",
|
||||
"version": "1.3.9",
|
||||
"version": "1.3.10",
|
||||
"workspaces": [
|
||||
"./packages/bun-types",
|
||||
"./packages/@types/bun"
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# Bun
|
||||
|
||||
This is the Windows ARM64 binary for Bun, a fast all-in-one JavaScript runtime. https://bun.com
|
||||
@@ -95,6 +95,12 @@ export const platforms: Platform[] = [
|
||||
bin: "bun-windows-x64-baseline",
|
||||
exe: "bin/bun.exe",
|
||||
},
|
||||
// {
|
||||
// os: "win32",
|
||||
// arch: "arm64",
|
||||
// bin: "bun-windows-aarch64",
|
||||
// exe: "bin/bun.exe",
|
||||
// },
|
||||
];
|
||||
|
||||
export const supportedPlatforms: Platform[] = platforms
|
||||
|
||||
89
packages/bun-types/bun.d.ts
vendored
89
packages/bun-types/bun.d.ts
vendored
@@ -2154,7 +2154,7 @@ declare module "bun" {
|
||||
interface Hash {
|
||||
wyhash: (data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, seed?: bigint) => bigint;
|
||||
adler32: (data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer) => number;
|
||||
crc32: (data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer) => number;
|
||||
crc32: (data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, seed?: number) => number;
|
||||
cityHash32: (data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer) => number;
|
||||
cityHash64: (data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, seed?: bigint) => bigint;
|
||||
xxHash32: (data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer, seed?: number) => number;
|
||||
@@ -2438,14 +2438,19 @@ declare module "bun" {
|
||||
| `bun-linux-${Architecture}-${Libc}`
|
||||
| `bun-linux-${Architecture}-${SIMD}`
|
||||
| `bun-linux-${Architecture}-${SIMD}-${Libc}`
|
||||
| "bun-windows-x64"
|
||||
| `bun-windows-${Architecture}`
|
||||
| `bun-windows-x64-${SIMD}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see [Bun.build API docs](https://bun.com/docs/bundler#api)
|
||||
*/
|
||||
interface BuildConfigBase {
|
||||
interface BuildConfig {
|
||||
/**
|
||||
* Enable code splitting
|
||||
*/
|
||||
splitting?: boolean;
|
||||
|
||||
/**
|
||||
* List of entrypoints, usually file paths
|
||||
*/
|
||||
@@ -2774,6 +2779,33 @@ declare module "bun" {
|
||||
metafile?: boolean;
|
||||
|
||||
outdir?: string;
|
||||
|
||||
/**
|
||||
* Create a standalone executable
|
||||
*
|
||||
* When `true`, creates an executable for the current platform.
|
||||
* When a target string, creates an executable for that platform.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Create executable for current platform
|
||||
* await Bun.build({
|
||||
* entrypoints: ['./app.js'],
|
||||
* compile: {
|
||||
* target: 'linux-x64',
|
||||
* },
|
||||
* outfile: './my-app'
|
||||
* });
|
||||
*
|
||||
* // Cross-compile for Linux x64
|
||||
* await Bun.build({
|
||||
* entrypoints: ['./app.js'],
|
||||
* compile: 'linux-x64',
|
||||
* outfile: './my-app'
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
compile?: boolean | Bun.Build.CompileTarget | CompileBuildOptions;
|
||||
}
|
||||
|
||||
interface CompileBuildOptions {
|
||||
@@ -2832,57 +2864,6 @@ declare module "bun" {
|
||||
};
|
||||
}
|
||||
|
||||
// Compile build config - uses outfile for executable output
|
||||
interface CompileBuildConfig extends BuildConfigBase {
|
||||
/**
|
||||
* Create a standalone executable
|
||||
*
|
||||
* When `true`, creates an executable for the current platform.
|
||||
* When a target string, creates an executable for that platform.
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* // Create executable for current platform
|
||||
* await Bun.build({
|
||||
* entrypoints: ['./app.js'],
|
||||
* compile: {
|
||||
* target: 'linux-x64',
|
||||
* },
|
||||
* outfile: './my-app'
|
||||
* });
|
||||
*
|
||||
* // Cross-compile for Linux x64
|
||||
* await Bun.build({
|
||||
* entrypoints: ['./app.js'],
|
||||
* compile: 'linux-x64',
|
||||
* outfile: './my-app'
|
||||
* });
|
||||
* ```
|
||||
*/
|
||||
compile: boolean | Bun.Build.CompileTarget | CompileBuildOptions;
|
||||
|
||||
/**
|
||||
* Splitting is not currently supported with `.compile`
|
||||
*/
|
||||
splitting?: never;
|
||||
}
|
||||
|
||||
interface NormalBuildConfig extends BuildConfigBase {
|
||||
/**
|
||||
* Enable code splitting
|
||||
*
|
||||
* This does not currently work with {@link CompileBuildConfig.compile `compile`}
|
||||
*
|
||||
* @default true
|
||||
*/
|
||||
splitting?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* @see [Bun.build API docs](https://bun.com/docs/bundler#api)
|
||||
*/
|
||||
type BuildConfig = CompileBuildConfig | NormalBuildConfig;
|
||||
|
||||
/**
|
||||
* Hash and verify passwords using argon2 or bcrypt
|
||||
*
|
||||
|
||||
1
packages/bun-types/test.d.ts
vendored
1
packages/bun-types/test.d.ts
vendored
@@ -2179,6 +2179,7 @@ declare module "bun:test" {
|
||||
mockResolvedValueOnce(value: ResolveType<T>): this;
|
||||
mockRejectedValue(value: RejectType<T>): this;
|
||||
mockRejectedValueOnce(value: RejectType<T>): this;
|
||||
[Symbol.dispose](): void;
|
||||
}
|
||||
|
||||
// export type MockMetadata<T, MetadataType = MockMetadataType> = {
|
||||
|
||||
@@ -188,6 +188,103 @@ struct us_loop_t *us_create_loop(void *hint, void (*wakeup_cb)(struct us_loop_t
|
||||
return loop;
|
||||
}
|
||||
|
||||
/* Shared dispatch loop for both us_loop_run and us_loop_run_bun_tick */
|
||||
static void us_internal_dispatch_ready_polls(struct us_loop_t *loop) {
|
||||
#ifdef LIBUS_USE_EPOLL
|
||||
for (loop->current_ready_poll = 0; loop->current_ready_poll < loop->num_ready_polls; loop->current_ready_poll++) {
|
||||
struct us_poll_t *poll = GET_READY_POLL(loop, loop->current_ready_poll);
|
||||
if (LIKELY(poll)) {
|
||||
if (CLEAR_POINTER_TAG(poll) != poll) {
|
||||
Bun__internal_dispatch_ready_poll(loop, poll);
|
||||
continue;
|
||||
}
|
||||
int events = loop->ready_polls[loop->current_ready_poll].events;
|
||||
const int error = events & EPOLLERR;
|
||||
const int eof = events & EPOLLHUP;
|
||||
events &= us_poll_events(poll);
|
||||
if (events || error || eof) {
|
||||
us_internal_dispatch_ready_poll(poll, error, eof, events);
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
/* Kqueue delivers each filter (READ, WRITE, TIMER, etc.) as a separate kevent,
|
||||
* so the same fd/poll can appear twice in ready_polls. We coalesce them into a
|
||||
* single set of flags per poll before dispatching, matching epoll's behavior
|
||||
* where each fd appears once with a combined bitmask. */
|
||||
struct kevent_flags {
|
||||
uint8_t readable : 1;
|
||||
uint8_t writable : 1;
|
||||
uint8_t error : 1;
|
||||
uint8_t eof : 1;
|
||||
uint8_t skip : 1;
|
||||
uint8_t _pad : 3;
|
||||
};
|
||||
|
||||
_Static_assert(sizeof(struct kevent_flags) == 1, "kevent_flags must be 1 byte");
|
||||
struct kevent_flags coalesced[LIBUS_MAX_READY_POLLS]; /* no zeroing needed — every index is written in the first pass */
|
||||
|
||||
/* First pass: decode kevents and coalesce same-poll entries */
|
||||
for (int i = 0; i < loop->num_ready_polls; i++) {
|
||||
struct us_poll_t *poll = GET_READY_POLL(loop, i);
|
||||
if (!poll || CLEAR_POINTER_TAG(poll) != poll) {
|
||||
coalesced[i] = (struct kevent_flags){ .skip = 1 };
|
||||
continue;
|
||||
}
|
||||
|
||||
const int16_t filter = loop->ready_polls[i].filter;
|
||||
const uint16_t flags = loop->ready_polls[i].flags;
|
||||
struct kevent_flags bits = {
|
||||
.readable = (filter == EVFILT_READ || filter == EVFILT_TIMER || filter == EVFILT_MACHPORT),
|
||||
.writable = (filter == EVFILT_WRITE),
|
||||
.error = !!(flags & EV_ERROR),
|
||||
.eof = !!(flags & EV_EOF),
|
||||
};
|
||||
|
||||
/* Look backward for a prior entry with the same poll to coalesce into.
|
||||
* Kqueue returns at most 2 kevents per fd (READ + WRITE). */
|
||||
int merged = 0;
|
||||
for (int j = i - 1; j >= 0; j--) {
|
||||
if (!coalesced[j].skip && GET_READY_POLL(loop, j) == poll) {
|
||||
coalesced[j].readable |= bits.readable;
|
||||
coalesced[j].writable |= bits.writable;
|
||||
coalesced[j].error |= bits.error;
|
||||
coalesced[j].eof |= bits.eof;
|
||||
coalesced[i] = (struct kevent_flags){ .skip = 1 };
|
||||
merged = 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!merged) {
|
||||
coalesced[i] = bits;
|
||||
}
|
||||
}
|
||||
|
||||
/* Second pass: dispatch everything in order — tagged pointers and coalesced events */
|
||||
for (loop->current_ready_poll = 0; loop->current_ready_poll < loop->num_ready_polls; loop->current_ready_poll++) {
|
||||
struct us_poll_t *poll = GET_READY_POLL(loop, loop->current_ready_poll);
|
||||
if (!poll) continue;
|
||||
|
||||
/* Tagged pointers (FilePoll) go through Bun's own dispatch */
|
||||
if (CLEAR_POINTER_TAG(poll) != poll) {
|
||||
Bun__internal_dispatch_ready_poll(loop, poll);
|
||||
continue;
|
||||
}
|
||||
|
||||
struct kevent_flags bits = coalesced[loop->current_ready_poll];
|
||||
if (bits.skip) continue;
|
||||
|
||||
int events = (bits.readable ? LIBUS_SOCKET_READABLE : 0)
|
||||
| (bits.writable ? LIBUS_SOCKET_WRITABLE : 0);
|
||||
|
||||
events &= us_poll_events(poll);
|
||||
if (events || bits.error || bits.eof) {
|
||||
us_internal_dispatch_ready_poll(poll, bits.error, bits.eof, events);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
void us_loop_run(struct us_loop_t *loop) {
|
||||
us_loop_integrate(loop);
|
||||
|
||||
@@ -205,41 +302,7 @@ void us_loop_run(struct us_loop_t *loop) {
|
||||
} while (IS_EINTR(loop->num_ready_polls));
|
||||
#endif
|
||||
|
||||
/* Iterate ready polls, dispatching them by type */
|
||||
for (loop->current_ready_poll = 0; loop->current_ready_poll < loop->num_ready_polls; loop->current_ready_poll++) {
|
||||
struct us_poll_t *poll = GET_READY_POLL(loop, loop->current_ready_poll);
|
||||
/* Any ready poll marked with nullptr will be ignored */
|
||||
if (LIKELY(poll)) {
|
||||
if (CLEAR_POINTER_TAG(poll) != poll) {
|
||||
Bun__internal_dispatch_ready_poll(loop, poll);
|
||||
continue;
|
||||
}
|
||||
#ifdef LIBUS_USE_EPOLL
|
||||
int events = loop->ready_polls[loop->current_ready_poll].events;
|
||||
const int error = events & EPOLLERR;
|
||||
const int eof = events & EPOLLHUP;
|
||||
#else
|
||||
const struct kevent64_s* current_kevent = &loop->ready_polls[loop->current_ready_poll];
|
||||
const int16_t filter = current_kevent->filter;
|
||||
const uint16_t flags = current_kevent->flags;
|
||||
const uint32_t fflags = current_kevent->fflags;
|
||||
|
||||
// > Multiple events which trigger the filter do not result in multiple kevents being placed on the kqueue
|
||||
// > Instead, the filter will aggregate the events into a single kevent struct
|
||||
// Note: EV_ERROR only sets the error in data as part of changelist. Not in this call!
|
||||
int events = 0
|
||||
| ((filter == EVFILT_READ) ? LIBUS_SOCKET_READABLE : 0)
|
||||
| ((filter == EVFILT_WRITE) ? LIBUS_SOCKET_WRITABLE : 0);
|
||||
const int error = (flags & (EV_ERROR)) ? ((int)fflags || 1) : 0;
|
||||
const int eof = (flags & (EV_EOF));
|
||||
#endif
|
||||
/* Always filter all polls by what they actually poll for (callback polls always poll for readable) */
|
||||
events &= us_poll_events(poll);
|
||||
if (events || error || eof) {
|
||||
us_internal_dispatch_ready_poll(poll, error, eof, events);
|
||||
}
|
||||
}
|
||||
}
|
||||
us_internal_dispatch_ready_polls(loop);
|
||||
|
||||
/* Emit post callback */
|
||||
us_internal_loop_post(loop);
|
||||
@@ -263,57 +326,33 @@ void us_loop_run_bun_tick(struct us_loop_t *loop, const struct timespec* timeout
|
||||
/* Emit pre callback */
|
||||
us_internal_loop_pre(loop);
|
||||
|
||||
|
||||
if (loop->data.jsc_vm)
|
||||
const unsigned int had_wakeups = __atomic_exchange_n(&loop->pending_wakeups, 0, __ATOMIC_ACQUIRE);
|
||||
const int will_idle_inside_event_loop = had_wakeups == 0 && (!timeout || (timeout->tv_nsec != 0 || timeout->tv_sec != 0));
|
||||
if (will_idle_inside_event_loop && loop->data.jsc_vm)
|
||||
Bun__JSC_onBeforeWait(loop->data.jsc_vm);
|
||||
|
||||
/* Fetch ready polls */
|
||||
#ifdef LIBUS_USE_EPOLL
|
||||
/* A zero timespec already has a fast path in ep_poll (fs/eventpoll.c):
|
||||
* it sets timed_out=1 (line 1952) and returns before any scheduler
|
||||
* interaction (line 1975). No equivalent of KEVENT_FLAG_IMMEDIATE needed. */
|
||||
loop->num_ready_polls = bun_epoll_pwait2(loop->fd, loop->ready_polls, 1024, timeout);
|
||||
#else
|
||||
do {
|
||||
loop->num_ready_polls = kevent64(loop->fd, NULL, 0, loop->ready_polls, 1024, 0, timeout);
|
||||
loop->num_ready_polls = kevent64(loop->fd, NULL, 0, loop->ready_polls, 1024,
|
||||
/* When we won't idle (pending wakeups or zero timeout), use KEVENT_FLAG_IMMEDIATE.
|
||||
* In XNU's kqueue_scan (bsd/kern/kern_event.c):
|
||||
* - KEVENT_FLAG_IMMEDIATE: returns immediately after kqueue_process() (line 8031)
|
||||
* - Zero timespec without the flag: falls through to assert_wait_deadline (line 8039)
|
||||
* and thread_block (line 8048), doing a full context switch cycle (~14us) even
|
||||
* though the deadline is already in the past. */
|
||||
will_idle_inside_event_loop ? 0 : KEVENT_FLAG_IMMEDIATE,
|
||||
timeout);
|
||||
} while (IS_EINTR(loop->num_ready_polls));
|
||||
#endif
|
||||
|
||||
|
||||
/* Iterate ready polls, dispatching them by type */
|
||||
for (loop->current_ready_poll = 0; loop->current_ready_poll < loop->num_ready_polls; loop->current_ready_poll++) {
|
||||
struct us_poll_t *poll = GET_READY_POLL(loop, loop->current_ready_poll);
|
||||
/* Any ready poll marked with nullptr will be ignored */
|
||||
if (LIKELY(poll)) {
|
||||
if (CLEAR_POINTER_TAG(poll) != poll) {
|
||||
Bun__internal_dispatch_ready_poll(loop, poll);
|
||||
continue;
|
||||
}
|
||||
#ifdef LIBUS_USE_EPOLL
|
||||
int events = loop->ready_polls[loop->current_ready_poll].events;
|
||||
const int error = events & EPOLLERR;
|
||||
const int eof = events & EPOLLHUP;
|
||||
#else
|
||||
const struct kevent64_s* current_kevent = &loop->ready_polls[loop->current_ready_poll];
|
||||
const int16_t filter = current_kevent->filter;
|
||||
const uint16_t flags = current_kevent->flags;
|
||||
const uint32_t fflags = current_kevent->fflags;
|
||||
|
||||
// > Multiple events which trigger the filter do not result in multiple kevents being placed on the kqueue
|
||||
// > Instead, the filter will aggregate the events into a single kevent struct
|
||||
int events = 0
|
||||
| ((filter & EVFILT_READ) ? LIBUS_SOCKET_READABLE : 0)
|
||||
| ((filter & EVFILT_WRITE) ? LIBUS_SOCKET_WRITABLE : 0);
|
||||
|
||||
// Note: EV_ERROR only sets the error in data as part of changelist. Not in this call!
|
||||
const int error = (flags & (EV_ERROR)) ? ((int)fflags || 1) : 0;
|
||||
const int eof = (flags & (EV_EOF));
|
||||
|
||||
#endif
|
||||
/* Always filter all polls by what they actually poll for (callback polls always poll for readable) */
|
||||
events &= us_poll_events(poll);
|
||||
if (events || error || eof) {
|
||||
us_internal_dispatch_ready_poll(poll, error, eof, events);
|
||||
}
|
||||
}
|
||||
}
|
||||
us_internal_dispatch_ready_polls(loop);
|
||||
|
||||
/* Emit post callback */
|
||||
us_internal_loop_post(loop);
|
||||
@@ -613,7 +652,7 @@ struct us_internal_async *us_internal_create_async(struct us_loop_t *loop, int f
|
||||
struct us_internal_callback_t *cb = (struct us_internal_callback_t *) p;
|
||||
cb->loop = loop;
|
||||
cb->cb_expects_the_loop = 1;
|
||||
cb->leave_poll_ready = 0;
|
||||
cb->leave_poll_ready = 1; /* Edge-triggered: skip reading eventfd on wakeup */
|
||||
|
||||
return (struct us_internal_async *) cb;
|
||||
}
|
||||
@@ -635,12 +674,28 @@ void us_internal_async_set(struct us_internal_async *a, void (*cb)(struct us_int
|
||||
internal_cb->cb = (void (*)(struct us_internal_callback_t *)) cb;
|
||||
|
||||
us_poll_start((struct us_poll_t *) a, internal_cb->loop, LIBUS_SOCKET_READABLE);
|
||||
#ifdef LIBUS_USE_EPOLL
|
||||
/* Upgrade to edge-triggered to avoid reading the eventfd on each wakeup */
|
||||
struct epoll_event event;
|
||||
event.events = EPOLLIN | EPOLLET;
|
||||
event.data.ptr = (struct us_poll_t *) a;
|
||||
epoll_ctl(internal_cb->loop->fd, EPOLL_CTL_MOD,
|
||||
us_poll_fd((struct us_poll_t *) a), &event);
|
||||
#endif
|
||||
}
|
||||
|
||||
void us_internal_async_wakeup(struct us_internal_async *a) {
|
||||
uint64_t one = 1;
|
||||
int written = write(us_poll_fd((struct us_poll_t *) a), &one, 8);
|
||||
(void)written;
|
||||
int fd = us_poll_fd((struct us_poll_t *) a);
|
||||
uint64_t val;
|
||||
for (val = 1; ; val = 1) {
|
||||
if (write(fd, &val, 8) >= 0) return;
|
||||
if (errno == EINTR) continue;
|
||||
if (errno == EAGAIN) {
|
||||
/* Counter overflow — drain and retry */
|
||||
if (read(fd, &val, 8) > 0 || errno == EAGAIN || errno == EINTR) continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
#else
|
||||
|
||||
|
||||
@@ -54,6 +54,10 @@ struct us_loop_t {
|
||||
/* Number of polls owned by bun */
|
||||
unsigned int bun_polls;
|
||||
|
||||
/* Incremented atomically by wakeup(), swapped to 0 before epoll/kqueue.
|
||||
* If non-zero, the event loop will return immediately so we can skip the GC safepoint. */
|
||||
unsigned int pending_wakeups;
|
||||
|
||||
/* The list of ready polls */
|
||||
#ifdef LIBUS_USE_EPOLL
|
||||
alignas(LIBUS_EXT_ALIGNMENT) struct epoll_event ready_polls[1024];
|
||||
|
||||
@@ -93,6 +93,9 @@ void us_internal_loop_data_free(struct us_loop_t *loop) {
|
||||
}
|
||||
|
||||
void us_wakeup_loop(struct us_loop_t *loop) {
|
||||
#ifndef LIBUS_USE_LIBUV
|
||||
__atomic_fetch_add(&loop->pending_wakeups, 1, __ATOMIC_RELEASE);
|
||||
#endif
|
||||
us_internal_async_wakeup(loop->data.wakeup_async);
|
||||
}
|
||||
|
||||
@@ -393,8 +396,12 @@ void us_internal_dispatch_ready_poll(struct us_poll_t *p, int error, int eof, in
|
||||
if (events & LIBUS_SOCKET_WRITABLE && !error) {
|
||||
s->flags.last_write_failed = 0;
|
||||
#ifdef LIBUS_USE_KQUEUE
|
||||
/* Kqueue is one-shot so is not writable anymore */
|
||||
p->state.poll_type = us_internal_poll_type(p) | ((events & LIBUS_SOCKET_READABLE) ? POLL_TYPE_POLLING_IN : 0);
|
||||
/* Kqueue EVFILT_WRITE is one-shot so the filter is removed after delivery.
|
||||
* Clear POLLING_OUT to reflect this.
|
||||
* Keep POLLING_IN from the poll's own state, NOT from `events`: kqueue delivers
|
||||
* each filter as a separate kevent, so a pure EVFILT_WRITE event won't have
|
||||
* LIBUS_SOCKET_READABLE set even though the socket is still registered for reads. */
|
||||
p->state.poll_type = us_internal_poll_type(p) | (p->state.poll_type & POLL_TYPE_POLLING_IN);
|
||||
#endif
|
||||
|
||||
s = s->context->on_writable(s);
|
||||
@@ -412,7 +419,7 @@ void us_internal_dispatch_ready_poll(struct us_poll_t *p, int error, int eof, in
|
||||
us_poll_change(&s->p, loop, us_poll_events(&s->p) & LIBUS_SOCKET_READABLE);
|
||||
} else {
|
||||
#ifdef LIBUS_USE_KQUEUE
|
||||
/* Kqueue one-shot writable needs to be re-enabled */
|
||||
/* Kqueue one-shot writable needs to be re-registered */
|
||||
us_poll_change(&s->p, loop, us_poll_events(&s->p) | LIBUS_SOCKET_WRITABLE);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -566,8 +566,10 @@ namespace uWS
|
||||
|
||||
|
||||
bool isHTTPMethod = (__builtin_expect(data[1] == '/', 1));
|
||||
bool isConnect = !isHTTPMethod && (isHTTPorHTTPSPrefixForProxies(data + 1, end) == 1 || ((data - start) == 7 && memcmp(start, "CONNECT", 7) == 0));
|
||||
if (isHTTPMethod || isConnect) [[likely]] {
|
||||
bool isConnect = !isHTTPMethod && ((data - start) == 7 && memcmp(start, "CONNECT", 7) == 0);
|
||||
/* Also accept proxy-style absolute URLs (http://... or https://...) as valid request targets */
|
||||
bool isProxyStyleURL = !isHTTPMethod && !isConnect && data[0] == 32 && isHTTPorHTTPSPrefixForProxies(data + 1, end) == 1;
|
||||
if (isHTTPMethod || isConnect || isProxyStyleURL) [[likely]] {
|
||||
header.key = {start, (size_t) (data - start)};
|
||||
data++;
|
||||
if(!isValidMethod(header.key, useStrictMethodValidation)) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Version: 11
|
||||
# Version: 12
|
||||
# A script that installs the dependencies needed to build and test Bun.
|
||||
# This should work on Windows 10 or newer with PowerShell.
|
||||
|
||||
@@ -387,7 +387,7 @@ function Install-PdbAddr2line {
|
||||
function Install-Llvm {
|
||||
Install-Package llvm `
|
||||
-Command clang-cl `
|
||||
-Version "19.1.7"
|
||||
-Version "21.1.8"
|
||||
Add-To-Path "$env:ProgramFiles\LLVM\bin"
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/sh
|
||||
# Version: 27
|
||||
# Version: 28
|
||||
|
||||
# A script that installs the dependencies needed to build and test Bun.
|
||||
# This should work on macOS and Linux with a POSIX shell.
|
||||
@@ -1096,7 +1096,7 @@ install_build_essentials() {
|
||||
}
|
||||
|
||||
llvm_version_exact() {
|
||||
print "19.1.7"
|
||||
print "21.1.8"
|
||||
}
|
||||
|
||||
llvm_version() {
|
||||
@@ -1106,23 +1106,20 @@ llvm_version() {
|
||||
install_llvm() {
|
||||
case "$pm" in
|
||||
apt)
|
||||
# Debian 13 (Trixie) has LLVM 19 natively, and apt.llvm.org doesn't have a trixie repo
|
||||
if [ "$distro" = "debian" ]; then
|
||||
install_packages \
|
||||
"llvm-$(llvm_version)" \
|
||||
"clang-$(llvm_version)" \
|
||||
"lld-$(llvm_version)" \
|
||||
"llvm-$(llvm_version)-dev" \
|
||||
"llvm-$(llvm_version)-tools" \
|
||||
"libclang-rt-$(llvm_version)-dev"
|
||||
else
|
||||
bash="$(require bash)"
|
||||
llvm_script="$(download_file "https://apt.llvm.org/llvm.sh")"
|
||||
execute_sudo "$bash" "$llvm_script" "$(llvm_version)" all
|
||||
|
||||
# Install llvm-symbolizer explicitly to ensure it's available for ASAN
|
||||
install_packages "llvm-$(llvm_version)-tools"
|
||||
# apt.llvm.org's GPG key uses SHA1, which Debian 13+ (sqv) rejects since 2026-02-01.
|
||||
# Override the sequoia crypto policy to extend the SHA1 deadline.
|
||||
# See: https://github.com/llvm/llvm-project/issues/153385
|
||||
if [ -x /usr/bin/sqv ] && [ -f /usr/share/apt/default-sequoia.config ]; then
|
||||
execute_sudo mkdir -p /etc/crypto-policies/back-ends
|
||||
execute_sudo /usr/bin/sh -c "sed 's/sha1.second_preimage_resistance = 2026-02-01/sha1.second_preimage_resistance = 2028-02-01/' /usr/share/apt/default-sequoia.config > /etc/crypto-policies/back-ends/apt-sequoia.config"
|
||||
fi
|
||||
|
||||
bash="$(require bash)"
|
||||
llvm_script="$(download_file "https://apt.llvm.org/llvm.sh")"
|
||||
execute_sudo "$bash" "$llvm_script" "$(llvm_version)" all
|
||||
|
||||
# Install llvm-symbolizer explicitly to ensure it's available for ASAN
|
||||
install_packages "llvm-$(llvm_version)-tools"
|
||||
;;
|
||||
brew)
|
||||
install_packages "llvm@$(llvm_version)"
|
||||
@@ -1177,7 +1174,7 @@ install_gcc() {
|
||||
;;
|
||||
esac
|
||||
|
||||
llvm_v="19"
|
||||
llvm_v="21"
|
||||
|
||||
append_to_profile "export CC=clang-${llvm_v}"
|
||||
append_to_profile "export CXX=clang++-${llvm_v}"
|
||||
|
||||
@@ -77,10 +77,10 @@ const HAS_CCACHE = CCACHE !== null;
|
||||
// On Windows, use clang-cl for MSVC compatibility
|
||||
const CC_BASE = IS_WINDOWS
|
||||
? findExecutable(["clang-cl.exe", "clang-cl"]) || "clang-cl"
|
||||
: findExecutable(["clang-19", "clang"]) || "clang";
|
||||
: findExecutable(["clang-21", "clang"]) || "clang";
|
||||
const CXX_BASE = IS_WINDOWS
|
||||
? findExecutable(["clang-cl.exe", "clang-cl"]) || "clang-cl"
|
||||
: findExecutable(["clang++-19", "clang++"]) || "clang++";
|
||||
: findExecutable(["clang++-21", "clang++"]) || "clang++";
|
||||
|
||||
const CC = HAS_CCACHE ? CCACHE : CC_BASE;
|
||||
const CXX = HAS_CCACHE ? CCACHE : CXX_BASE;
|
||||
|
||||
@@ -57,7 +57,11 @@ async function build(args) {
|
||||
if (process.platform === "win32" && !process.env["VSINSTALLDIR"]) {
|
||||
const shellPath = join(import.meta.dirname, "vs-shell.ps1");
|
||||
const scriptPath = import.meta.filename;
|
||||
return spawn("pwsh", ["-NoProfile", "-NoLogo", "-File", shellPath, process.argv0, scriptPath, ...args]);
|
||||
// When cross-compiling to ARM64, tell vs-shell.ps1 to set up the x64_arm64 VS environment
|
||||
const toolchainIdx = args.indexOf("--toolchain");
|
||||
const requestedVsArch = toolchainIdx !== -1 && args[toolchainIdx + 1] === "windows-aarch64" ? "arm64" : undefined;
|
||||
const env = requestedVsArch ? { ...process.env, BUN_VS_ARCH: requestedVsArch } : undefined;
|
||||
return spawn("pwsh", ["-NoProfile", "-NoLogo", "-File", shellPath, process.argv0, scriptPath, ...args], { env });
|
||||
}
|
||||
|
||||
if (isCI) {
|
||||
@@ -92,21 +96,9 @@ async function build(args) {
|
||||
generateOptions["--toolchain"] = toolchainPath;
|
||||
}
|
||||
|
||||
// Windows ARM64: automatically set required options
|
||||
// Windows ARM64: log detection (compiler is selected by CMake/toolchain)
|
||||
if (isWindowsARM64) {
|
||||
// Use clang-cl instead of MSVC cl.exe for proper ARM64 flag support
|
||||
if (!generateOptions["-DCMAKE_C_COMPILER"]) {
|
||||
generateOptions["-DCMAKE_C_COMPILER"] = "clang-cl";
|
||||
}
|
||||
if (!generateOptions["-DCMAKE_CXX_COMPILER"]) {
|
||||
generateOptions["-DCMAKE_CXX_COMPILER"] = "clang-cl";
|
||||
}
|
||||
// Skip codegen by default since x64 bun crashes under WoW64 emulation
|
||||
// Can be overridden with -DSKIP_CODEGEN=OFF once ARM64 bun is available
|
||||
if (!generateOptions["-DSKIP_CODEGEN"]) {
|
||||
generateOptions["-DSKIP_CODEGEN"] = "ON";
|
||||
}
|
||||
console.log("Windows ARM64 detected: using clang-cl and SKIP_CODEGEN=ON");
|
||||
console.log("Windows ARM64 detected");
|
||||
}
|
||||
|
||||
const generateArgs = Object.entries(generateOptions).flatMap(([flag, value]) =>
|
||||
|
||||
@@ -12,7 +12,7 @@ PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
MODE="${1:-format}"
|
||||
|
||||
# Use LLVM_VERSION_MAJOR from environment or default to 19
|
||||
LLVM_VERSION="${LLVM_VERSION_MAJOR:-19}"
|
||||
LLVM_VERSION="${LLVM_VERSION_MAJOR:-21}"
|
||||
|
||||
# Ensure we have the specific clang-format version
|
||||
CLANG_FORMAT="clang-format-${LLVM_VERSION}"
|
||||
|
||||
@@ -5,7 +5,22 @@ $ErrorActionPreference = "Stop"
|
||||
|
||||
# Detect system architecture
|
||||
$script:IsARM64 = [System.Runtime.InteropServices.RuntimeInformation]::OSArchitecture -eq [System.Runtime.InteropServices.Architecture]::Arm64
|
||||
$script:VsArch = if ($script:IsARM64) { "arm64" } else { "amd64" }
|
||||
|
||||
# Allow overriding the target arch (useful for cross-compiling on x64 -> ARM64)
|
||||
$script:VsArch = $null
|
||||
if ($env:BUN_VS_ARCH) {
|
||||
switch ($env:BUN_VS_ARCH.ToLowerInvariant()) {
|
||||
"arm64" { $script:VsArch = "arm64" }
|
||||
"aarch64" { $script:VsArch = "arm64" }
|
||||
"amd64" { $script:VsArch = "amd64" }
|
||||
"x64" { $script:VsArch = "amd64" }
|
||||
default { throw "Invalid BUN_VS_ARCH: $env:BUN_VS_ARCH (expected arm64|amd64)" }
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $script:VsArch) {
|
||||
$script:VsArch = if ($script:IsARM64) { "arm64" } else { "amd64" }
|
||||
}
|
||||
|
||||
if($env:VSINSTALLDIR -eq $null) {
|
||||
Write-Host "Loading Visual Studio environment, this may take a second..."
|
||||
@@ -17,17 +32,29 @@ if($env:VSINSTALLDIR -eq $null) {
|
||||
|
||||
$vsDir = (& $vswhere -prerelease -latest -property installationPath)
|
||||
if ($vsDir -eq $null) {
|
||||
$vsDir = Get-ChildItem -Path "C:\Program Files\Microsoft Visual Studio\2022" -Directory
|
||||
# Check common VS installation paths
|
||||
$searchPaths = @(
|
||||
"C:\Program Files\Microsoft Visual Studio\2022",
|
||||
"C:\Program Files (x86)\Microsoft Visual Studio\2022"
|
||||
)
|
||||
foreach ($searchPath in $searchPaths) {
|
||||
if (Test-Path $searchPath) {
|
||||
$vsDir = (Get-ChildItem -Path $searchPath -Directory | Select-Object -First 1).FullName
|
||||
if ($vsDir -ne $null) { break }
|
||||
}
|
||||
}
|
||||
if ($vsDir -eq $null) {
|
||||
throw "Visual Studio directory not found."
|
||||
}
|
||||
$vsDir = $vsDir.FullName
|
||||
}
|
||||
|
||||
Push-Location $vsDir
|
||||
try {
|
||||
$vsShell = (Join-Path -Path $vsDir -ChildPath "Common7\Tools\Launch-VsDevShell.ps1")
|
||||
. $vsShell -Arch $script:VsArch -HostArch $script:VsArch
|
||||
# Visual Studio's Launch-VsDevShell.ps1 only supports x86/amd64 for HostArch
|
||||
# For ARM64 builds, use amd64 as HostArch since it can cross-compile to ARM64
|
||||
$hostArch = if ($script:VsArch -eq "arm64") { "amd64" } else { $script:VsArch }
|
||||
. $vsShell -Arch $script:VsArch -HostArch $hostArch
|
||||
} finally {
|
||||
Pop-Location
|
||||
}
|
||||
@@ -61,7 +88,7 @@ if ($args.Count -gt 0) {
|
||||
$displayArgs += $arg
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Write-Host "$ $command $displayArgs"
|
||||
& $command $commandArgs
|
||||
exit $LASTEXITCODE
|
||||
|
||||
16
shell.nix
16
shell.nix
@@ -8,9 +8,9 @@ pkgs.mkShell rec {
|
||||
# Core build tools (matching bootstrap.sh)
|
||||
cmake
|
||||
ninja
|
||||
clang_19
|
||||
llvm_19
|
||||
lld_19
|
||||
clang_21
|
||||
llvm_21
|
||||
lld_21
|
||||
nodejs_24
|
||||
bun
|
||||
rustc
|
||||
@@ -77,10 +77,10 @@ pkgs.mkShell rec {
|
||||
];
|
||||
|
||||
shellHook = ''
|
||||
export CC="${pkgs.lib.getExe pkgs.clang_19}"
|
||||
export CXX="${pkgs.lib.getExe' pkgs.clang_19 "clang++"}"
|
||||
export AR="${pkgs.llvm_19}/bin/llvm-ar"
|
||||
export RANLIB="${pkgs.llvm_19}/bin/llvm-ranlib"
|
||||
export CC="${pkgs.lib.getExe pkgs.clang_21}"
|
||||
export CXX="${pkgs.lib.getExe' pkgs.clang_21 "clang++"}"
|
||||
export AR="${pkgs.llvm_21}/bin/llvm-ar"
|
||||
export RANLIB="${pkgs.llvm_21}/bin/llvm-ranlib"
|
||||
export CMAKE_C_COMPILER="$CC"
|
||||
export CMAKE_CXX_COMPILER="$CXX"
|
||||
export CMAKE_AR="$AR"
|
||||
@@ -88,7 +88,7 @@ pkgs.mkShell rec {
|
||||
export CMAKE_SYSTEM_PROCESSOR=$(uname -m)
|
||||
export TMPDIR=''${TMPDIR:-/tmp}
|
||||
'' + pkgs.lib.optionalString pkgs.stdenv.isLinux ''
|
||||
export LD="${pkgs.lib.getExe' pkgs.lld_19 "ld.lld"}"
|
||||
export LD="${pkgs.lib.getExe' pkgs.lld_21 "ld.lld"}"
|
||||
export NIX_CFLAGS_LINK="''${NIX_CFLAGS_LINK:+$NIX_CFLAGS_LINK }-fuse-ld=lld"
|
||||
export LD_LIBRARY_PATH="${pkgs.lib.makeLibraryPath packages}''${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}"
|
||||
'' + ''
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
|
||||
const Self = @This();
|
||||
|
||||
const safety_checks = bun.Environment.isDebug or bun.Environment.enable_asan;
|
||||
|
||||
#heap: *mimalloc.Heap,
|
||||
thread_id: if (safety_checks) std.Thread.Id else void,
|
||||
#heap: if (safety_checks) Owned(*DebugHeap) else *mimalloc.Heap,
|
||||
|
||||
/// Uses the default thread-local heap. This type is zero-sized.
|
||||
///
|
||||
@@ -23,18 +20,18 @@ pub const Default = struct {
|
||||
///
|
||||
/// This type is a `GenericAllocator`; see `src/allocators.zig`.
|
||||
pub const Borrowed = struct {
|
||||
#heap: *mimalloc.Heap,
|
||||
#heap: BorrowedHeap,
|
||||
|
||||
pub fn allocator(self: Borrowed) std.mem.Allocator {
|
||||
return .{ .ptr = self.#heap, .vtable = c_allocator_vtable };
|
||||
return .{ .ptr = self.#heap, .vtable = &c_allocator_vtable };
|
||||
}
|
||||
|
||||
pub fn getDefault() Borrowed {
|
||||
return .{ .#heap = mimalloc.mi_heap_main() };
|
||||
return .{ .#heap = getThreadHeap() };
|
||||
}
|
||||
|
||||
pub fn gc(self: Borrowed) void {
|
||||
mimalloc.mi_heap_collect(self.#heap, false);
|
||||
mimalloc.mi_heap_collect(self.getMimallocHeap(), false);
|
||||
}
|
||||
|
||||
pub fn helpCatchMemoryIssues(self: Borrowed) void {
|
||||
@@ -44,17 +41,30 @@ pub const Borrowed = struct {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ownsPtr(self: Borrowed, ptr: *const anyopaque) bool {
|
||||
return mimalloc.mi_heap_check_owned(self.getMimallocHeap(), ptr);
|
||||
}
|
||||
|
||||
fn fromOpaque(ptr: *anyopaque) Borrowed {
|
||||
return .{ .#heap = @ptrCast(@alignCast(ptr)) };
|
||||
}
|
||||
|
||||
fn getMimallocHeap(self: Borrowed) *mimalloc.Heap {
|
||||
return if (comptime safety_checks) self.#heap.inner else self.#heap;
|
||||
}
|
||||
|
||||
fn assertThreadLock(self: Borrowed) void {
|
||||
if (comptime safety_checks) self.#heap.thread_lock.assertLocked();
|
||||
}
|
||||
|
||||
fn alignedAlloc(self: Borrowed, len: usize, alignment: Alignment) ?[*]u8 {
|
||||
log("Malloc: {d}\n", .{len});
|
||||
|
||||
const heap = self.getMimallocHeap();
|
||||
const ptr: ?*anyopaque = if (mimalloc.mustUseAlignedAlloc(alignment))
|
||||
mimalloc.mi_heap_malloc_aligned(self.#heap, len, alignment.toByteUnits())
|
||||
mimalloc.mi_heap_malloc_aligned(heap, len, alignment.toByteUnits())
|
||||
else
|
||||
mimalloc.mi_heap_malloc(self.#heap, len);
|
||||
mimalloc.mi_heap_malloc(heap, len);
|
||||
|
||||
if (comptime bun.Environment.isDebug) {
|
||||
const usable = mimalloc.mi_malloc_usable_size(ptr);
|
||||
@@ -79,17 +89,42 @@ pub const Borrowed = struct {
|
||||
}
|
||||
};
|
||||
|
||||
const BorrowedHeap = if (safety_checks) *DebugHeap else *mimalloc.Heap;
|
||||
|
||||
const DebugHeap = struct {
|
||||
inner: *mimalloc.Heap,
|
||||
thread_lock: bun.safety.ThreadLock,
|
||||
|
||||
pub const deinit = void;
|
||||
};
|
||||
|
||||
threadlocal var thread_heap: if (safety_checks) ?DebugHeap else void = if (safety_checks) null;
|
||||
|
||||
fn getThreadHeap() BorrowedHeap {
|
||||
if (comptime !safety_checks) return mimalloc.mi_heap_get_default();
|
||||
if (thread_heap == null) {
|
||||
thread_heap = .{
|
||||
.inner = mimalloc.mi_heap_get_default(),
|
||||
.thread_lock = .initLocked(),
|
||||
};
|
||||
}
|
||||
return &thread_heap.?;
|
||||
}
|
||||
|
||||
const log = bun.Output.scoped(.mimalloc, .hidden);
|
||||
|
||||
pub fn allocator(self: Self) std.mem.Allocator {
|
||||
self.assertThreadOwnership();
|
||||
return self.borrow().allocator();
|
||||
}
|
||||
|
||||
pub fn borrow(self: Self) Borrowed {
|
||||
return .{ .#heap = self.#heap };
|
||||
return .{ .#heap = if (comptime safety_checks) self.#heap.get() else self.#heap };
|
||||
}
|
||||
|
||||
/// Internally, mimalloc calls mi_heap_get_default()
|
||||
/// to get the default heap.
|
||||
/// It uses pthread_getspecific to do that.
|
||||
/// We can save those extra calls if we just do it once in here
|
||||
pub fn getThreadLocalDefault() std.mem.Allocator {
|
||||
if (bun.Environment.enable_asan) return bun.default_allocator;
|
||||
return Borrowed.getDefault().allocator();
|
||||
@@ -122,15 +157,22 @@ pub fn dumpStats(_: Self) void {
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Self) void {
|
||||
mimalloc.mi_heap_destroy(self.#heap);
|
||||
const mimalloc_heap = self.borrow().getMimallocHeap();
|
||||
if (comptime safety_checks) {
|
||||
self.#heap.deinit();
|
||||
}
|
||||
mimalloc.mi_heap_destroy(mimalloc_heap);
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
pub fn init() Self {
|
||||
return .{
|
||||
.#heap = mimalloc.mi_heap_new() orelse bun.outOfMemory(),
|
||||
.thread_id = if (safety_checks) std.Thread.getCurrentId() else {},
|
||||
};
|
||||
const mimalloc_heap = mimalloc.mi_heap_new() orelse bun.outOfMemory();
|
||||
if (comptime !safety_checks) return .{ .#heap = mimalloc_heap };
|
||||
const heap: Owned(*DebugHeap) = .new(.{
|
||||
.inner = mimalloc_heap,
|
||||
.thread_lock = .initLocked(),
|
||||
});
|
||||
return .{ .#heap = heap };
|
||||
}
|
||||
|
||||
pub fn gc(self: Self) void {
|
||||
@@ -141,16 +183,8 @@ pub fn helpCatchMemoryIssues(self: Self) void {
|
||||
self.borrow().helpCatchMemoryIssues();
|
||||
}
|
||||
|
||||
fn assertThreadOwnership(self: Self) void {
|
||||
if (comptime safety_checks) {
|
||||
const current_thread = std.Thread.getCurrentId();
|
||||
if (current_thread != self.thread_id) {
|
||||
std.debug.panic(
|
||||
"MimallocArena used from wrong thread: arena belongs to thread {d}, but current thread is {d}",
|
||||
.{ self.thread_id, current_thread },
|
||||
);
|
||||
}
|
||||
}
|
||||
pub fn ownsPtr(self: Self, ptr: *const anyopaque) bool {
|
||||
return self.borrow().ownsPtr(ptr);
|
||||
}
|
||||
|
||||
fn alignedAllocSize(ptr: [*]u8) usize {
|
||||
@@ -159,10 +193,13 @@ fn alignedAllocSize(ptr: [*]u8) usize {
|
||||
|
||||
fn vtable_alloc(ptr: *anyopaque, len: usize, alignment: Alignment, _: usize) ?[*]u8 {
|
||||
const self: Borrowed = .fromOpaque(ptr);
|
||||
self.assertThreadLock();
|
||||
return self.alignedAlloc(len, alignment);
|
||||
}
|
||||
|
||||
fn vtable_resize(_: *anyopaque, buf: []u8, _: Alignment, new_len: usize, _: usize) bool {
|
||||
fn vtable_resize(ptr: *anyopaque, buf: []u8, _: Alignment, new_len: usize, _: usize) bool {
|
||||
const self: Borrowed = .fromOpaque(ptr);
|
||||
self.assertThreadLock();
|
||||
return mimalloc.mi_expand(buf.ptr, new_len) != null;
|
||||
}
|
||||
|
||||
@@ -186,17 +223,39 @@ fn vtable_free(
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to expand or shrink memory, allowing relocation.
|
||||
///
|
||||
/// `memory.len` must equal the length requested from the most recent
|
||||
/// successful call to `alloc`, `resize`, or `remap`. `alignment` must
|
||||
/// equal the same value that was passed as the `alignment` parameter to
|
||||
/// the original `alloc` call.
|
||||
///
|
||||
/// A non-`null` return value indicates the resize was successful. The
|
||||
/// allocation may have same address, or may have been relocated. In either
|
||||
/// case, the allocation now has size of `new_len`. A `null` return value
|
||||
/// indicates that the resize would be equivalent to allocating new memory,
|
||||
/// copying the bytes from the old memory, and then freeing the old memory.
|
||||
/// In such case, it is more efficient for the caller to perform the copy.
|
||||
///
|
||||
/// `new_len` must be greater than zero.
|
||||
///
|
||||
/// `ret_addr` is optionally provided as the first return address of the
|
||||
/// allocation call stack. If the value is `0` it means no return address
|
||||
/// has been provided.
|
||||
fn vtable_remap(ptr: *anyopaque, buf: []u8, alignment: Alignment, new_len: usize, _: usize) ?[*]u8 {
|
||||
const self: Borrowed = .fromOpaque(ptr);
|
||||
const value = mimalloc.mi_heap_realloc_aligned(self.#heap, buf.ptr, new_len, alignment.toByteUnits());
|
||||
self.assertThreadLock();
|
||||
const heap = self.getMimallocHeap();
|
||||
const aligned_size = alignment.toByteUnits();
|
||||
const value = mimalloc.mi_heap_realloc_aligned(heap, buf.ptr, new_len, aligned_size);
|
||||
return @ptrCast(value);
|
||||
}
|
||||
|
||||
pub fn isInstance(alloc: std.mem.Allocator) bool {
|
||||
return alloc.vtable == c_allocator_vtable;
|
||||
return alloc.vtable == &c_allocator_vtable;
|
||||
}
|
||||
|
||||
const c_allocator_vtable = &std.mem.Allocator.VTable{
|
||||
const c_allocator_vtable = std.mem.Allocator.VTable{
|
||||
.alloc = vtable_alloc,
|
||||
.resize = vtable_resize,
|
||||
.remap = vtable_remap,
|
||||
@@ -209,3 +268,5 @@ const Alignment = std.mem.Alignment;
|
||||
const bun = @import("bun");
|
||||
const assert = bun.assert;
|
||||
const mimalloc = bun.mimalloc;
|
||||
const Owned = bun.ptr.Owned;
|
||||
const safety_checks = bun.Environment.ci_assert;
|
||||
|
||||
@@ -60,29 +60,17 @@ pub const Heap = opaque {
|
||||
return mi_heap_realloc(self, p, newsize);
|
||||
}
|
||||
|
||||
pub fn isOwned(self: *Heap, p: ?*const anyopaque) bool {
|
||||
return mi_heap_contains(self, p);
|
||||
pub fn isOwned(self: *Heap, p: ?*anyopaque) bool {
|
||||
return mi_heap_check_owned(self, p);
|
||||
}
|
||||
};
|
||||
pub extern fn mi_heap_new() ?*Heap;
|
||||
pub extern fn mi_heap_delete(heap: *Heap) void;
|
||||
pub extern fn mi_heap_destroy(heap: *Heap) void;
|
||||
pub extern fn mi_heap_set_default(heap: *Heap) *Heap;
|
||||
pub extern fn mi_heap_get_default() *Heap;
|
||||
pub extern fn mi_heap_get_backing() *Heap;
|
||||
pub extern fn mi_heap_collect(heap: *Heap, force: bool) void;
|
||||
pub extern fn mi_heap_main() *Heap;
|
||||
|
||||
// Thread-local heap (theap) API - new in mimalloc v3
|
||||
pub const THeap = opaque {};
|
||||
pub extern fn mi_theap_get_default() *THeap;
|
||||
pub extern fn mi_theap_set_default(theap: *THeap) *THeap;
|
||||
pub extern fn mi_theap_collect(theap: *THeap, force: bool) void;
|
||||
pub extern fn mi_theap_malloc(theap: *THeap, size: usize) ?*anyopaque;
|
||||
pub extern fn mi_theap_zalloc(theap: *THeap, size: usize) ?*anyopaque;
|
||||
pub extern fn mi_theap_calloc(theap: *THeap, count: usize, size: usize) ?*anyopaque;
|
||||
pub extern fn mi_theap_malloc_small(theap: *THeap, size: usize) ?*anyopaque;
|
||||
pub extern fn mi_theap_malloc_aligned(theap: *THeap, size: usize, alignment: usize) ?*anyopaque;
|
||||
pub extern fn mi_theap_realloc(theap: *THeap, p: ?*anyopaque, newsize: usize) ?*anyopaque;
|
||||
pub extern fn mi_theap_destroy(theap: *THeap) void;
|
||||
pub extern fn mi_heap_theap(heap: *Heap) *THeap;
|
||||
pub extern fn mi_heap_malloc(heap: *Heap, size: usize) ?*anyopaque;
|
||||
pub extern fn mi_heap_zalloc(heap: *Heap, size: usize) ?*anyopaque;
|
||||
pub extern fn mi_heap_calloc(heap: *Heap, count: usize, size: usize) ?*anyopaque;
|
||||
@@ -114,7 +102,8 @@ pub extern fn mi_heap_rezalloc_aligned(heap: *Heap, p: ?*anyopaque, newsize: usi
|
||||
pub extern fn mi_heap_rezalloc_aligned_at(heap: *Heap, p: ?*anyopaque, newsize: usize, alignment: usize, offset: usize) ?*anyopaque;
|
||||
pub extern fn mi_heap_recalloc_aligned(heap: *Heap, p: ?*anyopaque, newcount: usize, size: usize, alignment: usize) ?*anyopaque;
|
||||
pub extern fn mi_heap_recalloc_aligned_at(heap: *Heap, p: ?*anyopaque, newcount: usize, size: usize, alignment: usize, offset: usize) ?*anyopaque;
|
||||
pub extern fn mi_heap_contains(heap: *const Heap, p: ?*const anyopaque) bool;
|
||||
pub extern fn mi_heap_contains_block(heap: *Heap, p: *const anyopaque) bool;
|
||||
pub extern fn mi_heap_check_owned(heap: *Heap, p: *const anyopaque) bool;
|
||||
pub extern fn mi_check_owned(p: ?*const anyopaque) bool;
|
||||
pub const struct_mi_heap_area_s = extern struct {
|
||||
blocks: ?*anyopaque,
|
||||
|
||||
@@ -345,7 +345,6 @@ pub const api = struct {
|
||||
yaml = 19,
|
||||
json5 = 20,
|
||||
md = 21,
|
||||
py = 22,
|
||||
_,
|
||||
|
||||
pub fn jsonStringify(self: @This(), writer: anytype) !void {
|
||||
|
||||
@@ -58,7 +58,6 @@ pub fn trackResolutionFailure(store: *DirectoryWatchStore, import_source: []cons
|
||||
.sqlite,
|
||||
.sqlite_embedded,
|
||||
.md,
|
||||
.py,
|
||||
=> bun.debugAssert(false),
|
||||
}
|
||||
|
||||
|
||||
@@ -370,78 +370,6 @@ pub const HardcodedModule = enum {
|
||||
.{ "bun:internal-for-testing", .{ .path = "bun:internal-for-testing" } },
|
||||
.{ "ffi", .{ .path = "bun:ffi" } },
|
||||
|
||||
// Python builtin modules
|
||||
entry("python:this"),
|
||||
entry("python:builtins"),
|
||||
entry("python:pathlib"),
|
||||
entry("python:os"),
|
||||
entry("python:json"),
|
||||
entry("python:sys"),
|
||||
entry("python:re"),
|
||||
entry("python:math"),
|
||||
entry("python:datetime"),
|
||||
entry("python:collections"),
|
||||
entry("python:itertools"),
|
||||
entry("python:functools"),
|
||||
entry("python:random"),
|
||||
entry("python:hashlib"),
|
||||
entry("python:base64"),
|
||||
entry("python:urllib"),
|
||||
entry("python:http"),
|
||||
entry("python:io"),
|
||||
entry("python:struct"),
|
||||
entry("python:copy"),
|
||||
entry("python:pickle"),
|
||||
entry("python:csv"),
|
||||
entry("python:sqlite3"),
|
||||
entry("python:subprocess"),
|
||||
entry("python:threading"),
|
||||
entry("python:multiprocessing"),
|
||||
entry("python:asyncio"),
|
||||
entry("python:typing"),
|
||||
entry("python:dataclasses"),
|
||||
entry("python:enum"),
|
||||
entry("python:abc"),
|
||||
entry("python:contextlib"),
|
||||
entry("python:logging"),
|
||||
entry("python:argparse"),
|
||||
entry("python:shutil"),
|
||||
entry("python:glob"),
|
||||
entry("python:fnmatch"),
|
||||
entry("python:tempfile"),
|
||||
entry("python:gzip"),
|
||||
entry("python:zipfile"),
|
||||
entry("python:tarfile"),
|
||||
entry("python:uuid"),
|
||||
entry("python:socket"),
|
||||
entry("python:ssl"),
|
||||
entry("python:email"),
|
||||
entry("python:html"),
|
||||
entry("python:xml"),
|
||||
entry("python:configparser"),
|
||||
entry("python:inspect"),
|
||||
entry("python:traceback"),
|
||||
entry("python:warnings"),
|
||||
entry("python:time"),
|
||||
entry("python:calendar"),
|
||||
entry("python:string"),
|
||||
entry("python:textwrap"),
|
||||
entry("python:difflib"),
|
||||
entry("python:pprint"),
|
||||
entry("python:statistics"),
|
||||
entry("python:decimal"),
|
||||
entry("python:fractions"),
|
||||
entry("python:operator"),
|
||||
entry("python:heapq"),
|
||||
entry("python:bisect"),
|
||||
entry("python:array"),
|
||||
entry("python:weakref"),
|
||||
entry("python:types"),
|
||||
entry("python:codecs"),
|
||||
entry("python:unicodedata"),
|
||||
entry("python:secrets"),
|
||||
entry("python:hmac"),
|
||||
|
||||
// Thirdparty packages we override
|
||||
.{ "@vercel/fetch", .{ .path = "@vercel/fetch" } },
|
||||
.{ "isomorphic-fetch", .{ .path = "isomorphic-fetch" } },
|
||||
|
||||
@@ -735,18 +735,6 @@ pub fn transpileSourceCode(
|
||||
};
|
||||
},
|
||||
|
||||
.py => {
|
||||
// Return the file path with .python tag - C++ will run Python
|
||||
// and create JSPyObject wrappers for exports
|
||||
return ResolvedSource{
|
||||
.allocator = null,
|
||||
.source_code = bun.String.cloneUTF8(path.text),
|
||||
.specifier = input_specifier,
|
||||
.source_url = input_specifier.createIfDifferent(path.text),
|
||||
.tag = .python,
|
||||
};
|
||||
},
|
||||
|
||||
else => {
|
||||
if (flags.disableTranspiling()) {
|
||||
return ResolvedSource{
|
||||
@@ -840,32 +828,17 @@ pub export fn Bun__resolveAndFetchBuiltinModule(
|
||||
var log = logger.Log.init(jsc_vm.transpiler.allocator);
|
||||
defer log.deinit();
|
||||
|
||||
// Check hardcoded aliases first
|
||||
if (HardcodedModule.Alias.bun_aliases.getWithEql(specifier.*, bun.String.eqlComptime)) |alias| {
|
||||
const hardcoded = HardcodedModule.map.get(alias.path) orelse {
|
||||
bun.debugAssert(false);
|
||||
return false;
|
||||
};
|
||||
ret.* = .ok(
|
||||
getHardcodedModule(jsc_vm, specifier.*, hardcoded) orelse
|
||||
return false,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle any python: prefixed module (for submodule imports like python:matplotlib.pyplot)
|
||||
if (specifier.hasPrefixComptime("python:")) {
|
||||
ret.* = .ok(.{
|
||||
.allocator = null,
|
||||
.source_code = specifier.dupeRef(),
|
||||
.specifier = specifier.dupeRef(),
|
||||
.source_url = specifier.dupeRef(),
|
||||
.tag = .python_builtin,
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
const alias = HardcodedModule.Alias.bun_aliases.getWithEql(specifier.*, bun.String.eqlComptime) orelse
|
||||
return false;
|
||||
const hardcoded = HardcodedModule.map.get(alias.path) orelse {
|
||||
bun.debugAssert(false);
|
||||
return false;
|
||||
};
|
||||
ret.* = .ok(
|
||||
getHardcodedModule(jsc_vm, specifier.*, hardcoded) orelse
|
||||
return false,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
pub export fn Bun__fetchBuiltinModule(
|
||||
@@ -1249,43 +1222,6 @@ pub fn fetchBuiltinModule(jsc_vm: *VirtualMachine, specifier: bun.String) !?Reso
|
||||
}
|
||||
}
|
||||
|
||||
// Handle python: prefix for Python builtin modules
|
||||
if (specifier.hasPrefixComptime("python:")) {
|
||||
// Pass the full specifier (python:pathlib) - C++ will strip the prefix
|
||||
return .{
|
||||
.allocator = null,
|
||||
.source_code = specifier.dupeRef(),
|
||||
.specifier = specifier.dupeRef(),
|
||||
.source_url = specifier.dupeRef(),
|
||||
.tag = .python_builtin,
|
||||
};
|
||||
}
|
||||
|
||||
// Check if this is a Python package in .venv/lib/python{version}/site-packages/
|
||||
// This allows `import numpy from "numpy"` to work for installed Python packages
|
||||
const specifier_utf8 = specifier.toUTF8(bun.default_allocator);
|
||||
defer specifier_utf8.deinit();
|
||||
const spec_slice = specifier_utf8.slice();
|
||||
|
||||
// Only check for bare specifiers (not paths)
|
||||
if (spec_slice.len > 0 and spec_slice[0] != '.' and spec_slice[0] != '/') {
|
||||
// Check if package exists in .venv/lib/python{version}/site-packages/
|
||||
var path_buf: bun.PathBuffer = undefined;
|
||||
const venv_path = std.fmt.bufPrint(&path_buf, pypi.venv_site_packages ++ "/{s}", .{spec_slice}) catch return null;
|
||||
|
||||
// Check if directory exists (Python package) or .py file exists
|
||||
if (bun.sys.directoryExistsAt(bun.FD.cwd(), venv_path).unwrap() catch false) {
|
||||
// Return as python_builtin - the module loader will import it via Python
|
||||
return .{
|
||||
.allocator = null,
|
||||
.source_code = specifier.dupeRef(),
|
||||
.specifier = specifier.dupeRef(),
|
||||
.source_url = specifier.dupeRef(),
|
||||
.tag = .python_builtin,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1427,7 +1363,6 @@ const dumpSourceString = @import("./RuntimeTranspilerStore.zig").dumpSourceStrin
|
||||
const setBreakPointOnFirstLine = @import("./RuntimeTranspilerStore.zig").setBreakPointOnFirstLine;
|
||||
|
||||
const bun = @import("bun");
|
||||
const pypi = bun.install.PyPI;
|
||||
const Environment = bun.Environment;
|
||||
const MutableString = bun.MutableString;
|
||||
const Output = bun.Output;
|
||||
|
||||
@@ -7,6 +7,7 @@ const VirtualMachine = @This();
|
||||
|
||||
export var has_bun_garbage_collector_flag_enabled = false;
|
||||
pub export var isBunTest: bool = false;
|
||||
pub export var Bun__defaultRemainingRunsUntilSkipReleaseAccess: c_int = 10;
|
||||
|
||||
// TODO: evaluate if this has any measurable performance impact.
|
||||
pub var synthetic_allocation_limit: usize = std.math.maxInt(u32);
|
||||
@@ -1808,12 +1809,6 @@ pub fn resolveMaybeNeedsTrailingSlash(
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle any python: prefixed module (allows submodule imports like python:matplotlib.pyplot)
|
||||
if (bun.strings.hasPrefixComptime(specifier_utf8.slice(), "python:")) {
|
||||
res.* = ErrorableString.ok(specifier);
|
||||
return;
|
||||
}
|
||||
|
||||
const old_log = jsc_vm.log;
|
||||
// the logger can end up being called on another thread, it must not use threadlocal Heap Allocator
|
||||
var log = logger.Log.init(bun.default_allocator);
|
||||
@@ -1827,58 +1822,6 @@ pub fn resolveMaybeNeedsTrailingSlash(
|
||||
jsc_vm.transpiler.resolver.log = old_log;
|
||||
}
|
||||
jsc_vm._resolve(&result, specifier_utf8.slice(), normalizeSource(source_utf8.slice()), is_esm, is_a_file_path) catch |err_| {
|
||||
// Check if this is a Python package in .venv (fallback after node_modules)
|
||||
// Only check for bare specifiers (not paths)
|
||||
const spec_slice = specifier_utf8.slice();
|
||||
if (spec_slice.len > 0 and spec_slice[0] != '.' and spec_slice[0] != '/') {
|
||||
// Handle submodule imports like "matplotlib/pyplot" -> "python:matplotlib.pyplot"
|
||||
// Extract the base package name (before any /)
|
||||
const slash_idx = bun.strings.indexOfChar(spec_slice, '/');
|
||||
const base_package = if (slash_idx) |idx| spec_slice[0..idx] else spec_slice;
|
||||
|
||||
// Check if package exists in .venv/lib/python{version}/site-packages/
|
||||
// Normalize package name: Python uses underscores in module names, pip uses hyphens
|
||||
var normalized_name_buf: [256]u8 = undefined;
|
||||
var normalized_name = normalized_name_buf[0..@min(base_package.len, normalized_name_buf.len)];
|
||||
for (base_package, 0..) |c, i| {
|
||||
if (i >= normalized_name.len) break;
|
||||
normalized_name[i] = if (c == '-') '_' else c;
|
||||
}
|
||||
|
||||
var path_buf: bun.PathBuffer = undefined;
|
||||
if (std.fmt.bufPrint(&path_buf, pypi.venv_site_packages ++ "/{s}", .{normalized_name})) |venv_path| {
|
||||
// Check if directory exists (Python package directory)
|
||||
const is_dir = bun.sys.directoryExistsAt(bun.FD.cwd(), venv_path).unwrap() catch false;
|
||||
|
||||
// Also check for single-file packages like typing_extensions.py, six.py
|
||||
var py_path_buf: bun.PathBuffer = undefined;
|
||||
const py_path = std.fmt.bufPrint(&py_path_buf, pypi.venv_site_packages ++ "/{s}.py", .{normalized_name}) catch null;
|
||||
const is_py_file = if (py_path) |p| brk: {
|
||||
break :brk switch (bun.sys.existsAtType(bun.FD.cwd(), p)) {
|
||||
.result => |t| t == .file,
|
||||
.err => false,
|
||||
};
|
||||
} else false;
|
||||
|
||||
if (is_dir or is_py_file) {
|
||||
// Add python: prefix so fetchBuiltinModule handles it
|
||||
// Normalize hyphens to underscores for Python module names
|
||||
// Keep slashes - BunPython.cpp will convert them to dots
|
||||
var module_buf: [512]u8 = undefined;
|
||||
var module_name = std.ArrayList(u8).initBuffer(&module_buf);
|
||||
module_name.appendSliceAssumeCapacity("python:");
|
||||
|
||||
// Append normalized spec_slice (hyphens -> underscores)
|
||||
for (spec_slice) |c| {
|
||||
module_name.appendAssumeCapacity(if (c == '-') '_' else c);
|
||||
}
|
||||
|
||||
res.* = ErrorableString.ok(bun.String.createAtomASCII(module_name.items));
|
||||
return;
|
||||
}
|
||||
} else |_| {}
|
||||
}
|
||||
|
||||
var err = err_;
|
||||
const msg: logger.Msg = brk: {
|
||||
const msgs: []logger.Msg = log.msgs.items;
|
||||
@@ -3842,5 +3785,3 @@ const ServerEntryPoint = bun.transpiler.EntryPoints.ServerEntryPoint;
|
||||
|
||||
const webcore = bun.webcore;
|
||||
const Body = webcore.Body;
|
||||
|
||||
const pypi = @import("../install/pypi.zig");
|
||||
|
||||
@@ -464,8 +464,8 @@ const ParseRenderer = struct {
|
||||
const entry = self.#stack.pop().?;
|
||||
const g = self.#globalObject;
|
||||
|
||||
// Determine HTML tag name
|
||||
const type_str: []const u8 = blockTypeName(block_type, entry.data);
|
||||
// Determine HTML tag index for cached string
|
||||
const tag_index = getBlockTypeTag(block_type, entry.data);
|
||||
|
||||
// For headings, compute slug before counting props
|
||||
const slug: ?[]const u8 = if (block_type == .h) self.#heading_tracker.leaveHeading(bun.default_allocator) else null;
|
||||
@@ -496,7 +496,7 @@ const ParseRenderer = struct {
|
||||
|
||||
// Build React element — use component override as type if set
|
||||
const component = self.getBlockComponent(block_type, entry.data);
|
||||
const type_val: JSValue = if (component != .zero) component else try bun.String.createUTF8ForJS(g, type_str);
|
||||
const type_val: JSValue = if (component != .zero) component else getCachedTagString(g, tag_index);
|
||||
|
||||
const props = JSValue.createEmptyObject(g, props_count);
|
||||
self.#marked_args.append(props);
|
||||
@@ -572,7 +572,7 @@ const ParseRenderer = struct {
|
||||
const entry = self.#stack.pop().?;
|
||||
const g = self.#globalObject;
|
||||
|
||||
const type_str: []const u8 = spanTypeName(span_type);
|
||||
const tag_index = getSpanTypeTag(span_type);
|
||||
|
||||
// Count props fields: always children (or alt for img) + metadata
|
||||
var props_count: usize = 1; // children (or alt for img)
|
||||
@@ -592,7 +592,7 @@ const ParseRenderer = struct {
|
||||
|
||||
// Build React element: { $$typeof, type, key, ref, props }
|
||||
const component = self.getSpanComponent(span_type);
|
||||
const type_val: JSValue = if (component != .zero) component else try bun.String.createUTF8ForJS(g, type_str);
|
||||
const type_val: JSValue = if (component != .zero) component else getCachedTagString(g, tag_index);
|
||||
|
||||
const props = JSValue.createEmptyObject(g, props_count);
|
||||
self.#marked_args.append(props);
|
||||
@@ -675,7 +675,7 @@ const ParseRenderer = struct {
|
||||
switch (text_type) {
|
||||
.br => {
|
||||
const br_component = self.#components.br;
|
||||
const br_type: JSValue = if (br_component != .zero) br_component else try bun.String.createUTF8ForJS(g, "br");
|
||||
const br_type: JSValue = if (br_component != .zero) br_component else getCachedTagString(g, .br);
|
||||
const empty_props = JSValue.createEmptyObject(g, 0);
|
||||
self.#marked_args.append(empty_props);
|
||||
const obj = self.createElement(br_type, empty_props);
|
||||
@@ -705,53 +705,6 @@ const ParseRenderer = struct {
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Type name mappings
|
||||
// ========================================
|
||||
|
||||
fn blockTypeName(block_type: md.BlockType, data: u32) []const u8 {
|
||||
return switch (block_type) {
|
||||
.h => switch (data) {
|
||||
1 => "h1",
|
||||
2 => "h2",
|
||||
3 => "h3",
|
||||
4 => "h4",
|
||||
5 => "h5",
|
||||
else => "h6",
|
||||
},
|
||||
.p => "p",
|
||||
.quote => "blockquote",
|
||||
.ul => "ul",
|
||||
.ol => "ol",
|
||||
.li => "li",
|
||||
.code => "pre",
|
||||
.hr => "hr",
|
||||
.html => "html",
|
||||
.table => "table",
|
||||
.thead => "thead",
|
||||
.tbody => "tbody",
|
||||
.tr => "tr",
|
||||
.th => "th",
|
||||
.td => "td",
|
||||
.doc => "div",
|
||||
};
|
||||
}
|
||||
|
||||
fn spanTypeName(span_type: md.SpanType) []const u8 {
|
||||
return switch (span_type) {
|
||||
.em => "em",
|
||||
.strong => "strong",
|
||||
.a => "a",
|
||||
.img => "img",
|
||||
.code => "code",
|
||||
.del => "del",
|
||||
.latexmath => "math",
|
||||
.latexmath_display => "math",
|
||||
.wikilink => "a",
|
||||
.u => "u",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/// Renderer that calls JavaScript callbacks for each markdown element.
|
||||
@@ -1125,6 +1078,89 @@ fn extractLanguage(src_text: []const u8, info_beg: u32) []const u8 {
|
||||
return "";
|
||||
}
|
||||
|
||||
// Cached tag string indices - must match BunMarkdownTagStrings.h
|
||||
const TagIndex = enum(u8) {
|
||||
h1 = 0,
|
||||
h2 = 1,
|
||||
h3 = 2,
|
||||
h4 = 3,
|
||||
h5 = 4,
|
||||
h6 = 5,
|
||||
p = 6,
|
||||
blockquote = 7,
|
||||
ul = 8,
|
||||
ol = 9,
|
||||
li = 10,
|
||||
pre = 11,
|
||||
hr = 12,
|
||||
html = 13,
|
||||
table = 14,
|
||||
thead = 15,
|
||||
tbody = 16,
|
||||
tr = 17,
|
||||
th = 18,
|
||||
td = 19,
|
||||
div = 20,
|
||||
em = 21,
|
||||
strong = 22,
|
||||
a = 23,
|
||||
img = 24,
|
||||
code = 25,
|
||||
del = 26,
|
||||
math = 27,
|
||||
u = 28,
|
||||
br = 29,
|
||||
};
|
||||
|
||||
extern fn BunMarkdownTagStrings__getTagString(*jsc.JSGlobalObject, u8) JSValue;
|
||||
|
||||
fn getCachedTagString(globalObject: *jsc.JSGlobalObject, tag: TagIndex) JSValue {
|
||||
return BunMarkdownTagStrings__getTagString(globalObject, @intFromEnum(tag));
|
||||
}
|
||||
|
||||
fn getBlockTypeTag(block_type: md.BlockType, data: u32) TagIndex {
|
||||
return switch (block_type) {
|
||||
.h => switch (data) {
|
||||
1 => .h1,
|
||||
2 => .h2,
|
||||
3 => .h3,
|
||||
4 => .h4,
|
||||
5 => .h5,
|
||||
else => .h6,
|
||||
},
|
||||
.p => .p,
|
||||
.quote => .blockquote,
|
||||
.ul => .ul,
|
||||
.ol => .ol,
|
||||
.li => .li,
|
||||
.code => .pre,
|
||||
.hr => .hr,
|
||||
.html => .html,
|
||||
.table => .table,
|
||||
.thead => .thead,
|
||||
.tbody => .tbody,
|
||||
.tr => .tr,
|
||||
.th => .th,
|
||||
.td => .td,
|
||||
.doc => .div,
|
||||
};
|
||||
}
|
||||
|
||||
fn getSpanTypeTag(span_type: md.SpanType) TagIndex {
|
||||
return switch (span_type) {
|
||||
.em => .em,
|
||||
.strong => .strong,
|
||||
.a => .a,
|
||||
.img => .img,
|
||||
.code => .code,
|
||||
.del => .del,
|
||||
.latexmath => .math,
|
||||
.latexmath_display => .math,
|
||||
.wikilink => .a,
|
||||
.u => .u,
|
||||
};
|
||||
}
|
||||
|
||||
const std = @import("std");
|
||||
|
||||
const bun = @import("bun");
|
||||
|
||||
@@ -245,6 +245,16 @@ pub const All = struct {
|
||||
}
|
||||
|
||||
pub fn getTimeout(this: *All, spec: *timespec, vm: *VirtualMachine) bool {
|
||||
// On POSIX, if there are pending immediate tasks, use a zero timeout
|
||||
// so epoll/kqueue returns immediately without the overhead of writing
|
||||
// to the eventfd via wakeup().
|
||||
if (comptime Environment.isPosix) {
|
||||
if (vm.event_loop.immediate_tasks.items.len > 0) {
|
||||
spec.* = .{ .nsec = 0, .sec = 0 };
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
var maybe_now: ?timespec = null;
|
||||
while (this.timers.peek()) |min| {
|
||||
const now = maybe_now orelse now: {
|
||||
|
||||
@@ -256,7 +256,7 @@ pub fn NewSocket(comptime ssl: bool) type {
|
||||
jsc.markBinding(@src());
|
||||
if (this.socket.isDetached()) return;
|
||||
const handlers = this.getHandlers();
|
||||
log("onTimeout {s}", .{if (handlers.is_server) "S" else "C"});
|
||||
log("onTimeout {s}", .{if (handlers.mode == .server) "S" else "C"});
|
||||
const callback = handlers.onTimeout;
|
||||
if (callback == .zero or this.flags.finalizing) return;
|
||||
if (handlers.vm.isShuttingDown()) {
|
||||
@@ -281,7 +281,7 @@ pub fn NewSocket(comptime ssl: bool) type {
|
||||
|
||||
pub fn handleConnectError(this: *This, errno: c_int) bun.JSError!void {
|
||||
const handlers = this.getHandlers();
|
||||
log("onConnectError {s} ({d}, {d})", .{ if (handlers.is_server) "S" else "C", errno, this.ref_count.get() });
|
||||
log("onConnectError {s} ({d}, {d})", .{ if (handlers.mode == .server) "S" else "C", errno, this.ref_count.get() });
|
||||
// Ensure the socket is still alive for any defer's we have
|
||||
this.ref();
|
||||
defer this.deref();
|
||||
@@ -397,7 +397,8 @@ pub fn NewSocket(comptime ssl: bool) type {
|
||||
}
|
||||
|
||||
pub fn isServer(this: *const This) bool {
|
||||
return this.getHandlers().is_server;
|
||||
const handlers = this.getHandlers();
|
||||
return handlers.mode.isServer();
|
||||
}
|
||||
|
||||
pub fn onOpen(this: *This, socket: Socket) void {
|
||||
@@ -502,7 +503,7 @@ pub fn NewSocket(comptime ssl: bool) type {
|
||||
jsc.markBinding(@src());
|
||||
if (this.socket.isDetached()) return;
|
||||
const handlers = this.getHandlers();
|
||||
log("onEnd {s}", .{if (handlers.is_server) "S" else "C"});
|
||||
log("onEnd {s}", .{if (handlers.mode == .server) "S" else "C"});
|
||||
// Ensure the socket remains alive until this is finished
|
||||
this.ref();
|
||||
defer this.deref();
|
||||
@@ -534,7 +535,7 @@ pub fn NewSocket(comptime ssl: bool) type {
|
||||
this.socket = s;
|
||||
if (this.socket.isDetached()) return;
|
||||
const handlers = this.getHandlers();
|
||||
log("onHandshake {s} ({d})", .{ if (handlers.is_server) "S" else "C", success });
|
||||
log("onHandshake {s} ({d})", .{ if (handlers.mode == .server) "S" else "C", success });
|
||||
|
||||
const authorized = if (success == 1) true else false;
|
||||
|
||||
@@ -571,7 +572,7 @@ pub fn NewSocket(comptime ssl: bool) type {
|
||||
result = callback.call(globalObject, this_value, &[_]JSValue{this_value}) catch |err| globalObject.takeException(err);
|
||||
|
||||
// only call onOpen once for clients
|
||||
if (!handlers.is_server) {
|
||||
if (handlers.mode != .server) {
|
||||
// clean onOpen callback so only called in the first handshake and not in every renegotiation
|
||||
// on servers this would require a different approach but it's not needed because our servers will not call handshake multiple times
|
||||
// servers don't support renegotiation
|
||||
@@ -600,7 +601,7 @@ pub fn NewSocket(comptime ssl: bool) type {
|
||||
pub fn onClose(this: *This, _: Socket, err: c_int, _: ?*anyopaque) bun.JSError!void {
|
||||
jsc.markBinding(@src());
|
||||
const handlers = this.getHandlers();
|
||||
log("onClose {s}", .{if (handlers.is_server) "S" else "C"});
|
||||
log("onClose {s}", .{if (handlers.mode == .server) "S" else "C"});
|
||||
this.detachNativeCallback();
|
||||
this.socket.detach();
|
||||
defer this.deref();
|
||||
@@ -648,7 +649,7 @@ pub fn NewSocket(comptime ssl: bool) type {
|
||||
this.socket = s;
|
||||
if (this.socket.isDetached()) return;
|
||||
const handlers = this.getHandlers();
|
||||
log("onData {s} ({d})", .{ if (handlers.is_server) "S" else "C", data.len });
|
||||
log("onData {s} ({d})", .{ if (handlers.mode == .server) "S" else "C", data.len });
|
||||
if (this.native_callback.onData(data)) return;
|
||||
|
||||
const callback = handlers.onData;
|
||||
@@ -691,7 +692,7 @@ pub fn NewSocket(comptime ssl: bool) type {
|
||||
pub fn getListener(this: *This, _: *jsc.JSGlobalObject) JSValue {
|
||||
const handlers = this.handlers orelse return .js_undefined;
|
||||
|
||||
if (!handlers.is_server or this.socket.isDetached()) {
|
||||
if (handlers.mode != .server or this.socket.isDetached()) {
|
||||
return .js_undefined;
|
||||
}
|
||||
|
||||
@@ -1352,7 +1353,7 @@ pub fn NewSocket(comptime ssl: bool) type {
|
||||
};
|
||||
|
||||
const this_handlers = this.getHandlers();
|
||||
const handlers = try Handlers.fromJS(globalObject, socket_obj, this_handlers.is_server);
|
||||
const handlers = try Handlers.fromJS(globalObject, socket_obj, this_handlers.mode == .server);
|
||||
this_handlers.deinit();
|
||||
this_handlers.* = handlers;
|
||||
|
||||
@@ -1380,6 +1381,9 @@ pub fn NewSocket(comptime ssl: bool) type {
|
||||
if (this.socket.isDetached() or this.socket.isNamedPipe()) {
|
||||
return .js_undefined;
|
||||
}
|
||||
if (this.isServer()) {
|
||||
return globalObject.throw("Server-side upgradeTLS is not supported. Use upgradeDuplexToTLS with isServer: true instead.", .{});
|
||||
}
|
||||
const args = callframe.arguments_old(1);
|
||||
|
||||
if (args.len < 1) {
|
||||
@@ -1571,7 +1575,7 @@ pub fn NewSocket(comptime ssl: bool) type {
|
||||
this.socket.detach();
|
||||
|
||||
// start TLS handshake after we set extension on the socket
|
||||
new_socket.startTLS(!handlers_ptr.is_server);
|
||||
new_socket.startTLS(handlers_ptr.mode != .server);
|
||||
|
||||
success = true;
|
||||
return array;
|
||||
@@ -1754,6 +1758,23 @@ pub fn NewWrappedHandler(comptime tls: bool) type {
|
||||
};
|
||||
}
|
||||
|
||||
/// Unified socket mode replacing the old is_server bool + TLSMode pair.
|
||||
pub const SocketMode = enum {
|
||||
/// Default — TLS client or non-TLS socket
|
||||
client,
|
||||
/// Listener-owned server. TLS (if any) configured at the listener level.
|
||||
server,
|
||||
/// Duplex upgraded to TLS server role. Not listener-owned —
|
||||
/// markInactive uses client lifecycle path.
|
||||
duplex_server,
|
||||
|
||||
/// Returns true for any mode that acts as a TLS server (ALPN, handshake direction).
|
||||
/// Both .server and .duplex_server present as server to peers.
|
||||
pub fn isServer(this: SocketMode) bool {
|
||||
return this == .server or this == .duplex_server;
|
||||
}
|
||||
};
|
||||
|
||||
pub const DuplexUpgradeContext = struct {
|
||||
upgrade: uws.UpgradedDuplex,
|
||||
// We only us a tls and not a raw socket when upgrading a Duplex, Duplex dont support socketpairs
|
||||
@@ -1764,6 +1785,7 @@ pub const DuplexUpgradeContext = struct {
|
||||
task_event: EventState = .StartTLS,
|
||||
ssl_config: ?jsc.API.ServerConfig.SSLConfig,
|
||||
is_open: bool = false,
|
||||
#mode: SocketMode = .client,
|
||||
|
||||
pub const EventState = enum(u8) {
|
||||
StartTLS,
|
||||
@@ -1846,7 +1868,8 @@ pub const DuplexUpgradeContext = struct {
|
||||
switch (this.task_event) {
|
||||
.StartTLS => {
|
||||
if (this.ssl_config) |config| {
|
||||
this.upgrade.startTLS(config, true) catch |err| {
|
||||
log("DuplexUpgradeContext.startTLS mode={s}", .{@tagName(this.#mode)});
|
||||
this.upgrade.startTLS(config, this.#mode == .client) catch |err| {
|
||||
switch (err) {
|
||||
error.OutOfMemory => {
|
||||
bun.outOfMemory();
|
||||
@@ -1914,8 +1937,15 @@ pub fn jsUpgradeDuplexToTLS(globalObject: *jsc.JSGlobalObject, callframe: *jsc.C
|
||||
return globalObject.throw("Expected \"socket\" option", .{});
|
||||
};
|
||||
|
||||
const is_server = false; // A duplex socket is always handled as a client
|
||||
const handlers = try Handlers.fromJS(globalObject, socket_obj, is_server);
|
||||
var is_server = false;
|
||||
if (try opts.getTruthy(globalObject, "isServer")) |is_server_val| {
|
||||
is_server = is_server_val.toBoolean();
|
||||
}
|
||||
// Note: Handlers.fromJS is_server=false because these handlers are standalone
|
||||
// allocations (not embedded in a Listener). The mode field on Handlers
|
||||
// controls lifecycle (markInactive expects a Listener parent when .server).
|
||||
// The TLS direction (client vs server) is controlled by DuplexUpgradeContext.mode.
|
||||
const handlers = try Handlers.fromJS(globalObject, socket_obj, false);
|
||||
|
||||
var ssl_opts: ?jsc.API.ServerConfig.SSLConfig = null;
|
||||
if (try opts.getTruthy(globalObject, "tls")) |tls| {
|
||||
@@ -1937,6 +1967,9 @@ pub fn jsUpgradeDuplexToTLS(globalObject: *jsc.JSGlobalObject, callframe: *jsc.C
|
||||
|
||||
const handlers_ptr = bun.handleOom(handlers.vm.allocator.create(Handlers));
|
||||
handlers_ptr.* = handlers;
|
||||
// Set mode to duplex_server so TLSSocket.isServer() returns true for ALPN server mode
|
||||
// without affecting markInactive lifecycle (which requires a Listener parent).
|
||||
handlers_ptr.mode = if (is_server) .duplex_server else .client;
|
||||
var tls = bun.new(TLSSocket, .{
|
||||
.ref_count = .init(),
|
||||
.handlers = handlers_ptr,
|
||||
@@ -1963,6 +1996,7 @@ pub fn jsUpgradeDuplexToTLS(globalObject: *jsc.JSGlobalObject, callframe: *jsc.C
|
||||
.vm = globalObject.bunVM(),
|
||||
.task = undefined,
|
||||
.ssl_config = socket_config.*,
|
||||
.#mode = if (is_server) .duplex_server else .client,
|
||||
});
|
||||
tls.ref();
|
||||
|
||||
|
||||
@@ -15,7 +15,7 @@ binary_type: BinaryType = .Buffer,
|
||||
vm: *jsc.VirtualMachine,
|
||||
globalObject: *jsc.JSGlobalObject,
|
||||
active_connections: u32 = 0,
|
||||
is_server: bool,
|
||||
mode: SocketMode = .client,
|
||||
promise: jsc.Strong.Optional = .empty,
|
||||
|
||||
protection_count: if (Environment.ci_assert) u32 else void = if (Environment.ci_assert) 0,
|
||||
@@ -81,7 +81,7 @@ pub fn markInactive(this: *Handlers) void {
|
||||
Listener.log("markInactive", .{});
|
||||
this.active_connections -= 1;
|
||||
if (this.active_connections == 0) {
|
||||
if (this.is_server) {
|
||||
if (this.mode == .server) {
|
||||
const listen_socket: *Listener = @fieldParentPtr("handlers", this);
|
||||
// allow it to be GC'd once the last connection is closed and it's not listening anymore
|
||||
if (listen_socket.listener == .none) {
|
||||
@@ -133,7 +133,7 @@ pub fn fromGenerated(
|
||||
var result: Handlers = .{
|
||||
.vm = globalObject.bunVM(),
|
||||
.globalObject = globalObject,
|
||||
.is_server = is_server,
|
||||
.mode = if (is_server) .server else .client,
|
||||
.binary_type = switch (generated.binary_type) {
|
||||
.arraybuffer => .ArrayBuffer,
|
||||
.buffer => .Buffer,
|
||||
@@ -217,7 +217,7 @@ pub fn clone(this: *const Handlers) Handlers {
|
||||
.vm = this.vm,
|
||||
.globalObject = this.globalObject,
|
||||
.binary_type = this.binary_type,
|
||||
.is_server = this.is_server,
|
||||
.mode = this.mode,
|
||||
};
|
||||
inline for (callback_fields) |field| {
|
||||
@field(result, field) = @field(this, field);
|
||||
@@ -346,6 +346,7 @@ const strings = bun.strings;
|
||||
const uws = bun.uws;
|
||||
const Listener = bun.api.Listener;
|
||||
const SSLConfig = bun.api.ServerConfig.SSLConfig;
|
||||
const SocketMode = bun.api.socket.SocketMode;
|
||||
|
||||
const jsc = bun.jsc;
|
||||
const JSValue = jsc.JSValue;
|
||||
|
||||
@@ -91,7 +91,7 @@ pub fn reload(this: *Listener, globalObject: *jsc.JSGlobalObject, callframe: *js
|
||||
return globalObject.throw("Expected \"socket\" object", .{});
|
||||
};
|
||||
|
||||
const handlers = try Handlers.fromJS(globalObject, socket_obj, this.handlers.is_server);
|
||||
const handlers = try Handlers.fromJS(globalObject, socket_obj, this.handlers.mode == .server);
|
||||
this.handlers.deinit();
|
||||
this.handlers = handlers;
|
||||
|
||||
@@ -773,7 +773,7 @@ pub fn connectInner(globalObject: *jsc.JSGlobalObject, prev_maybe_tcp: ?*TCPSock
|
||||
|
||||
const handlers_ptr = bun.handleOom(handlers.vm.allocator.create(Handlers));
|
||||
handlers_ptr.* = handlers.*;
|
||||
handlers_ptr.is_server = false;
|
||||
handlers_ptr.mode = .client;
|
||||
|
||||
var promise = jsc.JSPromise.create(globalObject);
|
||||
const promise_value = promise.toJS();
|
||||
|
||||
@@ -173,8 +173,10 @@ pub fn SSLWrapper(comptime T: type) type {
|
||||
|
||||
// flush buffered data and returns amount of pending data to write
|
||||
pub fn flush(this: *This) usize {
|
||||
const ssl = this.ssl orelse return 0;
|
||||
// handleTraffic may trigger a close callback which frees ssl,
|
||||
// so we must not capture the ssl pointer before calling it.
|
||||
this.handleTraffic();
|
||||
const ssl = this.ssl orelse return 0;
|
||||
const pending = BoringSSL.BIO_ctrl_pending(BoringSSL.SSL_get_wbio(ssl));
|
||||
if (pending > 0) return @intCast(pending);
|
||||
return 0;
|
||||
@@ -428,6 +430,8 @@ pub fn SSLWrapper(comptime T: type) type {
|
||||
if (read > 0) {
|
||||
log("triggering data callback (read {d})", .{read});
|
||||
this.triggerDataCallback(buffer[0..read]);
|
||||
// The data callback may have closed the connection
|
||||
if (this.ssl == null or this.flags.closed_notified) return false;
|
||||
}
|
||||
this.triggerCloseCallback();
|
||||
return false;
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
#include <JavaScriptCore/VM.h>
|
||||
#include <JavaScriptCore/Heap.h>
|
||||
|
||||
extern "C" int Bun__defaultRemainingRunsUntilSkipReleaseAccess;
|
||||
|
||||
extern "C" void Bun__JSC_onBeforeWait(JSC::VM* _Nonnull vm)
|
||||
{
|
||||
ASSERT(vm);
|
||||
@@ -46,7 +48,7 @@ extern "C" void Bun__JSC_onBeforeWait(JSC::VM* _Nonnull vm)
|
||||
// finalizers that might've been waiting to be run is a good idea.
|
||||
// But if you haven't, like if the process is just waiting on I/O
|
||||
// then don't bother.
|
||||
static constexpr int defaultRemainingRunsUntilSkipReleaseAccess = 10;
|
||||
const int defaultRemainingRunsUntilSkipReleaseAccess = Bun__defaultRemainingRunsUntilSkipReleaseAccess;
|
||||
|
||||
static thread_local int remainingRunsUntilSkipReleaseAccess = 0;
|
||||
|
||||
|
||||
59
src/bun.js/bindings/BunMarkdownTagStrings.cpp
Normal file
59
src/bun.js/bindings/BunMarkdownTagStrings.cpp
Normal file
@@ -0,0 +1,59 @@
|
||||
#include "root.h"
|
||||
#include "BunMarkdownTagStrings.h"
|
||||
#include <JavaScriptCore/JSString.h>
|
||||
#include <JavaScriptCore/JSGlobalObject.h>
|
||||
#include <JavaScriptCore/LazyProperty.h>
|
||||
#include <JavaScriptCore/LazyPropertyInlines.h>
|
||||
#include "ZigGlobalObject.h"
|
||||
#include <JavaScriptCore/SlotVisitorInlines.h>
|
||||
#include <JavaScriptCore/VMTrapsInlines.h>
|
||||
|
||||
namespace Bun {
|
||||
using namespace JSC;
|
||||
|
||||
#define MARKDOWN_TAG_STRINGS_LAZY_PROPERTY_DEFINITION(name, str, idx) \
|
||||
this->m_strings[idx].initLater( \
|
||||
[](const JSC::LazyProperty<JSGlobalObject, JSString>::Initializer& init) { \
|
||||
init.set(jsOwnedString(init.vm, str)); \
|
||||
});
|
||||
|
||||
#define MARKDOWN_TAG_STRINGS_LAZY_PROPERTY_VISITOR(name, str, idx) \
|
||||
this->m_strings[idx].visit(visitor);
|
||||
|
||||
void MarkdownTagStrings::initialize()
|
||||
{
|
||||
MARKDOWN_TAG_STRINGS_EACH_NAME(MARKDOWN_TAG_STRINGS_LAZY_PROPERTY_DEFINITION)
|
||||
}
|
||||
|
||||
template<typename Visitor>
|
||||
void MarkdownTagStrings::visit(Visitor& visitor)
|
||||
{
|
||||
MARKDOWN_TAG_STRINGS_EACH_NAME(MARKDOWN_TAG_STRINGS_LAZY_PROPERTY_VISITOR)
|
||||
}
|
||||
|
||||
template void MarkdownTagStrings::visit(JSC::AbstractSlotVisitor&);
|
||||
template void MarkdownTagStrings::visit(JSC::SlotVisitor&);
|
||||
|
||||
} // namespace Bun
|
||||
|
||||
// C API for Zig bindings
|
||||
extern "C" JSC::EncodedJSValue BunMarkdownTagStrings__getTagString(Zig::GlobalObject* globalObject, uint8_t tagIndex)
|
||||
{
|
||||
if (tagIndex >= MARKDOWN_TAG_STRINGS_COUNT)
|
||||
return JSC::JSValue::encode(JSC::jsUndefined());
|
||||
|
||||
auto& tagStrings = globalObject->markdownTagStrings();
|
||||
|
||||
// Use a switch to call the appropriate accessor
|
||||
switch (tagIndex) {
|
||||
#define MARKDOWN_TAG_STRINGS_CASE(name, str, idx) \
|
||||
case idx: \
|
||||
return JSC::JSValue::encode(tagStrings.name##String(globalObject));
|
||||
|
||||
MARKDOWN_TAG_STRINGS_EACH_NAME(MARKDOWN_TAG_STRINGS_CASE)
|
||||
|
||||
#undef MARKDOWN_TAG_STRINGS_CASE
|
||||
default:
|
||||
return JSC::JSValue::encode(JSC::jsUndefined());
|
||||
}
|
||||
}
|
||||
70
src/bun.js/bindings/BunMarkdownTagStrings.h
Normal file
70
src/bun.js/bindings/BunMarkdownTagStrings.h
Normal file
@@ -0,0 +1,70 @@
|
||||
#pragma once
|
||||
|
||||
#include <JavaScriptCore/JSString.h>
|
||||
#include <JavaScriptCore/LazyProperty.h>
|
||||
|
||||
// Markdown HTML tag names cached as JSStrings
|
||||
// These are commonly reused when rendering markdown to React elements
|
||||
|
||||
// clang-format off
|
||||
#define MARKDOWN_TAG_STRINGS_EACH_NAME(macro) \
|
||||
macro(h1, "h1"_s, 0) \
|
||||
macro(h2, "h2"_s, 1) \
|
||||
macro(h3, "h3"_s, 2) \
|
||||
macro(h4, "h4"_s, 3) \
|
||||
macro(h5, "h5"_s, 4) \
|
||||
macro(h6, "h6"_s, 5) \
|
||||
macro(p, "p"_s, 6) \
|
||||
macro(blockquote, "blockquote"_s, 7) \
|
||||
macro(ul, "ul"_s, 8) \
|
||||
macro(ol, "ol"_s, 9) \
|
||||
macro(li, "li"_s, 10) \
|
||||
macro(pre, "pre"_s, 11) \
|
||||
macro(hr, "hr"_s, 12) \
|
||||
macro(html, "html"_s, 13) \
|
||||
macro(table, "table"_s, 14) \
|
||||
macro(thead, "thead"_s, 15) \
|
||||
macro(tbody, "tbody"_s, 16) \
|
||||
macro(tr, "tr"_s, 17) \
|
||||
macro(th, "th"_s, 18) \
|
||||
macro(td, "td"_s, 19) \
|
||||
macro(div, "div"_s, 20) \
|
||||
macro(em, "em"_s, 21) \
|
||||
macro(strong, "strong"_s, 22) \
|
||||
macro(a, "a"_s, 23) \
|
||||
macro(img, "img"_s, 24) \
|
||||
macro(code, "code"_s, 25) \
|
||||
macro(del, "del"_s, 26) \
|
||||
macro(math, "math"_s, 27) \
|
||||
macro(u, "u"_s, 28) \
|
||||
macro(br, "br"_s, 29)
|
||||
// clang-format on
|
||||
|
||||
#define MARKDOWN_TAG_STRINGS_COUNT 30
|
||||
|
||||
namespace Bun {
|
||||
|
||||
using namespace JSC;
|
||||
|
||||
class MarkdownTagStrings {
|
||||
public:
|
||||
#define MARKDOWN_TAG_STRINGS_ACCESSOR_DEFINITION(name, str, idx) \
|
||||
JSC::JSString* name##String(JSC::JSGlobalObject* globalObject) \
|
||||
{ \
|
||||
return m_strings[idx].getInitializedOnMainThread(globalObject); \
|
||||
}
|
||||
|
||||
MARKDOWN_TAG_STRINGS_EACH_NAME(MARKDOWN_TAG_STRINGS_ACCESSOR_DEFINITION)
|
||||
|
||||
#undef MARKDOWN_TAG_STRINGS_ACCESSOR_DEFINITION
|
||||
|
||||
void initialize();
|
||||
|
||||
template<typename Visitor>
|
||||
void visit(Visitor& visitor);
|
||||
|
||||
private:
|
||||
JSC::LazyProperty<JSC::JSGlobalObject, JSC::JSString> m_strings[MARKDOWN_TAG_STRINGS_COUNT];
|
||||
};
|
||||
|
||||
} // namespace Bun
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,24 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "root.h"
|
||||
#include <JavaScriptCore/SyntheticModuleRecord.h>
|
||||
#include <Python.h>
|
||||
|
||||
namespace Bun::Python {
|
||||
|
||||
// Generate module source code for importing Python files as ES modules
|
||||
// If isMainEntry is true, __name__ will be "__main__", otherwise it's derived from the filename
|
||||
JSC::SyntheticSourceProvider::SyntheticSourceGenerator
|
||||
generatePythonModuleSourceCode(JSC::JSGlobalObject* globalObject, const WTF::String& filePath, bool isMainEntry);
|
||||
|
||||
// Generate module source code for importing Python builtin modules (e.g., "python:pathlib")
|
||||
JSC::SyntheticSourceProvider::SyntheticSourceGenerator
|
||||
generatePythonBuiltinModuleSourceCode(JSC::JSGlobalObject* globalObject, const WTF::String& moduleName);
|
||||
|
||||
JSC::JSValue toJS(JSC::JSGlobalObject* globalObject, PyObject* value);
|
||||
PyObject* fromJS(JSC::JSGlobalObject* globalObject, JSC::JSValue value);
|
||||
|
||||
// Ensure Python is initialized
|
||||
void ensurePythonInitialized();
|
||||
|
||||
} // namespace Bun::Python
|
||||
@@ -69,6 +69,7 @@ static uint8_t x86_cpu_features()
|
||||
#if CPU(ARM64)
|
||||
|
||||
#if OS(WINDOWS)
|
||||
#include <windows.h>
|
||||
#elif OS(MACOS)
|
||||
#include <sys/sysctl.h>
|
||||
#elif OS(LINUX)
|
||||
@@ -81,7 +82,18 @@ static uint8_t aarch64_cpu_features()
|
||||
uint8_t features = 0;
|
||||
|
||||
#if OS(WINDOWS)
|
||||
#pragma error "TODO: Implement AArch64 CPU features for Windows"
|
||||
// FP is mandatory on AArch64 — no separate PF_ constant exists for it
|
||||
features |= 1 << static_cast<uint8_t>(AArch64CPUFeature::fp);
|
||||
if (IsProcessorFeaturePresent(PF_ARM_NEON_INSTRUCTIONS_AVAILABLE))
|
||||
features |= 1 << static_cast<uint8_t>(AArch64CPUFeature::neon);
|
||||
if (IsProcessorFeaturePresent(PF_ARM_V8_CRYPTO_INSTRUCTIONS_AVAILABLE))
|
||||
features |= 1 << static_cast<uint8_t>(AArch64CPUFeature::aes);
|
||||
if (IsProcessorFeaturePresent(PF_ARM_V8_CRC32_INSTRUCTIONS_AVAILABLE))
|
||||
features |= 1 << static_cast<uint8_t>(AArch64CPUFeature::crc32);
|
||||
if (IsProcessorFeaturePresent(PF_ARM_V81_ATOMIC_INSTRUCTIONS_AVAILABLE))
|
||||
features |= 1 << static_cast<uint8_t>(AArch64CPUFeature::atomics);
|
||||
if (IsProcessorFeaturePresent(PF_ARM_SVE_INSTRUCTIONS_AVAILABLE))
|
||||
features |= 1 << static_cast<uint8_t>(AArch64CPUFeature::sve);
|
||||
#elif OS(MACOS)
|
||||
int value = 0;
|
||||
size_t size = sizeof(value);
|
||||
|
||||
@@ -39,7 +39,7 @@ static WebCore::ExceptionOr<void> encode(VM& vm, const WTF::BitSet<256>& doNotEs
|
||||
// 4-d-ii-1. Let V be the code unit value of C.
|
||||
char32_t codePoint;
|
||||
if (!U16_IS_LEAD(character))
|
||||
codePoint = character;
|
||||
codePoint = static_cast<char32_t>(character);
|
||||
else {
|
||||
// 4-d-iii. Else,
|
||||
// 4-d-iii-1. Increase k by 1.
|
||||
|
||||
@@ -55,6 +55,10 @@ template<typename CollectionType, typename KeyType> static auto findInSortedPair
|
||||
inline void checkEncodingTableInvariants() {}
|
||||
#endif
|
||||
|
||||
// LLVM 21+ -Wcharacter-conversion flags intentional char32_t/char16_t comparisons
|
||||
// used for Unicode code point range checks in findFirstInSortedPairs.
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wcharacter-conversion"
|
||||
struct CompareFirst {
|
||||
template<typename TypeA, typename TypeB> bool operator()(const TypeA& a, const TypeB& b)
|
||||
{
|
||||
@@ -132,5 +136,6 @@ template<typename CollectionType, typename KeyType> static auto findInSortedPair
|
||||
}
|
||||
return std::ranges::equal_range(collection, makeFirstAdapter(key), CompareFirst {});
|
||||
}
|
||||
#pragma clang diagnostic pop
|
||||
|
||||
}
|
||||
|
||||
@@ -119,6 +119,7 @@ JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_swap16);
|
||||
JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_swap32);
|
||||
JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_swap64);
|
||||
JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_toString);
|
||||
JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_slice);
|
||||
JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_write);
|
||||
JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_writeBigInt64LE);
|
||||
JSC_DECLARE_HOST_FUNCTION(jsBufferPrototypeFunction_writeBigInt64BE);
|
||||
@@ -1879,6 +1880,103 @@ bool inline parseArrayIndex(JSC::ThrowScope& scope, JSC::JSGlobalObject* globalO
|
||||
return true;
|
||||
}
|
||||
|
||||
static ALWAYS_INLINE size_t adjustSliceOffsetInt32(int32_t offset, size_t length)
|
||||
{
|
||||
if (offset < 0) {
|
||||
int64_t adjusted = static_cast<int64_t>(offset) + static_cast<int64_t>(length);
|
||||
return adjusted > 0 ? static_cast<size_t>(adjusted) : 0;
|
||||
}
|
||||
return static_cast<size_t>(offset) < length ? static_cast<size_t>(offset) : length;
|
||||
}
|
||||
|
||||
static ALWAYS_INLINE size_t adjustSliceOffsetDouble(double offset, size_t length)
|
||||
{
|
||||
if (std::isnan(offset)) {
|
||||
return 0;
|
||||
}
|
||||
offset = std::trunc(offset);
|
||||
if (offset == 0) {
|
||||
return 0;
|
||||
} else if (offset < 0) {
|
||||
double adjusted = offset + static_cast<double>(length);
|
||||
return adjusted > 0 ? static_cast<size_t>(adjusted) : 0;
|
||||
} else {
|
||||
return offset < static_cast<double>(length) ? static_cast<size_t>(offset) : length;
|
||||
}
|
||||
}
|
||||
|
||||
static JSC::EncodedJSValue jsBufferPrototypeFunction_sliceBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSArrayBufferView>::ClassParameter castedThis)
|
||||
{
|
||||
auto& vm = JSC::getVM(lexicalGlobalObject);
|
||||
auto throwScope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
auto* globalObject = defaultGlobalObject(lexicalGlobalObject);
|
||||
|
||||
size_t byteLength = castedThis->byteLength();
|
||||
size_t byteOffset = castedThis->byteOffset();
|
||||
|
||||
size_t startOffset = 0;
|
||||
size_t endOffset = byteLength;
|
||||
|
||||
unsigned argCount = callFrame->argumentCount();
|
||||
|
||||
if (argCount > 0) {
|
||||
JSValue startArg = callFrame->uncheckedArgument(0);
|
||||
if (startArg.isInt32()) {
|
||||
startOffset = adjustSliceOffsetInt32(startArg.asInt32(), byteLength);
|
||||
} else if (!startArg.isUndefined()) {
|
||||
double startD = startArg.toNumber(lexicalGlobalObject);
|
||||
RETURN_IF_EXCEPTION(throwScope, {});
|
||||
startOffset = adjustSliceOffsetDouble(startD, byteLength);
|
||||
}
|
||||
}
|
||||
|
||||
if (argCount > 1) {
|
||||
JSValue endArg = callFrame->uncheckedArgument(1);
|
||||
if (endArg.isInt32()) {
|
||||
endOffset = adjustSliceOffsetInt32(endArg.asInt32(), byteLength);
|
||||
} else if (!endArg.isUndefined()) {
|
||||
double endD = endArg.toNumber(lexicalGlobalObject);
|
||||
RETURN_IF_EXCEPTION(throwScope, {});
|
||||
endOffset = adjustSliceOffsetDouble(endD, byteLength);
|
||||
}
|
||||
}
|
||||
|
||||
size_t newLength = endOffset > startOffset ? endOffset - startOffset : 0;
|
||||
|
||||
if (castedThis->isDetached()) [[unlikely]] {
|
||||
throwVMTypeError(lexicalGlobalObject, throwScope, "Buffer is detached"_s);
|
||||
return {};
|
||||
}
|
||||
|
||||
RefPtr<ArrayBuffer> buffer = castedThis->possiblySharedBuffer();
|
||||
if (!buffer) {
|
||||
throwOutOfMemoryError(globalObject, throwScope);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (castedThis->isResizableOrGrowableShared()) {
|
||||
auto* subclassStructure = globalObject->JSResizableOrGrowableSharedBufferSubclassStructure();
|
||||
auto* uint8Array = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, WTF::move(buffer), byteOffset + startOffset, newLength);
|
||||
RETURN_IF_EXCEPTION(throwScope, {});
|
||||
if (!uint8Array) [[unlikely]] {
|
||||
throwOutOfMemoryError(globalObject, throwScope);
|
||||
return {};
|
||||
}
|
||||
RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(uint8Array));
|
||||
}
|
||||
|
||||
auto* subclassStructure = globalObject->JSBufferSubclassStructure();
|
||||
auto* uint8Array = JSC::JSUint8Array::create(lexicalGlobalObject, subclassStructure, WTF::move(buffer), byteOffset + startOffset, newLength);
|
||||
RETURN_IF_EXCEPTION(throwScope, {});
|
||||
if (!uint8Array) [[unlikely]] {
|
||||
throwOutOfMemoryError(globalObject, throwScope);
|
||||
return {};
|
||||
}
|
||||
|
||||
RELEASE_AND_RETURN(throwScope, JSC::JSValue::encode(uint8Array));
|
||||
}
|
||||
|
||||
// https://github.com/nodejs/node/blob/v22.9.0/lib/buffer.js#L834
|
||||
// using byteLength and byte offsets here is intentional
|
||||
static JSC::EncodedJSValue jsBufferPrototypeFunction_toStringBody(JSC::JSGlobalObject* lexicalGlobalObject, JSC::CallFrame* callFrame, typename IDLOperation<JSArrayBufferView>::ClassParameter castedThis)
|
||||
@@ -2430,6 +2528,11 @@ JSC_DEFINE_HOST_FUNCTION(jsBufferPrototypeFunction_swap64, (JSGlobalObject * lex
|
||||
return IDLOperation<JSArrayBufferView>::call<jsBufferPrototypeFunction_swap64Body>(*lexicalGlobalObject, *callFrame, "swap64");
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsBufferPrototypeFunction_slice, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
|
||||
{
|
||||
return IDLOperation<JSArrayBufferView>::call<jsBufferPrototypeFunction_sliceBody>(*lexicalGlobalObject, *callFrame, "slice");
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsBufferPrototypeFunction_toString, (JSGlobalObject * lexicalGlobalObject, CallFrame* callFrame))
|
||||
{
|
||||
return IDLOperation<JSArrayBufferView>::call<jsBufferPrototypeFunction_toStringBody>(*lexicalGlobalObject, *callFrame, "toString");
|
||||
@@ -2711,8 +2814,8 @@ static const HashTableValue JSBufferPrototypeTableValues[]
|
||||
{ "readUIntBE"_s, static_cast<unsigned>(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadUIntBECodeGenerator, 1 } },
|
||||
{ "readUIntLE"_s, static_cast<unsigned>(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeReadUIntLECodeGenerator, 1 } },
|
||||
|
||||
{ "slice"_s, static_cast<unsigned>(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeSliceCodeGenerator, 2 } },
|
||||
{ "subarray"_s, static_cast<unsigned>(JSC::PropertyAttribute::Builtin), NoIntrinsic, { HashTableValue::BuiltinGeneratorType, jsBufferPrototypeSliceCodeGenerator, 2 } },
|
||||
{ "slice"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_slice, 2 } },
|
||||
{ "subarray"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_slice, 2 } },
|
||||
{ "swap16"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_swap16, 0 } },
|
||||
{ "swap32"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_swap32, 0 } },
|
||||
{ "swap64"_s, static_cast<unsigned>(JSC::PropertyAttribute::Function), NoIntrinsic, { HashTableValue::NativeFunctionType, jsBufferPrototypeFunction_swap64, 0 } },
|
||||
|
||||
@@ -988,6 +988,10 @@ void JSMockFunctionPrototype::finishCreation(JSC::VM& vm, JSC::JSGlobalObject* g
|
||||
JSC_TO_STRING_TAG_WITHOUT_TRANSITION();
|
||||
|
||||
this->putDirect(vm, Identifier::fromString(vm, "_isMockFunction"_s), jsBoolean(true), 0);
|
||||
|
||||
// Support `using spy = spyOn(...)` — auto-restores when leaving scope.
|
||||
JSValue restoreFn = this->getDirect(vm, Identifier::fromString(vm, "mockRestore"_s));
|
||||
this->putDirect(vm, vm.propertyNames->disposeSymbol, restoreFn, static_cast<unsigned>(JSC::PropertyAttribute::Function | JSC::PropertyAttribute::DontEnum));
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(jsMockFunctionGetMockImplementation, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callframe))
|
||||
|
||||
@@ -1,634 +0,0 @@
|
||||
#include "JSPyObject.h"
|
||||
#include "BunPython.h"
|
||||
#include "ZigGlobalObject.h"
|
||||
#include "BunClientData.h"
|
||||
#include <JavaScriptCore/ObjectConstructor.h>
|
||||
#include <JavaScriptCore/FunctionPrototype.h>
|
||||
#include <JavaScriptCore/JSFunction.h>
|
||||
#include <wtf/text/WTFString.h>
|
||||
|
||||
namespace Bun {
|
||||
|
||||
using namespace JSC;
|
||||
|
||||
// Forward declaration for toString
|
||||
static JSC_DECLARE_HOST_FUNCTION(jsPyObjectToString);
|
||||
|
||||
// Forward declaration for call
|
||||
static JSC_DECLARE_HOST_FUNCTION(jsPyObjectCall);
|
||||
|
||||
// Forward declaration for iterator
|
||||
static JSC_DECLARE_HOST_FUNCTION(jsPyObjectIterator);
|
||||
|
||||
// Forward declaration for iterator next
|
||||
static JSC_DECLARE_HOST_FUNCTION(jsPyIteratorNext);
|
||||
|
||||
const ClassInfo JSPyObject::s_info = { "PythonValue"_s, &Base::s_info, nullptr, nullptr, CREATE_METHOD_TABLE(JSPyObject) };
|
||||
|
||||
template<typename Visitor>
|
||||
void JSPyObject::visitChildrenImpl(JSCell* cell, Visitor& visitor)
|
||||
{
|
||||
JSPyObject* thisObject = jsCast<JSPyObject*>(cell);
|
||||
ASSERT_GC_OBJECT_INHERITS(thisObject, info());
|
||||
Base::visitChildren(thisObject, visitor);
|
||||
}
|
||||
|
||||
DEFINE_VISIT_CHILDREN(JSPyObject);
|
||||
|
||||
void JSPyObject::finishCreation(VM& vm)
|
||||
{
|
||||
Base::finishCreation(vm);
|
||||
ASSERT(inherits(info()));
|
||||
}
|
||||
|
||||
JSC::GCClient::IsoSubspace* JSPyObject::subspaceForImpl(JSC::VM& vm)
|
||||
{
|
||||
return WebCore::subspaceForImpl<JSPyObject, WebCore::UseCustomHeapCellType::No>(
|
||||
vm,
|
||||
[](auto& spaces) { return spaces.m_clientSubspaceForPyObject.get(); },
|
||||
[](auto& spaces, auto&& space) { spaces.m_clientSubspaceForPyObject = std::forward<decltype(space)>(space); },
|
||||
[](auto& spaces) { return spaces.m_subspaceForPyObject.get(); },
|
||||
[](auto& spaces, auto&& space) { spaces.m_subspaceForPyObject = std::forward<decltype(space)>(space); });
|
||||
}
|
||||
|
||||
// Property access - proxy to Python's getattr
|
||||
bool JSPyObject::getOwnPropertySlot(JSObject* object, JSGlobalObject* globalObject, PropertyName propertyName, PropertySlot& slot)
|
||||
{
|
||||
JSPyObject* thisObject = jsCast<JSPyObject*>(object);
|
||||
VM& vm = globalObject->vm();
|
||||
|
||||
// Handle special JS properties
|
||||
if (propertyName == vm.propertyNames->toStringTagSymbol) {
|
||||
slot.setValue(object, static_cast<unsigned>(PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly), jsString(vm, String("PythonValue"_s)));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle toString
|
||||
if (propertyName == vm.propertyNames->toString) {
|
||||
slot.setValue(object, static_cast<unsigned>(PropertyAttribute::DontEnum),
|
||||
JSFunction::create(vm, globalObject, 0, "toString"_s, jsPyObjectToString, ImplementationVisibility::Public));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle nodejs.util.inspect.custom for console.log
|
||||
if (propertyName == Identifier::fromUid(vm.symbolRegistry().symbolForKey("nodejs.util.inspect.custom"_s))) {
|
||||
slot.setValue(object, static_cast<unsigned>(PropertyAttribute::DontEnum),
|
||||
JSFunction::create(vm, globalObject, 0, "inspect"_s, jsPyObjectToString, ImplementationVisibility::Public));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Handle Symbol.iterator for Python iterables
|
||||
if (propertyName == vm.propertyNames->iteratorSymbol) {
|
||||
// Check if this Python object is iterable
|
||||
if (PyIter_Check(thisObject->m_pyObject) || PyObject_HasAttrString(thisObject->m_pyObject, "__iter__")) {
|
||||
slot.setValue(object, static_cast<unsigned>(PropertyAttribute::DontEnum),
|
||||
JSFunction::create(vm, globalObject, 0, "[Symbol.iterator]"_s, jsPyObjectIterator, ImplementationVisibility::Public));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Handle length property for Python sequences (needed for Array.prototype methods)
|
||||
if (propertyName == vm.propertyNames->length) {
|
||||
if (PySequence_Check(thisObject->m_pyObject) && !PyUnicode_Check(thisObject->m_pyObject)) {
|
||||
Py_ssize_t len = PySequence_Size(thisObject->m_pyObject);
|
||||
if (len >= 0) {
|
||||
slot.setValue(object, static_cast<unsigned>(PropertyAttribute::DontEnum | PropertyAttribute::ReadOnly), jsNumber(len));
|
||||
return true;
|
||||
}
|
||||
PyErr_Clear();
|
||||
}
|
||||
}
|
||||
|
||||
// Convert property name to Python string
|
||||
auto* nameString = propertyName.publicName();
|
||||
if (!nameString) {
|
||||
return Base::getOwnPropertySlot(object, globalObject, propertyName, slot);
|
||||
}
|
||||
|
||||
auto nameUTF8 = nameString->utf8();
|
||||
PyObject* pyName = PyUnicode_FromStringAndSize(nameUTF8.data(), nameUTF8.length());
|
||||
if (!pyName) {
|
||||
PyErr_Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
// First try attribute access (for regular objects)
|
||||
PyObject* attr = PyObject_GetAttr(thisObject->m_pyObject, pyName);
|
||||
if (!attr) {
|
||||
PyErr_Clear();
|
||||
// If attribute access fails, try item access (for dicts/mappings)
|
||||
if (PyMapping_Check(thisObject->m_pyObject)) {
|
||||
attr = PyObject_GetItem(thisObject->m_pyObject, pyName);
|
||||
if (!attr) {
|
||||
PyErr_Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
Py_DECREF(pyName);
|
||||
|
||||
if (!attr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
JSValue jsAttr = Python::toJS(globalObject, attr);
|
||||
Py_DECREF(attr);
|
||||
|
||||
slot.setValue(object, static_cast<unsigned>(PropertyAttribute::None), jsAttr);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool JSPyObject::getOwnPropertySlotByIndex(JSObject* object, JSGlobalObject* globalObject, unsigned index, PropertySlot& slot)
|
||||
{
|
||||
JSPyObject* thisObject = jsCast<JSPyObject*>(object);
|
||||
|
||||
PyObject* item = PySequence_GetItem(thisObject->m_pyObject, static_cast<Py_ssize_t>(index));
|
||||
if (!item) {
|
||||
PyErr_Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
JSValue jsItem = Python::toJS(globalObject, item);
|
||||
Py_DECREF(item);
|
||||
|
||||
slot.setValue(object, static_cast<unsigned>(PropertyAttribute::None), jsItem);
|
||||
return true;
|
||||
}
|
||||
|
||||
void JSPyObject::getOwnPropertyNames(JSObject* object, JSGlobalObject* globalObject, PropertyNameArrayBuilder& propertyNames, DontEnumPropertiesMode mode)
|
||||
{
|
||||
JSPyObject* thisObject = jsCast<JSPyObject*>(object);
|
||||
VM& vm = globalObject->vm();
|
||||
|
||||
// Get dir() of the object
|
||||
PyObject* dir = PyObject_Dir(thisObject->m_pyObject);
|
||||
if (!dir) {
|
||||
PyErr_Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
Py_ssize_t len = PyList_Size(dir);
|
||||
for (Py_ssize_t i = 0; i < len; i++) {
|
||||
PyObject* name = PyList_GetItem(dir, i); // borrowed reference
|
||||
if (PyUnicode_Check(name)) {
|
||||
const char* nameStr = PyUnicode_AsUTF8(name);
|
||||
if (nameStr && nameStr[0] != '_') { // Skip private/dunder
|
||||
propertyNames.add(Identifier::fromString(vm, String::fromUTF8(nameStr)));
|
||||
}
|
||||
}
|
||||
}
|
||||
Py_DECREF(dir);
|
||||
}
|
||||
|
||||
// Helper to convert JSValue to PyObject
|
||||
static PyObject* jsValueToPyObject(JSGlobalObject* globalObject, JSValue value)
|
||||
{
|
||||
if (value.isNull() || value.isUndefined()) {
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
if (value.isBoolean()) {
|
||||
PyObject* result = value.asBoolean() ? Py_True : Py_False;
|
||||
Py_INCREF(result);
|
||||
return result;
|
||||
}
|
||||
if (value.isNumber()) {
|
||||
double num = value.asNumber();
|
||||
constexpr double maxSafeInt = 9007199254740992.0;
|
||||
if (std::floor(num) == num && num >= -maxSafeInt && num <= maxSafeInt) {
|
||||
return PyLong_FromLongLong(static_cast<long long>(num));
|
||||
}
|
||||
return PyFloat_FromDouble(num);
|
||||
}
|
||||
if (value.isString()) {
|
||||
auto str = value.toWTFString(globalObject);
|
||||
auto utf8 = str.utf8();
|
||||
return PyUnicode_FromStringAndSize(utf8.data(), utf8.length());
|
||||
}
|
||||
if (auto* pyVal = jsDynamicCast<JSPyObject*>(value)) {
|
||||
PyObject* obj = pyVal->pyObject();
|
||||
Py_INCREF(obj);
|
||||
return obj;
|
||||
}
|
||||
// For other JS objects, return None for now
|
||||
Py_INCREF(Py_None);
|
||||
return Py_None;
|
||||
}
|
||||
|
||||
bool JSPyObject::put(JSCell* cell, JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
|
||||
{
|
||||
JSPyObject* thisObject = jsCast<JSPyObject*>(cell);
|
||||
|
||||
auto* nameString = propertyName.publicName();
|
||||
if (!nameString) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto nameUTF8 = nameString->utf8();
|
||||
PyObject* pyName = PyUnicode_FromStringAndSize(nameUTF8.data(), nameUTF8.length());
|
||||
if (!pyName) {
|
||||
PyErr_Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
PyObject* pyValue = jsValueToPyObject(globalObject, value);
|
||||
if (!pyValue) {
|
||||
Py_DECREF(pyName);
|
||||
PyErr_Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
int result = -1;
|
||||
|
||||
// For dicts/mappings, use item assignment
|
||||
if (PyDict_Check(thisObject->m_pyObject)) {
|
||||
result = PyDict_SetItem(thisObject->m_pyObject, pyName, pyValue);
|
||||
} else if (PyMapping_Check(thisObject->m_pyObject)) {
|
||||
result = PyObject_SetItem(thisObject->m_pyObject, pyName, pyValue);
|
||||
} else {
|
||||
// For other objects, try attribute assignment
|
||||
result = PyObject_SetAttr(thisObject->m_pyObject, pyName, pyValue);
|
||||
}
|
||||
|
||||
Py_DECREF(pyName);
|
||||
Py_DECREF(pyValue);
|
||||
|
||||
if (result < 0) {
|
||||
PyErr_Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool JSPyObject::putByIndex(JSCell* cell, JSGlobalObject* globalObject, unsigned index, JSValue value, bool)
|
||||
{
|
||||
JSPyObject* thisObject = jsCast<JSPyObject*>(cell);
|
||||
|
||||
if (!PySequence_Check(thisObject->m_pyObject)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
PyObject* pyValue = jsValueToPyObject(globalObject, value);
|
||||
if (!pyValue) {
|
||||
PyErr_Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get current length
|
||||
Py_ssize_t length = PySequence_Size(thisObject->m_pyObject);
|
||||
if (length < 0) {
|
||||
PyErr_Clear();
|
||||
Py_DECREF(pyValue);
|
||||
return false;
|
||||
}
|
||||
|
||||
int result;
|
||||
if (static_cast<Py_ssize_t>(index) >= length) {
|
||||
// Index is beyond current length - we need to extend the list
|
||||
if (PyList_Check(thisObject->m_pyObject)) {
|
||||
// For lists, extend with None values up to the index, then set
|
||||
PyObject* list = thisObject->m_pyObject;
|
||||
for (Py_ssize_t i = length; i < static_cast<Py_ssize_t>(index); i++) {
|
||||
if (PyList_Append(list, Py_None) < 0) {
|
||||
PyErr_Clear();
|
||||
Py_DECREF(pyValue);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
result = PyList_Append(list, pyValue);
|
||||
} else {
|
||||
// For other sequences, try insert or set item
|
||||
result = PySequence_SetItem(thisObject->m_pyObject, static_cast<Py_ssize_t>(index), pyValue);
|
||||
}
|
||||
} else {
|
||||
result = PySequence_SetItem(thisObject->m_pyObject, static_cast<Py_ssize_t>(index), pyValue);
|
||||
}
|
||||
|
||||
Py_DECREF(pyValue);
|
||||
|
||||
if (result < 0) {
|
||||
PyErr_Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// toString - returns Python's str() representation
|
||||
JSC_DEFINE_HOST_FUNCTION(jsPyObjectToString, (JSGlobalObject * globalObject, CallFrame* callFrame))
|
||||
{
|
||||
VM& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
JSValue thisValue = callFrame->thisValue();
|
||||
JSPyObject* thisObject = jsDynamicCast<JSPyObject*>(thisValue);
|
||||
if (!thisObject) {
|
||||
return JSValue::encode(jsString(vm, String("[object PythonValue]"_s)));
|
||||
}
|
||||
|
||||
PyObject* str = PyObject_Str(thisObject->pyObject());
|
||||
if (!str) {
|
||||
PyErr_Clear();
|
||||
return JSValue::encode(jsString(vm, String("[object PythonValue]"_s)));
|
||||
}
|
||||
|
||||
const char* utf8 = PyUnicode_AsUTF8(str);
|
||||
if (!utf8) {
|
||||
Py_DECREF(str);
|
||||
PyErr_Clear();
|
||||
return JSValue::encode(jsString(vm, String("[object PythonValue]"_s)));
|
||||
}
|
||||
|
||||
JSValue result = jsString(vm, WTF::String::fromUTF8(utf8));
|
||||
Py_DECREF(str);
|
||||
return JSValue::encode(result);
|
||||
}
|
||||
|
||||
// Iterator next - called from the JS iterator's next() method
|
||||
JSC_DEFINE_HOST_FUNCTION(jsPyIteratorNext, (JSGlobalObject * globalObject, CallFrame* callFrame))
|
||||
{
|
||||
VM& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
// Get the Python iterator from the thisValue (which should be the iterator wrapper object)
|
||||
JSValue thisValue = callFrame->thisValue();
|
||||
JSObject* thisObject = thisValue.toObject(globalObject);
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
|
||||
// Get the stored Python iterator
|
||||
JSValue pyIterValue = thisObject->getDirect(vm, Identifier::fromString(vm, "_pyIter"_s));
|
||||
if (!pyIterValue) {
|
||||
return JSValue::encode(constructEmptyObject(globalObject));
|
||||
}
|
||||
|
||||
JSPyObject* pyIter = jsDynamicCast<JSPyObject*>(pyIterValue);
|
||||
if (!pyIter) {
|
||||
return JSValue::encode(constructEmptyObject(globalObject));
|
||||
}
|
||||
|
||||
// Call Python's next() on the iterator
|
||||
PyObject* nextItem = PyIter_Next(pyIter->pyObject());
|
||||
|
||||
// Create the result object { value, done }
|
||||
JSObject* result = constructEmptyObject(globalObject);
|
||||
|
||||
if (nextItem) {
|
||||
// Got an item
|
||||
result->putDirect(vm, Identifier::fromString(vm, "value"_s), Python::toJS(globalObject, nextItem));
|
||||
result->putDirect(vm, Identifier::fromString(vm, "done"_s), jsBoolean(false));
|
||||
Py_DECREF(nextItem);
|
||||
} else {
|
||||
// Check if it's StopIteration or an error
|
||||
if (PyErr_Occurred()) {
|
||||
if (PyErr_ExceptionMatches(PyExc_StopIteration)) {
|
||||
PyErr_Clear();
|
||||
} else {
|
||||
// Real error - propagate it
|
||||
PyErr_Print();
|
||||
PyErr_Clear();
|
||||
throwTypeError(globalObject, scope, "Python iterator error"_s);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
// Iterator exhausted
|
||||
result->putDirect(vm, Identifier::fromString(vm, "value"_s), jsUndefined());
|
||||
result->putDirect(vm, Identifier::fromString(vm, "done"_s), jsBoolean(true));
|
||||
}
|
||||
|
||||
return JSValue::encode(result);
|
||||
}
|
||||
|
||||
// Symbol.iterator - returns a JS iterator that wraps Python iteration
|
||||
JSC_DEFINE_HOST_FUNCTION(jsPyObjectIterator, (JSGlobalObject * globalObject, CallFrame* callFrame))
|
||||
{
|
||||
VM& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
JSValue thisValue = callFrame->thisValue();
|
||||
JSPyObject* thisObject = jsDynamicCast<JSPyObject*>(thisValue);
|
||||
if (!thisObject) {
|
||||
throwTypeError(globalObject, scope, "Not a Python object"_s);
|
||||
return {};
|
||||
}
|
||||
|
||||
// Get a Python iterator for this object
|
||||
PyObject* pyIter = PyObject_GetIter(thisObject->pyObject());
|
||||
if (!pyIter) {
|
||||
PyErr_Clear();
|
||||
throwTypeError(globalObject, scope, "Python object is not iterable"_s);
|
||||
return {};
|
||||
}
|
||||
|
||||
// Create a JS iterator object
|
||||
JSObject* jsIter = constructEmptyObject(globalObject);
|
||||
|
||||
// Store the Python iterator (as JSPyObject) on the JS iterator object
|
||||
auto* zigGlobalObject = jsCast<Zig::GlobalObject*>(globalObject);
|
||||
Structure* structure = zigGlobalObject->m_JSPyObjectStructure.get();
|
||||
if (!structure) {
|
||||
structure = JSPyObject::createStructure(vm, globalObject, globalObject->objectPrototype());
|
||||
zigGlobalObject->m_JSPyObjectStructure.set(vm, zigGlobalObject, structure);
|
||||
}
|
||||
JSPyObject* wrappedIter = JSPyObject::create(vm, globalObject, structure, pyIter);
|
||||
Py_DECREF(pyIter); // JSPyObject takes ownership
|
||||
|
||||
jsIter->putDirect(vm, Identifier::fromString(vm, "_pyIter"_s), wrappedIter);
|
||||
|
||||
// Add the next() method
|
||||
jsIter->putDirect(vm, Identifier::fromString(vm, "next"_s),
|
||||
JSFunction::create(vm, globalObject, 0, "next"_s, jsPyIteratorNext, ImplementationVisibility::Public));
|
||||
|
||||
return JSValue::encode(jsIter);
|
||||
}
|
||||
|
||||
// Helper to check if a JSValue is a plain object (not array, not wrapped Python object)
|
||||
static bool isPlainJSObject(JSGlobalObject* globalObject, JSValue value)
|
||||
{
|
||||
if (!value.isObject())
|
||||
return false;
|
||||
JSObject* obj = value.getObject();
|
||||
// Not a plain object if it's a JSPyObject (wrapped Python object)
|
||||
if (jsDynamicCast<JSPyObject*>(obj))
|
||||
return false;
|
||||
// Not a plain object if it's an array
|
||||
if (isJSArray(obj))
|
||||
return false;
|
||||
// Not a plain object if it's a function
|
||||
if (obj->isCallable())
|
||||
return false;
|
||||
// Check if it's a plain Object (not a special type like Date, Map, etc.)
|
||||
// We consider it kwargs-eligible if its prototype is Object.prototype or null
|
||||
JSValue proto = obj->getPrototype(globalObject);
|
||||
return proto.isNull() || proto == globalObject->objectPrototype();
|
||||
}
|
||||
|
||||
// Get the expected positional argument count for a Python callable
|
||||
// Returns -1 if we can't determine (e.g., built-in functions)
|
||||
static int getExpectedArgCount(PyObject* callable)
|
||||
{
|
||||
PyObject* codeObj = nullptr;
|
||||
|
||||
// For regular functions, get __code__
|
||||
if (PyFunction_Check(callable)) {
|
||||
codeObj = PyFunction_GET_CODE(callable);
|
||||
}
|
||||
// For methods, get the underlying function's __code__
|
||||
else if (PyMethod_Check(callable)) {
|
||||
PyObject* func = PyMethod_GET_FUNCTION(callable);
|
||||
if (PyFunction_Check(func)) {
|
||||
codeObj = PyFunction_GET_CODE(func);
|
||||
}
|
||||
}
|
||||
// Try getting __code__ attribute for other callables (like lambdas assigned to variables)
|
||||
else if (PyObject_HasAttrString(callable, "__code__")) {
|
||||
codeObj = PyObject_GetAttrString(callable, "__code__");
|
||||
if (codeObj) {
|
||||
PyObject* argCountObj = PyObject_GetAttrString(codeObj, "co_argcount");
|
||||
Py_DECREF(codeObj);
|
||||
if (argCountObj) {
|
||||
int count = static_cast<int>(PyLong_AsLong(argCountObj));
|
||||
Py_DECREF(argCountObj);
|
||||
return count;
|
||||
}
|
||||
}
|
||||
PyErr_Clear();
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!codeObj) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Get co_argcount from the code object
|
||||
PyCodeObject* code = reinterpret_cast<PyCodeObject*>(codeObj);
|
||||
return code->co_argcount;
|
||||
}
|
||||
|
||||
// Call Python function from JS
|
||||
JSC_DEFINE_HOST_FUNCTION(jsPyObjectCall, (JSGlobalObject * globalObject, CallFrame* callFrame))
|
||||
{
|
||||
VM& vm = globalObject->vm();
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
JSPyObject* thisObject = jsDynamicCast<JSPyObject*>(callFrame->jsCallee());
|
||||
if (!thisObject) {
|
||||
throwTypeError(globalObject, scope, "Not a Python callable"_s);
|
||||
return {};
|
||||
}
|
||||
|
||||
PyObject* pyFunc = thisObject->pyObject();
|
||||
if (!PyCallable_Check(pyFunc)) {
|
||||
throwTypeError(globalObject, scope, "Python object is not callable"_s);
|
||||
return {};
|
||||
}
|
||||
|
||||
// Convert all arguments as positional args
|
||||
// TODO: Support kwargs via a special marker like $kwargs from "bun:python"
|
||||
size_t argCount = callFrame->argumentCount();
|
||||
|
||||
// Check if the Python function expects fewer arguments than provided
|
||||
// If so, trim the argument list to match (allows flexible callback signatures)
|
||||
int expectedArgs = getExpectedArgCount(pyFunc);
|
||||
if (expectedArgs >= 0 && static_cast<size_t>(expectedArgs) < argCount) {
|
||||
argCount = static_cast<size_t>(expectedArgs);
|
||||
}
|
||||
PyObject* kwargs = nullptr;
|
||||
|
||||
// Convert JS arguments to Python tuple
|
||||
PyObject* args = PyTuple_New(static_cast<Py_ssize_t>(argCount));
|
||||
if (!args) {
|
||||
Py_XDECREF(kwargs);
|
||||
throwOutOfMemoryError(globalObject, scope);
|
||||
return {};
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < argCount; i++) {
|
||||
JSValue jsArg = callFrame->uncheckedArgument(i);
|
||||
PyObject* pyArg = nullptr;
|
||||
|
||||
// Check if it's already a wrapped Python object first
|
||||
if (auto* pyVal = jsDynamicCast<JSPyObject*>(jsArg)) {
|
||||
// Unwrap JSPyObject back to PyObject
|
||||
pyArg = pyVal->pyObject();
|
||||
Py_INCREF(pyArg);
|
||||
} else {
|
||||
// Convert JS value to Python using the standard conversion
|
||||
// This handles primitives, arrays (as list), and objects (as dict)
|
||||
pyArg = Python::fromJS(globalObject, jsArg);
|
||||
}
|
||||
|
||||
if (!pyArg) {
|
||||
Py_DECREF(args);
|
||||
Py_XDECREF(kwargs);
|
||||
throwTypeError(globalObject, scope, "Failed to convert argument to Python"_s);
|
||||
return {};
|
||||
}
|
||||
PyTuple_SET_ITEM(args, i, pyArg); // steals reference
|
||||
}
|
||||
|
||||
// Call the Python function with args and optional kwargs
|
||||
PyObject* result = PyObject_Call(pyFunc, args, kwargs);
|
||||
Py_DECREF(args);
|
||||
Py_XDECREF(kwargs);
|
||||
|
||||
if (!result) {
|
||||
// Get Python exception info
|
||||
PyObject *type, *value, *traceback;
|
||||
PyErr_Fetch(&type, &value, &traceback);
|
||||
PyErr_NormalizeException(&type, &value, &traceback);
|
||||
|
||||
WTF::String errorMessage = "Python error"_s;
|
||||
if (value) {
|
||||
PyObject* str = PyObject_Str(value);
|
||||
if (str) {
|
||||
const char* errStr = PyUnicode_AsUTF8(str);
|
||||
if (errStr) {
|
||||
errorMessage = WTF::String::fromUTF8(errStr);
|
||||
}
|
||||
Py_DECREF(str);
|
||||
}
|
||||
}
|
||||
|
||||
Py_XDECREF(type);
|
||||
Py_XDECREF(value);
|
||||
Py_XDECREF(traceback);
|
||||
|
||||
throwTypeError(globalObject, scope, errorMessage);
|
||||
return {};
|
||||
}
|
||||
|
||||
JSValue jsResult = Python::toJS(globalObject, result);
|
||||
Py_DECREF(result);
|
||||
|
||||
return JSValue::encode(jsResult);
|
||||
}
|
||||
|
||||
CallData JSPyObject::getCallData(JSCell* cell)
|
||||
{
|
||||
JSPyObject* thisObject = jsCast<JSPyObject*>(cell);
|
||||
|
||||
CallData callData;
|
||||
// Only allow direct calls for non-type callables (functions, lambdas, etc.)
|
||||
// Python types (classes) should require `new`, like JS classes
|
||||
if (thisObject->isCallable() && !PyType_Check(thisObject->m_pyObject)) {
|
||||
callData.type = CallData::Type::Native;
|
||||
callData.native.function = jsPyObjectCall;
|
||||
}
|
||||
return callData;
|
||||
}
|
||||
|
||||
// For Python, constructing and calling are the same thing
|
||||
// This allows `new Counter()` to work for Python classes
|
||||
CallData JSPyObject::getConstructData(JSCell* cell)
|
||||
{
|
||||
JSPyObject* thisObject = jsCast<JSPyObject*>(cell);
|
||||
|
||||
CallData constructData;
|
||||
if (thisObject->isCallable()) {
|
||||
constructData.type = CallData::Type::Native;
|
||||
constructData.native.function = jsPyObjectCall;
|
||||
}
|
||||
return constructData;
|
||||
}
|
||||
|
||||
} // namespace Bun
|
||||
@@ -1,86 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "root.h"
|
||||
#include <Python.h>
|
||||
|
||||
namespace Bun {
|
||||
using namespace JSC;
|
||||
|
||||
// JSPyObject wraps a PyObject* and proxies property access, calls, etc. to Python.
|
||||
// When created, it increments the Python refcount; when finalized by GC, it decrements it.
|
||||
class JSPyObject : public JSC::JSDestructibleObject {
|
||||
using Base = JSC::JSDestructibleObject;
|
||||
|
||||
public:
|
||||
JSPyObject(JSC::VM& vm, JSC::Structure* structure, PyObject* pyObject)
|
||||
: Base(vm, structure)
|
||||
, m_pyObject(pyObject)
|
||||
{
|
||||
// Prevent Python from freeing this object while we hold it
|
||||
Py_INCREF(m_pyObject);
|
||||
}
|
||||
|
||||
DECLARE_INFO;
|
||||
DECLARE_VISIT_CHILDREN;
|
||||
|
||||
static constexpr unsigned StructureFlags = Base::StructureFlags | OverridesGetOwnPropertySlot | OverridesGetOwnPropertyNames | OverridesPut | OverridesGetCallData | InterceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero;
|
||||
|
||||
template<typename, JSC::SubspaceAccess mode>
|
||||
static JSC::GCClient::IsoSubspace* subspaceFor(JSC::VM& vm)
|
||||
{
|
||||
if constexpr (mode == JSC::SubspaceAccess::Concurrently)
|
||||
return nullptr;
|
||||
return subspaceForImpl(vm);
|
||||
}
|
||||
|
||||
static JSC::GCClient::IsoSubspace* subspaceForImpl(JSC::VM& vm);
|
||||
|
||||
static JSC::Structure* createStructure(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::JSValue prototype)
|
||||
{
|
||||
return JSC::Structure::create(vm, globalObject, prototype,
|
||||
JSC::TypeInfo(JSC::ObjectType, StructureFlags), info());
|
||||
}
|
||||
|
||||
static JSPyObject* create(JSC::VM& vm, JSC::JSGlobalObject* globalObject, JSC::Structure* structure, PyObject* pyObject)
|
||||
{
|
||||
JSPyObject* value = new (NotNull, JSC::allocateCell<JSPyObject>(vm)) JSPyObject(vm, structure, pyObject);
|
||||
value->finishCreation(vm);
|
||||
return value;
|
||||
}
|
||||
|
||||
void finishCreation(JSC::VM& vm);
|
||||
|
||||
static void destroy(JSCell* thisObject)
|
||||
{
|
||||
JSPyObject* value = static_cast<JSPyObject*>(thisObject);
|
||||
// Release Python reference
|
||||
Py_DECREF(value->m_pyObject);
|
||||
value->~JSPyObject();
|
||||
}
|
||||
|
||||
// Property access - proxy to Python's __getattr__
|
||||
static bool getOwnPropertySlot(JSObject*, JSGlobalObject*, PropertyName, PropertySlot&);
|
||||
static bool getOwnPropertySlotByIndex(JSObject*, JSGlobalObject*, unsigned, PropertySlot&);
|
||||
static void getOwnPropertyNames(JSObject*, JSGlobalObject*, PropertyNameArrayBuilder&, DontEnumPropertiesMode);
|
||||
|
||||
// Property set - proxy to Python's __setattr__
|
||||
static bool put(JSCell*, JSGlobalObject*, PropertyName, JSValue, PutPropertySlot&);
|
||||
static bool putByIndex(JSCell*, JSGlobalObject*, unsigned, JSValue, bool);
|
||||
|
||||
// If callable, proxy to Python's __call__
|
||||
static CallData getCallData(JSCell*);
|
||||
|
||||
// If callable, also make constructible (for Python classes)
|
||||
static CallData getConstructData(JSCell*);
|
||||
|
||||
// Get the wrapped PyObject
|
||||
PyObject* pyObject() const { return m_pyObject; }
|
||||
|
||||
// Helper to check if Python object is callable
|
||||
bool isCallable() const { return PyCallable_Check(m_pyObject); }
|
||||
|
||||
private:
|
||||
PyObject* m_pyObject;
|
||||
};
|
||||
|
||||
} // namespace Bun
|
||||
@@ -40,7 +40,6 @@
|
||||
#include "JSCommonJSExtensions.h"
|
||||
|
||||
#include "BunProcess.h"
|
||||
#include "BunPython.h"
|
||||
|
||||
namespace Bun {
|
||||
using namespace JSC;
|
||||
@@ -66,7 +65,6 @@ public:
|
||||
};
|
||||
|
||||
extern "C" BunLoaderType Bun__getDefaultLoader(JSC::JSGlobalObject*, BunString* specifier);
|
||||
extern "C" JSC::EncodedJSValue BunObject_getter_main(JSC::JSGlobalObject*);
|
||||
|
||||
static JSC::JSInternalPromise* rejectedInternalPromise(JSC::JSGlobalObject* globalObject, JSC::JSValue value)
|
||||
{
|
||||
@@ -979,15 +977,6 @@ static JSValue fetchESMSourceCode(
|
||||
auto&& provider = Zig::SourceProvider::create(globalObject, res->result.value, JSC::SourceProviderSourceType::Module, true);
|
||||
RELEASE_AND_RETURN(scope, rejectOrResolve(JSSourceCode::create(vm, JSC::SourceCode(provider))));
|
||||
}
|
||||
case SyntheticModuleType::PythonBuiltin: {
|
||||
// Python builtin module - import from Python's standard library
|
||||
WTF::String moduleName = res->result.value.source_code.toWTFString(BunString::NonNull);
|
||||
auto function = Python::generatePythonBuiltinModuleSourceCode(globalObject, moduleName);
|
||||
auto source = JSC::SourceCode(
|
||||
JSC::SyntheticSourceProvider::create(WTF::move(function),
|
||||
JSC::SourceOrigin(), WTF::move(moduleKey)));
|
||||
RELEASE_AND_RETURN(scope, rejectOrResolve(JSSourceCode::create(vm, WTF::move(source))));
|
||||
}
|
||||
|
||||
#define CASE(str, name) \
|
||||
case (SyntheticModuleType::name): { \
|
||||
@@ -1114,21 +1103,6 @@ static JSValue fetchESMSourceCode(
|
||||
JSC::SourceOrigin(), specifier->toWTFString(BunString::ZeroCopy)));
|
||||
JSC::ensureStillAliveHere(value);
|
||||
RELEASE_AND_RETURN(scope, rejectOrResolve(JSSourceCode::create(globalObject->vm(), WTF::move(source))));
|
||||
} else if (res->result.value.tag == SyntheticModuleType::Python) {
|
||||
// Python module - run Python file and wrap exports as JSPyObject
|
||||
WTF::String filePath = res->result.value.source_code.toWTFString(BunString::NonNull);
|
||||
// Check if this is the main entry point by comparing against Bun.main
|
||||
bool isMainEntry = false;
|
||||
JSValue mainValue = JSValue::decode(BunObject_getter_main(globalObject));
|
||||
if (mainValue.isString()) {
|
||||
WTF::String mainPath = mainValue.toWTFString(globalObject);
|
||||
isMainEntry = (filePath == mainPath);
|
||||
}
|
||||
auto function = Python::generatePythonModuleSourceCode(globalObject, filePath, isMainEntry);
|
||||
auto source = JSC::SourceCode(
|
||||
JSC::SyntheticSourceProvider::create(WTF::move(function),
|
||||
JSC::SourceOrigin(), specifier->toWTFString(BunString::ZeroCopy)));
|
||||
RELEASE_AND_RETURN(scope, rejectOrResolve(JSSourceCode::create(globalObject->vm(), WTF::move(source))));
|
||||
}
|
||||
|
||||
RELEASE_AND_RETURN(scope, rejectOrResolve(JSC::JSSourceCode::create(vm, JSC::SourceCode(Zig::SourceProvider::create(globalObject, res->result.value)))));
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,53 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "root.h"
|
||||
#include "Python.h"
|
||||
|
||||
namespace Bun {
|
||||
|
||||
using namespace JSC;
|
||||
|
||||
// Base wrapper for JS values in Python - used for functions and other non-container types
|
||||
struct PyJSValueObject {
|
||||
PyObject_HEAD
|
||||
JSValue jsValue;
|
||||
JSGlobalObject* globalObject;
|
||||
|
||||
static PyJSValueObject* New();
|
||||
static PyJSValueObject* NewDict(JSGlobalObject* globalObject, JSValue value);
|
||||
static PyJSValueObject* NewList(JSGlobalObject* globalObject, JSValue value);
|
||||
static void initType();
|
||||
};
|
||||
|
||||
// Dict subclass wrapper - makes isinstance(obj, dict) return True
|
||||
// Uses same memory layout as PyJSValueObject but with dict as base type
|
||||
struct PyJSDictObject {
|
||||
PyDictObject dict; // Must be first - inherits from dict
|
||||
JSValue jsValue;
|
||||
JSGlobalObject* globalObject;
|
||||
};
|
||||
|
||||
// List subclass wrapper - makes isinstance(obj, list) return True
|
||||
struct PyJSListObject {
|
||||
PyListObject list; // Must be first - inherits from list
|
||||
JSValue jsValue;
|
||||
JSGlobalObject* globalObject;
|
||||
};
|
||||
|
||||
// Bound method wrapper - preserves 'this' context when accessing methods on JS objects
|
||||
// When you do `obj.method()` in Python, we need to call method with `this` = obj
|
||||
struct PyJSBoundMethod {
|
||||
PyObject_HEAD
|
||||
JSValue function; // The JS function
|
||||
JSValue thisObject; // The object the function was accessed from
|
||||
JSGlobalObject* globalObject;
|
||||
|
||||
static PyJSBoundMethod* New(JSGlobalObject* globalObject, JSValue function, JSValue thisObject);
|
||||
static void initType();
|
||||
};
|
||||
|
||||
// Try to unwrap a PyObject that wraps a JSValue back to the underlying JSValue
|
||||
// Returns empty JSValue if the object is not a PyJSValueObject, PyJSDictObject, or PyJSListObject
|
||||
JSValue tryUnwrapJSValue(PyObject* obj);
|
||||
|
||||
} // namespace Bun
|
||||
@@ -136,23 +136,23 @@ private:
|
||||
bool load_functions()
|
||||
{
|
||||
CFRelease = (void (*)(CFTypeRef))dlsym(cf_handle, "CFRelease");
|
||||
CFStringCreateWithCString = (CFStringRef(*)(CFAllocatorRef, const char*, CFStringEncoding))dlsym(cf_handle, "CFStringCreateWithCString");
|
||||
CFDataCreate = (CFDataRef(*)(CFAllocatorRef, const UInt8*, CFIndex))dlsym(cf_handle, "CFDataCreate");
|
||||
CFStringCreateWithCString = (CFStringRef (*)(CFAllocatorRef, const char*, CFStringEncoding))dlsym(cf_handle, "CFStringCreateWithCString");
|
||||
CFDataCreate = (CFDataRef (*)(CFAllocatorRef, const UInt8*, CFIndex))dlsym(cf_handle, "CFDataCreate");
|
||||
CFDataGetBytePtr = (const UInt8* (*)(CFDataRef))dlsym(cf_handle, "CFDataGetBytePtr");
|
||||
CFDataGetLength = (CFIndex(*)(CFDataRef))dlsym(cf_handle, "CFDataGetLength");
|
||||
CFDictionaryCreateMutable = (CFMutableDictionaryRef(*)(CFAllocatorRef, CFIndex, const CFDictionaryKeyCallBacks*, const CFDictionaryValueCallBacks*))dlsym(cf_handle, "CFDictionaryCreateMutable");
|
||||
CFDataGetLength = (CFIndex (*)(CFDataRef))dlsym(cf_handle, "CFDataGetLength");
|
||||
CFDictionaryCreateMutable = (CFMutableDictionaryRef (*)(CFAllocatorRef, CFIndex, const CFDictionaryKeyCallBacks*, const CFDictionaryValueCallBacks*))dlsym(cf_handle, "CFDictionaryCreateMutable");
|
||||
CFDictionaryAddValue = (void (*)(CFMutableDictionaryRef, const void*, const void*))dlsym(cf_handle, "CFDictionaryAddValue");
|
||||
CFStringGetCString = (Boolean(*)(CFStringRef, char*, CFIndex, CFStringEncoding))dlsym(cf_handle, "CFStringGetCString");
|
||||
CFStringGetCString = (Boolean (*)(CFStringRef, char*, CFIndex, CFStringEncoding))dlsym(cf_handle, "CFStringGetCString");
|
||||
CFStringGetCStringPtr = (const char* (*)(CFStringRef, CFStringEncoding))dlsym(cf_handle, "CFStringGetCStringPtr");
|
||||
CFStringGetLength = (CFIndex(*)(CFStringRef))dlsym(cf_handle, "CFStringGetLength");
|
||||
CFStringGetMaximumSizeForEncoding = (CFIndex(*)(CFIndex, CFStringEncoding))dlsym(cf_handle, "CFStringGetMaximumSizeForEncoding");
|
||||
CFStringGetLength = (CFIndex (*)(CFStringRef))dlsym(cf_handle, "CFStringGetLength");
|
||||
CFStringGetMaximumSizeForEncoding = (CFIndex (*)(CFIndex, CFStringEncoding))dlsym(cf_handle, "CFStringGetMaximumSizeForEncoding");
|
||||
|
||||
SecItemAdd = (OSStatus(*)(CFDictionaryRef, CFTypeRef*))dlsym(handle, "SecItemAdd");
|
||||
SecItemCopyMatching = (OSStatus(*)(CFDictionaryRef, CFTypeRef*))dlsym(handle, "SecItemCopyMatching");
|
||||
SecItemUpdate = (OSStatus(*)(CFDictionaryRef, CFDictionaryRef))dlsym(handle, "SecItemUpdate");
|
||||
SecItemDelete = (OSStatus(*)(CFDictionaryRef))dlsym(handle, "SecItemDelete");
|
||||
SecCopyErrorMessageString = (CFStringRef(*)(OSStatus, void*))dlsym(handle, "SecCopyErrorMessageString");
|
||||
SecAccessCreate = (OSStatus(*)(CFStringRef, CFArrayRef, SecAccessRef*))dlsym(handle, "SecAccessCreate");
|
||||
SecItemAdd = (OSStatus (*)(CFDictionaryRef, CFTypeRef*))dlsym(handle, "SecItemAdd");
|
||||
SecItemCopyMatching = (OSStatus (*)(CFDictionaryRef, CFTypeRef*))dlsym(handle, "SecItemCopyMatching");
|
||||
SecItemUpdate = (OSStatus (*)(CFDictionaryRef, CFDictionaryRef))dlsym(handle, "SecItemUpdate");
|
||||
SecItemDelete = (OSStatus (*)(CFDictionaryRef))dlsym(handle, "SecItemDelete");
|
||||
SecCopyErrorMessageString = (CFStringRef (*)(OSStatus, void*))dlsym(handle, "SecCopyErrorMessageString");
|
||||
SecAccessCreate = (OSStatus (*)(CFStringRef, CFArrayRef, SecAccessRef*))dlsym(handle, "SecAccessCreate");
|
||||
|
||||
return CFRelease && CFStringCreateWithCString && CFDataCreate && CFDataGetBytePtr && CFDataGetLength && CFDictionaryCreateMutable && CFDictionaryAddValue && SecItemAdd && SecItemCopyMatching && SecItemUpdate && SecItemDelete && SecCopyErrorMessageString && SecAccessCreate && CFStringGetCString && CFStringGetCStringPtr && CFStringGetLength && CFStringGetMaximumSizeForEncoding;
|
||||
}
|
||||
|
||||
@@ -199,19 +199,19 @@ private:
|
||||
g_free = (void (*)(gpointer))dlsym(glib_handle, "g_free");
|
||||
g_hash_table_new = (GHashTable * (*)(void*, void*)) dlsym(glib_handle, "g_hash_table_new");
|
||||
g_hash_table_destroy = (void (*)(GHashTable*))dlsym(glib_handle, "g_hash_table_destroy");
|
||||
g_hash_table_lookup = (gpointer(*)(GHashTable*, gpointer))dlsym(glib_handle, "g_hash_table_lookup");
|
||||
g_hash_table_lookup = (gpointer (*)(GHashTable*, gpointer))dlsym(glib_handle, "g_hash_table_lookup");
|
||||
g_hash_table_insert = (void (*)(GHashTable*, gpointer, gpointer))dlsym(glib_handle, "g_hash_table_insert");
|
||||
g_list_free = (void (*)(GList*))dlsym(glib_handle, "g_list_free");
|
||||
g_list_free_full = (void (*)(GList*, void (*)(gpointer)))dlsym(glib_handle, "g_list_free_full");
|
||||
g_str_hash = (guint(*)(gpointer))dlsym(glib_handle, "g_str_hash");
|
||||
g_str_equal = (gboolean(*)(gpointer, gpointer))dlsym(glib_handle, "g_str_equal");
|
||||
g_str_hash = (guint (*)(gpointer))dlsym(glib_handle, "g_str_hash");
|
||||
g_str_equal = (gboolean (*)(gpointer, gpointer))dlsym(glib_handle, "g_str_equal");
|
||||
|
||||
// Load libsecret functions
|
||||
secret_password_store_sync = (gboolean(*)(const SecretSchema*, const gchar*, const gchar*, const gchar*, void*, GError**, ...))
|
||||
secret_password_store_sync = (gboolean (*)(const SecretSchema*, const gchar*, const gchar*, const gchar*, void*, GError**, ...))
|
||||
dlsym(secret_handle, "secret_password_store_sync");
|
||||
secret_password_lookup_sync = (gchar * (*)(const SecretSchema*, void*, GError**, ...))
|
||||
dlsym(secret_handle, "secret_password_lookup_sync");
|
||||
secret_password_clear_sync = (gboolean(*)(const SecretSchema*, void*, GError**, ...))
|
||||
secret_password_clear_sync = (gboolean (*)(const SecretSchema*, void*, GError**, ...))
|
||||
dlsym(secret_handle, "secret_password_clear_sync");
|
||||
secret_password_free = (void (*)(gchar*))dlsym(secret_handle, "secret_password_free");
|
||||
secret_service_search_sync = (GList * (*)(SecretService*, const SecretSchema*, GHashTable*, SecretSearchFlags, void*, GError**))
|
||||
@@ -220,7 +220,7 @@ private:
|
||||
secret_value_get_text = (const gchar* (*)(SecretValue*))dlsym(secret_handle, "secret_value_get_text");
|
||||
secret_value_unref = (void (*)(gpointer))dlsym(secret_handle, "secret_value_unref");
|
||||
secret_item_get_attributes = (GHashTable * (*)(SecretItem*)) dlsym(secret_handle, "secret_item_get_attributes");
|
||||
secret_item_load_secret_sync = (gboolean(*)(SecretItem*, void*, GError**))dlsym(secret_handle, "secret_item_load_secret_sync");
|
||||
secret_item_load_secret_sync = (gboolean (*)(SecretItem*, void*, GError**))dlsym(secret_handle, "secret_item_load_secret_sync");
|
||||
|
||||
return g_error_free && g_free && g_hash_table_new && g_hash_table_destroy && g_hash_table_lookup && g_hash_table_insert && g_list_free && secret_password_store_sync && secret_password_lookup_sync && secret_password_clear_sync && secret_password_free;
|
||||
}
|
||||
|
||||
@@ -890,7 +890,7 @@ static const GB18030EncodeIndex& gb18030EncodeIndex()
|
||||
// https://unicode-org.atlassian.net/browse/ICU-22357
|
||||
// The 2-byte values are handled correctly by values from gb18030()
|
||||
// but these need to be exceptions from gb18030Ranges().
|
||||
static std::optional<uint16_t> gb18030AsymmetricEncode(char16_t codePoint)
|
||||
static std::optional<uint16_t> gb18030AsymmetricEncode(char32_t codePoint)
|
||||
{
|
||||
switch (codePoint) {
|
||||
case 0xE81E:
|
||||
|
||||
@@ -302,7 +302,6 @@ extern "C" void JSCInitialize(const char* envp[], size_t envc, void (*onCrash)(c
|
||||
JSC::Options::useJITCage() = false;
|
||||
JSC::Options::useShadowRealm() = true;
|
||||
JSC::Options::useV8DateParser() = true;
|
||||
// JSC::Options::useMathSumPreciseMethod() = true;
|
||||
JSC::Options::evalMode() = evalMode;
|
||||
JSC::Options::heapGrowthSteepnessFactor() = 1.0;
|
||||
JSC::Options::heapGrowthMaxIncrease() = 2.0;
|
||||
@@ -1699,6 +1698,7 @@ void GlobalObject::finishCreation(VM& vm)
|
||||
m_commonStrings.initialize();
|
||||
m_http2CommonStrings.initialize();
|
||||
m_bakeAdditions.initialize();
|
||||
m_markdownTagStrings.initialize();
|
||||
|
||||
Bun::addNodeModuleConstructorProperties(vm, this);
|
||||
m_JSNodeHTTPServerSocketStructure.initLater(
|
||||
|
||||
@@ -58,6 +58,7 @@ struct node_module;
|
||||
#include "headers-handwritten.h"
|
||||
#include "BunCommonStrings.h"
|
||||
#include "BunHttp2CommonStrings.h"
|
||||
#include "BunMarkdownTagStrings.h"
|
||||
#include "BunGlobalScope.h"
|
||||
#include <js_native_api.h>
|
||||
#include <node_api.h>
|
||||
@@ -526,6 +527,7 @@ public:
|
||||
V(private, std::unique_ptr<WebCore::DOMConstructors>, m_constructors) \
|
||||
V(private, Bun::CommonStrings, m_commonStrings) \
|
||||
V(private, Bun::Http2CommonStrings, m_http2CommonStrings) \
|
||||
V(private, Bun::MarkdownTagStrings, m_markdownTagStrings) \
|
||||
\
|
||||
/* JSC's hashtable code-generator tries to access these properties, so we make them public. */ \
|
||||
/* However, we'd like it better if they could be protected. */ \
|
||||
@@ -638,11 +640,7 @@ public:
|
||||
V(public, LazyPropertyOfGlobalObject<Symbol>, m_nodeVMDontContextify) \
|
||||
V(public, LazyPropertyOfGlobalObject<Symbol>, m_nodeVMUseMainContextDefaultLoader) \
|
||||
V(public, LazyPropertyOfGlobalObject<JSFunction>, m_ipcSerializeFunction) \
|
||||
V(public, LazyPropertyOfGlobalObject<JSFunction>, m_ipcParseHandleFunction) \
|
||||
\
|
||||
/* Python integration */ \
|
||||
V(public, WriteBarrier<Structure>, m_JSPyObjectStructure) \
|
||||
V(public, WriteBarrier<Structure>, m_JSPyArrayStructure)
|
||||
V(public, LazyPropertyOfGlobalObject<JSFunction>, m_ipcParseHandleFunction)
|
||||
|
||||
#define DECLARE_GLOBALOBJECT_GC_MEMBER(visibility, T, name) \
|
||||
visibility: \
|
||||
@@ -720,6 +718,7 @@ public:
|
||||
|
||||
Bun::CommonStrings& commonStrings() { return m_commonStrings; }
|
||||
Bun::Http2CommonStrings& http2CommonStrings() { return m_http2CommonStrings; }
|
||||
Bun::MarkdownTagStrings& markdownTagStrings() { return m_markdownTagStrings; }
|
||||
#include "ZigGeneratedClasses+lazyStructureHeader.h"
|
||||
|
||||
void finishCreation(JSC::VM&);
|
||||
|
||||
@@ -50,15 +50,14 @@
|
||||
macro(JSPrinter.printWithSourceMap, 46) \
|
||||
macro(ModuleResolver.resolve, 47) \
|
||||
macro(PackageInstaller.install, 48) \
|
||||
macro(PackageInstaller.installPythonPackage, 49) \
|
||||
macro(PackageManifest.Serializer.loadByFile, 50) \
|
||||
macro(PackageManifest.Serializer.save, 51) \
|
||||
macro(RuntimeTranspilerCache.fromFile, 52) \
|
||||
macro(RuntimeTranspilerCache.save, 53) \
|
||||
macro(RuntimeTranspilerCache.toFile, 54) \
|
||||
macro(StandaloneModuleGraph.serialize, 55) \
|
||||
macro(Symbols.followAll, 56) \
|
||||
macro(TestCommand.printCodeCoverageLCov, 57) \
|
||||
macro(TestCommand.printCodeCoverageLCovAndText, 58) \
|
||||
macro(TestCommand.printCodeCoverageText, 59) \
|
||||
macro(PackageManifest.Serializer.loadByFile, 49) \
|
||||
macro(PackageManifest.Serializer.save, 50) \
|
||||
macro(RuntimeTranspilerCache.fromFile, 51) \
|
||||
macro(RuntimeTranspilerCache.save, 52) \
|
||||
macro(RuntimeTranspilerCache.toFile, 53) \
|
||||
macro(StandaloneModuleGraph.serialize, 54) \
|
||||
macro(Symbols.followAll, 55) \
|
||||
macro(TestCommand.printCodeCoverageLCov, 56) \
|
||||
macro(TestCommand.printCodeCoverageLCovAndText, 57) \
|
||||
macro(TestCommand.printCodeCoverageText, 58) \
|
||||
// end
|
||||
|
||||
@@ -80,16 +80,20 @@ size_t IndexOfAnyCharImpl(const uint8_t* HWY_RESTRICT text, size_t text_len, con
|
||||
return text_len;
|
||||
} else {
|
||||
ASSERT(chars_len <= 16);
|
||||
constexpr size_t kMaxPreloadedChars = 16;
|
||||
|
||||
const size_t simd_text_len = text_len - (text_len % N);
|
||||
size_t i = 0;
|
||||
|
||||
#if !HWY_HAVE_SCALABLE && !HWY_TARGET_IS_SVE
|
||||
// Preload search characters into native-width vectors.
|
||||
// On non-SVE targets, Vec has a known size and can be stored in arrays.
|
||||
static constexpr size_t kMaxPreloadedChars = 16;
|
||||
hn::Vec<D8> char_vecs[kMaxPreloadedChars];
|
||||
const size_t num_chars_to_preload = std::min(chars_len, kMaxPreloadedChars);
|
||||
for (size_t c = 0; c < num_chars_to_preload; ++c) {
|
||||
char_vecs[c] = hn::Set(d, chars[c]);
|
||||
}
|
||||
|
||||
const size_t simd_text_len = text_len - (text_len % N);
|
||||
size_t i = 0;
|
||||
|
||||
for (; i < simd_text_len; i += N) {
|
||||
const auto text_vec = hn::LoadN(d, text + i, N);
|
||||
auto found_mask = hn::MaskFalse(d);
|
||||
@@ -97,11 +101,18 @@ size_t IndexOfAnyCharImpl(const uint8_t* HWY_RESTRICT text, size_t text_len, con
|
||||
for (size_t c = 0; c < num_chars_to_preload; ++c) {
|
||||
found_mask = hn::Or(found_mask, hn::Eq(text_vec, char_vecs[c]));
|
||||
}
|
||||
if (chars_len > num_chars_to_preload) {
|
||||
for (size_t c = num_chars_to_preload; c < chars_len; ++c) {
|
||||
found_mask = hn::Or(found_mask, hn::Eq(text_vec, hn::Set(d, chars[c])));
|
||||
}
|
||||
#else
|
||||
// SVE types are sizeless and cannot be stored in arrays.
|
||||
// hn::Set is a single broadcast instruction; the compiler will
|
||||
// hoist these loop-invariant broadcasts out of the outer loop.
|
||||
for (; i < simd_text_len; i += N) {
|
||||
const auto text_vec = hn::LoadN(d, text + i, N);
|
||||
auto found_mask = hn::MaskFalse(d);
|
||||
|
||||
for (size_t c = 0; c < chars_len; ++c) {
|
||||
found_mask = hn::Or(found_mask, hn::Eq(text_vec, hn::Set(d, chars[c])));
|
||||
}
|
||||
#endif
|
||||
|
||||
const intptr_t pos = hn::FindFirstTrue(d, found_mask);
|
||||
if (pos >= 0) {
|
||||
|
||||
@@ -171,8 +171,8 @@ void AbortSignal::runAbortSteps()
|
||||
algorithm.second(reason);
|
||||
|
||||
// 3. Fire an event named abort at signal.
|
||||
dispatchEvent(Event::create(eventNames().abortEvent, Event::CanBubble::No, Event::IsCancelable::No));
|
||||
|
||||
if (hasEventListeners(eventNames().abortEvent))
|
||||
dispatchEvent(Event::create(eventNames().abortEvent, Event::CanBubble::No, Event::IsCancelable::No));
|
||||
setIsFiringEventListeners(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -954,6 +954,5 @@ public:
|
||||
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSConnectionsList;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForJSHTTPParser;
|
||||
std::unique_ptr<GCClient::IsoSubspace> m_clientSubspaceForPyObject;
|
||||
};
|
||||
} // namespace WebCore
|
||||
|
||||
@@ -957,7 +957,6 @@ public:
|
||||
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForJSConnectionsList;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForJSHTTPParser;
|
||||
std::unique_ptr<IsoSubspace> m_subspaceForPyObject;
|
||||
};
|
||||
} // namespace WebCore
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ bool EventTarget::addEventListener(const AtomString& eventType, Ref<EventListene
|
||||
if (options.signal) {
|
||||
options.signal->addAlgorithm([weakThis = WeakPtr { *this }, eventType, listener = WeakPtr { listener }, capture = options.capture](JSC::JSValue) {
|
||||
if (weakThis && listener)
|
||||
Ref { *weakThis } -> removeEventListener(eventType, *listener, capture);
|
||||
Ref { *weakThis }->removeEventListener(eventType, *listener, capture);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -78,6 +78,9 @@
|
||||
#include <JavaScriptCore/ArrayBuffer.h>
|
||||
#include <JavaScriptCore/JSArrayBufferView.h>
|
||||
#include <JavaScriptCore/JSCInlines.h>
|
||||
#include <JavaScriptCore/JSArrayInlines.h>
|
||||
#include <JavaScriptCore/ButterflyInlines.h>
|
||||
#include <JavaScriptCore/ObjectInitializationScope.h>
|
||||
#include <JavaScriptCore/JSDataView.h>
|
||||
#include <JavaScriptCore/JSMapInlines.h>
|
||||
#include <JavaScriptCore/JSMapIterator.h>
|
||||
@@ -5574,6 +5577,13 @@ SerializedScriptValue::SerializedScriptValue(WTF::FixedVector<SimpleInMemoryProp
|
||||
m_memoryCost = computeMemoryCost();
|
||||
}
|
||||
|
||||
SerializedScriptValue::SerializedScriptValue(WTF::FixedVector<SimpleCloneableValue>&& elements)
|
||||
: m_simpleArrayElements(WTF::move(elements))
|
||||
, m_fastPath(FastPath::SimpleArray)
|
||||
{
|
||||
m_memoryCost = computeMemoryCost();
|
||||
}
|
||||
|
||||
SerializedScriptValue::SerializedScriptValue(const String& fastPathString)
|
||||
: m_fastPathString(fastPathString)
|
||||
, m_fastPath(FastPath::String)
|
||||
@@ -5581,6 +5591,14 @@ SerializedScriptValue::SerializedScriptValue(const String& fastPathString)
|
||||
m_memoryCost = computeMemoryCost();
|
||||
}
|
||||
|
||||
SerializedScriptValue::SerializedScriptValue(Vector<uint8_t>&& butterflyData, uint32_t length, FastPath fastPath)
|
||||
: m_arrayButterflyData(WTF::move(butterflyData))
|
||||
, m_arrayLength(length)
|
||||
, m_fastPath(fastPath)
|
||||
{
|
||||
m_memoryCost = computeMemoryCost();
|
||||
}
|
||||
|
||||
size_t SerializedScriptValue::computeMemoryCost() const
|
||||
{
|
||||
size_t cost = m_data.size();
|
||||
@@ -5652,6 +5670,19 @@ size_t SerializedScriptValue::computeMemoryCost() const
|
||||
}
|
||||
}
|
||||
|
||||
break;
|
||||
case FastPath::SimpleArray:
|
||||
cost += m_simpleArrayElements.byteSize();
|
||||
for (const auto& elem : m_simpleArrayElements) {
|
||||
std::visit(WTF::makeVisitor(
|
||||
[&](JSC::JSValue) { /* already included in byteSize() */ },
|
||||
[&](const String& s) { cost += s.sizeInBytes(); }),
|
||||
elem);
|
||||
}
|
||||
break;
|
||||
case FastPath::Int32Array:
|
||||
case FastPath::DoubleArray:
|
||||
cost += m_arrayButterflyData.size();
|
||||
break;
|
||||
case FastPath::None:
|
||||
break;
|
||||
@@ -5843,7 +5874,9 @@ ExceptionOr<Ref<SerializedScriptValue>> SerializedScriptValue::create(JSGlobalOb
|
||||
if (canUseFastPath) {
|
||||
bool canUseStringFastPath = false;
|
||||
bool canUseObjectFastPath = false;
|
||||
bool canUseArrayFastPath = false;
|
||||
JSObject* object = nullptr;
|
||||
JSArray* array = nullptr;
|
||||
Structure* structure = nullptr;
|
||||
if (value.isCell()) {
|
||||
auto* cell = value.asCell();
|
||||
@@ -5853,7 +5886,10 @@ ExceptionOr<Ref<SerializedScriptValue>> SerializedScriptValue::create(JSGlobalOb
|
||||
object = cell->getObject();
|
||||
structure = object->structure();
|
||||
|
||||
if (isObjectFastPathCandidate(structure)) {
|
||||
if (auto* jsArray = jsDynamicCast<JSArray*>(object)) {
|
||||
canUseArrayFastPath = true;
|
||||
array = jsArray;
|
||||
} else if (isObjectFastPathCandidate(structure)) {
|
||||
canUseObjectFastPath = true;
|
||||
}
|
||||
}
|
||||
@@ -5866,6 +5902,84 @@ ExceptionOr<Ref<SerializedScriptValue>> SerializedScriptValue::create(JSGlobalOb
|
||||
return SerializedScriptValue::createStringFastPath(stringValue);
|
||||
}
|
||||
|
||||
if (canUseArrayFastPath) {
|
||||
ASSERT(array != nullptr);
|
||||
// Arrays with named properties (e.g. arr.foo = "bar") cannot use fast path
|
||||
// as we only copy indexed elements. maxOffset == invalidOffset means no named properties.
|
||||
if (structure->maxOffset() != invalidOffset)
|
||||
canUseArrayFastPath = false;
|
||||
}
|
||||
|
||||
if (canUseArrayFastPath) {
|
||||
ASSERT(array != nullptr);
|
||||
unsigned length = array->length();
|
||||
auto arrayType = array->indexingType();
|
||||
|
||||
// Tier 1/2: Int32 / Double butterfly memcpy fast path
|
||||
if ((arrayType == ArrayWithInt32 || arrayType == ArrayWithDouble)
|
||||
&& length <= array->butterfly()->vectorLength()
|
||||
&& !array->structure()->holesMustForwardToPrototype(array)) {
|
||||
|
||||
if (arrayType == ArrayWithInt32) {
|
||||
auto* data = array->butterfly()->contiguous().data();
|
||||
if (!containsHole(data, length)) {
|
||||
size_t byteSize = sizeof(JSValue) * length;
|
||||
Vector<uint8_t> buffer(byteSize, 0);
|
||||
memcpy(buffer.mutableSpan().data(), data, byteSize);
|
||||
return SerializedScriptValue::createInt32ArrayFastPath(WTF::move(buffer), length);
|
||||
}
|
||||
} else {
|
||||
auto* data = array->butterfly()->contiguousDouble().data();
|
||||
if (!containsHole(data, length)) {
|
||||
size_t byteSize = sizeof(double) * length;
|
||||
Vector<uint8_t> buffer(byteSize, 0);
|
||||
memcpy(buffer.mutableSpan().data(), data, byteSize);
|
||||
return SerializedScriptValue::createDoubleArrayFastPath(WTF::move(buffer), length);
|
||||
}
|
||||
}
|
||||
// Holes present → fall through to normal path
|
||||
}
|
||||
|
||||
// Tier 3: Contiguous array with butterfly direct access
|
||||
if (arrayType == ArrayWithContiguous
|
||||
&& length <= array->butterfly()->vectorLength()
|
||||
&& !array->structure()->holesMustForwardToPrototype(array)) {
|
||||
|
||||
auto* data = array->butterfly()->contiguous().data();
|
||||
WTF::Vector<SimpleCloneableValue> elements;
|
||||
elements.reserveInitialCapacity(length);
|
||||
bool ok = true;
|
||||
|
||||
for (unsigned i = 0; i < length; i++) {
|
||||
JSValue elem = data[i].get();
|
||||
if (!elem) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
|
||||
if (elem.isCell()) {
|
||||
if (!elem.isString()) {
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
auto* str = asString(elem);
|
||||
String strValue = str->value(&lexicalGlobalObject);
|
||||
RETURN_IF_EXCEPTION(scope, Exception { ExistingExceptionError });
|
||||
elements.append(Bun::toCrossThreadShareable(strValue));
|
||||
} else {
|
||||
elements.append(elem);
|
||||
}
|
||||
}
|
||||
|
||||
if (ok) {
|
||||
return SerializedScriptValue::createArrayFastPath(
|
||||
WTF::FixedVector<SimpleCloneableValue>(WTF::move(elements)));
|
||||
}
|
||||
}
|
||||
|
||||
// ArrayStorage / Undecided / holes forwarding → fall through to normal serialization path
|
||||
}
|
||||
|
||||
if (canUseObjectFastPath) {
|
||||
ASSERT(object != nullptr);
|
||||
|
||||
@@ -6142,6 +6256,21 @@ Ref<SerializedScriptValue> SerializedScriptValue::createObjectFastPath(WTF::Fixe
|
||||
return adoptRef(*new SerializedScriptValue(WTF::move(object)));
|
||||
}
|
||||
|
||||
Ref<SerializedScriptValue> SerializedScriptValue::createArrayFastPath(WTF::FixedVector<SimpleCloneableValue>&& elements)
|
||||
{
|
||||
return adoptRef(*new SerializedScriptValue(WTF::move(elements)));
|
||||
}
|
||||
|
||||
Ref<SerializedScriptValue> SerializedScriptValue::createInt32ArrayFastPath(Vector<uint8_t>&& data, uint32_t length)
|
||||
{
|
||||
return adoptRef(*new SerializedScriptValue(WTF::move(data), length, FastPath::Int32Array));
|
||||
}
|
||||
|
||||
Ref<SerializedScriptValue> SerializedScriptValue::createDoubleArrayFastPath(Vector<uint8_t>&& data, uint32_t length)
|
||||
{
|
||||
return adoptRef(*new SerializedScriptValue(WTF::move(data), length, FastPath::DoubleArray));
|
||||
}
|
||||
|
||||
RefPtr<SerializedScriptValue> SerializedScriptValue::create(JSContextRef originContext, JSValueRef apiValue, JSValueRef* exception)
|
||||
{
|
||||
JSGlobalObject* lexicalGlobalObject = toJS(originContext);
|
||||
@@ -6288,6 +6417,78 @@ JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject,
|
||||
|
||||
return object;
|
||||
}
|
||||
case FastPath::SimpleArray: {
|
||||
unsigned length = m_simpleArrayElements.size();
|
||||
|
||||
// Pre-convert all elements to JSValues (including creating JSStrings)
|
||||
// before entering ObjectInitializationScope, since jsString() allocates
|
||||
// GC cells which is not allowed inside the initialization scope.
|
||||
MarkedArgumentBuffer values;
|
||||
values.ensureCapacity(length);
|
||||
for (unsigned i = 0; i < length; i++) {
|
||||
JSValue elemValue = std::visit(
|
||||
WTF::makeVisitor(
|
||||
[](JSValue v) -> JSValue { return v; },
|
||||
[&](const String& s) -> JSValue { return jsString(vm, s); }),
|
||||
m_simpleArrayElements[i]);
|
||||
values.append(elemValue);
|
||||
}
|
||||
|
||||
Structure* resultStructure = globalObject->arrayStructureForIndexingTypeDuringAllocation(ArrayWithContiguous);
|
||||
ObjectInitializationScope initScope(vm);
|
||||
JSArray* resultArray = JSArray::tryCreateUninitializedRestricted(initScope, resultStructure, length);
|
||||
|
||||
if (!resultArray) [[unlikely]] {
|
||||
if (didFail)
|
||||
*didFail = true;
|
||||
return {};
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < length; i++)
|
||||
resultArray->initializeIndex(initScope, i, values.at(i));
|
||||
|
||||
if (didFail)
|
||||
*didFail = false;
|
||||
return resultArray;
|
||||
}
|
||||
case FastPath::Int32Array:
|
||||
case FastPath::DoubleArray: {
|
||||
IndexingType arrayType = (m_fastPath == FastPath::Int32Array) ? ArrayWithInt32 : ArrayWithDouble;
|
||||
Structure* resultStructure = globalObject->arrayStructureForIndexingTypeDuringAllocation(arrayType);
|
||||
|
||||
if (hasAnyArrayStorage(resultStructure->indexingType())) [[unlikely]]
|
||||
break; // isHavingABadTime → fall through to normal deserialization
|
||||
|
||||
unsigned outOfLineStorage = resultStructure->outOfLineCapacity();
|
||||
unsigned vectorLength = Butterfly::optimalContiguousVectorLength(resultStructure, m_arrayLength);
|
||||
void* memory = vm.auxiliarySpace().allocate(
|
||||
vm,
|
||||
Butterfly::totalSize(0, outOfLineStorage, true, vectorLength * sizeof(EncodedJSValue)),
|
||||
nullptr, AllocationFailureMode::ReturnNull);
|
||||
|
||||
if (!memory) [[unlikely]] {
|
||||
if (didFail)
|
||||
*didFail = true;
|
||||
return {};
|
||||
}
|
||||
|
||||
Butterfly* butterfly = Butterfly::fromBase(memory, 0, outOfLineStorage);
|
||||
butterfly->setVectorLength(vectorLength);
|
||||
butterfly->setPublicLength(m_arrayLength);
|
||||
|
||||
if (m_fastPath == FastPath::DoubleArray)
|
||||
memcpy(butterfly->contiguousDouble().data(), m_arrayButterflyData.span().data(), m_arrayButterflyData.size());
|
||||
else
|
||||
memcpy(butterfly->contiguous().data(), m_arrayButterflyData.span().data(), m_arrayButterflyData.size());
|
||||
|
||||
// Clear unused tail slots with hole values
|
||||
Butterfly::clearRange(arrayType, butterfly, m_arrayLength, vectorLength);
|
||||
|
||||
JSArray* resultArray = JSArray::createWithButterfly(vm, nullptr, resultStructure, butterfly);
|
||||
if (didFail)
|
||||
*didFail = false;
|
||||
return resultArray;
|
||||
}
|
||||
case FastPath::None: {
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -60,15 +60,12 @@ class MemoryHandle;
|
||||
|
||||
namespace WebCore {
|
||||
|
||||
// Shared value type for fast path cloning: primitives (JSValue) or strings.
|
||||
using SimpleCloneableValue = std::variant<JSC::JSValue, WTF::String>;
|
||||
|
||||
class SimpleInMemoryPropertyTableEntry {
|
||||
public:
|
||||
// Only:
|
||||
// - String
|
||||
// - Number
|
||||
// - Boolean
|
||||
// - Null
|
||||
// - Undefined
|
||||
using Value = std::variant<JSC::JSValue, WTF::String>;
|
||||
using Value = SimpleCloneableValue;
|
||||
|
||||
WTF::String propertyName;
|
||||
Value value;
|
||||
@@ -78,6 +75,9 @@ enum class FastPath : uint8_t {
|
||||
None,
|
||||
String,
|
||||
SimpleObject,
|
||||
SimpleArray,
|
||||
Int32Array,
|
||||
DoubleArray,
|
||||
};
|
||||
|
||||
#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS)
|
||||
@@ -129,6 +129,13 @@ public:
|
||||
// Fast path for postMessage with simple objects
|
||||
static Ref<SerializedScriptValue> createObjectFastPath(WTF::FixedVector<SimpleInMemoryPropertyTableEntry>&& object);
|
||||
|
||||
// Fast path for postMessage with dense arrays of primitives/strings
|
||||
static Ref<SerializedScriptValue> createArrayFastPath(WTF::FixedVector<SimpleCloneableValue>&& elements);
|
||||
|
||||
// Fast path for postMessage with dense Int32/Double arrays (butterfly memcpy)
|
||||
static Ref<SerializedScriptValue> createInt32ArrayFastPath(Vector<uint8_t>&& butterflyData, uint32_t length);
|
||||
static Ref<SerializedScriptValue> createDoubleArrayFastPath(Vector<uint8_t>&& butterflyData, uint32_t length);
|
||||
|
||||
static Ref<SerializedScriptValue> nullValue();
|
||||
|
||||
WEBCORE_EXPORT JSC::JSValue deserialize(JSC::JSGlobalObject&, JSC::JSGlobalObject*, SerializationErrorMode = SerializationErrorMode::Throwing, bool* didFail = nullptr);
|
||||
@@ -231,6 +238,9 @@ private:
|
||||
// Constructor for string fast path
|
||||
explicit SerializedScriptValue(const String& fastPathString);
|
||||
explicit SerializedScriptValue(WTF::FixedVector<SimpleInMemoryPropertyTableEntry>&& object);
|
||||
explicit SerializedScriptValue(WTF::FixedVector<SimpleCloneableValue>&& elements);
|
||||
// Constructor for Int32Array/DoubleArray butterfly memcpy fast path
|
||||
SerializedScriptValue(Vector<uint8_t>&& butterflyData, uint32_t length, FastPath fastPath);
|
||||
|
||||
size_t computeMemoryCost() const;
|
||||
|
||||
@@ -260,6 +270,13 @@ private:
|
||||
size_t m_memoryCost { 0 };
|
||||
|
||||
FixedVector<SimpleInMemoryPropertyTableEntry> m_simpleInMemoryPropertyTable {};
|
||||
// m_simpleArrayElements and m_arrayButterflyData/m_arrayLength are used exclusively:
|
||||
// SimpleArray uses m_simpleArrayElements; Int32Array/DoubleArray use m_arrayButterflyData + m_arrayLength.
|
||||
FixedVector<SimpleCloneableValue> m_simpleArrayElements {};
|
||||
|
||||
// Int32Array / DoubleArray fast path: raw butterfly data
|
||||
Vector<uint8_t> m_arrayButterflyData {};
|
||||
uint32_t m_arrayLength { 0 };
|
||||
};
|
||||
|
||||
template<class Encoder>
|
||||
|
||||
@@ -470,7 +470,7 @@ String generatePatternString(const Vector<Part>& partList, const URLPatternStrin
|
||||
|
||||
if (!needsGrouping && part.prefix.isEmpty() && previousPart && previousPart->type == PartType::FixedText && !previousPart->value.isEmpty()) {
|
||||
if (options.prefixCodepoint.length() == 1
|
||||
&& options.prefixCodepoint.startsWith(*StringView(previousPart->value).codePoints().codePointAt(previousPart->value.length() - 1)))
|
||||
&& options.prefixCodepoint.startsWith(static_cast<char16_t>(*StringView(previousPart->value).codePoints().codePointAt(previousPart->value.length() - 1))))
|
||||
needsGrouping = true;
|
||||
}
|
||||
|
||||
@@ -541,7 +541,7 @@ String escapePatternString(StringView input)
|
||||
}
|
||||
|
||||
// https://urlpattern.spec.whatwg.org/#is-a-valid-name-code-point
|
||||
bool isValidNameCodepoint(char16_t codepoint, URLPatternUtilities::IsFirst first)
|
||||
bool isValidNameCodepoint(char32_t codepoint, URLPatternUtilities::IsFirst first)
|
||||
{
|
||||
if (first == URLPatternUtilities::IsFirst::Yes)
|
||||
return u_hasBinaryProperty(codepoint, UCHAR_ID_START) || codepoint == '_' || codepoint == '$';
|
||||
|
||||
@@ -104,7 +104,7 @@ ASCIILiteral convertModifierToString(Modifier);
|
||||
std::pair<String, Vector<String>> generateRegexAndNameList(const Vector<Part>& partList, const URLPatternStringOptions&);
|
||||
String generatePatternString(const Vector<Part>& partList, const URLPatternStringOptions&);
|
||||
String escapePatternString(StringView input);
|
||||
bool isValidNameCodepoint(char16_t codepoint, URLPatternUtilities::IsFirst);
|
||||
bool isValidNameCodepoint(char32_t codepoint, URLPatternUtilities::IsFirst);
|
||||
|
||||
} // namespace URLPatternUtilities
|
||||
} // namespace WebCore
|
||||
|
||||
@@ -49,14 +49,14 @@ WebCoreTypedArrayController::WebCoreTypedArrayController(bool allowAtomicsWait)
|
||||
|
||||
WebCoreTypedArrayController::~WebCoreTypedArrayController() = default;
|
||||
|
||||
JSC::JSArrayBuffer* WebCoreTypedArrayController::toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSGlobalObject* globalObject, JSC::ArrayBuffer* buffer)
|
||||
JSC::JSArrayBuffer* WebCoreTypedArrayController::toJS(JSC::JSGlobalObject* lexicalGlobalObject, JSC::JSGlobalObject* globalObject, JSC::ArrayBuffer& buffer)
|
||||
{
|
||||
return JSC::jsCast<JSC::JSArrayBuffer*>(WebCore::toJS(lexicalGlobalObject, getDefaultGlobal(globalObject), buffer));
|
||||
}
|
||||
|
||||
void WebCoreTypedArrayController::registerWrapper(JSC::JSGlobalObject* globalObject, JSC::ArrayBuffer* native, JSC::JSArrayBuffer* wrapper)
|
||||
void WebCoreTypedArrayController::registerWrapper(JSC::JSGlobalObject* globalObject, JSC::ArrayBuffer& native, JSC::JSArrayBuffer& wrapper)
|
||||
{
|
||||
cacheWrapper(static_cast<JSVMClientData*>(JSC::getVM(globalObject).clientData)->normalWorld(), native, wrapper);
|
||||
cacheWrapper(static_cast<JSVMClientData*>(JSC::getVM(globalObject).clientData)->normalWorld(), &native, &wrapper);
|
||||
}
|
||||
|
||||
bool WebCoreTypedArrayController::isAtomicsWaitAllowedOnCurrentThread()
|
||||
|
||||
@@ -35,8 +35,8 @@ public:
|
||||
WebCoreTypedArrayController(bool allowAtomicsWait);
|
||||
virtual ~WebCoreTypedArrayController();
|
||||
|
||||
JSC::JSArrayBuffer* toJS(JSC::JSGlobalObject*, JSC::JSGlobalObject*, JSC::ArrayBuffer*) override;
|
||||
void registerWrapper(JSC::JSGlobalObject*, ArrayBuffer*, JSC::JSArrayBuffer*) override;
|
||||
JSC::JSArrayBuffer* toJS(JSC::JSGlobalObject*, JSC::JSGlobalObject*, JSC::ArrayBuffer&) override;
|
||||
void registerWrapper(JSC::JSGlobalObject*, ArrayBuffer&, JSC::JSArrayBuffer&) override;
|
||||
bool isAtomicsWaitAllowedOnCurrentThread() override;
|
||||
|
||||
JSC::WeakHandleOwner* wrapperOwner() { return &m_owner; }
|
||||
|
||||
@@ -1323,6 +1323,7 @@ void WebSocket::didReceiveBinaryData(const AtomString& eventName, const std::spa
|
||||
|
||||
if (auto* context = scriptExecutionContext()) {
|
||||
RefPtr<Blob> blob = Blob::create(binaryData, context->jsGlobalObject());
|
||||
this->incPendingActivityCount();
|
||||
context->postTask([this, name = eventName, blob = blob.releaseNonNull(), protectedThis = Ref { *this }](ScriptExecutionContext& context) {
|
||||
ASSERT(scriptExecutionContext());
|
||||
protectedThis->dispatchEvent(MessageEvent::create(name, blob, protectedThis->m_url.string()));
|
||||
|
||||
@@ -249,32 +249,9 @@ extern "C" __attribute__((used)) char __libc_single_threaded = 0;
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef _LIBCPP_VERBOSE_ABORT_NOEXCEPT
|
||||
// Workaround for this error:
|
||||
// workaround-missing-symbols.cpp:245:11: error: '__libcpp_verbose_abort' is missing exception specification 'noexcept'
|
||||
// 2025-07-10 15:59:47 PDT
|
||||
// 245 | void std::__libcpp_verbose_abort(char const* format, ...)
|
||||
// 2025-07-10 15:59:47 PDT
|
||||
// | ^
|
||||
// 2025-07-10 15:59:47 PDT
|
||||
// | noexcept
|
||||
// 2025-07-10 15:59:47 PDT
|
||||
// /opt/homebrew/Cellar/llvm/20.1.7/bin/../include/c++/v1/__verbose_abort:30:28: note: previous declaration is here
|
||||
// 2025-07-10 15:59:47 PDT
|
||||
// 30 | __printf__, 1, 2) void __libcpp_verbose_abort(const char* __format, ...) _LIBCPP_VERBOSE_ABORT_NOEXCEPT;
|
||||
// 2025-07-10 15:59:47 PDT
|
||||
// | ^
|
||||
// 2025-07-10 15:59:47 PDT
|
||||
// 1 error generated.
|
||||
// 2025-07-10 15:59:47 PDT
|
||||
// [515/540] Building CXX
|
||||
#define BUN_VERBOSE_ABORT_NOEXCEPT _LIBCPP_VERBOSE_ABORT_NOEXCEPT
|
||||
#else
|
||||
#define BUN_VERBOSE_ABORT_NOEXCEPT
|
||||
#endif
|
||||
|
||||
// Provide our implementation
|
||||
void std::__libcpp_verbose_abort(char const* format, ...) BUN_VERBOSE_ABORT_NOEXCEPT
|
||||
// LLVM 20 used _LIBCPP_VERBOSE_ABORT_NOEXCEPT, LLVM 21+ uses _NOEXCEPT (always noexcept).
|
||||
void std::__libcpp_verbose_abort(char const* format, ...) noexcept
|
||||
{
|
||||
va_list list;
|
||||
va_start(list, format);
|
||||
|
||||
@@ -33,7 +33,7 @@ static char32_t decodeUTF16(const UChar* ptr, size_t available, size_t& outLen)
|
||||
}
|
||||
|
||||
outLen = 1;
|
||||
return c;
|
||||
return static_cast<char32_t>(c);
|
||||
}
|
||||
|
||||
static inline uint8_t getVisibleWidth(char32_t cp, bool ambiguousIsWide)
|
||||
|
||||
@@ -351,11 +351,13 @@ pub fn autoTick(this: *EventLoop) void {
|
||||
const ctx = this.virtual_machine;
|
||||
|
||||
this.tickImmediateTasks(ctx);
|
||||
if (comptime Environment.isPosix) {
|
||||
if (comptime Environment.isWindows) {
|
||||
if (this.immediate_tasks.items.len > 0) {
|
||||
this.wakeup();
|
||||
}
|
||||
}
|
||||
// On POSIX, pending immediates are handled via an immediate timeout in
|
||||
// getTimeout() instead of writing to the eventfd, avoiding that overhead.
|
||||
|
||||
if (comptime Environment.isPosix) {
|
||||
// Some tasks need to keep the event loop alive for one more tick.
|
||||
@@ -438,11 +440,13 @@ pub fn autoTickActive(this: *EventLoop) void {
|
||||
var ctx = this.virtual_machine;
|
||||
|
||||
this.tickImmediateTasks(ctx);
|
||||
if (comptime Environment.isPosix) {
|
||||
if (comptime Environment.isWindows) {
|
||||
if (this.immediate_tasks.items.len > 0) {
|
||||
this.wakeup();
|
||||
}
|
||||
}
|
||||
// On POSIX, pending immediates are handled via an immediate timeout in
|
||||
// getTimeout() instead of writing to the eventfd, avoiding that overhead.
|
||||
|
||||
if (comptime Environment.isPosix) {
|
||||
const pending_unref = ctx.pending_unref_counter;
|
||||
|
||||
@@ -52,6 +52,14 @@ pub fn init(this: *GarbageCollectionController, vm: *VirtualMachine) void {
|
||||
}
|
||||
this.gc_timer_interval = gc_timer_interval;
|
||||
|
||||
if (vm.transpiler.env.get("BUN_GC_RUNS_UNTIL_SKIP_RELEASE_ACCESS")) |val| {
|
||||
if (std.fmt.parseInt(c_int, val, 10)) |parsed| {
|
||||
if (parsed >= 0) {
|
||||
VirtualMachine.Bun__defaultRemainingRunsUntilSkipReleaseAccess = parsed;
|
||||
}
|
||||
} else |_| {}
|
||||
}
|
||||
|
||||
this.disabled = vm.transpiler.env.has("BUN_GC_TIMER_DISABLE");
|
||||
|
||||
if (!this.disabled)
|
||||
|
||||
@@ -79,11 +79,13 @@ JSC_DEFINE_HOST_FUNCTION(functionStartRemoteDebugger,
|
||||
JSC::JSValue hostValue = callFrame->argument(0);
|
||||
JSC::JSValue portValue = callFrame->argument(1);
|
||||
const char* host = defaultHost;
|
||||
WTF::CString hostCString;
|
||||
if (hostValue.isString()) {
|
||||
|
||||
auto str = hostValue.toWTFString(globalObject);
|
||||
hostCString = toCString(str);
|
||||
if (!str.isEmpty())
|
||||
host = toCString(str).span().data();
|
||||
host = hostCString.span().data();
|
||||
} else if (!hostValue.isUndefined()) {
|
||||
throwVMError(globalObject, scope,
|
||||
createTypeError(globalObject, "host must be a string"_s));
|
||||
|
||||
@@ -510,7 +510,7 @@ pub const LinkerContext = struct {
|
||||
const loader = loaders[record.source_index.get()];
|
||||
|
||||
switch (loader) {
|
||||
.jsx, .js, .ts, .tsx, .napi, .sqlite, .json, .jsonc, .json5, .yaml, .html, .sqlite_embedded, .md, .py => {
|
||||
.jsx, .js, .ts, .tsx, .napi, .sqlite, .json, .jsonc, .json5, .yaml, .html, .sqlite_embedded, .md => {
|
||||
log.addErrorFmt(
|
||||
source,
|
||||
record.range.loc,
|
||||
|
||||
@@ -606,7 +606,7 @@ fn getAST(
|
||||
return ast;
|
||||
},
|
||||
// TODO:
|
||||
.dataurl, .base64, .bunsh, .py => {
|
||||
.dataurl, .base64, .bunsh => {
|
||||
return try getEmptyAST(log, transpiler, opts, allocator, source, E.String);
|
||||
},
|
||||
.file, .wasm => {
|
||||
|
||||
@@ -424,10 +424,6 @@ pub const ResolvedSourceTag = enum(u32) {
|
||||
export_default_object = 9,
|
||||
/// Signal upwards that the matching value in 'require.extensions' should be used.
|
||||
common_js_custom_extension = 10,
|
||||
/// Python module - execute via embedded Python interpreter
|
||||
python = 11,
|
||||
/// Python builtin module - import a module from Python's standard library
|
||||
python_builtin = 12,
|
||||
|
||||
// Built in modules are loaded through InternalModuleRegistry by numerical ID.
|
||||
// In this enum are represented as \`(1 << 9) & id\`
|
||||
@@ -458,8 +454,6 @@ writeIfNotChanged(
|
||||
ExportsObject = 8,
|
||||
ExportDefaultObject = 9,
|
||||
CommonJSCustomExtension = 10,
|
||||
Python = 11,
|
||||
PythonBuiltin = 12,
|
||||
// Built in modules are loaded through InternalModuleRegistry by numerical ID.
|
||||
// In this enum are represented as \`(1 << 9) & id\`
|
||||
InternalModuleRegistryFlag = 1 << 9,
|
||||
|
||||
@@ -1700,8 +1700,8 @@ pub fn dumpStackTrace(trace: std.builtin.StackTrace, limits: WriteStackTraceLimi
|
||||
|
||||
const programs: []const [:0]const u8 = switch (bun.Environment.os) {
|
||||
.windows => &.{"pdb-addr2line"},
|
||||
// if `llvm-symbolizer` doesn't work, also try `llvm-symbolizer-19`
|
||||
else => &.{ "llvm-symbolizer", "llvm-symbolizer-19" },
|
||||
// if `llvm-symbolizer` doesn't work, also try `llvm-symbolizer-21`
|
||||
else => &.{ "llvm-symbolizer", "llvm-symbolizer-21" },
|
||||
};
|
||||
for (programs) |program| {
|
||||
var arena = bun.ArenaAllocator.init(bun.default_allocator);
|
||||
|
||||
@@ -16,6 +16,10 @@ pub const PosixLoop = extern struct {
|
||||
/// Number of polls owned by Bun
|
||||
active: u32 = 0,
|
||||
|
||||
/// Incremented atomically by wakeup(), swapped to 0 before epoll/kqueue.
|
||||
/// If non-zero, the event loop will return immediately so we can skip the GC safepoint.
|
||||
pending_wakeups: u32 = 0,
|
||||
|
||||
/// The list of ready polls
|
||||
ready_polls: [1024]EventType align(16),
|
||||
|
||||
|
||||
@@ -49,7 +49,6 @@ pub const PerfEvent = enum(i32) {
|
||||
@"JSPrinter.printWithSourceMap",
|
||||
@"ModuleResolver.resolve",
|
||||
@"PackageInstaller.install",
|
||||
@"PackageInstaller.installPythonPackage",
|
||||
@"PackageManifest.Serializer.loadByFile",
|
||||
@"PackageManifest.Serializer.save",
|
||||
@"RuntimeTranspilerCache.fromFile",
|
||||
|
||||
@@ -17,9 +17,6 @@ callback: union(Task.Tag) {
|
||||
git_clone: void,
|
||||
git_checkout: void,
|
||||
local_tarball: void,
|
||||
pypi_manifest: struct {
|
||||
name: strings.StringOrTinyString,
|
||||
},
|
||||
},
|
||||
/// Key in patchedDependencies in package.json
|
||||
apply_patch_task: ?*PatchTask = null,
|
||||
@@ -247,71 +244,6 @@ pub fn getCompletionCallback(this: *NetworkTask) HTTP.HTTPClientResult.Callback
|
||||
return HTTP.HTTPClientResult.Callback.New(*NetworkTask, notify).init(this);
|
||||
}
|
||||
|
||||
/// Configure the network task to fetch a PyPI manifest
|
||||
pub fn forPyPIManifest(
|
||||
this: *NetworkTask,
|
||||
name: string,
|
||||
version: ?string,
|
||||
allocator: std.mem.Allocator,
|
||||
) ForManifestError!void {
|
||||
// PyPI JSON API URL: https://pypi.org/pypi/{package}/json
|
||||
// or with version: https://pypi.org/pypi/{package}/{version}/json
|
||||
const pypi_base = "https://pypi.org/pypi/";
|
||||
|
||||
// Build URL: base + name + [/version] + /json
|
||||
const version_len = if (version) |v| v.len + 1 else 0; // +1 for leading slash
|
||||
const url_len = pypi_base.len + name.len + version_len + "/json".len;
|
||||
const url_buf = try allocator.alloc(u8, url_len);
|
||||
var pos: usize = 0;
|
||||
|
||||
@memcpy(url_buf[pos..][0..pypi_base.len], pypi_base);
|
||||
pos += pypi_base.len;
|
||||
|
||||
@memcpy(url_buf[pos..][0..name.len], name);
|
||||
pos += name.len;
|
||||
|
||||
if (version) |v| {
|
||||
url_buf[pos] = '/';
|
||||
pos += 1;
|
||||
@memcpy(url_buf[pos..][0..v.len], v);
|
||||
pos += v.len;
|
||||
}
|
||||
|
||||
@memcpy(url_buf[pos..][0.."/json".len], "/json");
|
||||
|
||||
this.url_buf = url_buf;
|
||||
|
||||
// Simple headers - just Accept: application/json
|
||||
var header_builder = HeaderBuilder{};
|
||||
header_builder.count("Accept", "application/json");
|
||||
try header_builder.allocate(allocator);
|
||||
header_builder.append("Accept", "application/json");
|
||||
|
||||
this.response_buffer = try MutableString.init(allocator, 0);
|
||||
this.allocator = allocator;
|
||||
|
||||
const url = URL.parse(this.url_buf);
|
||||
this.unsafe_http_client = AsyncHTTP.init(allocator, .GET, url, header_builder.entries, header_builder.content.ptr.?[0..header_builder.content.len], &this.response_buffer, "", this.getCompletionCallback(), HTTP.FetchRedirect.follow, .{
|
||||
.http_proxy = this.package_manager.httpProxy(url),
|
||||
});
|
||||
this.unsafe_http_client.client.flags.reject_unauthorized = this.package_manager.tlsRejectUnauthorized();
|
||||
|
||||
if (PackageManager.verbose_install) {
|
||||
this.unsafe_http_client.client.verbose = .headers;
|
||||
}
|
||||
|
||||
this.callback = .{
|
||||
.pypi_manifest = .{
|
||||
.name = try strings.StringOrTinyString.initAppendIfNeeded(name, *FileSystem.FilenameStore, FileSystem.FilenameStore.instance),
|
||||
},
|
||||
};
|
||||
|
||||
if (PackageManager.verbose_install) {
|
||||
this.unsafe_http_client.verbose = .headers;
|
||||
this.unsafe_http_client.client.verbose = .headers;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn schedule(this: *NetworkTask, batch: *ThreadPool.Batch) void {
|
||||
this.unsafe_http_client.schedule(this.allocator, batch);
|
||||
}
|
||||
|
||||
@@ -1463,157 +1463,6 @@ pub const PackageInstall = struct {
|
||||
// TODO: linux io_uring
|
||||
return this.installWithCopyfile(destination_dir);
|
||||
}
|
||||
|
||||
/// Install a Python package from a wheel cache to site-packages.
|
||||
/// Unlike npm packages, wheel contents (package dirs + dist-info) are copied directly
|
||||
/// to site-packages, not wrapped in a subdirectory.
|
||||
pub fn installPythonPackage(this: *@This(), site_packages_dir: std.fs.Dir, method_: Method) Result {
|
||||
const tracer = bun.perf.trace("PackageInstaller.installPythonPackage");
|
||||
defer tracer.end();
|
||||
|
||||
// Open the cache directory containing the extracted wheel
|
||||
var cached_wheel_dir = bun.openDir(this.cache_dir, this.cache_dir_subpath) catch |err| {
|
||||
return Result.fail(err, .opening_cache_dir, @errorReturnTrace());
|
||||
};
|
||||
defer cached_wheel_dir.close();
|
||||
|
||||
// Save original values
|
||||
const original_cache_dir = this.cache_dir;
|
||||
const original_cache_subpath = this.cache_dir_subpath;
|
||||
const original_dest_subpath = this.destination_dir_subpath;
|
||||
defer {
|
||||
this.cache_dir = original_cache_dir;
|
||||
this.cache_dir_subpath = original_cache_subpath;
|
||||
this.destination_dir_subpath = original_dest_subpath;
|
||||
}
|
||||
|
||||
// Set cache_dir to the wheel directory
|
||||
this.cache_dir = cached_wheel_dir;
|
||||
|
||||
// Iterate through all entries in the wheel cache and copy each entry
|
||||
var iter = cached_wheel_dir.iterate();
|
||||
while (iter.next() catch |err| {
|
||||
return Result.fail(err, .opening_cache_dir, @errorReturnTrace());
|
||||
}) |entry| {
|
||||
if (entry.kind == .directory) {
|
||||
// Build null-terminated subdir name for both source and dest
|
||||
if (entry.name.len >= this.destination_dir_subpath_buf.len) continue;
|
||||
@memcpy(this.destination_dir_subpath_buf[0..entry.name.len], entry.name);
|
||||
this.destination_dir_subpath_buf[entry.name.len] = 0;
|
||||
const subdir_name_z: [:0]u8 = this.destination_dir_subpath_buf[0..entry.name.len :0];
|
||||
|
||||
// Set paths to point to this subdirectory
|
||||
this.cache_dir_subpath = subdir_name_z;
|
||||
this.destination_dir_subpath = subdir_name_z;
|
||||
|
||||
// Use the existing install method which handles clonefile/hardlink/copy fallback
|
||||
const result = this.install(false, site_packages_dir, method_, .pypi);
|
||||
if (result != .success) {
|
||||
return result;
|
||||
}
|
||||
} else if (entry.kind == .file) {
|
||||
// Copy individual files at the wheel root (e.g., typing_extensions.py, six.py)
|
||||
// Build null-terminated filename
|
||||
if (entry.name.len >= this.destination_dir_subpath_buf.len) continue;
|
||||
@memcpy(this.destination_dir_subpath_buf[0..entry.name.len], entry.name);
|
||||
this.destination_dir_subpath_buf[entry.name.len] = 0;
|
||||
const filename_z: [:0]const u8 = this.destination_dir_subpath_buf[0..entry.name.len :0];
|
||||
|
||||
// Copy the file from cache to site-packages
|
||||
if (comptime Environment.isMac) {
|
||||
// Try clonefile first, then fall back to fcopyfile
|
||||
switch (bun.c.clonefileat(
|
||||
cached_wheel_dir.fd,
|
||||
filename_z,
|
||||
site_packages_dir.fd,
|
||||
filename_z,
|
||||
0,
|
||||
)) {
|
||||
0 => continue,
|
||||
else => |errno| switch (std.posix.errno(errno)) {
|
||||
.EXIST => continue,
|
||||
else => {
|
||||
// Fall back to fcopyfile
|
||||
var in_file = bun.sys.openat(.fromStdDir(cached_wheel_dir), filename_z, bun.O.RDONLY, 0).unwrap() catch |open_err| {
|
||||
return Result.fail(open_err, .copyfile, @errorReturnTrace());
|
||||
};
|
||||
defer in_file.close();
|
||||
|
||||
var out_file = site_packages_dir.createFile(entry.name, .{}) catch |create_err| {
|
||||
return Result.fail(create_err, .copyfile, @errorReturnTrace());
|
||||
};
|
||||
defer out_file.close();
|
||||
|
||||
switch (bun.sys.fcopyfile(in_file, .fromStdFile(out_file), std.posix.system.COPYFILE{ .DATA = true })) {
|
||||
.result => continue,
|
||||
.err => |copy_err| switch (copy_err.getErrno()) {
|
||||
.EXIST => continue,
|
||||
else => return Result.fail(copy_err.toZigErr(), .copyfile, @errorReturnTrace()),
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
} else if (comptime Environment.isLinux) {
|
||||
// Try hardlink first, then fall back to copy
|
||||
switch (bun.sys.linkat(
|
||||
.fromStdDir(cached_wheel_dir),
|
||||
filename_z,
|
||||
.fromStdDir(site_packages_dir),
|
||||
filename_z,
|
||||
)) {
|
||||
.result => continue,
|
||||
.err => |err| switch (err.getErrno()) {
|
||||
.EXIST => continue,
|
||||
else => {
|
||||
// Fall back to copy
|
||||
var in_file = bun.sys.openat(.fromStdDir(cached_wheel_dir), filename_z, bun.O.RDONLY, 0).unwrap() catch |open_err| {
|
||||
return Result.fail(open_err, .copyfile, @errorReturnTrace());
|
||||
};
|
||||
defer in_file.close();
|
||||
|
||||
var out_file = site_packages_dir.createFile(entry.name, .{}) catch |create_err| {
|
||||
return Result.fail(create_err, .copyfile, @errorReturnTrace());
|
||||
};
|
||||
defer out_file.close();
|
||||
|
||||
var copy_state: bun.CopyFileState = .{};
|
||||
bun.copyFileWithState(in_file, .fromStdFile(out_file), ©_state).unwrap() catch |copy_err| {
|
||||
return Result.fail(copy_err, .copyfile, @errorReturnTrace());
|
||||
};
|
||||
},
|
||||
},
|
||||
}
|
||||
} else if (comptime Environment.isWindows) {
|
||||
// Use Windows CopyFileW
|
||||
var src_buf: bun.WPathBuffer = undefined;
|
||||
var dst_buf: bun.WPathBuffer = undefined;
|
||||
|
||||
const src_path = bun.strings.toWPathNormalized(&src_buf, filename_z);
|
||||
const dst_path = bun.strings.toWPathNormalized(&dst_buf, filename_z);
|
||||
|
||||
src_buf[src_path.len] = 0;
|
||||
dst_buf[dst_path.len] = 0;
|
||||
|
||||
if (bun.windows.CopyFileExW(
|
||||
src_buf[0..src_path.len :0].ptr,
|
||||
dst_buf[0..dst_path.len :0].ptr,
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
0,
|
||||
) == 0) {
|
||||
const win_err = bun.windows.Win32Error.get();
|
||||
if (win_err != .ERROR_FILE_EXISTS) {
|
||||
return Result.fail(win_err.toSystemErrno().?.toZigErr(), .copyfile, @errorReturnTrace());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return .success;
|
||||
}
|
||||
};
|
||||
|
||||
const string = []const u8;
|
||||
|
||||
@@ -10,8 +10,6 @@ pub const PackageInstaller = struct {
|
||||
skip_delete: bool,
|
||||
force_install: bool,
|
||||
root_node_modules_folder: std.fs.Dir,
|
||||
/// .venv/lib/python{version}/site-packages/ directory for Python packages
|
||||
site_packages_folder: ?std.fs.Dir,
|
||||
summary: *PackageInstall.Summary,
|
||||
options: *const PackageManager.Options,
|
||||
metas: []const Lockfile.Package.Meta,
|
||||
@@ -944,10 +942,6 @@ pub const PackageInstaller = struct {
|
||||
installer.cache_dir = directory;
|
||||
}
|
||||
},
|
||||
.pypi => {
|
||||
installer.cache_dir_subpath = this.manager.cachedTarballFolderName(resolution.value.pypi.url, patch_contents_hash);
|
||||
installer.cache_dir = this.manager.getCacheDirectory();
|
||||
},
|
||||
else => {
|
||||
if (comptime Environment.allow_assert) {
|
||||
@panic("Internal assertion failure: unexpected resolution tag");
|
||||
@@ -1116,13 +1110,6 @@ pub const PackageInstaller = struct {
|
||||
|
||||
const install_result: PackageInstall.Result = switch (resolution.tag) {
|
||||
.symlink, .workspace => installer.installFromLink(this.skip_delete, destination_dir),
|
||||
.pypi => result: {
|
||||
// Python packages are installed to .venv/lib/python{version}/site-packages/
|
||||
const site_packages = this.site_packages_folder orelse {
|
||||
break :result .fail(error.FileNotFound, .opening_cache_dir, null);
|
||||
};
|
||||
break :result installer.installPythonPackage(site_packages, installer.getInstallMethod());
|
||||
},
|
||||
else => result: {
|
||||
if (resolution.tag == .root or (resolution.tag == .folder and !this.lockfile.isWorkspaceTreeId(this.current_tree_id))) {
|
||||
// This is a transitive folder dependency. It is installed with a single symlink to the target folder/file,
|
||||
@@ -1543,7 +1530,6 @@ const PackageInstall = install.PackageInstall;
|
||||
const PackageNameHash = install.PackageNameHash;
|
||||
const PatchTask = install.PatchTask;
|
||||
const PostinstallOptimizer = install.PostinstallOptimizer;
|
||||
const pypi = install.PyPI;
|
||||
const Resolution = install.Resolution;
|
||||
const Task = install.Task;
|
||||
const TaskCallbackContext = install.TaskCallbackContext;
|
||||
|
||||
@@ -50,7 +50,6 @@ task_batch: ThreadPool.Batch = .{},
|
||||
task_queue: TaskDependencyQueue = .{},
|
||||
|
||||
manifests: PackageManifestMap = .{},
|
||||
pypi_manifests: PyPIManifestMap = .{},
|
||||
folders: FolderResolution.Map = .{},
|
||||
git_repositories: RepositoryMap = .{},
|
||||
|
||||
@@ -1197,7 +1196,6 @@ pub const enqueueGitForCheckout = enqueue.enqueueGitForCheckout;
|
||||
pub const enqueueNetworkTask = enqueue.enqueueNetworkTask;
|
||||
pub const enqueuePackageForDownload = enqueue.enqueuePackageForDownload;
|
||||
pub const enqueueParseNPMPackage = enqueue.enqueueParseNPMPackage;
|
||||
pub const enqueueParsePyPIPackage = enqueue.enqueueParsePyPIPackage;
|
||||
pub const enqueuePatchTask = enqueue.enqueuePatchTask;
|
||||
pub const enqueuePatchTaskPre = enqueue.enqueuePatchTaskPre;
|
||||
pub const enqueueTarballForDownload = enqueue.enqueueTarballForDownload;
|
||||
@@ -1315,8 +1313,6 @@ const PackageID = bun.install.PackageID;
|
||||
const PackageManager = bun.install.PackageManager;
|
||||
const PackageManifestMap = bun.install.PackageManifestMap;
|
||||
const PackageNameAndVersionHash = bun.install.PackageNameAndVersionHash;
|
||||
const PyPI = bun.install.PyPI;
|
||||
const PyPIManifestMap = std.HashMapUnmanaged(PackageNameHash, PyPI.PackageManifest, IdentityContext(PackageNameHash), 80);
|
||||
const PackageNameHash = bun.install.PackageNameHash;
|
||||
const PatchTask = bun.install.PatchTask;
|
||||
const PostinstallOptimizer = bun.install.PostinstallOptimizer;
|
||||
|
||||
@@ -407,7 +407,7 @@ pub fn edit(
|
||||
var i: usize = 0;
|
||||
loop: while (i < updates.len) {
|
||||
var request = &updates.*[i];
|
||||
inline for ([_]string{ "dependencies", "devDependencies", "optionalDependencies", "peerDependencies", "pythonDependencies" }) |list| {
|
||||
inline for ([_]string{ "dependencies", "devDependencies", "optionalDependencies", "peerDependencies" }) |list| {
|
||||
if (current_package_json.asProperty(list)) |query| {
|
||||
if (query.expr.data == .e_object) {
|
||||
const name = request.getName();
|
||||
@@ -421,7 +421,7 @@ pub fn edit(
|
||||
const version_literal = try value.expr.asStringCloned(allocator) orelse break :add_packages_to_update;
|
||||
var tag = Dependency.Version.Tag.infer(version_literal);
|
||||
|
||||
if (tag != .npm and tag != .dist_tag and tag != .pypi) break :add_packages_to_update;
|
||||
if (tag != .npm and tag != .dist_tag) break :add_packages_to_update;
|
||||
|
||||
const entry = bun.handleOom(manager.updating_packages.getOrPut(allocator, name));
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user