Skip to content

Commit f2f99f2

Browse files
committed
ffi/cc: compile inline C source via code option, drop C REPL temp files
cc() (bun:ffi) previously required a file path (`source`); the C REPL wrote its accumulated buffer to a temp file on every eval. Add a `code` option carrying inline C source text: Rust Source::String compiles it directly with tcc_compile_string, and the JS wrapper passes it through without path normalization. The C REPL now compiles from memory (no /tmp/poly-c-repl-*.c), which also removes the pid-based collision window between concurrent sessions. Entry compilation (source: file path) is unchanged.
1 parent fc0f55e commit f2f99f2

3 files changed

Lines changed: 49 additions & 12 deletions

File tree

src/js/bun/ffi.ts

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -212,18 +212,28 @@ function cc(options) {
212212
throw new Error("Expected options to be an object");
213213
}
214214

215-
let path = options?.source;
216-
if (!path) {
217-
throw new Error("Expected source to be a string to a file path");
218-
}
219-
if ($isJSArray(path)) {
220-
for (let i = 0; i < path.length; i++) {
221-
path[i] = normalizePath(path[i]);
215+
let displayName;
216+
if (options.code !== undefined) {
217+
// Inline C source text — compiled directly by TinyCC, no temp file.
218+
if (typeof options.code !== "string") {
219+
throw new Error("Expected code to be a string of C source code");
222220
}
221+
displayName = "<cc>";
223222
} else {
224-
path = normalizePath(path);
223+
let path = options?.source;
224+
if (!path) {
225+
throw new Error("Expected source to be a string to a file path");
226+
}
227+
if ($isJSArray(path)) {
228+
for (let i = 0; i < path.length; i++) {
229+
path[i] = normalizePath(path[i]);
230+
}
231+
} else {
232+
path = normalizePath(path);
233+
}
234+
options.source = path;
235+
displayName = path;
225236
}
226-
options.source = path;
227237

228238
const result = ccFn(options);
229239
if (Error.isError(result)) throw result;
@@ -240,7 +250,9 @@ function cc(options) {
240250
// "/usr/lib/sqlite3.so"
241251
// we want
242252
// "sqlite3_get_version() - sqlit3.so"
243-
path.includes("/") ? `${key} (${path.split("/").pop()})` : `${key} (${path})`,
253+
displayName.includes("/")
254+
? `${key} (${displayName.split("/").pop()})`
255+
: `${key} (${displayName})`,
244256
);
245257
} else {
246258
// consistentcy

src/runtime/cli/repl.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2067,7 +2067,6 @@ impl<'a> Repl<'a> {
20672067
g.__polyCStmts = g.__polyCStmts ?? \"\";\n\
20682068
g.__polyCDefs = g.__polyCDefs ?? \"\";\n\
20692069
g.__polyC = g.__polyC ?? {{}};\n\
2070-
const __path = (await import(\"node:os\")).tmpdir() + \"/poly-c-repl-\" + process.pid + \".c\";\n\
20712070
const __pre = \"#include <stdio.h>\\n#include <stdlib.h>\\n#include <string.h>\\nint __poly_marker(void) {{ return 0; }}\\n\";\n\
20722071
const __tmap = {{ int: \"i32\", \"unsigned int\": \"u32\", unsigned: \"u32\", long: \"i64\", \"unsigned long\": \"u64\", \"long long\": \"i64\", \"unsigned long long\": \"u64\", short: \"i16\", \"unsigned short\": \"u16\", char: \"i8\", \"unsigned char\": \"u8\", \"signed char\": \"i8\", float: \"f32\", double: \"f64\", size_t: \"usize\", ssize_t: \"isize\", int32_t: \"i32\", uint32_t: \"u32\", int64_t: \"i64\", uint64_t: \"u64\", int16_t: \"i16\", uint16_t: \"u16\", int8_t: \"i8\", uint8_t: \"u8\", void: \"void\" }};\n\
20732072
const __ctoffi = (t) => {{ t = String(t).trim().replace(/\\s+/g, \" \"); if (t.includes(\"*\")) return \"ptr\"; return __tmap[t] ?? \"i32\"; }};\n\
@@ -2083,7 +2082,7 @@ impl<'a> Repl<'a> {
20832082
}}\n\
20842083
return sigs;\n\
20852084
}};\n\
2086-
const __compile = async (src, syms) => {{ await Bun.write(__path, src); return cc({{ source: __path, symbols: Object.assign({{ __poly_marker: {{ args: [], returns: \"int\" }} }}, syms) }}); }};\n\
2085+
const __compile = (src, syms) => cc({{ code: src, symbols: Object.assign({{ __poly_marker: {{ args: [], returns: \"int\" }} }}, syms) }});\n\
20872086
const __in = {json};\n\
20882087
const __isExpr = !(__in.trimEnd().endsWith(\";\") || __in.trimEnd().endsWith(\"}}\"));\n\
20892088
const __ln = \"__poly_line_\" + (g.__polyLineN = (g.__polyLineN ?? 0) + 1);\n\

src/runtime/ffi/ffi_body.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -276,13 +276,16 @@ impl Default for CompileC {
276276
enum Source {
277277
File(ZBox),
278278
Files(Vec<ZBox>),
279+
/// Inline C source text (the `code` option) — compiled without a file.
280+
String(ZBox),
279281
}
280282

281283
impl Source {
282284
pub(crate) fn first(&self) -> &ZStr {
283285
match self {
284286
Source::File(f) => f,
285287
Source::Files(files) => &files[0],
288+
Source::String(code) => code,
286289
}
287290
}
288291

@@ -308,6 +311,13 @@ impl Source {
308311
*current_file_for_errors = ZBox::from_bytes(b"");
309312
}
310313
}
314+
Source::String(code) => {
315+
*current_file_for_errors = ZBox::from_bytes(b"<cc>");
316+
state
317+
.compile_string(code)
318+
.map_err(|_| crate::Error::CompilationError)?;
319+
*current_file_for_errors = ZBox::from_bytes(b"");
320+
}
311321
}
312322
Ok(())
313323
}
@@ -1169,6 +1179,22 @@ impl FFI {
11691179
}
11701180
}
11711181

1182+
// Inline C source text — compiled directly, no temp file. Takes
1183+
// precedence over `source` (the REPL compiles accumulated buffers).
1184+
if let Some(code_value) =
1185+
object.get_own(global_this, &bun_core::String::borrow_utf8(b"code"))?
1186+
{
1187+
if !code_value.is_string() {
1188+
return Err(global_this.throw_invalid_argument_type_value(
1189+
b"code",
1190+
b"string",
1191+
code_value,
1192+
));
1193+
}
1194+
let code = code_value.get_zig_string(global_this)?.to_owned_slice_z();
1195+
compile_c.source = Source::String(code);
1196+
}
1197+
11721198
if global_this.has_exception() {
11731199
return Err(JsError::Thrown);
11741200
}

0 commit comments

Comments
 (0)