369 lines
14 KiB
Rust
369 lines
14 KiB
Rust
use crate::Source;
|
|
use std::collections::{BTreeMap, BTreeSet};
|
|
|
|
/// Tracks all of the import and intrinsics that a given codegen
|
|
/// requires and how to generate them when needed.
|
|
#[derive(Default)]
|
|
pub struct Dependencies {
|
|
pub needs_clamp: bool,
|
|
pub needs_store: bool,
|
|
pub needs_load: bool,
|
|
pub needs_validate_guest_char: bool,
|
|
pub needs_expected: bool,
|
|
pub needs_i32_to_f32: bool,
|
|
pub needs_f32_to_i32: bool,
|
|
pub needs_i64_to_f64: bool,
|
|
pub needs_f64_to_i64: bool,
|
|
pub needs_decode_utf8: bool,
|
|
pub needs_encode_utf8: bool,
|
|
pub needs_list_canon_lift: bool,
|
|
pub needs_list_canon_lower: bool,
|
|
pub needs_t_typevar: bool,
|
|
pub needs_resources: bool,
|
|
pub pyimports: BTreeMap<String, Option<BTreeSet<String>>>,
|
|
}
|
|
|
|
impl Dependencies {
|
|
/// Record that a Python import is required
|
|
///
|
|
/// Examples
|
|
/// ```
|
|
/// # use wit_bindgen_gen_wasmtime_py::dependencies::Dependencies;
|
|
/// # let mut deps = Dependencies::default();
|
|
/// // Import a specific item from a module
|
|
/// deps.pyimport("typing", "NamedTuple");
|
|
/// // Import an entire module
|
|
/// deps.pyimport("collections", None);
|
|
/// ```
|
|
pub fn pyimport<'a>(&mut self, module: &str, name: impl Into<Option<&'a str>>) {
|
|
let name = name.into();
|
|
let list = self
|
|
.pyimports
|
|
.entry(module.to_string())
|
|
.or_insert(match name {
|
|
Some(_) => Some(BTreeSet::new()),
|
|
None => None,
|
|
});
|
|
match name {
|
|
Some(name) => {
|
|
assert!(list.is_some());
|
|
list.as_mut().unwrap().insert(name.to_string());
|
|
}
|
|
None => assert!(list.is_none()),
|
|
}
|
|
}
|
|
|
|
/// Create a `Source` containing all of the intrinsics
|
|
/// required according to this `Dependencies` struct.
|
|
pub fn intrinsics(&mut self) -> Source {
|
|
let mut src = Source::default();
|
|
|
|
if self.needs_clamp {
|
|
src.push_str(
|
|
"
|
|
def _clamp(i: int, min: int, max: int) -> int:
|
|
if i < min or i > max:
|
|
raise OverflowError(f'must be between {min} and {max}')
|
|
return i
|
|
",
|
|
);
|
|
}
|
|
if self.needs_store {
|
|
// TODO: this uses native endianness
|
|
self.pyimport("ctypes", None);
|
|
src.push_str(
|
|
"
|
|
def _store(ty: Any, mem: wasmtime.Memory, store: wasmtime.Storelike, base: int, offset: int, val: Any) -> None:
|
|
ptr = (base & 0xffffffff) + offset
|
|
if ptr + ctypes.sizeof(ty) > mem.data_len(store):
|
|
raise IndexError('out-of-bounds store')
|
|
raw_base = mem.data_ptr(store)
|
|
c_ptr = ctypes.POINTER(ty)(
|
|
ty.from_address(ctypes.addressof(raw_base.contents) + ptr)
|
|
)
|
|
c_ptr[0] = val
|
|
",
|
|
);
|
|
}
|
|
if self.needs_load {
|
|
// TODO: this uses native endianness
|
|
self.pyimport("ctypes", None);
|
|
src.push_str(
|
|
"
|
|
def _load(ty: Any, mem: wasmtime.Memory, store: wasmtime.Storelike, base: int, offset: int) -> Any:
|
|
ptr = (base & 0xffffffff) + offset
|
|
if ptr + ctypes.sizeof(ty) > mem.data_len(store):
|
|
raise IndexError('out-of-bounds store')
|
|
raw_base = mem.data_ptr(store)
|
|
c_ptr = ctypes.POINTER(ty)(
|
|
ty.from_address(ctypes.addressof(raw_base.contents) + ptr)
|
|
)
|
|
return c_ptr[0]
|
|
",
|
|
);
|
|
}
|
|
if self.needs_validate_guest_char {
|
|
src.push_str(
|
|
"
|
|
def _validate_guest_char(i: int) -> str:
|
|
if i > 0x10ffff or (i >= 0xd800 and i <= 0xdfff):
|
|
raise TypeError('not a valid char')
|
|
return chr(i)
|
|
",
|
|
);
|
|
}
|
|
if self.needs_expected {
|
|
self.pyimport("dataclasses", "dataclass");
|
|
self.pyimport("typing", "TypeVar");
|
|
self.pyimport("typing", "Generic");
|
|
self.pyimport("typing", "Union");
|
|
self.needs_t_typevar = true;
|
|
src.push_str(
|
|
"
|
|
@dataclass
|
|
class Ok(Generic[T]):
|
|
value: T
|
|
E = TypeVar('E')
|
|
@dataclass
|
|
class Err(Generic[E]):
|
|
value: E
|
|
|
|
Expected = Union[Ok[T], Err[E]]
|
|
",
|
|
);
|
|
}
|
|
if self.needs_i32_to_f32 || self.needs_f32_to_i32 {
|
|
self.pyimport("ctypes", None);
|
|
src.push_str("_i32_to_f32_i32 = ctypes.pointer(ctypes.c_int32(0))\n");
|
|
src.push_str(
|
|
"_i32_to_f32_f32 = ctypes.cast(_i32_to_f32_i32, ctypes.POINTER(ctypes.c_float))\n",
|
|
);
|
|
if self.needs_i32_to_f32 {
|
|
src.push_str(
|
|
"
|
|
def _i32_to_f32(i: int) -> float:
|
|
_i32_to_f32_i32[0] = i # type: ignore
|
|
return _i32_to_f32_f32[0] # type: ignore
|
|
",
|
|
);
|
|
}
|
|
if self.needs_f32_to_i32 {
|
|
src.push_str(
|
|
"
|
|
def _f32_to_i32(i: float) -> int:
|
|
_i32_to_f32_f32[0] = i # type: ignore
|
|
return _i32_to_f32_i32[0] # type: ignore
|
|
",
|
|
);
|
|
}
|
|
}
|
|
if self.needs_i64_to_f64 || self.needs_f64_to_i64 {
|
|
self.pyimport("ctypes", None);
|
|
src.push_str("_i64_to_f64_i64 = ctypes.pointer(ctypes.c_int64(0))\n");
|
|
src.push_str(
|
|
"_i64_to_f64_f64 = ctypes.cast(_i64_to_f64_i64, ctypes.POINTER(ctypes.c_double))\n",
|
|
);
|
|
if self.needs_i64_to_f64 {
|
|
src.push_str(
|
|
"
|
|
def _i64_to_f64(i: int) -> float:
|
|
_i64_to_f64_i64[0] = i # type: ignore
|
|
return _i64_to_f64_f64[0] # type: ignore
|
|
",
|
|
);
|
|
}
|
|
if self.needs_f64_to_i64 {
|
|
src.push_str(
|
|
"
|
|
def _f64_to_i64(i: float) -> int:
|
|
_i64_to_f64_f64[0] = i # type: ignore
|
|
return _i64_to_f64_i64[0] # type: ignore
|
|
",
|
|
);
|
|
}
|
|
}
|
|
if self.needs_decode_utf8 {
|
|
self.pyimport("ctypes", None);
|
|
src.push_str(
|
|
"
|
|
def _decode_utf8(mem: wasmtime.Memory, store: wasmtime.Storelike, ptr: int, len: int) -> str:
|
|
ptr = ptr & 0xffffffff
|
|
len = len & 0xffffffff
|
|
if ptr + len > mem.data_len(store):
|
|
raise IndexError('string out of bounds')
|
|
base = mem.data_ptr(store)
|
|
base = ctypes.POINTER(ctypes.c_ubyte)(
|
|
ctypes.c_ubyte.from_address(ctypes.addressof(base.contents) + ptr)
|
|
)
|
|
return ctypes.string_at(base, len).decode('utf-8')
|
|
",
|
|
);
|
|
}
|
|
if self.needs_encode_utf8 {
|
|
self.pyimport("ctypes", None);
|
|
self.pyimport("typing", "Tuple");
|
|
src.push_str(
|
|
"
|
|
def _encode_utf8(val: str, realloc: wasmtime.Func, mem: wasmtime.Memory, store: wasmtime.Storelike) -> Tuple[int, int]:
|
|
bytes = val.encode('utf8')
|
|
ptr = realloc(store, 0, 0, 1, len(bytes))
|
|
assert(isinstance(ptr, int))
|
|
ptr = ptr & 0xffffffff
|
|
if ptr + len(bytes) > mem.data_len(store):
|
|
raise IndexError('string out of bounds')
|
|
base = mem.data_ptr(store)
|
|
base = ctypes.POINTER(ctypes.c_ubyte)(
|
|
ctypes.c_ubyte.from_address(ctypes.addressof(base.contents) + ptr)
|
|
)
|
|
ctypes.memmove(base, bytes, len(bytes))
|
|
return (ptr, len(bytes))
|
|
",
|
|
);
|
|
}
|
|
if self.needs_list_canon_lift {
|
|
self.pyimport("ctypes", None);
|
|
self.pyimport("typing", "List");
|
|
// TODO: this is doing a native-endian read, not a little-endian
|
|
// read
|
|
src.push_str(
|
|
"
|
|
def _list_canon_lift(ptr: int, len: int, size: int, ty: Any, mem: wasmtime.Memory ,store: wasmtime.Storelike) -> Any:
|
|
ptr = ptr & 0xffffffff
|
|
len = len & 0xffffffff
|
|
if ptr + len * size > mem.data_len(store):
|
|
raise IndexError('list out of bounds')
|
|
raw_base = mem.data_ptr(store)
|
|
base = ctypes.POINTER(ty)(
|
|
ty.from_address(ctypes.addressof(raw_base.contents) + ptr)
|
|
)
|
|
if ty == ctypes.c_uint8:
|
|
return ctypes.string_at(base, len)
|
|
return base[:len]
|
|
",
|
|
);
|
|
}
|
|
if self.needs_list_canon_lower {
|
|
self.pyimport("ctypes", None);
|
|
self.pyimport("typing", "List");
|
|
self.pyimport("typing", "Tuple");
|
|
// TODO: is there a faster way to memcpy other than iterating over
|
|
// the input list?
|
|
// TODO: this is doing a native-endian write, not a little-endian
|
|
// write
|
|
src.push_str(
|
|
"
|
|
def _list_canon_lower(list: Any, ty: Any, size: int, align: int, realloc: wasmtime.Func, mem: wasmtime.Memory, store: wasmtime.Storelike) -> Tuple[int, int]:
|
|
total_size = size * len(list)
|
|
ptr = realloc(store, 0, 0, align, total_size)
|
|
assert(isinstance(ptr, int))
|
|
ptr = ptr & 0xffffffff
|
|
if ptr + total_size > mem.data_len(store):
|
|
raise IndexError('list realloc return of bounds')
|
|
raw_base = mem.data_ptr(store)
|
|
base = ctypes.POINTER(ty)(
|
|
ty.from_address(ctypes.addressof(raw_base.contents) + ptr)
|
|
)
|
|
for i, val in enumerate(list):
|
|
base[i] = val
|
|
return (ptr, len(list))
|
|
",
|
|
);
|
|
}
|
|
|
|
if self.needs_resources {
|
|
self.pyimport("typing", "TypeVar");
|
|
self.pyimport("typing", "Generic");
|
|
self.pyimport("typing", "List");
|
|
self.pyimport("typing", "Optional");
|
|
self.pyimport("dataclasses", "dataclass");
|
|
self.needs_t_typevar = true;
|
|
src.push_str(
|
|
"
|
|
@dataclass
|
|
class SlabEntry(Generic[T]):
|
|
next: int
|
|
val: Optional[T]
|
|
|
|
class Slab(Generic[T]):
|
|
head: int
|
|
list: List[SlabEntry[T]]
|
|
|
|
def __init__(self) -> None:
|
|
self.list = []
|
|
self.head = 0
|
|
|
|
def insert(self, val: T) -> int:
|
|
if self.head >= len(self.list):
|
|
self.list.append(SlabEntry(next = len(self.list) + 1, val = None))
|
|
ret = self.head
|
|
slot = self.list[ret]
|
|
self.head = slot.next
|
|
slot.next = -1
|
|
slot.val = val
|
|
return ret
|
|
|
|
def get(self, idx: int) -> T:
|
|
if idx >= len(self.list):
|
|
raise IndexError('handle index not valid')
|
|
slot = self.list[idx]
|
|
if slot.next == -1:
|
|
assert(slot.val is not None)
|
|
return slot.val
|
|
raise IndexError('handle index not valid')
|
|
|
|
def remove(self, idx: int) -> T:
|
|
ret = self.get(idx)
|
|
slot = self.list[idx]
|
|
slot.val = None
|
|
slot.next = self.head
|
|
self.head = idx
|
|
return ret
|
|
",
|
|
);
|
|
}
|
|
src
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use std::collections::{BTreeMap, BTreeSet};
|
|
|
|
use super::Dependencies;
|
|
|
|
#[test]
|
|
fn test_pyimport_only_contents() {
|
|
let mut deps = Dependencies::default();
|
|
deps.pyimport("typing", None);
|
|
deps.pyimport("typing", None);
|
|
assert_eq!(deps.pyimports, BTreeMap::from([("typing".into(), None)]));
|
|
}
|
|
|
|
#[test]
|
|
fn test_pyimport_only_module() {
|
|
let mut deps = Dependencies::default();
|
|
deps.pyimport("typing", "Union");
|
|
deps.pyimport("typing", "List");
|
|
deps.pyimport("typing", "NamedTuple");
|
|
assert_eq!(
|
|
deps.pyimports,
|
|
BTreeMap::from([(
|
|
"typing".into(),
|
|
Some(BTreeSet::from([
|
|
"Union".into(),
|
|
"List".into(),
|
|
"NamedTuple".into()
|
|
]))
|
|
)])
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
#[should_panic]
|
|
fn test_pyimport_conflicting() {
|
|
let mut deps = Dependencies::default();
|
|
deps.pyimport("typing", "NamedTuple");
|
|
deps.pyimport("typing", None);
|
|
}
|
|
}
|