warp_runtime/
standalone.rs1use crate::engine::{limited_store, metered_config, ticking};
7use crate::fuel::{time_limit_from_environment, DEFAULT_FUEL};
8use crate::host_words::{FOREIGN_CALL, HANDLER_PREFIX};
9use crate::macho;
10use crate::system_signals::{NEW_EMPTY, NEW_INT};
11use std::fs::File;
12use std::io::{Read, Seek, SeekFrom};
13use std::ops::Range;
14use std::path::Path;
15use std::time::Duration;
16use wasmtime::{Engine, Linker, Module, Result, Val};
17
18const TRAILER_MAGIC: &[u8; 8] = b"WRPCwasm";
19const TRAILER_BYTES: usize = 16;
20const COMPRESSION_LEVEL: u8 = 9;
22const ALIGNMENT: usize = 16;
24pub const ENTRY_POINTS: [&str; 2] = ["main", "_start"];
26const MEMORY_EXPORT: &str = "memory";
28#[cfg(all(feature = "webview", target_os = "macos"))]
30const CHECK_FLAG: &str = "--check";
31
32pub fn stub_calls_export(name: &str) -> bool {
35 ENTRY_POINTS.contains(&name)
36 || [MEMORY_EXPORT, NEW_EMPTY, NEW_INT].contains(&name)
37 || name.starts_with(HANDLER_PREFIX)
38}
39
40pub fn stub_calls_export_with_foreign_calls(name: &str) -> bool {
43 stub_calls_export(name) || crate::gc_values::BUILDERS.contains(&name) || name == crate::gc_values::TEXT_HEAP
44}
45
46pub fn standalone_engine() -> Result<Engine> {
50 let mut config = metered_config(false);
51 config.generate_address_map(false);
52 Engine::new(&config)
53}
54
55pub fn with_machine_code(runtime: &[u8], machine_code: &[u8]) -> Vec<u8> {
58 let mut executable = without_machine_code(runtime);
59 let compressed = miniz_oxide::deflate::compress_to_vec(machine_code, COMPRESSION_LEVEL);
60 executable.extend_from_slice(&compressed);
61 executable.extend_from_slice(&(compressed.len() as u64).to_le_bytes());
62 executable.extend_from_slice(TRAILER_MAGIC);
63 executable.resize(executable.len().next_multiple_of(ALIGNMENT), 0);
64 macho::extend_last_segment_to_end(&mut executable);
65 executable
66}
67
68pub fn with_site(runtime: &[u8], site: &[crate::site_files::SiteFile]) -> Vec<u8> {
70 with_machine_code(runtime, &crate::site_files::packed(site))
71}
72
73pub fn without_machine_code(executable: &[u8]) -> Vec<u8> {
75 let mut unsigned = macho::unsigned(executable);
76 if let Some(carried) = carried_range(&unsigned) {
77 unsigned.truncate(carried.start);
78 }
79 unsigned
80}
81
82fn carried_range(executable: &[u8]) -> Option<Range<usize>> {
84 let end = macho::signature_offset(executable).unwrap_or(executable.len());
85 let (trailer_start, length) = trailer_before(executable.get(..end)?)?;
86 Some(trailer_start.checked_sub(length)?..trailer_start)
87}
88
89fn trailer_before(bytes: &[u8]) -> Option<(usize, usize)> {
91 let padding = bytes.iter().rev().take(ALIGNMENT - 1).take_while(|&&byte| byte == 0).count();
92 let trailer_start = (bytes.len() - padding).checked_sub(TRAILER_BYTES)?;
93 let trailer: &[u8; TRAILER_BYTES] = bytes[trailer_start..trailer_start + TRAILER_BYTES].try_into().ok()?;
94 (&trailer[8..] == TRAILER_MAGIC)
95 .then(|| (trailer_start, u64::from_le_bytes(trailer[..8].try_into().unwrap()) as usize))
96}
97
98pub fn embedded_machine_code(path: &Path) -> Option<Vec<u8>> {
100 let mut file = File::open(path).ok()?;
101 let size = file.metadata().ok()?.len() as usize;
102 let header = read_at(&mut file, 0, size.min(macho::MAX_HEADER_BYTES))?;
103 let end = macho::signature_offset(&header).unwrap_or(size);
104 let tail_start = end.checked_sub(TRAILER_BYTES + ALIGNMENT)?;
105 let (trailer_start, length) = trailer_before(&read_at(&mut file, tail_start, end - tail_start)?)?;
106 let compressed = read_at(&mut file, (tail_start + trailer_start).checked_sub(length)?, length)?;
107 miniz_oxide::inflate::decompress_to_vec(&compressed).ok()
108}
109
110fn read_at(file: &mut File, offset: usize, length: usize) -> Option<Vec<u8>> {
111 file.seek(SeekFrom::Start(offset as u64)).ok()?;
112 let mut bytes = vec![0u8; length];
113 file.read_exact(&mut bytes).ok()?;
114 Some(bytes)
115}
116
117pub fn run_carried_program() -> Option<i32> {
119 let executable = std::env::current_exe().ok()?;
120 let machine_code = embedded_machine_code(&executable)?;
121 if let Some(site) = crate::site_files::unpacked(&machine_code) {
122 return Some(show_site(&executable, site));
123 }
124 Some(match run(&machine_code) {
125 Ok(()) => 0,
126 Err(failure) => {
127 eprintln!("{failure:#}");
128 1
129 }
130 })
131}
132
133#[cfg(all(feature = "webview", target_os = "macos"))]
135fn show_site(executable: &Path, site: Vec<crate::site_files::SiteFile>) -> i32 {
136 let title = executable.file_stem().map(|stem| stem.to_string_lossy().into_owned()).unwrap_or_default();
137 let check = std::env::args().any(|argument| argument == CHECK_FLAG);
138 match crate::webview::show(&title, check, Box::new(move |path| crate::site_files::served(&site, path))) {
139 Ok(()) => 0,
140 Err(failure) => {
141 eprintln!("{failure}");
142 1
143 }
144 }
145}
146
147#[cfg(not(all(feature = "webview", target_os = "macos")))]
148fn show_site(_: &Path, _: Vec<crate::site_files::SiteFile>) -> i32 {
149 eprintln!("this app shows a page, and this warp-runtime has no web view (macOS only for now)");
150 1
151}
152
153pub fn run(machine_code: &[u8]) -> Result<()> {
156 let time_limit = time_limit_from_environment();
158 let engine = match time_limit {
159 Some(_) => ticking(standalone_engine()?),
160 None => standalone_engine()?,
161 };
162 let module = unsafe { Module::deserialize(&engine, machine_code)? };
164 let mut linker = Linker::new(&engine);
165 crate::host_words::link_host_words(&mut linker)?;
166 crate::output::link_output(&mut linker)?;
167 crate::libm::link_libm(&mut linker)?;
168 linker.func_wrap(
169 crate::host_words::HOST_LIBRARY,
170 FOREIGN_CALL,
171 |mut caller: wasmtime::Caller<'_, ()>, runtime, module, member, call, arguments| {
172 crate::gc_values::foreign_call(&mut caller, [runtime, module, member, call, arguments])
173 },
174 )?;
175 let mut store = limited_store(&engine, (), DEFAULT_FUEL, time_limit.unwrap_or(Duration::MAX));
176 let instance = linker.instantiate(&mut store, &module)?;
177 let main = ENTRY_POINTS
178 .iter()
179 .find_map(|name| instance.get_func(&mut store, name))
180 .ok_or_else(|| wasmtime::Error::msg(format!("the program has no entry point {ENTRY_POINTS:?}")))?;
181 let mut results = vec![Val::AnyRef(None); main.ty(&store).results().len()];
182 crate::system_signals::allow_staying();
183 let outcome = main
184 .call(&mut store, &[], &mut results)
185 .and_then(|_| crate::system_signals::stay_while_listening(&mut store, &instance));
186 let outcome = crate::system_signals::with_exit_handler(outcome, &mut store, |store, name| {
187 instance.get_func(&mut *store, name)
188 });
189 if let Ok(None) = outcome {
190 use std::io::Write;
191 let _ = std::io::stdout().flush();
192 std::process::exit(crate::system_signals::take_exit_code().unwrap_or(0)); }
194 outcome.map(|_| ())
195}
196
197pub fn provided_imports() -> impl Iterator<Item = (&'static str, &'static str)> {
199 let host_words =
200 crate::host_words::BASIC_HOST_WORDS.into_iter().map(|word| (crate::host_words::HOST_LIBRARY, word));
201 let libm = crate::libm::libm_names().map(|name| (crate::libm::LIBM, name));
202 host_words.chain(libm).chain([
203 (crate::output::WASI_LIBRARY, crate::output::FD_WRITE),
204 (crate::host_words::HOST_LIBRARY, FOREIGN_CALL),
205 ])
206}