/src/wasm-tools/crates/wit-component/src/dummy.rs
Line | Count | Source |
1 | | use wit_parser::abi::WasmType; |
2 | | use wit_parser::{ |
3 | | Function, FutureIntrinsic, LiftLowerAbi, ManglingAndAbi, Resolve, ResourceIntrinsic, |
4 | | StreamIntrinsic, TypeDefKind, TypeId, WasmExport, WasmExportKind, WasmImport, WorldId, |
5 | | WorldItem, WorldKey, |
6 | | }; |
7 | | |
8 | | /// Generate a dummy implementation core Wasm module for a given WIT document |
9 | 3.82k | pub fn dummy_module(resolve: &Resolve, world: WorldId, mangling: ManglingAndAbi) -> Vec<u8> { |
10 | 3.82k | let world = &resolve.worlds[world]; |
11 | 3.82k | let mut wat = String::new(); |
12 | 3.82k | wat.push_str("(module\n"); |
13 | 5.12k | for (name, import) in world.imports.iter() { |
14 | 5.12k | match import { |
15 | 901 | WorldItem::Function(func) => { |
16 | 901 | push_imported_func(&mut wat, resolve, None, func, mangling); |
17 | 901 | } |
18 | 704 | WorldItem::Interface { id: import, .. } => { |
19 | 951 | for (_, func) in resolve.interfaces[*import].functions.iter() { |
20 | 951 | push_imported_func(&mut wat, resolve, Some(name), func, mangling); |
21 | 951 | } |
22 | 1.31k | for (_, ty) in resolve.interfaces[*import].types.iter() { |
23 | 1.31k | push_imported_type_intrinsics(&mut wat, resolve, Some(name), *ty, mangling); |
24 | 1.31k | } |
25 | | } |
26 | 3.52k | WorldItem::Type { id, .. } => { |
27 | 3.52k | push_imported_type_intrinsics(&mut wat, resolve, None, *id, mangling); |
28 | 3.52k | } |
29 | | } |
30 | | } |
31 | | |
32 | 3.82k | if mangling.is_async() { |
33 | 562 | push_root_async_intrinsics(&mut wat); |
34 | 3.25k | } |
35 | | |
36 | | // Append any intrinsics which are imported but used in exported items |
37 | | // (e.g. resources) |
38 | 3.82k | for (name, export) in world.exports.iter() { |
39 | 747 | match export { |
40 | 348 | WorldItem::Function(func) => { |
41 | 348 | push_exported_func_intrinsics(&mut wat, resolve, None, func, mangling); |
42 | 348 | } |
43 | 399 | WorldItem::Interface { id: export, .. } => { |
44 | 785 | for (_, func) in resolve.interfaces[*export].functions.iter() { |
45 | 785 | push_exported_func_intrinsics(&mut wat, resolve, Some(name), func, mangling); |
46 | 785 | } |
47 | 511 | for (_, ty) in resolve.interfaces[*export].types.iter() { |
48 | 511 | push_exported_type_intrinsics(&mut wat, resolve, Some(name), *ty, mangling); |
49 | 511 | } |
50 | | } |
51 | 0 | WorldItem::Type { .. } => {} |
52 | | } |
53 | | } |
54 | | |
55 | 3.82k | for (name, export) in world.exports.iter() { |
56 | 747 | match export { |
57 | 348 | WorldItem::Function(func) => { |
58 | 348 | push_func_export(&mut wat, resolve, None, func, mangling); |
59 | 348 | } |
60 | 399 | WorldItem::Interface { id: export, .. } => { |
61 | 785 | for (_, func) in resolve.interfaces[*export].functions.iter() { |
62 | 785 | push_func_export(&mut wat, resolve, Some(name), func, mangling); |
63 | 785 | } |
64 | 511 | for (_, ty) in resolve.interfaces[*export].types.iter() { |
65 | 511 | push_exported_resource_functions(&mut wat, resolve, name, *ty, mangling); |
66 | 511 | } |
67 | | } |
68 | 0 | WorldItem::Type { .. } => {} |
69 | | } |
70 | | } |
71 | | |
72 | 3.82k | let memory = resolve.wasm_export_name(mangling, WasmExport::Memory); |
73 | 3.82k | wat.push_str(&format!("(memory (export {memory:?}) 0)\n")); |
74 | 3.82k | let realloc = resolve.wasm_export_name(mangling, WasmExport::Realloc); |
75 | 3.82k | wat.push_str(&format!( |
76 | 3.82k | "(func (export {realloc:?}) (param i32 i32 i32 i32) (result i32) unreachable)\n" |
77 | 3.82k | )); |
78 | | |
79 | 3.82k | let initialize = resolve.wasm_export_name(mangling, WasmExport::Initialize); |
80 | 3.82k | wat.push_str(&format!("(func (export {initialize:?}))")); |
81 | 3.82k | wat.push_str(")\n"); |
82 | | |
83 | 3.82k | return wat::parse_str(&wat).unwrap(); |
84 | 3.82k | } |
85 | | |
86 | 1.85k | fn push_imported_func( |
87 | 1.85k | wat: &mut String, |
88 | 1.85k | resolve: &Resolve, |
89 | 1.85k | interface: Option<&WorldKey>, |
90 | 1.85k | func: &Function, |
91 | 1.85k | mangling: ManglingAndAbi, |
92 | 1.85k | ) { |
93 | 1.85k | let sig = resolve.wasm_signature(mangling.import_variant(), func); |
94 | | |
95 | 1.85k | let (module, name) = resolve.wasm_import_name(mangling, WasmImport::Func { interface, func }); |
96 | 1.85k | wat.push_str(&format!("(import {module:?} {name:?} (func")); |
97 | 1.85k | push_tys(wat, "param", &sig.params); |
98 | 1.85k | push_tys(wat, "result", &sig.results); |
99 | 1.85k | wat.push_str("))\n"); |
100 | | |
101 | 1.85k | if mangling.is_async() { |
102 | 281 | push_imported_future_and_stream_intrinsics(wat, resolve, mangling, false, interface, func); |
103 | 1.57k | } |
104 | 1.85k | } |
105 | | |
106 | 4.83k | fn push_imported_type_intrinsics( |
107 | 4.83k | wat: &mut String, |
108 | 4.83k | resolve: &Resolve, |
109 | 4.83k | interface: Option<&WorldKey>, |
110 | 4.83k | resource: TypeId, |
111 | 4.83k | mangling: ManglingAndAbi, |
112 | 4.83k | ) { |
113 | 4.83k | let ty = &resolve.types[resource]; |
114 | 4.83k | match ty.kind { |
115 | | TypeDefKind::Resource => { |
116 | 261 | let (module, name) = resolve.wasm_import_name( |
117 | 261 | // Force using a sync ABI here at this time as support for async |
118 | 261 | // resource drop isn't implemented yet. |
119 | 261 | mangling.sync(), |
120 | 261 | WasmImport::ResourceIntrinsic { |
121 | 261 | interface, |
122 | 261 | resource, |
123 | 261 | intrinsic: ResourceIntrinsic::ImportedDrop, |
124 | 261 | }, |
125 | 261 | ); |
126 | 261 | wat.push_str(&format!("(import {module:?} {name:?} (func (param i32)))")); |
127 | | |
128 | 261 | if mangling.is_async() { |
129 | 38 | // TODO: when wit-component supports async resource drop, |
130 | 38 | // implement it here too. |
131 | 38 | // let name = format!("[async-lower]{name}"); |
132 | 38 | // wat.push_str(&format!("(import {module:?} {name:?} (func (param i32)))")); |
133 | 223 | } |
134 | | } |
135 | | |
136 | | // No other types with intrinsics at this time (futures/streams are |
137 | | // relative to where they show up in function types. |
138 | 4.57k | _ => {} |
139 | | } |
140 | 4.83k | } |
141 | | |
142 | 1.13k | fn push_exported_func_intrinsics( |
143 | 1.13k | wat: &mut String, |
144 | 1.13k | resolve: &Resolve, |
145 | 1.13k | interface: Option<&WorldKey>, |
146 | 1.13k | func: &Function, |
147 | 1.13k | mangling: ManglingAndAbi, |
148 | 1.13k | ) { |
149 | 1.13k | if !mangling.is_async() { |
150 | 902 | return; |
151 | 231 | } |
152 | | |
153 | | // For exported async functions, generate a `task.return` intrinsic. |
154 | 231 | let (module, name, sig) = func.task_return_import(resolve, interface, mangling.mangling()); |
155 | 231 | wat.push_str(&format!("(import {module:?} {name:?} (func")); |
156 | 231 | push_tys(wat, "param", &sig.params); |
157 | 231 | push_tys(wat, "result", &sig.results); |
158 | 231 | wat.push_str("))\n"); |
159 | | |
160 | 231 | push_imported_future_and_stream_intrinsics(wat, resolve, mangling, true, interface, func); |
161 | 1.13k | } |
162 | | |
163 | 512 | fn push_imported_future_and_stream_intrinsics( |
164 | 512 | wat: &mut String, |
165 | 512 | resolve: &Resolve, |
166 | 512 | mangling: ManglingAndAbi, |
167 | 512 | exported: bool, |
168 | 512 | interface: Option<&WorldKey>, |
169 | 512 | func: &Function, |
170 | 512 | ) { |
171 | 512 | for id in func.find_futures_and_streams(resolve).into_iter() { |
172 | 140 | match &resolve.types[id].kind { |
173 | | TypeDefKind::Future(_) => { |
174 | 22 | let mut module = None; |
175 | 242 | let mut intrinsic_name = |intrinsic, async_| { |
176 | 242 | let (m, name) = resolve.wasm_import_name( |
177 | 242 | mangling, |
178 | 242 | WasmImport::FutureIntrinsic { |
179 | 242 | interface, |
180 | 242 | func, |
181 | 242 | ty: Some(id), |
182 | 242 | intrinsic, |
183 | 242 | exported, |
184 | 242 | async_, |
185 | 242 | }, |
186 | 242 | ); |
187 | 242 | if let Some(prev) = &module { |
188 | 220 | debug_assert_eq!(prev, &m); |
189 | 22 | } else { |
190 | 22 | module = Some(m); |
191 | 22 | } |
192 | 242 | name |
193 | 242 | }; |
194 | | |
195 | 22 | let new = intrinsic_name(FutureIntrinsic::New, false); |
196 | 22 | let read = intrinsic_name(FutureIntrinsic::Read, false); |
197 | 22 | let write = intrinsic_name(FutureIntrinsic::Write, false); |
198 | 22 | let cancel_read = intrinsic_name(FutureIntrinsic::CancelRead, false); |
199 | 22 | let cancel_write = intrinsic_name(FutureIntrinsic::CancelWrite, false); |
200 | 22 | let drop_readable = intrinsic_name(FutureIntrinsic::DropReadable, false); |
201 | 22 | let drop_writable = intrinsic_name(FutureIntrinsic::DropWritable, false); |
202 | 22 | let async_read = intrinsic_name(FutureIntrinsic::Read, true); |
203 | 22 | let async_write = intrinsic_name(FutureIntrinsic::Write, true); |
204 | 22 | let async_cancel_read = intrinsic_name(FutureIntrinsic::CancelRead, true); |
205 | 22 | let async_cancel_write = intrinsic_name(FutureIntrinsic::CancelWrite, true); |
206 | 22 | let module = module.unwrap(); |
207 | | |
208 | 22 | wat.push_str(&format!( |
209 | 22 | r#" |
210 | 22 | (import {module:?} {new:?} (func (result i64))) |
211 | 22 | (import {module:?} {read:?} (func (param i32 i32) (result i32))) |
212 | 22 | (import {module:?} {write:?} (func (param i32 i32) (result i32))) |
213 | 22 | (import {module:?} {cancel_read:?} (func (param i32) (result i32))) |
214 | 22 | (import {module:?} {cancel_write:?} (func (param i32) (result i32))) |
215 | 22 | (import {module:?} {drop_readable:?} (func (param i32))) |
216 | 22 | (import {module:?} {drop_writable:?} (func (param i32))) |
217 | 22 | (import {module:?} {async_read:?} (func (param i32 i32) (result i32))) |
218 | 22 | (import {module:?} {async_write:?} (func (param i32 i32) (result i32))) |
219 | 22 | |
220 | 22 | ;; deferred behind ๐ |
221 | 22 | ;;(import {module:?} {async_cancel_read:?} (func (param i32) (result i32))) |
222 | 22 | ;;(import {module:?} {async_cancel_write:?} (func (param i32) (result i32))) |
223 | 22 | "# |
224 | 22 | )); |
225 | | } |
226 | | TypeDefKind::Stream(_) => { |
227 | 118 | let mut module = None; |
228 | 1.29k | let mut intrinsic_name = |intrinsic, async_| { |
229 | 1.29k | let (m, name) = resolve.wasm_import_name( |
230 | 1.29k | mangling, |
231 | 1.29k | WasmImport::StreamIntrinsic { |
232 | 1.29k | interface, |
233 | 1.29k | func, |
234 | 1.29k | ty: Some(id), |
235 | 1.29k | intrinsic, |
236 | 1.29k | exported, |
237 | 1.29k | async_, |
238 | 1.29k | }, |
239 | 1.29k | ); |
240 | 1.29k | if let Some(prev) = &module { |
241 | 1.18k | debug_assert_eq!(prev, &m); |
242 | 118 | } else { |
243 | 118 | module = Some(m); |
244 | 118 | } |
245 | 1.29k | name |
246 | 1.29k | }; |
247 | | |
248 | 118 | let new = intrinsic_name(StreamIntrinsic::New, false); |
249 | 118 | let read = intrinsic_name(StreamIntrinsic::Read, false); |
250 | 118 | let write = intrinsic_name(StreamIntrinsic::Write, false); |
251 | 118 | let cancel_read = intrinsic_name(StreamIntrinsic::CancelRead, false); |
252 | 118 | let cancel_write = intrinsic_name(StreamIntrinsic::CancelWrite, false); |
253 | 118 | let drop_readable = intrinsic_name(StreamIntrinsic::DropReadable, false); |
254 | 118 | let drop_writable = intrinsic_name(StreamIntrinsic::DropWritable, false); |
255 | 118 | let async_read = intrinsic_name(StreamIntrinsic::Read, true); |
256 | 118 | let async_write = intrinsic_name(StreamIntrinsic::Write, true); |
257 | 118 | let async_cancel_read = intrinsic_name(StreamIntrinsic::CancelRead, true); |
258 | 118 | let async_cancel_write = intrinsic_name(StreamIntrinsic::CancelWrite, true); |
259 | 118 | let module = module.unwrap(); |
260 | | |
261 | 118 | wat.push_str(&format!( |
262 | 118 | r#" |
263 | 118 | (import {module:?} {new:?} (func (result i64))) |
264 | 118 | (import {module:?} {read:?} (func (param i32 i32 i32) (result i32))) |
265 | 118 | (import {module:?} {write:?} (func (param i32 i32 i32) (result i32))) |
266 | 118 | (import {module:?} {cancel_read:?} (func (param i32) (result i32))) |
267 | 118 | (import {module:?} {cancel_write:?} (func (param i32) (result i32))) |
268 | 118 | (import {module:?} {drop_readable:?} (func (param i32))) |
269 | 118 | (import {module:?} {drop_writable:?} (func (param i32))) |
270 | 118 | (import {module:?} {async_read:?} (func (param i32 i32 i32) (result i32))) |
271 | 118 | (import {module:?} {async_write:?} (func (param i32 i32 i32) (result i32))) |
272 | 118 | |
273 | 118 | ;; deferred behind ๐ |
274 | 118 | ;;(import {module:?} {async_cancel_read:?} (func (param i32) (result i32))) |
275 | 118 | ;;(import {module:?} {async_cancel_write:?} (func (param i32) (result i32))) |
276 | 118 | "# |
277 | 118 | )); |
278 | | } |
279 | 0 | _ => unreachable!(), |
280 | | } |
281 | | } |
282 | 512 | } |
283 | | |
284 | 511 | fn push_exported_type_intrinsics( |
285 | 511 | wat: &mut String, |
286 | 511 | resolve: &Resolve, |
287 | 511 | interface: Option<&WorldKey>, |
288 | 511 | resource: TypeId, |
289 | 511 | mangling: ManglingAndAbi, |
290 | 511 | ) { |
291 | 511 | let ty = &resolve.types[resource]; |
292 | 511 | match ty.kind { |
293 | | TypeDefKind::Resource => { |
294 | 84 | let intrinsics = [ |
295 | 84 | (ResourceIntrinsic::ExportedDrop, "(func (param i32))"), |
296 | 84 | ( |
297 | 84 | ResourceIntrinsic::ExportedNew, |
298 | 84 | "(func (param i32) (result i32))", |
299 | 84 | ), |
300 | 84 | ( |
301 | 84 | ResourceIntrinsic::ExportedRep, |
302 | 84 | "(func (param i32) (result i32))", |
303 | 84 | ), |
304 | 84 | ]; |
305 | 252 | for (intrinsic, sig) in intrinsics { |
306 | 252 | let (module, name) = resolve.wasm_import_name( |
307 | 252 | mangling.sync(), |
308 | 252 | WasmImport::ResourceIntrinsic { |
309 | 252 | interface, |
310 | 252 | resource, |
311 | 252 | intrinsic, |
312 | 252 | }, |
313 | 252 | ); |
314 | 252 | wat.push_str(&format!("(import {module:?} {name:?} {sig})\n")); |
315 | 252 | } |
316 | | } |
317 | | |
318 | | // No other types with intrinsics at this time (futures/streams |
319 | | // relative to where they are in a function). |
320 | 427 | _ => {} |
321 | | } |
322 | 511 | } |
323 | | |
324 | 511 | fn push_exported_resource_functions( |
325 | 511 | wat: &mut String, |
326 | 511 | resolve: &Resolve, |
327 | 511 | interface: &WorldKey, |
328 | 511 | resource: TypeId, |
329 | 511 | mangling: ManglingAndAbi, |
330 | 511 | ) { |
331 | 511 | let ty = &resolve.types[resource]; |
332 | 511 | match ty.kind { |
333 | 84 | TypeDefKind::Resource => {} |
334 | 427 | _ => return, |
335 | | } |
336 | | // Feign destructors for any resource that this interface |
337 | | // exports |
338 | 84 | let name = resolve.wasm_export_name( |
339 | 84 | mangling, |
340 | 84 | WasmExport::ResourceDtor { |
341 | 84 | interface, |
342 | 84 | resource, |
343 | 84 | }, |
344 | | ); |
345 | 84 | wat.push_str(&format!("(func (export {name:?}) (param i32))")); |
346 | 511 | } |
347 | | |
348 | 1.13k | fn push_func_export( |
349 | 1.13k | wat: &mut String, |
350 | 1.13k | resolve: &Resolve, |
351 | 1.13k | interface: Option<&WorldKey>, |
352 | 1.13k | func: &Function, |
353 | 1.13k | mangling: ManglingAndAbi, |
354 | 1.13k | ) { |
355 | 1.13k | let sig = resolve.wasm_signature(mangling.export_variant(), func); |
356 | 1.13k | let name = resolve.wasm_export_name( |
357 | 1.13k | mangling, |
358 | 1.13k | WasmExport::Func { |
359 | 1.13k | interface, |
360 | 1.13k | func, |
361 | 1.13k | kind: WasmExportKind::Normal, |
362 | 1.13k | }, |
363 | | ); |
364 | 1.13k | wat.push_str(&format!("(func (export \"{name}\")")); |
365 | 1.13k | push_tys(wat, "param", &sig.params); |
366 | 1.13k | push_tys(wat, "result", &sig.results); |
367 | 1.13k | wat.push_str(" unreachable)\n"); |
368 | | |
369 | 1.02k | match mangling { |
370 | 902 | ManglingAndAbi::Standard32 | ManglingAndAbi::Legacy(LiftLowerAbi::Sync) => { |
371 | 902 | let name = resolve.wasm_export_name( |
372 | 902 | mangling, |
373 | 902 | WasmExport::Func { |
374 | 902 | interface, |
375 | 902 | func, |
376 | 902 | kind: WasmExportKind::PostReturn, |
377 | 902 | }, |
378 | 902 | ); |
379 | 902 | wat.push_str(&format!("(func (export \"{name}\")")); |
380 | 902 | push_tys(wat, "param", &sig.results); |
381 | 902 | wat.push_str(")\n"); |
382 | 902 | } |
383 | 166 | ManglingAndAbi::Legacy(LiftLowerAbi::AsyncCallback) => { |
384 | 166 | let name = resolve.wasm_export_name( |
385 | 166 | mangling, |
386 | 166 | WasmExport::Func { |
387 | 166 | interface, |
388 | 166 | func, |
389 | 166 | kind: WasmExportKind::Callback, |
390 | 166 | }, |
391 | 166 | ); |
392 | 166 | wat.push_str(&format!( |
393 | 166 | "(func (export \"{name}\") (param i32 i32 i32) (result i32) unreachable)\n" |
394 | 166 | )); |
395 | 166 | } |
396 | 65 | ManglingAndAbi::Legacy(LiftLowerAbi::AsyncStackful) => {} |
397 | | } |
398 | 1.13k | } |
399 | | |
400 | 7.33k | fn push_tys(dst: &mut String, desc: &str, params: &[WasmType]) { |
401 | 7.33k | if params.is_empty() { |
402 | 2.01k | return; |
403 | 5.32k | } |
404 | 5.32k | dst.push_str(" ("); |
405 | 5.32k | dst.push_str(desc); |
406 | 10.9k | for ty in params { |
407 | 10.9k | dst.push(' '); |
408 | 10.9k | match ty { |
409 | 8.61k | WasmType::I32 => dst.push_str("i32"), |
410 | 608 | WasmType::I64 => dst.push_str("i64"), |
411 | 382 | WasmType::F32 => dst.push_str("f32"), |
412 | 256 | WasmType::F64 => dst.push_str("f64"), |
413 | 940 | WasmType::Pointer => dst.push_str("i32"), |
414 | 4 | WasmType::PointerOrI64 => dst.push_str("i64"), |
415 | 181 | WasmType::Length => dst.push_str("i32"), |
416 | | } |
417 | | } |
418 | 5.32k | dst.push(')'); |
419 | 7.33k | } |
420 | | |
421 | 562 | fn push_root_async_intrinsics(dst: &mut String) { |
422 | 562 | dst.push_str( |
423 | 562 | r#" |
424 | 562 | (import "[export]$root" "[task-cancel]" (func)) |
425 | 562 | (import "$root" "[backpressure-inc]" (func)) |
426 | 562 | (import "$root" "[backpressure-dec]" (func)) |
427 | 562 | (import "$root" "[waitable-set-new]" (func (result i32))) |
428 | 562 | (import "$root" "[waitable-set-wait]" (func (param i32 i32) (result i32))) |
429 | 562 | (import "$root" "[waitable-set-poll]" (func (param i32 i32) (result i32))) |
430 | 562 | (import "$root" "[waitable-set-drop]" (func (param i32))) |
431 | 562 | (import "$root" "[waitable-join]" (func (param i32 i32))) |
432 | 562 | (import "$root" "[thread-yield]" (func (result i32))) |
433 | 562 | (import "$root" "[subtask-drop]" (func (param i32))) |
434 | 562 | (import "$root" "[subtask-cancel]" (func (param i32) (result i32))) |
435 | 562 | (import "$root" "[context-get-0]" (func (result i32))) |
436 | 562 | (import "$root" "[context-set-0]" (func (param i32))) |
437 | 562 | |
438 | 562 | ;; deferred behind ๐งต upstream |
439 | 562 | ;;(import "$root" "[cancellable][waitable-set-wait]" (func (param i32 i32) (result i32))) |
440 | 562 | ;;(import "$root" "[cancellable][waitable-set-poll]" (func (param i32 i32) (result i32))) |
441 | 562 | ;;(import "$root" "[cancellable][thread-yield]" (func (result i32))) |
442 | 562 | ;;(import "$root" "[context-get-1]" (func (result i32))) |
443 | 562 | ;;(import "$root" "[context-set-1]" (func (param i32))) |
444 | 562 | |
445 | 562 | ;; deferred behind ๐ upstream |
446 | 562 | ;;(import "$root" "[error-context-new-utf8]" (func (param i32 i32) (result i32))) |
447 | 562 | ;;(import "$root" "[error-context-new-utf16]" (func (param i32 i32) (result i32))) |
448 | 562 | ;;(import "$root" "[error-context-new-latin1+utf16]" (func (param i32 i32) (result i32))) |
449 | 562 | ;;(import "$root" "[error-context-debug-message-utf8]" (func (param i32 i32))) |
450 | 562 | ;;(import "$root" "[error-context-debug-message-utf16]" (func (param i32 i32))) |
451 | 562 | ;;(import "$root" "[error-context-debug-message-latin1+utf16]" (func (param i32 i32))) |
452 | 562 | ;;(import "$root" "[error-context-drop]" (func (param i32))) |
453 | 562 | "#, |
454 | | ); |
455 | 562 | } |