We need it so that we can build TCC to make it run on WASM, or make it possible so that we can run TCC on Compiler Explorer.
I personally uses libtcc mainly for experimental headers and libraries embedding, so I can just compile libtcc.c and leverage the ONE_SOURCE feature. And then I instruct the C compiler to override the necessary functions.
if cfg!(feature = "vfs") {
cc.define("open", "vfs_open");
cc.define("read", "vfs_read");
cc.define("lseek", "vfs_lseek");
cc.define("close", "vfs_close");
}
And then I implemented a tiny VFS wrapper:
extern "C" {
fn open(path: *const c_char, oflag: c_int, ap: VaList) -> c_int;
}
#[no_mangle]
pub unsafe extern "C" fn vfs_open(path: *const c_char, oflag: c_int, mut args: ...) -> c_int {
fn insert_vfs(vfs: Box<impl VFS + Send + Sync + Clone + 'static>) -> c_int {
loop {
let vfs = vfs.clone();
let key: c_int = unsafe { RNG.gen_range(0..c_int::MAX) };
if let Ok(_) = unsafe { FILES.try_insert(key, vfs) } {
return key;
}
}
}
if let Ok(path) = CStr::from_ptr(path).to_str() {
let prefix = "memory:///headers/";
if path.starts_with(prefix) {
let path = path.strip_prefix(prefix).unwrap();
if let Some(file) = crate::headers::ASSET_MAP.get(path) {
return insert_vfs(Box::new(MemoryVFS::from_static(file.contents_bytes)));
}
}
let prefix = "memory:///libraries/";
if path.starts_with(prefix) {
let path = path.strip_prefix(prefix).unwrap();
if let Some(file) = crate::LIBRARIES.get_file(path) {
return insert_vfs(Box::new(MemoryVFS::from_static(file.contents())));
}
}
}
let fd = open(path, oflag, args.as_va_list());
if fd >= 0 {
insert_vfs(Box::new(PosixVFS::new(fd)))
} else {
fd
}
}
This one worked out pretty well, but I think this is very hacky, and there is a lack of write support. Specifically, if we want to run this on Godbolt, we need to capture the output executable, but the current implementation of TCC writes directly to a file descriptor. Instead, we should have a dynamic stream API that supports byte stream or file stream.
We need it so that we can build TCC to make it run on WASM, or make it possible so that we can run TCC on Compiler Explorer.
I personally uses libtcc mainly for experimental headers and libraries embedding, so I can just compile libtcc.c and leverage the
ONE_SOURCEfeature. And then I instruct the C compiler to override the necessary functions.And then I implemented a tiny VFS wrapper:
This one worked out pretty well, but I think this is very hacky, and there is a lack of write support. Specifically, if we want to run this on Godbolt, we need to capture the output executable, but the current implementation of TCC writes directly to a file descriptor. Instead, we should have a dynamic stream API that supports byte stream or file stream.