tree-sitter.tree-sitter/lib/binding_rust/wasm_allocator.rs
Max Brunsfeld 0e2af0d8d1
Allow using Tree-sitter rust lib in multi-threaded web apps compiled to wasm32-unknown-unknown (#5851)
* feat(wasm): make syntax trees sendable

* test(wasm): transfer trees across workers

* test(wasm): use JSON grammar for tree transfer

* test(wasm): edit trees across workers

* test(wasm): share dlmalloc with tree-sitter

* test(wasm): simplify worker tree exchange

* test(wasm): drive tree exchange from Rust

* test(wasm): split sendable-tree xtask

* test(wasm): generalize Rust web fixture

* test(wasm): exercise parallel Rust tree access

* fix(wasm): use Rust global allocator for C core

* test(wasm): use default Rust allocator

* feat(wasm): support external scanners in Rust web apps

* Simplify example further, add a readme

* Regenerate wasm-stdlib

* Fix wasm_stdlib check script

* Vendor the Wasm standard library subset

* Test Unicode Ruby scanner behavior in Wasm

* Make Wasm tree languages instance-aware

* Test multi-threaded use of queries in wasm32-unknown

* Refactor reference-counted language storage

* Check ABI version compat before loading rest of language

* 🎨 Remove redundant #ifdef block

* Reject unsupported Rust Wasm builds on 0.26
2026-08-14 17:05:42 -07:00

108 lines
3.2 KiB
Rust

//! C allocation functions backed by Rust's application-selected global allocator.
//!
//! This is intentionally a Tree-sitter compatibility shim rather than a
//! general-purpose implementation of the WebAssembly C allocation ABI.
//! Tree-sitter and its supported external scanners only allocate types whose
//! alignment is at most eight bytes.
use alloc::alloc::{Layout, alloc, alloc_zeroed, dealloc, realloc as rust_realloc};
use core::{ffi::c_void, mem, ptr};
const C_ALIGNMENT: usize = 8;
#[repr(C, align(8))]
struct Header {
payload_size: usize,
}
const HEADER_SIZE: usize = mem::size_of::<Header>();
const _: () = assert!(HEADER_SIZE == C_ALIGNMENT);
fn layout(payload_size: usize) -> Option<Layout> {
let allocation_size = HEADER_SIZE.checked_add(payload_size)?;
Layout::from_size_align(allocation_size, C_ALIGNMENT).ok()
}
unsafe fn header_from_payload(payload: *mut c_void) -> *mut Header {
unsafe { payload.cast::<u8>().sub(HEADER_SIZE).cast() }
}
#[unsafe(no_mangle)]
unsafe extern "C" fn malloc(size: usize) -> *mut c_void {
let Some(layout) = layout(size).filter(|_| size != 0) else {
return ptr::null_mut();
};
let header = unsafe { alloc(layout).cast::<Header>() };
if header.is_null() {
return ptr::null_mut();
}
unsafe {
header.write(Header { payload_size: size });
header.add(1).cast()
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn calloc(count: usize, size: usize) -> *mut c_void {
let Some(payload_size) = count.checked_mul(size) else {
return ptr::null_mut();
};
let Some(layout) = layout(payload_size).filter(|_| payload_size != 0) else {
return ptr::null_mut();
};
let header = unsafe { alloc_zeroed(layout).cast::<Header>() };
if header.is_null() {
return ptr::null_mut();
}
unsafe {
header.write(Header { payload_size });
header.add(1).cast()
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn realloc(payload: *mut c_void, new_size: usize) -> *mut c_void {
if payload.is_null() {
return unsafe { malloc(new_size) };
}
if new_size == 0 {
unsafe { free(payload) };
return ptr::null_mut();
}
let old_header = unsafe { header_from_payload(payload) };
let old_size = unsafe { (*old_header).payload_size };
let Some(old_layout) = layout(old_size) else {
return ptr::null_mut();
};
let Some(new_layout) = layout(new_size) else {
return ptr::null_mut();
};
let new_header =
unsafe { rust_realloc(old_header.cast(), old_layout, new_layout.size()).cast::<Header>() };
if new_header.is_null() {
return ptr::null_mut();
}
unsafe {
(*new_header).payload_size = new_size;
new_header.add(1).cast()
}
}
#[unsafe(no_mangle)]
unsafe extern "C" fn free(payload: *mut c_void) {
if payload.is_null() {
return;
}
let header = unsafe { header_from_payload(payload) };
let payload_size = unsafe { (*header).payload_size };
let allocation_layout = layout(payload_size).unwrap();
unsafe { dealloc(header.cast(), allocation_layout) };
}
#[unsafe(no_mangle)]
extern "C" fn abort() -> ! {
core::arch::wasm32::unreachable()
}