Skip to main content

warp_runtime/
c_structs.rs

1//! C structs as warp maps (card wasmtime-ffi): a struct a header defines (`wasmtime_instance_t {uint64_t store_id;
2//! size_t __private;}`) has a layout by the C rules of LP64 platforms; a call writes a map into its bytes and reads
3//! them back as one. Numbers are numbers, pointers handle ids, arrays lists, nested structs and unions maps (a union
4//! read back holds each of its members, the same bytes seen as each). A layout travels as text, so an executable
5//! carries it with the C function: `{store_id:u64,__private:u64}`, a union in `<…>`, an array `[u8;16]`, a pointer
6//! `ptr`; a field without a name is an anonymous member (C11), its fields the struct's own
7use serde_json::{Map, Value};
8
9#[derive(Clone, Debug, PartialEq)]
10pub enum CField {
11	Int { bytes: usize, signed: bool },
12	Float { bytes: usize },
13	Pointer,
14	Array(Box<CField>, usize),
15	Struct(CStruct),
16}
17
18#[derive(Clone, Debug, PartialEq)]
19pub struct CStruct {
20	pub fields: Vec<(String, CField)>,
21	/// all fields at offset 0
22	pub is_union: bool,
23}
24
25const POINTER_BYTES: usize = 8;
26const SCALARS: [(&str, CField); 11] = [
27	("i8", CField::Int { bytes: 1, signed: true }),
28	("u8", CField::Int { bytes: 1, signed: false }),
29	("i16", CField::Int { bytes: 2, signed: true }),
30	("u16", CField::Int { bytes: 2, signed: false }),
31	("i32", CField::Int { bytes: 4, signed: true }),
32	("u32", CField::Int { bytes: 4, signed: false }),
33	("i64", CField::Int { bytes: 8, signed: true }),
34	("u64", CField::Int { bytes: 8, signed: false }),
35	("f32", CField::Float { bytes: 4 }),
36	("f64", CField::Float { bytes: 8 }),
37	("ptr", CField::Pointer),
38];
39
40impl CField {
41	pub fn size(&self) -> usize {
42		match self {
43			CField::Int { bytes, .. } | CField::Float { bytes } => *bytes,
44			CField::Pointer => POINTER_BYTES,
45			CField::Array(item, count) => item.size() * count,
46			CField::Struct(inner) => inner.size(),
47		}
48	}
49
50	pub fn align(&self) -> usize {
51		match self {
52			CField::Array(item, _) => item.align(),
53			CField::Struct(inner) => inner.align(),
54			scalar => scalar.size(),
55		}
56	}
57
58	/// The field's text: `u64`, `[u8;16]`, a struct's
59	pub fn text(&self) -> String {
60		match self {
61			CField::Array(item, count) => format!("[{};{count}]", item.text()),
62			CField::Struct(inner) => inner.text(),
63			scalar => SCALARS.iter().find(|(_, known)| known == scalar).map_or("?", |(name, _)| name).to_string(),
64		}
65	}
66
67	fn read(&self, bytes: &[u8], pointer_id: &mut dyn FnMut(usize) -> i32) -> Value {
68		let integer = |count: usize| {
69			let mut buffer = [0u8; 8];
70			buffer[..count].copy_from_slice(&bytes[..count]);
71			u64::from_le_bytes(buffer)
72		};
73		match self {
74			CField::Int { bytes: count, signed: true } => {
75				let shift = 64 - 8 * count;
76				Value::from(((integer(*count) << shift) as i64) >> shift)
77			}
78			CField::Int { bytes: count, signed: false } => Value::from(integer(*count)),
79			CField::Float { bytes: 4 } => Value::from(f32::from_bits(integer(4) as u32) as f64),
80			CField::Float { .. } => Value::from(f64::from_bits(integer(8))),
81			CField::Pointer => Value::from(pointer_id(integer(POINTER_BYTES) as usize)),
82			CField::Array(item, count) => {
83				Value::Array((0..*count).map(|index| item.read(&bytes[index * item.size()..], pointer_id)).collect())
84			}
85			CField::Struct(inner) => inner.read(bytes, pointer_id),
86		}
87	}
88
89	fn write(
90		&self,
91		value: &Value,
92		bytes: &mut [u8],
93		pointer_of: &mut dyn FnMut(i32) -> Option<usize>,
94	) -> Result<(), String> {
95		let mut put = |number: u64, count: usize| bytes[..count].copy_from_slice(&number.to_le_bytes()[..count]);
96		match self {
97			CField::Int { bytes: count, .. } => put(number_of(value)? as i64 as u64, *count),
98			CField::Float { bytes: 4 } => put((number_of(value)? as f32).to_bits() as u64, 4),
99			CField::Float { .. } => put(number_of(value)?.to_bits(), 8),
100			CField::Pointer => {
101				let id = number_of(value)? as i32;
102				put(pointer_of(id).ok_or(format!("{id} is no C handle of this run"))? as u64, POINTER_BYTES)
103			}
104			CField::Array(item, count) => {
105				let items = value.as_array().ok_or(format!("a list for a C array, not {value}"))?;
106				for (index, item_value) in items.iter().take(*count).enumerate() {
107					item.write(item_value, &mut bytes[index * item.size()..], pointer_of)?;
108				}
109			}
110			CField::Struct(inner) => inner.write(value, bytes, pointer_of)?,
111		}
112		Ok(())
113	}
114}
115
116fn number_of(value: &Value) -> Result<f64, String> {
117	match value {
118		Value::Bool(truth) => Ok(f64::from(u8::from(*truth))),
119		Value::Number(number) => {
120			number.as_i64().map(|integer| integer as f64).or(number.as_f64()).ok_or(number.to_string())
121		}
122		other => Err(format!("a number for a C field, not {other}")),
123	}
124}
125
126impl CStruct {
127	/// Each field with its offset
128	fn placed(&self) -> Vec<(usize, &String, &CField)> {
129		let mut offset: usize = 0;
130		self.fields
131			.iter()
132			.map(|(name, field)| {
133				if self.is_union {
134					return (0, name, field);
135				}
136				offset = offset.next_multiple_of(field.align());
137				let placed = (offset, name, field);
138				offset += field.size();
139				placed
140			})
141			.collect()
142	}
143
144	pub fn align(&self) -> usize {
145		self.fields.iter().map(|(_, field)| field.align()).max().unwrap_or(1)
146	}
147
148	pub fn size(&self) -> usize {
149		let end = self.placed().iter().map(|(offset, _, field)| offset + field.size()).max().unwrap_or(0);
150		end.next_multiple_of(self.align())
151	}
152
153	pub fn text(&self) -> String {
154		let fields: Vec<String> = self.fields.iter().map(|(name, field)| format!("{name}:{}", field.text())).collect();
155		let (open, close) = if self.is_union { ('<', '>') } else { ('{', '}') };
156		format!("{open}{}{close}", fields.join(","))
157	}
158
159	/// The layout a text gives, none for a damaged one
160	pub fn of_text(text: &str) -> Option<CStruct> {
161		match parse_field(text)? {
162			(CField::Struct(layout), "") => Some(layout),
163			_ => None,
164		}
165	}
166
167	/// The struct in `bytes` as a map; `pointer_id` gives a pointer field its handle id
168	pub fn read(&self, bytes: &[u8], pointer_id: &mut dyn FnMut(usize) -> i32) -> Value {
169		let mut entries = Map::new();
170		for (offset, name, field) in self.placed() {
171			match field.read(&bytes[offset..], pointer_id) {
172				// an anonymous member's fields are the struct's own
173				Value::Object(inner) if name.is_empty() => entries.extend(inner),
174				value => {
175					entries.insert(name.clone(), value);
176				}
177			}
178		}
179		Value::Object(entries)
180	}
181
182	/// The map `value` written into `bytes` (a struct's size, zeroed): fields it leaves out stay 0
183	pub fn write(
184		&self,
185		value: &Value,
186		bytes: &mut [u8],
187		pointer_of: &mut dyn FnMut(i32) -> Option<usize>,
188	) -> Result<(), String> {
189		let entries = value.as_object().ok_or(format!("a map for a C struct, not {value}"))?;
190		for (offset, name, field) in self.placed() {
191			if name.is_empty() {
192				field.write(value, &mut bytes[offset..], pointer_of)?;
193			} else if let Some(field_value) = entries.get(name) {
194				field.write(field_value, &mut bytes[offset..], pointer_of)?;
195			}
196		}
197		Ok(())
198	}
199}
200
201/// The field a text starts with, and the text after it
202fn parse_field(text: &str) -> Option<(CField, &str)> {
203	if let Some(rest) = text.strip_prefix('[') {
204		let (item, rest) = parse_field(rest)?;
205		let (count, rest) = rest.strip_prefix(';')?.split_once(']')?;
206		return Some((CField::Array(Box::new(item), count.parse().ok()?), rest));
207	}
208	let close = match text.chars().next()? {
209		'{' => '}',
210		'<' => '>',
211		_ => {
212			let end = text.find([',', '}', '>', ';', ']']).unwrap_or(text.len());
213			let (_, scalar) = SCALARS.iter().find(|(name, _)| *name == &text[..end])?;
214			return Some((scalar.clone(), &text[end..]));
215		}
216	};
217	let mut fields = vec![];
218	let mut rest = &text[1..];
219	while !rest.starts_with(close) {
220		let (name, after) = rest.split_once(':')?;
221		let (field, after) = parse_field(after)?;
222		fields.push((name.to_string(), field));
223		rest = after.strip_prefix(',').unwrap_or(after);
224	}
225	Some((CField::Struct(CStruct { fields, is_union: close == '>' }), &rest[1..]))
226}