1use std::fmt::Display;
16use std::path::{Path, PathBuf};
17use std::pin::Pin;
18use std::time::Duration;
19
20use anyhow::{Context, Result, anyhow, bail, ensure};
21use clap::{ValueEnum, ValueHint};
22use rusb::GlobalContext;
23use tokio::fs::File;
24use tokio::io::{AsyncBufRead, AsyncBufReadExt as _, AsyncWrite, AsyncWriteExt, BufReader};
25use tokio::process::Command;
26use wasefire_common::platform::Side;
27use wasefire_protocol::{self as service, Connection, ConnectionExt as _, applet};
28use wasefire_wire::{self as wire, Yoke};
29
30use crate::cargo::metadata;
31use crate::error::root_cause_is;
32use crate::{cmd, fs};
33
34mod protocol;
35pub mod usb_serial;
36
37#[derive(Clone, clap::Args)]
39pub struct ConnectionOptions {
40 #[arg(long, default_value = "usb", env = "WASEFIRE_PROTOCOL", verbatim_doc_comment)]
49 protocol: protocol::Protocol,
50
51 #[arg(long, default_value = "0s")]
53 timeout: humantime::Duration,
54}
55
56impl ConnectionOptions {
57 pub async fn connect(&self) -> Result<Box<dyn Connection>> {
59 self.protocol.connect(*self.timeout).await
60 }
61
62 pub fn reboot_stable(&self) -> bool {
64 match &self.protocol {
65 protocol::Protocol::Usb(x) => match x {
66 protocol::ProtocolUsb::Auto => true,
67 protocol::ProtocolUsb::Serial(_) => true,
68 protocol::ProtocolUsb::BusDev { .. } => false,
69 },
70 protocol::Protocol::Unix(_) => true,
71 protocol::Protocol::Tcp(_) => true,
72 }
73 }
74}
75
76#[derive(clap::Args)]
78pub struct PlatformApiVersion {}
79
80impl PlatformApiVersion {
81 pub async fn run(self, connection: &mut dyn Connection) -> Result<u32> {
82 let PlatformApiVersion {} = self;
83 connection.call::<service::ApiVersion>(()).await.map(|x| *x.get())
84 }
85}
86
87#[derive(clap::Args)]
89pub struct AppletInstall {
90 #[arg(value_hint = ValueHint::FilePath)]
92 pub applet: PathBuf,
93
94 #[clap(flatten)]
95 pub transfer: Transfer,
96
97 #[command(subcommand)]
98 pub wait: Option<AppletInstallWait>,
99}
100
101#[derive(clap::Subcommand)]
102pub enum AppletInstallWait {
103 #[group(id = "AppletInstallWait::Wait")]
105 Wait {
106 #[command(flatten)]
107 action: AppletExitStatus,
108 },
109}
110
111impl AppletInstall {
112 pub async fn run(self, connection: &mut dyn Connection) -> Result<()> {
113 let AppletInstall { applet, transfer, wait } = self;
114 transfer
115 .run::<service::AppletInstall>(
116 connection,
117 Some(applet),
118 "Installed",
119 None::<fn(_) -> _>,
120 )
121 .await?;
122 match wait {
123 Some(AppletInstallWait::Wait { mut action }) => {
124 action.wait.ensure_wait();
125 action.run(connection).await
126 }
127 None => Ok(()),
128 }
129 }
130}
131
132#[derive(clap::Args)]
134pub struct AppletUninstall {}
135
136impl AppletUninstall {
137 pub async fn run(self, connection: &mut dyn Connection) -> Result<()> {
138 let AppletUninstall {} = self;
139 let transfer = Transfer { dry_run: false };
140 transfer.run::<service::AppletInstall>(connection, None, "Erased", None::<fn(_) -> _>).await
141 }
142}
143
144#[derive(clap::Parser)]
146#[non_exhaustive]
147pub struct AppletExitStatus {
148 #[clap(flatten)]
149 pub wait: Wait,
150
151 #[arg(long)]
153 exit_code: bool,
154}
155
156impl AppletExitStatus {
157 fn print(status: Option<applet::ExitStatus>) {
158 match status {
159 Some(status) => println!("{status}."),
160 None => println!("The applet is still running."),
161 }
162 }
163
164 fn code(status: Option<applet::ExitStatus>) -> i32 {
165 match status {
166 Some(applet::ExitStatus::Exit) => 0,
167 Some(applet::ExitStatus::Abort) => 1,
168 Some(applet::ExitStatus::Trap) => 2,
169 Some(applet::ExitStatus::Kill) => 62,
170 None => 63,
171 }
172 }
173
174 pub fn ensure_exit(&mut self) {
175 self.exit_code = true;
176 }
177
178 pub async fn run(self, connection: &mut dyn Connection) -> Result<()> {
179 let AppletExitStatus { wait, exit_code } = self;
180 let status = wait
181 .run::<service::AppletExitStatus, applet::ExitStatus>(connection, applet::AppletId)
182 .await?
183 .map(|x| *x.get());
184 Self::print(status);
185 if exit_code {
186 std::process::exit(Self::code(status))
187 }
188 Ok(())
189 }
190}
191
192#[derive(clap::Args)]
194pub struct AppletReboot {}
195
196impl AppletReboot {
197 pub async fn run(self, connection: &mut dyn Connection) -> Result<()> {
198 let AppletReboot {} = self;
199 connection.call::<service::AppletReboot>(applet::AppletId).await.map(|x| *x.get())
200 }
201}
202
203#[derive(clap::Args)]
205struct Rpc {
206 #[arg(long, value_hint = ValueHint::FilePath)]
208 input: Option<PathBuf>,
209
210 #[arg(long, value_hint = ValueHint::AnyPath)]
212 output: Option<PathBuf>,
213
214 #[arg(long)]
216 repl: bool,
217}
218
219enum RpcState {
220 One { input: Option<PathBuf>, output: Option<PathBuf>, read: bool },
221 Loop { input: Pin<Box<dyn AsyncBufRead>>, output: Pin<Box<dyn AsyncWrite>> },
222}
223
224impl Rpc {
225 async fn start(self) -> Result<RpcState> {
226 let Rpc { input, output, repl } = self;
227 if !repl {
228 return Ok(RpcState::One { input, output, read: false });
229 }
230 let input: Pin<Box<dyn AsyncBufRead>> = match input {
231 None => Box::pin(BufReader::new(tokio::io::stdin())),
232 Some(path) => Box::pin(BufReader::new(File::open(path).await?)),
233 };
234 let output: Pin<Box<dyn AsyncWrite>> = match output {
235 None => Box::pin(tokio::io::stdout()),
236 Some(path) => Box::pin(File::create(path).await?),
237 };
238 Ok(RpcState::Loop { input, output })
239 }
240}
241
242impl RpcState {
243 async fn read(&mut self) -> Result<Option<Vec<u8>>> {
244 match self {
245 RpcState::One { read: x @ false, .. } => *x = true,
246 RpcState::One { .. } => return Ok(None),
247 RpcState::Loop { .. } => (),
248 }
249 Ok(Some(match self {
250 RpcState::One { input: None, .. } => fs::read_stdin().await?,
251 RpcState::One { input: Some(path), .. } => fs::read(path).await?,
252 RpcState::Loop { input, .. } => {
253 let mut line = String::new();
254 if input.read_line(&mut line).await? == 0 {
255 return Ok(None);
256 }
257 line.into_bytes()
258 }
259 }))
260 }
261
262 async fn write(&mut self, response: &[u8]) -> Result<()> {
263 match self {
264 RpcState::One { output: None, .. } => fs::write_stdout(response).await,
265 RpcState::One { output: Some(path), .. } => fs::write(path, response).await,
266 RpcState::Loop { output, .. } => {
267 output.write_all(response).await?;
268 output.flush().await?;
269 Ok(())
270 }
271 }
272 }
273}
274
275#[derive(clap::Args)]
277pub struct AppletRpc {
278 applet: Option<String>,
280
281 #[clap(flatten)]
282 rpc: Rpc,
283
284 #[clap(flatten)]
285 wait: Wait,
286}
287
288impl AppletRpc {
289 pub async fn run(self, connection: &mut dyn Connection) -> Result<()> {
290 let AppletRpc { applet, rpc, mut wait } = self;
291 let applet_id = match applet {
292 Some(_) => bail!("applet identifiers are not supported yet"),
293 None => applet::AppletId,
294 };
295 wait.ensure_wait();
296 let mut rpc = rpc.start().await?;
297 while let Some(request) = rpc.read().await? {
298 let request = applet::Request { applet_id, request: &request };
299 connection.call::<service::AppletRequest>(request).await?.get();
300 match wait.run::<service::AppletResponse, &[u8]>(connection, applet_id).await? {
301 None => bail!("did not receive a response"),
302 Some(response) => rpc.write(response.get()).await?,
303 }
304 }
305 Ok(())
306 }
307}
308
309#[derive(clap::Parser)]
311pub struct Wait {
312 #[arg(long)]
316 wait: bool,
317
318 #[arg(long, conflicts_with = "wait")]
322 period: Option<humantime::Duration>,
323}
324
325impl Wait {
326 pub fn ensure_wait(&mut self) {
327 if self.wait || self.period.is_some() {
328 return;
329 }
330 self.wait = true;
331 }
332
333 pub fn set_period(&mut self, period: Duration) {
334 self.wait = false;
335 self.period = Some(period.into());
336 }
337
338 pub async fn run<S, T: wire::Wire<'static>>(
339 &self, connection: &mut dyn Connection, request: S::Request<'_>,
340 ) -> Result<Option<Yoke<T::Type<'static>>>>
341 where S: for<'a> service::Service<Response<'a> = Option<T::Type<'a>>> {
342 let Wait { wait, period } = self;
343 let period = match (wait, period) {
344 (true, None) => Some(Duration::from_millis(100)),
345 (true, Some(_)) => unreachable!(),
346 (false, None) => None,
347 (false, Some(x)) => Some(**x),
348 };
349 let request = S::request(request);
350 loop {
351 match connection.call_ref::<S>(&request).await?.try_map(|x| x.ok_or(())) {
352 Ok(x) => break Ok(Some(x)),
353 Err(()) => match period {
354 Some(period) => tokio::time::sleep(period).await,
355 None => break Ok(None),
356 },
357 }
358 }
359 }
360}
361
362#[derive(clap::Args)]
364pub struct PlatformClearStore {
365 #[arg(default_value_t = 0)]
367 min_key: usize,
368}
369
370impl PlatformClearStore {
371 pub async fn run(self, connection: &mut dyn Connection) -> Result<()> {
372 let PlatformClearStore { min_key } = self;
373 connection.call::<service::PlatformClearStore>(min_key).await.map(|x| *x.get())
374 }
375}
376
377#[derive(clap::Args)]
379pub struct PlatformInfo {}
380
381impl PlatformInfo {
382 pub async fn print(self, connection: &mut dyn Connection) -> Result<()> {
383 Ok(print!("{}", self.run(connection).await?.get()))
384 }
385
386 pub async fn run(
387 self, connection: &mut dyn Connection,
388 ) -> Result<Yoke<service::platform::Info<'static>>> {
389 let PlatformInfo {} = self;
390 connection.call::<service::PlatformInfo>(()).await
391 }
392}
393
394#[derive(clap::Args)]
396pub struct PlatformList {
397 #[arg(long, default_value = "1s")]
399 timeout: humantime::Duration,
400}
401
402impl PlatformList {
403 pub async fn run(self) -> Result<()> {
404 let PlatformList { timeout } = self;
405 let context = GlobalContext::default();
406 let candidates = wasefire_protocol_usb::list(&context)?;
407 println!("There are {} connected platforms on USB:", candidates.len());
408 for candidate in candidates {
409 let mut connection = candidate.connect(*timeout)?;
410 let serial = protocol::ProtocolUsb::Serial(protocol::serial(&mut connection).await?);
411 let bus = connection.device().bus_number();
412 let dev = connection.device().address();
413 let busdev = protocol::ProtocolUsb::BusDev { bus, dev };
414 println!("- {serial} or {busdev}");
415 }
416 Ok(())
417 }
418}
419
420#[derive(clap::Args)]
422pub struct PlatformUpdate {
423 #[arg(value_hint = ValueHint::FilePath)]
428 pub platform_a: PathBuf,
429
430 #[arg(value_hint = ValueHint::FilePath)]
434 pub platform_b: Option<PathBuf>,
435
436 #[clap(flatten)]
437 pub transfer: Transfer,
438}
439
440impl PlatformUpdate {
441 pub async fn run(self, connection: &mut dyn Connection) -> Result<()> {
442 let PlatformUpdate { platform_a, platform_b, transfer } = self;
443 let platform = match platform_b {
444 Some(platform_b) => match (PlatformInfo {}).run(connection).await?.get().running_side {
445 Side::A => platform_b,
446 Side::B => platform_a,
447 },
448 None => platform_a,
449 };
450 transfer
451 .run::<service::PlatformUpdate>(
452 connection,
453 Some(platform),
454 "Updated",
455 Some(|_| bail!("device responded to a transfer finish")),
456 )
457 .await
458 }
459}
460
461#[derive(Clone, clap::Args)]
463pub struct Transfer {
464 #[arg(long)]
466 dry_run: bool,
467}
468
469impl Transfer {
470 async fn run<S>(
471 self, connection: &mut dyn Connection, payload: Option<PathBuf>, message: &'static str,
472 finish: Option<impl FnOnce(Yoke<S::Response<'static>>) -> Result<!>>,
473 ) -> Result<()>
474 where
475 S: for<'a> service::Service<
476 Request<'a> = service::transfer::Request<'a>,
477 Response<'a> = service::transfer::Response,
478 >,
479 {
480 use wasefire_protocol::transfer::{Request, Response};
481 let Transfer { dry_run } = self;
482 let payload = match payload {
483 None => Vec::new(),
484 Some(x) => fs::read(x).await?,
485 };
486 let Response::Start { chunk_size, num_pages } =
487 *connection.call::<S>(Request::Start { dry_run }).await?.get()
488 else {
489 bail!("received unexpected response");
490 };
491 let multi_progress = indicatif::MultiProgress::new();
492 let style = indicatif::ProgressStyle::with_template(
493 "{msg:9} {elapsed:>3} {spinner} [{wide_bar}] {bytes:>10} / {total_bytes:<10}",
494 )?
495 .tick_chars("-\\|/ ")
496 .progress_chars("##-");
497 let mut progress = None;
498 if 0 < num_pages {
499 let progress = progress.insert(
500 multi_progress.add(indicatif::ProgressBar::new((num_pages * chunk_size) as u64)),
501 );
502 progress.set_style(style.clone());
503 progress.set_message("Erasing");
504 for _ in 0 .. num_pages {
505 connection.call::<S>(Request::Erase).await?.get();
506 progress.inc(chunk_size as u64);
507 }
508 }
509 if !payload.is_empty() {
510 if let Some(progress) = progress.take() {
511 progress.finish_with_message("Erased");
512 }
513 let progress = progress
514 .insert(multi_progress.add(indicatif::ProgressBar::new(payload.len() as u64)));
515 progress.set_style(style.clone());
516 progress.set_message("Writing");
517 for chunk in payload.chunks(chunk_size) {
518 connection.call::<S>(Request::Write { chunk }).await?.get();
519 progress.inc(chunk.len() as u64);
520 }
521 }
522 let progress = progress.unwrap_or_else(|| indicatif::ProgressBar::new(0).with_style(style));
523 progress.set_message("Finishing");
524 match (dry_run, finish) {
525 (false, Some(finish)) => final_call::<S>(connection, Request::Finish, finish).await?,
526 _ => drop(connection.call::<S>(Request::Finish).await?.get()),
527 }
528 progress.finish_with_message(message);
529 Ok(())
530 }
531}
532
533async fn final_call<S: service::Service>(
534 connection: &mut dyn Connection, request: S::Request<'_>,
535 proof: impl FnOnce(Yoke<S::Response<'static>>) -> Result<!>,
536) -> Result<()> {
537 connection.send(&S::request(request)).await?;
538 match connection.receive::<S>().await {
539 Ok(x) => proof(x)?,
540 Err(e) => {
541 if root_cause_is::<rusb::Error>(&e, |x| {
542 use rusb::Error::*;
543 matches!(x, NoDevice | Pipe | Io)
544 }) {
545 return Ok(());
546 }
547 if root_cause_is::<std::io::Error>(&e, |x| {
548 use std::io::ErrorKind::*;
549 matches!(x.kind(), NotConnected | BrokenPipe | UnexpectedEof)
550 }) {
551 return Ok(());
552 }
553 Err(e)
554 }
555 }
556}
557
558#[derive(clap::Args)]
560pub struct PlatformReboot {}
561
562impl PlatformReboot {
563 pub async fn run(self, connection: &mut dyn Connection) -> Result<()> {
564 let PlatformReboot {} = self;
565 final_call::<service::PlatformReboot>(connection, (), |x| match *x.get() {}).await
566 }
567}
568
569#[derive(clap::Args)]
571pub struct PlatformLock {}
572
573impl PlatformLock {
574 pub async fn run(self, connection: &mut dyn Connection) -> Result<()> {
575 let PlatformLock {} = self;
576 connection.call::<service::PlatformLock>(()).await.map(|x| *x.get())
577 }
578}
579
580#[derive(clap::Args)]
582pub struct PlatformRpc {
583 #[clap(flatten)]
584 rpc: Rpc,
585}
586
587impl PlatformRpc {
588 pub async fn run(self, connection: &mut dyn Connection) -> Result<()> {
589 let PlatformRpc { rpc } = self;
590 let mut rpc = rpc.start().await?;
591 while let Some(request) = rpc.read().await? {
592 let response = connection.call::<service::PlatformVendor>(&request).await?;
593 rpc.write(response.get()).await?;
594 }
595 Ok(())
596 }
597}
598
599#[derive(clap::Args)]
601pub struct RustAppletNew {
602 #[arg(value_hint = ValueHint::AnyPath)]
604 path: PathBuf,
605
606 #[arg(long)]
608 name: Option<String>,
609}
610
611impl RustAppletNew {
612 pub async fn run(self) -> Result<()> {
613 let RustAppletNew { path, name } = self;
614 let mut cargo = Command::new("cargo");
615 cargo.args(["new", "--lib"]).arg(&path);
616 if let Some(name) = name {
617 cargo.arg(format!("--name={name}"));
618 }
619 cmd::execute(&mut cargo).await?;
620 cmd::execute(Command::new("cargo").args(["add", "wasefire"]).current_dir(&path)).await?;
621 let mut cargo = Command::new("cargo");
622 cargo.args(["add", "wasefire-stub", "--optional"]);
623 cmd::execute(cargo.current_dir(&path)).await?;
624 let mut sed = Command::new("sed");
625 sed.arg("-i");
626 sed.arg("s#^wasefire-stub\\( = .\"dep:wasefire-stub\"\\)#test\\1, \"wasefire/test\"#");
627 sed.arg("Cargo.toml");
628 cmd::execute(sed.current_dir(&path)).await?;
629 tokio::fs::remove_file(path.join("src/lib.rs")).await?;
630 fs::write(path.join("src/lib.rs"), include_str!("data/lib.rs")).await?;
631 Ok(())
632 }
633}
634
635#[derive(clap::Parser)]
637pub struct RustAppletBuild {
638 #[arg(long)]
640 pub prod: bool,
641
642 #[arg(long, value_name = "TARGET")]
644 pub native: Option<String>,
645
646 #[arg(long, conflicts_with = "native")]
648 pub pulley: bool,
649
650 #[arg(long, value_name = "DIRECTORY", default_value = ".")]
652 #[arg(value_hint = ValueHint::DirPath)]
653 pub crate_dir: PathBuf,
654
655 #[arg(long, value_name = "DIRECTORY", default_value = "wasefire")]
657 #[arg(value_hint = ValueHint::DirPath)]
658 pub output_dir: PathBuf,
659
660 #[arg(long, default_value = "release")]
662 pub profile: String,
663
664 #[clap(long, short = 'O')]
666 pub opt_level: Option<OptLevel>,
667
668 #[clap(long, default_value = "16384")]
670 pub stack_size: usize,
671
672 #[clap(last = true)]
674 pub cargo: Vec<String>,
675}
676
677impl RustAppletBuild {
678 pub async fn run(self) -> Result<()> {
679 let metadata = metadata(&self.crate_dir).await?;
680 let package = &metadata.packages[0];
681 let target_dir =
682 fs::try_relative(std::env::current_dir()?, &metadata.target_directory).await?;
683 let name = package.name.replace('-', "_");
684 let mut cargo = Command::new("cargo");
685 let mut rustflags = Vec::new();
686 cargo.args(["rustc", "--lib"]);
687 cargo.arg("--config=profile.release.codegen-units=1");
690 cargo.arg("--config=profile.release.lto=true");
691 cargo.arg("--config=profile.release.panic=\"abort\"");
692 match &self.native {
693 None => {
694 rustflags.push(format!("-C link-arg=-zstack-size={}", self.stack_size));
695 rustflags.push("-C target-feature=+bulk-memory".to_string());
696 cargo.args(["--crate-type=cdylib", "--target=wasm32-unknown-unknown"]);
697 wasefire_feature(package, "wasm", &mut cargo)?;
698 }
699 Some(target) => {
700 cargo.args(["--crate-type=staticlib", &format!("--target={target}")]);
701 wasefire_feature(package, "native", &mut cargo)?;
702 if target == "riscv32imc-unknown-none-elf" {
703 wasefire_feature(package, "unsafe-assume-single-core", &mut cargo)?;
704 }
705 }
706 }
707 let profile = &self.profile;
708 cargo.arg(format!("--profile={profile}"));
709 if let Some(level) = self.opt_level {
710 cargo.arg(format!("--config=profile.{profile}.opt-level={level}"));
711 }
712 cargo.args(&self.cargo);
713 if self.prod {
714 cargo.arg("-Zbuild-std=core,alloc");
715 rustflags.push("--allow=unused-crate-dependencies".to_string());
717 let mut features = "-Zbuild-std-features=panic_immediate_abort".to_string();
718 if self.opt_level.is_some_and(OptLevel::optimize_for_size) {
719 features.push_str(",optimize_for_size");
720 }
721 cargo.arg(features);
722 } else {
723 cargo.env("WASEFIRE_DEBUG", "");
724 }
725 cargo.env("RUSTFLAGS", rustflags.join(" "));
726 cargo.current_dir(&self.crate_dir);
727 cmd::execute(&mut cargo).await?;
728 if let Some(target) = &self.native {
729 let src = target_dir.join(format!("{target}/{profile}/lib{name}.a"));
730 let dst = self.output_dir.join("libapplet.a");
731 if fs::has_changed(&src, &dst).await? {
732 fs::copy(src, dst).await?;
733 }
734 return Ok(());
735 }
736 let src = target_dir.join(format!("wasm32-unknown-unknown/{profile}/{name}.wasm"));
737 let opt = self.output_dir.join("applet-opt.wasm");
738 optimize_wasm(src, self.opt_level, &opt).await?;
739 if self.pulley {
740 let dst = self.output_dir.join("applet.pulley");
741 compile_pulley(opt, dst).await?;
742 } else {
743 let dst = self.output_dir.join("applet.wasm");
744 compute_sidetable(opt, dst).await?;
745 }
746 Ok(())
747 }
748}
749
750#[derive(clap::Args)]
752pub struct RustAppletTest {
753 #[arg(long, value_name = "DIRECTORY", default_value = ".")]
755 #[arg(value_hint = ValueHint::DirPath)]
756 crate_dir: PathBuf,
757
758 #[clap(last = true)]
760 cargo: Vec<String>,
761}
762
763impl RustAppletTest {
764 pub async fn run(self) -> Result<()> {
765 let metadata = metadata(&self.crate_dir).await?;
766 let package = &metadata.packages[0];
767 ensure!(package.features.contains_key("test"), "missing test feature");
768 let mut cargo = Command::new("cargo");
769 cargo.args(["test", "--features=test"]);
770 cargo.args(&self.cargo);
771 cargo.current_dir(&self.crate_dir);
772 cmd::replace(cargo)
773 }
774}
775
776#[derive(clap::Parser)]
778pub struct RustAppletInstall {
779 #[clap(flatten)]
780 build: RustAppletBuild,
781
782 #[clap(flatten)]
783 transfer: Transfer,
784
785 #[command(subcommand)]
786 wait: Option<AppletInstallWait>,
787}
788
789impl RustAppletInstall {
790 pub async fn run(self, connection: &mut dyn Connection) -> Result<()> {
791 let RustAppletInstall { build, transfer, wait } = self;
792 let output = build.output_dir.clone();
793 build.run().await?;
794 let install = AppletInstall { applet: output.join("applet.wasm"), transfer, wait };
795 install.run(connection).await
796 }
797}
798
799#[derive(Copy, Clone, ValueEnum)]
800pub enum OptLevel {
801 #[value(name = "0")]
802 O0,
803 #[value(name = "1")]
804 O1,
805 #[value(name = "2")]
806 O2,
807 #[value(name = "3")]
808 O3,
809 #[value(name = "s")]
810 Os,
811 #[value(name = "z")]
812 Oz,
813}
814
815impl OptLevel {
816 pub fn optimize_for_size(self) -> bool {
818 matches!(self, OptLevel::Os | OptLevel::Oz)
819 }
820}
821
822impl Display for OptLevel {
823 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
824 let value = self.to_possible_value().unwrap();
825 let name = value.get_name();
826 if f.alternate() || !matches!(self, OptLevel::Os | OptLevel::Oz) {
827 write!(f, "{name}")
828 } else {
829 write!(f, "{name:?}")
830 }
831 }
832}
833
834pub async fn optimize_wasm(
836 src: impl AsRef<Path>, opt_level: Option<OptLevel>, dst: impl AsRef<Path>,
837) -> Result<()> {
838 if !fs::has_changed(src.as_ref(), dst.as_ref()).await? {
839 return Ok(());
840 }
841 let result: Result<()> = try {
842 fs::copy(src, dst.as_ref()).await?;
843 cmd::execute(Command::new("wasm-strip").arg(dst.as_ref())).await?;
844 let mut opt = Command::new("wasm-opt");
845 opt.args(["--enable-bulk-memory", "--enable-sign-ext", "--enable-mutable-globals"]);
846 match opt_level {
847 Some(level) => drop(opt.arg(format!("-O{level:#}"))),
848 None => drop(opt.arg("-O")),
849 }
850 opt.arg(dst.as_ref());
851 opt.arg("-o");
852 opt.arg(dst.as_ref());
853 cmd::execute(&mut opt).await?;
854 };
855 if result.is_err() {
856 let _ = fs::remove_file(dst).await;
857 }
858 result.context("optimizing wasm")
859}
860
861pub async fn compile_pulley(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()> {
863 if !fs::has_changed(src.as_ref(), dst.as_ref()).await? {
864 return Ok(());
865 }
866 let result: Result<()> = try {
867 let wasm = fs::read(src).await?;
868 let mut config = wasmtime::Config::new();
869 config.target("pulley32")?;
870 config.generate_address_map(false);
872 config.memory_init_cow(false);
873 config.memory_reservation(0);
874 config.wasm_relaxed_simd(false);
875 config.wasm_simd(false);
876 let engine = wasmtime::Engine::new(&config)?;
877 fs::write(dst.as_ref(), &engine.precompile_module(&wasm)?).await?;
878 };
879 if result.is_err() {
880 let _ = fs::remove_file(dst).await;
881 }
882 result.context("compiling to pulley")
883}
884
885pub async fn compute_sidetable(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> Result<()> {
887 if !fs::has_changed(src.as_ref(), dst.as_ref()).await? {
888 return Ok(());
889 }
890 let result: Result<()> = try {
891 let wasm = fs::read(src).await?;
892 let wasm = wasefire_interpreter::prepare(&wasm)
893 .map_err(|_| anyhow!("failed to compute side-table"))?;
894 fs::write(&dst, &wasm).await?;
895 };
896 if result.is_err() {
897 let _ = fs::remove_file(dst).await;
898 }
899 result
900}
901
902fn wasefire_feature(
903 package: &cargo_metadata::Package, feature: &str, cargo: &mut Command,
904) -> Result<()> {
905 if package.features.contains_key(feature) {
906 cargo.arg(format!("--features={feature}"));
907 } else {
908 ensure!(
909 package.dependencies.iter().any(|x| x.name == "wasefire"),
910 "wasefire must be a direct dependency"
911 );
912 cargo.arg(format!("--features=wasefire/{feature}"));
913 }
914 Ok(())
915}