Skip to main content

submilli_engine/runtime/prelude/textcodec/
install.rs

1//! ABI wiring for the Rust `TextEncoder` / `TextDecoder` (+ their constructors):
2//! registers each method under its dispatch key and declares the value symbols
3//! codegen routes through. Instances are stateless — `new` builds an empty
4//! `$ObjectShape` carrying the host `object` vtable; the methods ignore the
5//! receiver (`*abi_arg(params, 0)?`).
6
7use crate::runtime::host::{abi_arg, abi_result};
8use wasmtime::{
9    ArrayRef, ArrayRefPre, Caller, FuncType, HeapType, Linker, RefType, StructRef, StructRefPre,
10    Val, ValType,
11};
12
13use crate::runtime::StoreData;
14use crate::runtime::fuel;
15use crate::runtime::host::{
16    host_object_vtable, intrinsic_string_type, intrinsic_uint8_array_type, read_string_arg,
17    read_uint8_array_arg, register_host_fn, write_submilli_string_struct,
18    write_submilli_uint8array_struct,
19};
20use crate::runtime::intrinsic_types::{build_intrinsic_types, intrinsic_types};
21use crate::runtime::prelude::{MODULE_NAME, declare_method};
22use crate::{MangledName, PackageDeclaration, Param, Type};
23
24fn enc_key(method: &str) -> MangledName {
25    crate::mangle::extend(&crate::mangle::prelude("TextEncoder"), method)
26}
27
28fn dec_key(method: &str) -> MangledName {
29    crate::mangle::extend(&crate::mangle::prelude("TextDecoder"), method)
30}
31
32fn enc_ctor_key(method: &str) -> MangledName {
33    crate::mangle::extend(&crate::mangle::prelude("TextEncoderConstructor"), method)
34}
35
36fn dec_ctor_key(method: &str) -> MangledName {
37    crate::mangle::extend(&crate::mangle::prelude("TextDecoderConstructor"), method)
38}
39
40fn ref_to(struct_ty: wasmtime::StructType) -> ValType {
41    ValType::Ref(RefType::new(false, HeapType::ConcreteStruct(struct_ty)))
42}
43
44/// Build a fresh stateless instance: an empty `$ObjectShape { object_vtable, [], [] }`.
45fn new_instance(caller: &mut Caller<'_, StoreData>) -> wasmtime::Result<Val> {
46    let intr = intrinsic_types(&mut *caller)?;
47    let vtable = host_object_vtable(caller)?;
48    let names = {
49        let pre = ArrayRefPre::new(&mut *caller, intr.field_names.clone());
50        let arr = ArrayRef::new_fixed(&mut *caller, &pre, &[])?;
51        Val::AnyRef(Some(arr.to_anyref()))
52    };
53    let fields = {
54        let pre = ArrayRefPre::new(&mut *caller, intr.object_fields.clone());
55        let arr = ArrayRef::new_fixed(&mut *caller, &pre, &[])?;
56        Val::AnyRef(Some(arr.to_anyref()))
57    };
58    let pre = StructRefPre::new(&mut *caller, intr.object_shape.clone());
59    let st = StructRef::new(
60        &mut *caller,
61        &pre,
62        &[vtable, names, fields, Val::AnyRef(None)],
63    )?;
64    Ok(Val::AnyRef(Some(st.to_anyref())))
65}
66
67pub fn install(linker: &mut Linker<StoreData>) -> wasmtime::Result<()> {
68    let engine = linker.engine().clone();
69    let intr = build_intrinsic_types(&engine)?;
70    let obj_null = ValType::Ref(RefType::new(
71        true,
72        HeapType::ConcreteStruct(intr.object.clone()),
73    ));
74    let string = ref_to(intrinsic_string_type(&engine)?);
75    let uint8 = ref_to(intrinsic_uint8_array_type(&engine)?);
76
77    let ft = |params: Vec<ValType>, results: Vec<ValType>| FuncType::new(&engine, params, results);
78
79    register_host_fn(
80        linker,
81        MODULE_NAME,
82        enc_key("encode"),
83        ft(vec![obj_null.clone(), string.clone()], vec![uint8.clone()]),
84        true,
85        |caller, params, results| {
86            let s = read_string_arg(caller, abi_arg(params, 1)?, "TextEncoder#encode")?;
87            let st = write_submilli_uint8array_struct(caller, s.as_bytes())?;
88            *abi_result(results, 0)? = Val::AnyRef(Some(st.to_anyref()));
89            Ok(())
90        },
91    )?;
92    register_host_fn(
93        linker,
94        MODULE_NAME,
95        dec_key("decode"),
96        ft(vec![obj_null.clone(), uint8.clone()], vec![string.clone()]),
97        true,
98        |caller, params, results| {
99            let bytes = read_uint8_array_arg(caller, abi_arg(params, 1)?, "TextDecoder#decode")?;
100            fuel::charge(&mut *caller, fuel::SCAN, bytes.len() as u64)?;
101            let s = std::str::from_utf8(&bytes).map_err(|e| {
102                crate::runtime::host::type_error(format!(
103                    "TextDecoder.decode: invalid UTF-8 at byte {}: {e}",
104                    e.valid_up_to(),
105                ))
106            })?;
107            let st = write_submilli_string_struct(caller, s)?;
108            *abi_result(results, 0)? = Val::AnyRef(Some(st.to_anyref()));
109            Ok(())
110        },
111    )?;
112    for key in [enc_ctor_key("new"), dec_ctor_key("new")] {
113        register_host_fn(
114            linker,
115            MODULE_NAME,
116            key,
117            ft(Vec::new(), vec![obj_null.clone()]),
118            true,
119            |caller, _params, results| {
120                *abi_result(results, 0)? = new_instance(caller)?;
121                Ok(())
122            },
123        )?;
124    }
125    Ok(())
126}
127
128pub fn declare(defs: &mut PackageDeclaration) {
129    let encoder = || Type::prelude_interface("TextEncoder".to_string(), Vec::new());
130    let decoder = || Type::prelude_interface("TextDecoder".to_string(), Vec::new());
131    declare_method(
132        defs,
133        "encode",
134        enc_key("encode"),
135        vec![Param::new("self", encoder()), Param::new("s", Type::String)],
136        Type::Uint8Array,
137    );
138    declare_method(
139        defs,
140        "decode",
141        dec_key("decode"),
142        vec![
143            Param::new("self", decoder()),
144            Param::new("bytes", Type::Uint8Array),
145        ],
146        Type::String,
147    );
148    declare_method(defs, "new", enc_ctor_key("new"), Vec::new(), encoder());
149    declare_method(defs, "new", dec_ctor_key("new"), Vec::new(), decoder());
150}
151
152/// The type/interface surface this module implements — its slice of the
153/// prelude declaration (see `declaration::prelude_package_declaration`).
154#[allow(clippy::too_many_lines)]
155pub(crate) fn declare_types(defs: &mut crate::PackageDeclaration) {
156    use crate::runtime::prelude::declaration::doc;
157    use crate::{
158        Dispatch, MethodSig, Param, Span, Type, TypeKind, TypeSymbol, ValueKind, ValueSymbol,
159    };
160    use std::collections::BTreeMap;
161    defs.types.insert(
162        "TextEncoder".to_string(),
163        TypeSymbol {
164            name: "TextEncoder".to_string(),
165            mangled_name: crate::mangle::prelude("TextEncoder"),
166            declaration_span: Span::at(crate::FileId::PRELUDE),
167            kind: TypeKind::Interface { index: None,
168                generics: Vec::new(),
169                methods: BTreeMap::from([(
170                    "encode".to_string(),
171                    MethodSig {
172                        optional: false,
173                        generics: Vec::new(),
174                        params: vec![Param::new("s", Type::String)],
175                        ret: Type::Uint8Array,
176                        predicate: None,
177                        doc: doc(
178                            "/**\n * UTF-8 encode `s` into a `Uint8Array`. Strings are UTF-16 internally; each Unicode code point becomes one to four UTF-8 bytes.\n * @param s The string to encode.\n */",
179                        ),
180                    },
181                )]),
182                properties: BTreeMap::new(),
183                dispatch: Dispatch::Direct,
184                doc: doc(
185                    "/** Encodes UTF-16 strings to UTF-8 byte arrays. Mirrors the WHATWG Encoding Standard `TextEncoder`. */",
186                ),
187            },
188        },
189    );
190
191    defs.types.insert(
192        "TextDecoder".to_string(),
193        TypeSymbol {
194            name: "TextDecoder".to_string(),
195            mangled_name: crate::mangle::prelude("TextDecoder"),
196            declaration_span: Span::at(crate::FileId::PRELUDE),
197            kind: TypeKind::Interface { index: None,
198                generics: Vec::new(),
199                methods: BTreeMap::from([(
200                    "decode".to_string(),
201                    MethodSig {
202                        optional: false,
203                        generics: Vec::new(),
204                        params: vec![Param::new("bytes", Type::Uint8Array)],
205                        ret: Type::String,
206                        predicate: None,
207                        doc: doc(
208                            "/**\n * Decode `bytes` as UTF-8 into a UTF-16 string. Throws a catchable `TypeError` on invalid UTF-8.\n * @param bytes The bytes to decode.\n */",
209                        ),
210                    },
211                )]),
212                properties: BTreeMap::new(),
213                dispatch: Dispatch::Direct,
214                doc: doc(
215                    "/** Decodes UTF-8 byte arrays into UTF-16 strings. Mirrors the WHATWG Encoding Standard `TextDecoder`. UTF-8 only in v1 (no `encoding` / `fatal` / `stream` options). */",
216                ),
217            },
218        },
219    );
220
221    defs.types.insert(
222        "TextEncoderConstructor".to_string(),
223        TypeSymbol {
224            name: "TextEncoderConstructor".to_string(),
225            mangled_name: crate::mangle::prelude("TextEncoderConstructor"),
226            declaration_span: Span::at(crate::FileId::PRELUDE),
227            kind: TypeKind::Interface { index: None,
228                generics: Vec::new(),
229                methods: BTreeMap::from([(
230                    "new".to_string(),
231                    MethodSig {
232                        optional: false,
233                        generics: Vec::new(),
234                        params: Vec::new(),
235                        ret: Type::prelude_interface("TextEncoder".to_string(), Vec::new()),
236                        predicate: None,
237                        doc: doc(
238                            "/** Construct a new `TextEncoder` instance. v1 takes no options (UTF-8 only). */",
239                        ),
240                    },
241                )]),
242                properties: BTreeMap::new(),
243                dispatch: Dispatch::Static,
244                doc: doc(
245                    "/** Constructor object for `TextEncoder`. Accessed via the global `TextEncoder` binding — call `new TextEncoder()`. */",
246                ),
247            },
248        },
249    );
250
251    defs.types.insert(
252        "TextDecoderConstructor".to_string(),
253        TypeSymbol {
254            name: "TextDecoderConstructor".to_string(),
255            mangled_name: crate::mangle::prelude("TextDecoderConstructor"),
256            declaration_span: Span::at(crate::FileId::PRELUDE),
257            kind: TypeKind::Interface { index: None,
258                generics: Vec::new(),
259                methods: BTreeMap::from([(
260                    "new".to_string(),
261                    MethodSig {
262                        optional: false,
263                        generics: Vec::new(),
264                        params: Vec::new(),
265                        ret: Type::prelude_interface("TextDecoder".to_string(), Vec::new()),
266                        predicate: None,
267                        doc: doc(
268                            "/** Construct a new `TextDecoder` instance. v1 takes no options (UTF-8 only; throws on invalid bytes). */",
269                        ),
270                    },
271                )]),
272                properties: BTreeMap::new(),
273                dispatch: Dispatch::Static,
274                doc: doc(
275                    "/** Constructor object for `TextDecoder`. Accessed via the global `TextDecoder` binding — call `new TextDecoder()`. */",
276                ),
277            },
278        },
279    );
280    defs.values.insert(
281        "TextEncoder".to_string(),
282        ValueSymbol {
283            name: "TextEncoder".to_string(),
284            mangled_name: crate::mangle::prelude("TextEncoder"),
285            declaration_span: Span::at(crate::FileId::PRELUDE),
286            kind: ValueKind::Const {
287                ty: Type::prelude_interface("TextEncoderConstructor".to_string(), Vec::new()),
288                doc: doc("/** The `TextEncoder` constructor. */"),
289            },
290        },
291    );
292    defs.values.insert(
293        "TextDecoder".to_string(),
294        ValueSymbol {
295            name: "TextDecoder".to_string(),
296            mangled_name: crate::mangle::prelude("TextDecoder"),
297            declaration_span: Span::at(crate::FileId::PRELUDE),
298            kind: ValueKind::Const {
299                ty: Type::prelude_interface("TextDecoderConstructor".to_string(), Vec::new()),
300                doc: doc("/** The `TextDecoder` constructor. */"),
301            },
302        },
303    );
304}