1use crate::import::Instr;
2use crate::{Abi, AbiFunction, AbiObject, AbiType, FunctionType, Interface, NumType, Return, Var};
3use anyhow::Result;
4use genco::prelude::*;
5use genco::tokens::static_literal;
6use heck::*;
7use std::path::Path;
8use std::process::Command;
9
10pub struct JsGenerator {
11 abi: Abi,
12}
13
14impl Default for JsGenerator {
15 fn default() -> Self {
16 Self { abi: Abi::Wasm32 }
17 }
18}
19
20pub struct TsGenerator {
21 docs: bool,
22}
23
24impl Default for TsGenerator {
25 fn default() -> Self {
26 Self { docs: true }
27 }
28}
29
30static RESERVED_IDENTIFIERS: [&str; 64] = [
32 "abstract",
33 "arguments",
34 "await",
35 "boolean",
36 "break",
37 "byte",
38 "case",
39 "catch",
40 "char",
41 "class",
42 "const",
43 "continue",
44 "debugger",
45 "default",
46 "delete",
47 "do",
48 "double",
49 "else",
50 "enum",
51 "eval",
52 "export",
53 "extends",
54 "false",
55 "final",
56 "finally",
57 "float",
58 "for",
59 "function",
60 "goto",
61 "if",
62 "implements",
63 "import",
64 "in",
65 "instanceof",
66 "int",
67 "interface",
68 "let",
69 "long",
70 "native",
71 "new",
72 "null",
73 "package",
74 "private",
75 "protected",
76 "public",
77 "return",
78 "short",
79 "static",
80 "super",
81 "switch",
82 "synchronized",
83 "this",
84 "throw",
85 "throws",
86 "transient",
87 "true",
88 "try",
89 "typeof",
90 "var",
91 "void",
92 "volatile",
93 "while",
94 "with",
95 "yield",
96];
97
98fn sanitize_identifier(id: &str) -> String {
99 if RESERVED_IDENTIFIERS.contains(&id) {
100 format!("_{}", id)
101 } else {
102 id.to_string()
103 }
104}
105
106impl TsGenerator {
107 fn gen_doc(
108 &self,
109 items: impl IntoIterator<Item = impl Into<genco::tokens::ItemStr>>,
110 ) -> impl FormatInto<js::JavaScript> {
111 let gen = self.docs;
112 genco::tokens::from_fn(move |t| {
113 let mut it = items.into_iter().peekable();
114
115 if gen && it.peek().is_some() {
116 quote_in! { *t =>
117 #(static_literal("/**"))
118 #(for line in it join (#<push>) {
119 #<space>* #(line.into())
120 })
121 #<space>#(static_literal("*/"))
122 }
123 }
124 })
125 }
126 pub fn generate(&self, iface: Interface) -> js::Tokens {
127 quote! {
128 #(static_literal("//")) AUTO GENERATED FILE, DO NOT EDIT.
129 #(static_literal("//"))
130 #(static_literal("//")) Generated by "ffi-gen".
131 #(static_literal("/* tslint:disable */"))
132 #(static_literal("/* eslint:disable */"))
133
134 #(self.gen_doc(&["Main entry point to the library."]))
135 export class Api {
136 #(self.gen_doc(&["API constructor.","","@returns an `Api` instance."]))
137 constructor();
138
139 #(self.gen_doc(&["Initialize the API.", "", "@returns a promise resolved when initialization is done."]))
140 fetch(url, imports): Promise<void>;
141
142 #(for func in iface.functions() join (#<line>#<line>) => #(self.generate_function(func)))
143 }
144
145 #(for obj in iface.objects() => #(self.generate_object(obj)))
146 }
147 }
148
149 fn generate_function(&self, func: AbiFunction) -> js::Tokens {
150 let ffi = Abi::Wasm32.import(&func);
151 let args = self.generate_args(&ffi.abi_args);
152 let ret = self.generate_return_type(ffi.abi_ret.as_ref());
153 let name = self.ident(&func.name);
154 let fun = match &func.ty {
155 FunctionType::Constructor(_) => {
156 quote!(static #name(api: Api, #args): #ret;)
157 }
158 _ => {
159 quote!(#(name)(#args): #ret;)
160 }
161 };
162 quote! {
163 #(self.gen_doc(func.doc))
164 #fun
165 }
166 }
167
168 fn generate_args(&self, abi_args: &[(String, AbiType)]) -> js::Tokens {
169 let len = abi_args.len();
170 let args = quote!(#(for (idx, (name, ty)) in abi_args.iter().enumerate() join (, ) =>
171 #(match ty {
172 AbiType::Option(inner) if idx < len - 1 => #(self.ident(name)): #(self.generate_return_type(Some(inner))) #("| null"),
173 _ => #(self.ident(name)): #(self.generate_return_type(Some(ty)))
174 })
175 ));
176 args
177 }
178
179 fn generate_return_type(&self, ret: Option<&AbiType>) -> js::Tokens {
180 if let Some(ret) = ret {
181 match ret {
182 AbiType::Num(prim) => match prim {
183 NumType::U8
184 | NumType::U16
185 | NumType::U32
186 | NumType::I8
187 | NumType::I16
188 | NumType::I32
189 | NumType::F32
190 | NumType::F64 => quote!(number),
191 NumType::U64 | NumType::I64 => quote!(BigInt),
192 },
193 AbiType::Isize | AbiType::Usize => quote!(number),
194 AbiType::Bool => quote!(boolean),
195 AbiType::RefStr | AbiType::String => quote!(string),
196 AbiType::RefSlice(prim) | AbiType::Vec(prim) => {
197 let inner = self.generate_return_type(Some(&AbiType::Num(*prim)));
198 quote!(Array<#inner>)
199 }
200 AbiType::RefObject(i) | AbiType::Object(i) => {
201 quote!(#(self.type_ident(i)))
202 }
203 AbiType::Option(i) => {
204 let inner = self.generate_return_type(Some(i));
205 quote!(#inner?)
206 }
207 AbiType::Result(i) => quote!(#(self.generate_return_type(Some(i)))),
208 AbiType::RefIter(i) | AbiType::Iter(i) => {
209 let inner = self.generate_return_type(Some(i));
210 quote!(Iterable<#inner>)
211 }
212 AbiType::RefFuture(i) | AbiType::Future(i) => {
213 let inner = self.generate_return_type(Some(i));
214 quote!(Promise<#inner>)
215 }
216 AbiType::RefStream(i) | AbiType::Stream(i) => {
217 let inner = self.generate_return_type(Some(i));
218 quote!(ReadableStream<#inner>)
219 }
220 AbiType::Tuple(tys) => match tys.len() {
221 0 => quote!(void),
222 1 => self.generate_return_type(Some(&tys[0])),
223 _ => {
224 quote!([#(for ty in tys join (, ) => #(self.generate_return_type(Some(ty))))])
225 }
226 },
227 }
228 } else {
229 quote!(void)
230 }
231 }
232
233 fn generate_object(&self, obj: AbiObject) -> js::Tokens {
234 quote! {
235 export class #(self.type_ident(&obj.name)) {
236 #(for method in obj.methods join (#<line>#<line>) => #(self.generate_function(method)))
237
238 drop(): void;
239 }
240 }
241 }
242
243 fn type_ident(&self, s: &str) -> String {
244 sanitize_identifier(&s.to_upper_camel_case())
245 }
246
247 fn ident(&self, s: &str) -> String {
248 sanitize_identifier(&s.to_lower_camel_case())
249 }
250}
251
252impl JsGenerator {
253 pub fn generate(&self, iface: Interface) -> js::Tokens {
254 quote! {
255 #(static_literal("//")) AUTO GENERATED FILE, DO NOT EDIT.
256 #(static_literal("//"))
257 #(static_literal("//")) Generated by "ffi-gen".
258 #(static_literal("/* tslint:disable */"))
259 #(static_literal("/* eslint:disable */"))
260
261 let fs;
265 const fetch_polyfill = async (file) => {
266 const readFile = await eval("mport('fs')".replace(/^/, 'i'))
267 .then(({ readFile }) => readFile);
268 return new Promise((resolve, reject) => {
269 readFile(
270 file,
271 function(err, data) {
272 return (err)
273 ? reject(err)
274 : resolve({
275 arrayBuffer: () => Promise.resolve(data),
276 ok: true,
277 });
278 }
279 );
280 });
281 }
282
283 let ReadableStream;
284 if (typeof window == "object") {
285 ReadableStream = window.ReadableStream;
286 #(static_literal("// Workaround for combined use with `wasm-bindgen`, so we don't have to"))
287 #(static_literal("// patch the `importObject` while loading the WASM module."))
288 window.__notifier_callback = (idx) => notifierRegistry.callbacks[idx]();
289 } else {
290 eval("mport('node:stream/web')".replace(/^/, 'i')).then(pkg => {
291 ReadableStream = pkg.ReadableStream;
292 });
293 #(static_literal("// Workaround for combined use with `wasm-bindgen`, so we don't have to"))
294 #(static_literal("// patch the `importObject` while loading the WASM module."))
295 global.__notifier_callback = (idx) => notifierRegistry.callbacks[idx]();
296 };
297
298 const fetchFn = (typeof fetch === "function" && fetch) || fetch_polyfill;
299
300 function fetchAndInstantiate(url, imports) {
303 const env = imports.env || {};
304 env.__notifier_callback = (idx) => notifierRegistry.callbacks[idx]();
305 imports.env = env;
306 return fetchFn(url)
307 .then((resp) => {
308 if (!resp.ok) {
309 throw new Error("Got a ${resp.status} fetching wasm @ ${url}");
310 }
311
312 const wasm = "application/wasm";
313 const type = resp.headers && resp.headers.get("content-type");
314
315 return (WebAssembly.instantiateStreaming && type === wasm)
316 ? WebAssembly.instantiateStreaming(resp, imports)
317 : resp.arrayBuffer().then(buf => WebAssembly.instantiate(buf, imports));
318 })
319 .then(result => result.instance);
320 }
321
322 const dropRegistry = new FinalizationRegistry(drop => drop());
323
324 class Box {
325 constructor(ptr, destructor) {
326 this.ptr = ptr;
327 this.dropped = false;
328 this.moved = false;
329 dropRegistry.register(this, destructor, this);
330 this.destructor = destructor;
331 }
332
333 borrow() {
334 if (this.dropped) {
335 throw new Error("use after free");
336 }
337 if (this.moved) {
338 throw new Error("use after move");
339 }
340 return this.ptr;
341 }
342
343 move() {
344 if (this.dropped) {
345 throw new Error("use after free");
346 }
347 if (this.moved) {
348 throw new Error("can't move value twice");
349 }
350 this.moved = true;
351 dropRegistry.unregister(this);
352 return this.ptr;
353 }
354
355 drop() {
356 if (this.dropped) {
357 throw new Error("double free");
358 }
359 if (this.moved) {
360 throw new Error("can't drop moved value");
361 }
362 this.dropped = true;
363 dropRegistry.unregister(this);
364 this.destructor();
365 }
366 }
367
368 class NotifierRegistry {
369 constructor() {
370 this.counter = 0;
371 this.callbacks = {};
372 }
373
374 reserveSlot() {
375 const idx = this.counter;
376 this.counter += 1;
377 return idx;
378 }
379
380 registerNotifier(idx, notifier) {
381 this.callbacks[idx] = notifier;
382 }
383
384 unregisterNotifier(idx) {
385 delete this.callbacks[idx];
386 }
387 }
388
389 const notifierRegistry = new NotifierRegistry();
390
391 const nativeFuture = (box, nativePoll) => {
392 const poll = (resolve, reject, idx) => {
393 try {
394 const ret = nativePoll(box.borrow(), 0, BigInt(idx));
395 if (ret == null) {
396 return;
397 }
398 resolve(ret);
399 } catch(err) {
400 reject(err);
401 }
402 notifierRegistry.unregisterNotifier(idx);
403 box.drop();
404 };
405 return new Promise((resolve, reject) => {
406 const idx = notifierRegistry.reserveSlot();
407 const notifier = () => poll(resolve, reject, idx);
408 notifierRegistry.registerNotifier(idx, notifier);
409 poll(resolve, reject, idx);
410 });
411 };
412
413 function* nativeIter(box, nxt) {
414 let el;
415 while(true) {
416 el = nxt(box.borrow());
417 if (el === null) {
418 break;
419 }
420 yield el;
421 }
422 box.drop();
423 }
424
425 const nativeStream = (box, nativePoll) => {
426 const poll = (next, nextIdx, doneIdx) => {
427 const ret = nativePoll(box.borrow(), 0, BigInt(nextIdx), BigInt(doneIdx));
428 if (ret != null) {
429 next(ret);
430 }
431 };
432 return new ReadableStream({
433 start(controller) {
434 const nextIdx = notifierRegistry.reserveSlot();
435 const doneIdx = notifierRegistry.reserveSlot();
436 const nextNotifier = () => setTimeout(() =>
437 poll(x => controller.enqueue(x), nextIdx, doneIdx),
438 0);
439 const doneNotifier = () => {
440 notifierRegistry.unregisterNotifier(nextIdx);
441 notifierRegistry.unregisterNotifier(doneIdx);
442 controller.close();
443 box.drop();
444 };
445 notifierRegistry.registerNotifier(nextIdx, nextNotifier);
446 notifierRegistry.registerNotifier(doneIdx, doneNotifier);
447 nextNotifier();
448 },
449 });
450 };
451
452 export class Api {
453 async fetch(url, imports) {
454 this.instance = await fetchAndInstantiate(url, imports);
455 }
456
457 initWithInstance(instance) {
458 this.instance = instance;
459 }
460
461 allocate(size, align) {
462 return this.instance.exports.allocate(size, align);
463 }
464
465 deallocate(ptr, size, align) {
466 this.instance.exports.deallocate(ptr, size, align);
467 }
468
469 drop(symbol, ptr) {
470 this.instance.exports[symbol](0, ptr);
471 }
472
473 #(for func in iface.functions() => #(self.generate_function(&func)))
474 #(for iter in iface.iterators() => #(self.generate_function(&iter.next())))
475 #(for fut in iface.futures() => #(self.generate_function(&fut.poll())))
476 #(for stream in iface.streams() => #(self.generate_function(&stream.poll())))
477 }
478
479 #(for obj in iface.objects() => #(self.generate_object(obj)))
480
481 export default Api;
482 }
483 }
484
485 fn generate_object(&self, obj: AbiObject) -> js::Tokens {
486 quote! {
487 export class #(self.type_ident(&obj.name)) {
488 constructor(api, box) {
489 this.api = api;
490 this.box = box;
491 }
492
493 #(for method in obj.methods => #(self.generate_function(&method)))
494
495 drop() {
496 this.box.drop();
497 }
498 }
499 }
500 }
501
502 fn generate_function(&self, func: &AbiFunction) -> js::Tokens {
503 let ffi = self.abi.import(func);
504 let api = match &func.ty {
505 FunctionType::Constructor(_) => quote!(api),
506 FunctionType::Method(_) => quote!(this.api),
507 FunctionType::Function
508 | FunctionType::NextIter(_, _)
509 | FunctionType::PollFuture(_, _)
510 | FunctionType::PollStream(_, _) => quote!(this),
511 };
512 let func_name = self.ident(match &func.ty {
513 FunctionType::PollFuture(_, _)
514 | FunctionType::PollStream(_, _)
515 | &FunctionType::NextIter(_, _) => &ffi.symbol,
516 _ => &func.name,
517 });
518 let args = quote!(#(for (name, _) in &ffi.abi_args => #(self.ident(name)),));
519 let body = quote!(#(for instr in &ffi.instr => #(self.generate_instr(&api, instr))));
520 match &func.ty {
521 FunctionType::Constructor(_) => quote! {
522 static #(self.ident(&func.name))(api, #args) {
523 #body
524 }
525 },
526 _ => quote! {
527 #func_name(#args) {
528 #body
529 }
530 },
531 }
532 }
533
534 fn generate_instr(&self, api: &js::Tokens, instr: &Instr) -> js::Tokens {
535 match instr {
536 Instr::BorrowSelf(out) => quote!(#(self.var(out)) = this.box.borrow();),
537 Instr::BorrowObject(in_, out)
538 | Instr::BorrowIter(in_, out)
539 | Instr::BorrowFuture(in_, out)
540 | Instr::BorrowStream(in_, out) => {
541 quote!(#(self.var(out)) = #(self.var(in_)).box.borrow();)
542 }
543 Instr::MoveObject(in_, out)
544 | Instr::MoveIter(in_, out)
545 | Instr::MoveFuture(in_, out)
546 | Instr::MoveStream(in_, out) => {
547 quote!(#(self.var(out)) = #(self.var(in_)).box.move();)
548 }
549 Instr::LiftObject(obj, box_, drop, out) => quote! {
550 const #(self.var(box_))_0 = () => { #api.drop(#_(#drop), #(self.var(box_))); };
551 const #(self.var(box_))_1 = new Box(#(self.var(box_)), #(self.var(box_))_0);
552 const #(self.var(out)) = new #obj(#api, #(self.var(box_))_1);
553 },
554 Instr::BindArg(arg, out) => quote!(const #(self.var(out)) = #(self.ident(arg));),
555 Instr::BindRets(ret, vars) => match vars.len() {
556 0 => quote!(),
557 1 => quote!(const #(self.var(&vars[0])) = #(self.var(ret));),
558 _ => quote! {
559 #(for (idx, var) in vars.iter().enumerate() =>
560 const #(self.var(var)) = #(self.var(ret))[#(idx)];)
561 },
562 },
563 Instr::LiftNumFromU32Tuple(low, high, out, num_type) => {
564 let arr = match num_type {
565 NumType::U64 => quote!(BigUint64Array),
566 NumType::I64 => quote!(BigInt64Array),
567 _ => unreachable!(),
568 };
569 quote! {
570 const #(self.var(out))_0 = new Uint32Array(2);
571 #(self.var(out))_0[0] = #(self.var(low));
572 #(self.var(out))_0[1] = #(self.var(high));
573 const #(self.var(out)) = new #(arr)(#(self.var(out))_0.buffer)[0];
574 }
575 }
576 Instr::LiftNum(r#in, out, NumType::U32) => {
577 quote!(const #(self.var(out)) = #(self.var(r#in)) >>> 0;)
578 }
579 Instr::LowerNumFromU32Tuple(r#in, out_low, out_high, num_type) => {
580 let arr = match num_type {
581 NumType::U64 => quote!(BigUint64Array),
582 NumType::I64 => quote!(BigInt64Array),
583 _ => unreachable!(),
584 };
585 quote! {
586 const #(self.var(out_low))_0 = new #(arr)(1);
587 #(self.var(out_low))_0[0] = #(self.var(r#in));
588 const #(self.var(out_low))_1 = new Uint32Array(#(self.var(out_low))_0.buffer);
589 #(self.var(out_low)) = #(self.var(out_low))_1[0];
590 #(self.var(out_high)) = #(self.var(out_low))_1[1];
591 }
592 }
593 Instr::LowerNum(in_, out, _num) => {
594 quote!(#(self.var(out)) = #(self.var(in_));)
595 }
596 Instr::LiftNum(in_, out, _num) => {
597 quote!(const #(self.var(out)) = #(self.var(in_));)
598 }
599 Instr::LowerBool(in_, out) => {
600 quote!(#(self.var(out)) = #(self.var(in_)) ? 1 : 0;)
601 }
602 Instr::LiftBool(in_, out) => quote!(const #(self.var(out)) = #(self.var(in_)) > 0;),
603 Instr::Deallocate(ptr, len, size, align) => quote! {
604 if (#(self.var(len)) > 0) {
605 #api.deallocate(#(self.var(ptr)), #(self.var(len)) * #(*size), #(*align));
606 }
607 },
608 Instr::LowerString(in_, ptr, len, cap, size, align) => quote! {
609 const #(self.var(in_))_0 = new TextEncoder();
610 const #(self.var(in_))_1 = #(self.var(in_))_0.encode(#(self.var(in_)));
611 #(self.var(len)) = #(self.var(in_))_1.length;
612 #(self.var(ptr)) = #api.allocate(#(self.var(len)) * #(*size), #(*align));
613 const #(self.var(ptr))_0 =
614 new Uint8Array(#api.instance.exports.memory.buffer, #(self.var(ptr)), #(self.var(len)));
615 #(self.var(ptr))_0.set(#(self.var(in_))_1, 0);
616 #(self.var(cap)) = #(self.var(len));
617 },
618 Instr::LiftString(ptr, len, out) => quote! {
619 const #(self.var(out))_0 =
620 new Uint8Array(#api.instance.exports.memory.buffer, #(self.var(ptr)), #(self.var(len)));
621 const #(self.var(out))_1 = new TextDecoder();
622 const #(self.var(out)) = #(self.var(out))_1.decode(#(self.var(out))_0);
623 },
624 Instr::LowerVec(in_, ptr, len, cap, ty, size, align) => quote! {
625 #(self.var(len)) = #(self.var(in_)).length;
626 #(self.var(ptr)) = #api.allocate(#(self.var(len)) * #(*size), #(*align));
627 const #(self.var(ptr))_0 =
628 new #(self.generate_array(*ty))(
629 #api.instance.exports.memory.buffer, #(self.var(ptr)), #(self.var(len)));
630 #(self.var(ptr))_0.set(#(self.var(in_)), 0);
631 #(self.var(cap)) = #(self.var(len));
632 },
633 Instr::LiftVec(ptr, len, out, ty) => quote! {
634 const #(self.var(out))_0 =
635 new #(self.generate_array(*ty))(
636 #api.instance.exports.memory.buffer, #(self.var(ptr)), #(self.var(len)));
637 const #(self.var(out)) = Array.from(#(self.var(out))_0);
638 },
639 Instr::Call(symbol, ret, args) => {
640 let invoke =
641 quote!(#api.instance.exports.#symbol(#(for arg in args => #(self.var(arg)),)););
642 if let Some(ret) = ret {
643 quote!(const #(self.var(ret)) = #invoke)
644 } else {
645 invoke
646 }
647 }
648 Instr::DefineArgs(vars) => quote! {
649 #(for var in vars => let #(self.var(var)) = 0;)
650 },
651 Instr::ReturnValue(ret) => quote!(return #(self.var(ret));),
652 Instr::ReturnVoid => quote!(return;),
653 Instr::HandleNull(var) => quote! {
654 if (#(self.var(var)) === 0) {
655 return null;
656 }
657 },
658 Instr::LowerOption(arg, var, some, some_instr) => quote! {
659 if (#(self.var(arg)) == null) {
660 #(self.var(var)) = 0;
661 } else {
662 #(self.var(var)) = 1;
663 const #(self.var(some)) = #(self.var(arg));
664 #(for inst in some_instr => #(self.generate_instr(api, inst)))
665 }
666 },
667 Instr::HandleError(var, ptr, len, cap) => quote! {
668 if (#(self.var(var)) === 0) {
669 const #(self.var(var))_0 =
670 new Uint8Array(#api.instance.exports.memory.buffer, #(self.var(ptr)), #(self.var(len)));
671 const #(self.var(var))_1 = new TextDecoder();
672 const #(self.var(var))_2 = #(self.var(var))_1.decode(#(self.var(var))_0);
673 if (#(self.var(len)) > 0) {
674 #api.deallocate(#(self.var(ptr)), #(self.var(cap)), 1);
675 }
676 throw #(self.var(var))_2;
677 }
678 },
679 Instr::LiftIter(box_, next, drop, out) => quote! {
680 const #(self.var(box_))_0 = () => { #api.drop(#_(#drop), #(self.var(box_))); };
681 const #(self.var(box_))_1 = new Box(#(self.var(box_)), #(self.var(box_))_0);
682 const #(self.var(out)) = nativeIter(#(self.var(box_))_1, (a) => {
683 return #api.#(self.ident(next))(a);
684 });
685 },
686 Instr::LiftFuture(box_, poll, drop, out) => quote! {
687 const #(self.var(box_))_0 = () => { #api.drop(#_(#drop), #(self.var(box_))); };
688 const #(self.var(box_))_1 = new Box(#(self.var(box_)), #(self.var(box_))_0);
689 const #(self.var(out)) = nativeFuture(#(self.var(box_))_1, (a, b, c) => {
690 return #api.#(self.ident(poll))(a, b, c);
691 });
692 },
693 Instr::LiftStream(box_, poll, drop, out) => quote! {
694 const #(self.var(box_))_0 = () => { #api.drop(#_(#drop), #(self.var(box_))); };
695 const #(self.var(box_))_1 = new Box(#(self.var(box_)), #(self.var(box_))_0);
696 const #(self.var(out)) = nativeStream(#(self.var(box_))_1, (a, b, c, d) => {
697 return #api.#(self.ident(poll))(a, b, c, d);
698 });
699 },
700 Instr::LiftTuple(vars, out) => match vars.len() {
701 0 => quote!(),
702 1 => quote!(const #(self.var(out)) = #(self.var(&vars[0]));),
703 _ => quote! {
704 const #(self.var(out)) = [];
705 #(for var in vars => #(self.var(out)).push(#(self.var(var)));)
706 },
707 },
708 }
709 }
710
711 fn var(&self, var: &Var) -> js::Tokens {
712 quote!(#(format!("tmp{}", var.binding)))
713 }
714
715 fn generate_array(&self, ty: NumType) -> js::Tokens {
716 match ty {
717 NumType::U8 => quote!(Uint8Array),
718 NumType::U16 => quote!(Uint16Array),
719 NumType::U32 => quote!(Uint32Array),
720 NumType::U64 => quote!(BigUint64Array),
721 NumType::I8 => quote!(Int8Array),
722 NumType::I16 => quote!(Int16Array),
723 NumType::I32 => quote!(Int32Array),
724 NumType::I64 => quote!(BigInt64Array),
725 NumType::F32 => quote!(Float32Array),
726 NumType::F64 => quote!(Float64Array),
727 }
728 }
729
730 fn type_ident(&self, s: &str) -> String {
731 sanitize_identifier(&s.to_upper_camel_case())
732 }
733
734 fn ident(&self, s: &str) -> String {
735 sanitize_identifier(&s.to_lower_camel_case())
736 }
737}
738
739pub struct WasmMultiValueShim {
740 abi: Abi,
741}
742
743impl Default for WasmMultiValueShim {
744 fn default() -> Self {
745 Self::new()
746 }
747}
748
749impl WasmMultiValueShim {
750 pub fn new() -> Self {
751 Self { abi: Abi::Wasm32 }
752 }
753
754 #[cfg(feature = "test_runner")]
755 pub fn generate(&self, path: &str, iface: Interface) -> rust::Tokens {
756 let args = self.generate_args(iface);
757 if !args.is_empty() {
758 quote! {
759 let ret = Command::new("multi-value-reverse-polyfill")
760 .arg(#_(#path))
761 #(for arg in args => .arg(#(quoted(arg))))
762 .status()
763 .unwrap()
764 .success();
765 assert!(ret);
766 }
767 } else {
768 quote! {
769 let ret = Command::new("cp")
770 .arg(#_(#path))
771 .arg(#_(#(path).multivalue.wasm))
772 .status()
773 .unwrap()
774 .success();
775 assert!(ret);
776 }
777 }
778 }
779
780 pub fn run<P: AsRef<Path>>(&self, path: P, iface: Interface) -> Result<()> {
781 let args = self.generate_args(iface);
782 let path = path.as_ref().to_str().unwrap();
783 if !args.is_empty() {
784 let mut cmd = Command::new("multi-value-reverse-polyfill");
785 cmd.arg(path);
786 for arg in args {
787 cmd.arg(arg);
788 }
789 let status = cmd.status()?;
790 if !status.success() {
791 anyhow::bail!("multi-value-reverse-polyfill failed for: {:?}", cmd);
792 }
793 } else {
794 let status = Command::new("cp")
795 .arg(path)
796 .arg(format!("{}.multivalue.wasm", path))
797 .status()?;
798 if !status.success() {
799 anyhow::bail!("cp failed");
800 }
801 }
802 Ok(())
803 }
804
805 fn generate_args(&self, iface: Interface) -> Vec<String> {
806 iface
807 .imports(&self.abi)
808 .into_iter()
809 .filter_map(|import| match &import.ffi_ret {
810 Return::Struct(fields, _) => {
811 let mut ret = String::new();
812 for field in fields {
813 let (size, _) = self.abi.layout(field.ty.num());
814 let r = match field.ty.num() {
815 NumType::F32 => "f32 ",
816 NumType::F64 => "f64 ",
817 _ if size > 4 => "i64 ",
818 _ => "i32 ",
819 };
820 ret.push_str(r);
821 }
822
823 Some(format!("{} {}", import.symbol, ret))
824 }
825 _ => None,
826 })
827 .collect()
828 }
829}
830
831#[cfg(feature = "test_runner")]
832#[doc(hidden)]
833pub mod test_runner {
834 use super::*;
835 use crate::{Abi, RustGenerator};
836 use anyhow::Result;
837 use std::io::Write;
838 use tempfile::NamedTempFile;
839 use trybuild::TestCases;
840
841 pub fn compile_pass(iface: &str, rust: rust::Tokens, js: js::Tokens) -> Result<()> {
842 let iface = Interface::parse(iface)?;
843 let mut rust_file = NamedTempFile::new()?;
844 let rust_gen = RustGenerator::new(Abi::Wasm32);
845 let rust_tokens = rust_gen.generate(iface.clone());
846 let mut js_file = tempfile::Builder::new().suffix(".mjs").tempfile()?;
847 let js_gen = JsGenerator::default();
848 let js_tokens = js_gen.generate(iface.clone());
849
850 let library_tokens = quote! {
851 #rust_tokens
852 #rust
853
854 extern "C" {
855 fn __panic(ptr: isize, len: usize);
856 fn __log(ptr: isize, len: usize);
857 }
858
859 pub fn panic(msg: &str) {
860 unsafe { __panic(msg.as_ptr() as _, msg.len()) };
861 }
862
863 pub fn log(msg: &str) {
864 unsafe { __log(msg.as_ptr() as _, msg.len()) };
865 }
866 };
867
868 let library_file = NamedTempFile::new()?;
869 let bin_tokens = quote! {
870 import assert from "assert";
871 #js_tokens
872
873 async function main() {
874 const api = new Api();
875 await api.fetch(#_(#(library_file.as_ref().to_str().unwrap()).multivalue.wasm), {
876 env: {
877 __panic: (ptr, len) => {
878 const buf = new Uint8Array(api.instance.exports.memory.buffer, ptr, len);
879 const decoder = new TextDecoder();
880 throw decoder.decode(buf);
881 },
882 __log: (ptr, len) => {
883 const buf = new Uint8Array(api.instance.exports.memory.buffer, ptr, len);
884 const decoder = new TextDecoder();
885 console.log(decoder.decode(buf));
886 },
887 }
888 });
889 #js
890 }
891 main();
892 };
893
894 let library = library_tokens.to_file_string()?;
895 rust_file.write_all(library.as_bytes())?;
896 let bin = bin_tokens.to_file_string()?;
897 js_file.write_all(bin.as_bytes())?;
898
899 let wasm_multi_value =
900 WasmMultiValueShim::new().generate(library_file.as_ref().to_str().unwrap(), iface);
901
902 let runner_tokens: rust::Tokens = quote! {
903 fn main() {
904 use std::process::Command;
905 let ret = Command::new("rustc")
906 .arg("--edition")
907 .arg("2021")
908 .arg("--crate-name")
909 .arg("compile_pass")
910 .arg("--crate-type")
911 .arg("cdylib")
912 .arg("-o")
913 .arg(#(quoted(library_file.as_ref().to_str().unwrap())))
914 .arg("--cfg")
915 .arg("feature=\"test_runner\"")
916 .arg("--target")
917 .arg("wasm32-unknown-unknown")
918 .arg(#(quoted(rust_file.as_ref().to_str().unwrap())))
919 .status()
920 .expect("Compiling lib")
921 .success();
922 assert!(ret);
923 #wasm_multi_value
925 let ret = Command::new("node")
926 .arg("--expose-gc")
927 .arg("--unhandled-rejections=strict")
928 .arg(#(quoted(js_file.as_ref().to_str().unwrap())))
929 .status()
930 .expect("Running node")
931 .success();
932 assert!(ret);
933 }
934 };
935
936 let mut runner_file = NamedTempFile::new()?;
937 let runner = runner_tokens.to_file_string()?;
938 runner_file.write_all(runner.as_bytes())?;
939
940 let test = TestCases::new();
945 test.pass(runner_file.as_ref());
947 Ok(())
948 }
949
950 pub fn compile_pass_ts(iface: &str, ts_tokens: js::Tokens) -> Result<()> {
951 let iface = Interface::parse(iface)?;
952 let ts_gen = TsGenerator { docs: false };
953 let js_tokens = ts_gen.generate(iface);
954 let left = js_tokens.to_file_string().unwrap().replace(
956 r#"// AUTO GENERATED FILE, DO NOT EDIT.
957//
958// Generated by "ffi-gen".
959/* tslint:disable */
960/* eslint:disable */
961
962"#,
963 "",
964 );
965 let right = ts_tokens.to_file_string().unwrap();
966
967 assert_eq!(left, right, "left: {}\nright: {}", left, right);
968 Ok(())
969 }
970}