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        |caller, params, results| {
85            let s = read_string_arg(caller, abi_arg(params, 1)?, "TextEncoder#encode")?;
86            let st = write_submilli_uint8array_struct(caller, s.as_bytes())?;
87            *abi_result(results, 0)? = Val::AnyRef(Some(st.to_anyref()));
88            Ok(())
89        },
90    )?;
91    register_host_fn(
92        linker,
93        MODULE_NAME,
94        dec_key("decode"),
95        ft(vec![obj_null.clone(), uint8.clone()], vec![string.clone()]),
96        |caller, params, results| {
97            let bytes = read_uint8_array_arg(caller, abi_arg(params, 1)?, "TextDecoder#decode")?;
98            fuel::charge(&mut *caller, fuel::SCAN, bytes.len() as u64)?;
99            let s = std::str::from_utf8(&bytes).map_err(|e| {
100                crate::runtime::host::type_error(format!(
101                    "TextDecoder.decode: invalid UTF-8 at byte {}: {e}",
102                    e.valid_up_to(),
103                ))
104            })?;
105            let st = write_submilli_string_struct(caller, s)?;
106            *abi_result(results, 0)? = Val::AnyRef(Some(st.to_anyref()));
107            Ok(())
108        },
109    )?;
110    for key in [enc_ctor_key("new"), dec_ctor_key("new")] {
111        register_host_fn(
112            linker,
113            MODULE_NAME,
114            key,
115            ft(Vec::new(), vec![obj_null.clone()]),
116            |caller, _params, results| {
117                *abi_result(results, 0)? = new_instance(caller)?;
118                Ok(())
119            },
120        )?;
121    }
122    Ok(())
123}
124
125pub fn declare(defs: &mut PackageDeclaration) {
126    let encoder = || Type::prelude_interface("TextEncoder".to_string(), Vec::new());
127    let decoder = || Type::prelude_interface("TextDecoder".to_string(), Vec::new());
128    declare_method(
129        defs,
130        "encode",
131        enc_key("encode"),
132        vec![Param::new("self", encoder()), Param::new("s", Type::String)],
133        Type::Uint8Array,
134    );
135    declare_method(
136        defs,
137        "decode",
138        dec_key("decode"),
139        vec![
140            Param::new("self", decoder()),
141            Param::new("bytes", Type::Uint8Array),
142        ],
143        Type::String,
144    );
145    declare_method(defs, "new", enc_ctor_key("new"), Vec::new(), encoder());
146    declare_method(defs, "new", dec_ctor_key("new"), Vec::new(), decoder());
147}
148
149/// The type/interface surface this module implements — its slice of the
150/// prelude declaration (see `declaration::prelude_package_declaration`).
151#[allow(clippy::too_many_lines)]
152pub(crate) fn declare_types(defs: &mut crate::PackageDeclaration) {
153    use crate::runtime::prelude::declaration::doc;
154    use crate::{
155        Dispatch, MethodSig, Param, Span, Type, TypeKind, TypeSymbol, ValueKind, ValueSymbol,
156    };
157    use std::collections::BTreeMap;
158    defs.types.insert(
159        "TextEncoder".to_string(),
160        TypeSymbol {
161            name: "TextEncoder".to_string(),
162            mangled_name: crate::mangle::prelude("TextEncoder"),
163            declaration_span: Span::at(crate::FileId::PRELUDE),
164            kind: TypeKind::Interface { index: None,
165                generics: Vec::new(),
166                methods: BTreeMap::from([(
167                    "encode".to_string(),
168                    MethodSig {
169                        optional: false,
170                        generics: Vec::new(),
171                        params: vec![Param::new("s", Type::String)],
172                        ret: Type::Uint8Array,
173                        predicate: None,
174                        doc: doc(
175                            "/**\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 */",
176                        ),
177                    },
178                )]),
179                properties: BTreeMap::new(),
180                dispatch: Dispatch::Direct,
181                doc: doc(
182                    "/** Encodes UTF-16 strings to UTF-8 byte arrays. Mirrors the WHATWG Encoding Standard `TextEncoder`. */",
183                ),
184            },
185        },
186    );
187
188    defs.types.insert(
189        "TextDecoder".to_string(),
190        TypeSymbol {
191            name: "TextDecoder".to_string(),
192            mangled_name: crate::mangle::prelude("TextDecoder"),
193            declaration_span: Span::at(crate::FileId::PRELUDE),
194            kind: TypeKind::Interface { index: None,
195                generics: Vec::new(),
196                methods: BTreeMap::from([(
197                    "decode".to_string(),
198                    MethodSig {
199                        optional: false,
200                        generics: Vec::new(),
201                        params: vec![Param::new("bytes", Type::Uint8Array)],
202                        ret: Type::String,
203                        predicate: None,
204                        doc: doc(
205                            "/**\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 */",
206                        ),
207                    },
208                )]),
209                properties: BTreeMap::new(),
210                dispatch: Dispatch::Direct,
211                doc: doc(
212                    "/** 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). */",
213                ),
214            },
215        },
216    );
217
218    defs.types.insert(
219        "TextEncoderConstructor".to_string(),
220        TypeSymbol {
221            name: "TextEncoderConstructor".to_string(),
222            mangled_name: crate::mangle::prelude("TextEncoderConstructor"),
223            declaration_span: Span::at(crate::FileId::PRELUDE),
224            kind: TypeKind::Interface { index: None,
225                generics: Vec::new(),
226                methods: BTreeMap::from([(
227                    "new".to_string(),
228                    MethodSig {
229                        optional: false,
230                        generics: Vec::new(),
231                        params: Vec::new(),
232                        ret: Type::prelude_interface("TextEncoder".to_string(), Vec::new()),
233                        predicate: None,
234                        doc: doc(
235                            "/** Construct a new `TextEncoder` instance. v1 takes no options (UTF-8 only). */",
236                        ),
237                    },
238                )]),
239                properties: BTreeMap::new(),
240                dispatch: Dispatch::Static,
241                doc: doc(
242                    "/** Constructor object for `TextEncoder`. Accessed via the global `TextEncoder` binding — call `new TextEncoder()`. */",
243                ),
244            },
245        },
246    );
247
248    defs.types.insert(
249        "TextDecoderConstructor".to_string(),
250        TypeSymbol {
251            name: "TextDecoderConstructor".to_string(),
252            mangled_name: crate::mangle::prelude("TextDecoderConstructor"),
253            declaration_span: Span::at(crate::FileId::PRELUDE),
254            kind: TypeKind::Interface { index: None,
255                generics: Vec::new(),
256                methods: BTreeMap::from([(
257                    "new".to_string(),
258                    MethodSig {
259                        optional: false,
260                        generics: Vec::new(),
261                        params: Vec::new(),
262                        ret: Type::prelude_interface("TextDecoder".to_string(), Vec::new()),
263                        predicate: None,
264                        doc: doc(
265                            "/** Construct a new `TextDecoder` instance. v1 takes no options (UTF-8 only; throws on invalid bytes). */",
266                        ),
267                    },
268                )]),
269                properties: BTreeMap::new(),
270                dispatch: Dispatch::Static,
271                doc: doc(
272                    "/** Constructor object for `TextDecoder`. Accessed via the global `TextDecoder` binding — call `new TextDecoder()`. */",
273                ),
274            },
275        },
276    );
277    defs.values.insert(
278        "TextEncoder".to_string(),
279        ValueSymbol {
280            name: "TextEncoder".to_string(),
281            mangled_name: crate::mangle::prelude("TextEncoder"),
282            declaration_span: Span::at(crate::FileId::PRELUDE),
283            kind: ValueKind::Const {
284                ty: Type::prelude_interface("TextEncoderConstructor".to_string(), Vec::new()),
285                doc: doc("/** The `TextEncoder` constructor. */"),
286            },
287        },
288    );
289    defs.values.insert(
290        "TextDecoder".to_string(),
291        ValueSymbol {
292            name: "TextDecoder".to_string(),
293            mangled_name: crate::mangle::prelude("TextDecoder"),
294            declaration_span: Span::at(crate::FileId::PRELUDE),
295            kind: ValueKind::Const {
296                ty: Type::prelude_interface("TextDecoderConstructor".to_string(), Vec::new()),
297                doc: doc("/** The `TextDecoder` constructor. */"),
298            },
299        },
300    );
301}