1use super::code_memory::{ARCH_FUNCTION_ALIGNMENT, DATA_SECTION_ALIGNMENT};
4use super::executable::{unrkyv, UniversalExecutableRef};
5use super::{CodeMemory, UniversalArtifact, UniversalExecutable};
6use crate::EngineId;
7use rkyv::de::deserializers::SharedDeserializeMap;
8use std::collections::BTreeMap;
9use std::convert::TryFrom;
10use std::sync::{Arc, Mutex};
11use unc_vm_compiler::Compiler;
12use unc_vm_compiler::{
13 CompileError, CustomSectionProtection, CustomSectionRef, FunctionBodyRef, JumpTable,
14 SectionIndex, Target,
15};
16use unc_vm_types::entity::{EntityRef, PrimaryMap};
17use unc_vm_types::{
18 DataInitializer, ExportIndex, Features, FunctionIndex, FunctionType, FunctionTypeRef,
19 GlobalInit, GlobalType, ImportCounts, ImportIndex, LocalFunctionIndex, LocalGlobalIndex,
20 MemoryIndex, SignatureIndex, TableIndex,
21};
22use unc_vm_vm::{
23 FuncDataRegistry, FunctionBodyPtr, SectionBodyPtr, SignatureRegistry, Tunables,
24 VMCallerCheckedAnyfunc, VMFuncRef, VMImportType, VMLocalFunction, VMOffsets,
25 VMSharedSignatureIndex, VMTrampoline,
26};
27
28#[derive(Clone)]
30pub struct UniversalEngine {
31 inner: Arc<Mutex<UniversalEngineInner>>,
32 target: Arc<Target>,
34 engine_id: EngineId,
35}
36
37impl UniversalEngine {
38 pub fn new(
40 compiler: Box<dyn Compiler>,
41 target: Target,
42 features: Features,
43 memory_allocator: super::LimitedMemoryPool,
44 ) -> Self {
45 Self {
46 inner: Arc::new(Mutex::new(UniversalEngineInner {
47 compiler: Some(compiler),
48 code_memory_pool: memory_allocator,
49 signatures: SignatureRegistry::new(),
50 func_data: Arc::new(FuncDataRegistry::new()),
51 features,
52 })),
53 target: Arc::new(target),
54 engine_id: EngineId::default(),
55 }
56 }
57
58 pub fn headless(memory_allocator: super::LimitedMemoryPool) -> Self {
72 Self {
73 inner: Arc::new(Mutex::new(UniversalEngineInner {
74 compiler: None,
75 code_memory_pool: memory_allocator,
76 signatures: SignatureRegistry::new(),
77 func_data: Arc::new(FuncDataRegistry::new()),
78 features: Features::default(),
79 })),
80 target: Arc::new(Target::default()),
81 engine_id: EngineId::default(),
82 }
83 }
84
85 pub(crate) fn inner(&self) -> std::sync::MutexGuard<'_, UniversalEngineInner> {
86 self.inner.lock().unwrap()
87 }
88
89 pub(crate) fn inner_mut(&self) -> std::sync::MutexGuard<'_, UniversalEngineInner> {
90 self.inner.lock().unwrap()
91 }
92
93 #[tracing::instrument(target = "unc_vm", level = "trace", skip_all)]
95 pub fn compile_universal(
96 &self,
97 binary: &[u8],
98 tunables: &dyn Tunables,
99 ) -> Result<super::UniversalExecutable, CompileError> {
100 let instrumentation = finite_wasm::Analysis::new()
102 .with_stack(tunables.stack_limiter_cfg())
103 .with_gas(tunables.gas_cfg())
104 .analyze(binary)
105 .map_err(CompileError::Analyze)?;
106
107 let inner_engine = self.inner_mut();
108 let features = inner_engine.features();
109 let compiler = inner_engine.compiler()?;
110 let environ = unc_vm_compiler::ModuleEnvironment::new();
111 let translation = environ.translate(binary).map_err(CompileError::Wasm)?;
112
113 let memory_styles: PrimaryMap<unc_vm_types::MemoryIndex, _> = translation
114 .module
115 .memories
116 .values()
117 .map(|memory_type| tunables.memory_style(memory_type))
118 .collect();
119 let table_styles: PrimaryMap<unc_vm_types::TableIndex, _> = translation
120 .module
121 .tables
122 .values()
123 .map(|table_type| tunables.table_style(table_type))
124 .collect();
125
126 let compile_info = unc_vm_compiler::CompileModuleInfo {
128 module: Arc::new(translation.module),
129 features: features.clone(),
130 memory_styles,
131 table_styles,
132 };
133 let unc_vm_compiler::Compilation {
134 functions,
135 custom_sections,
136 function_call_trampolines,
137 dynamic_function_trampolines,
138 debug,
139 trampolines,
140 } = compiler.compile_module(
141 &self.target(),
142 &compile_info,
143 translation.function_body_inputs,
144 tunables,
145 &instrumentation,
146 )?;
147 let data_initializers = translation
148 .data_initializers
149 .iter()
150 .map(unc_vm_types::OwnedDataInitializer::new)
151 .collect();
152 let mut function_frame_info = PrimaryMap::with_capacity(functions.len());
153 let mut function_bodies = PrimaryMap::with_capacity(functions.len());
154 let mut function_relocations = PrimaryMap::with_capacity(functions.len());
155 let mut function_jt_offsets = PrimaryMap::with_capacity(functions.len());
156 for (_, func) in functions.into_iter() {
157 function_bodies.push(func.body);
158 function_relocations.push(func.relocations);
159 function_jt_offsets.push(func.jt_offsets);
160 function_frame_info.push(func.frame_info);
161 }
162 let custom_section_relocations = custom_sections
163 .iter()
164 .map(|(_, section)| section.relocations.clone())
165 .collect::<PrimaryMap<SectionIndex, _>>();
166 Ok(super::UniversalExecutable {
167 function_bodies,
168 function_relocations,
169 function_jt_offsets,
170 function_frame_info,
171 function_call_trampolines,
172 dynamic_function_trampolines,
173 custom_sections,
174 custom_section_relocations,
175 debug,
176 trampolines,
177 compile_info,
178 data_initializers,
179 cpu_features: self.target().cpu_features().as_u64(),
180 })
181 }
182
183 #[tracing::instrument(target = "unc_vm", level = "trace", skip_all)]
185 pub fn load_universal_executable(
186 &self,
187 executable: &UniversalExecutable,
188 ) -> Result<UniversalArtifact, CompileError> {
189 let info = &executable.compile_info;
190 let module = &info.module;
191 let local_memories = (module.import_counts.memories as usize..module.memories.len())
192 .map(|idx| {
193 let idx = MemoryIndex::new(idx);
194 (module.memories[idx], info.memory_styles[idx].clone())
195 })
196 .collect();
197 let local_tables = (module.import_counts.tables as usize..module.tables.len())
198 .map(|idx| {
199 let idx = TableIndex::new(idx);
200 (module.tables[idx], info.table_styles[idx].clone())
201 })
202 .collect();
203 let local_globals: Vec<(GlobalType, GlobalInit)> = module
204 .globals
205 .iter()
206 .skip(module.import_counts.globals as usize)
207 .enumerate()
208 .map(|(idx, (_, t))| {
209 let init = module.global_initializers[LocalGlobalIndex::new(idx)];
210 (*t, init)
211 })
212 .collect();
213 let mut inner_engine = self.inner_mut();
214
215 let local_functions = executable.function_bodies.iter().map(|(_, b)| b.into());
216 let function_call_trampolines = &executable.function_call_trampolines;
217 let dynamic_function_trampolines = &executable.dynamic_function_trampolines;
218 let signatures = module
219 .signatures
220 .iter()
221 .map(|(_, sig)| inner_engine.signatures.register(sig.clone()))
222 .collect::<PrimaryMap<SignatureIndex, _>>()
223 .into_boxed_slice();
224 let (functions, trampolines, dynamic_trampolines, custom_sections, mut code_memory) =
225 inner_engine.allocate(
226 local_functions,
227 function_call_trampolines.iter().map(|(_, b)| b.into()),
228 dynamic_function_trampolines.iter().map(|(_, b)| b.into()),
229 executable.custom_sections.iter().map(|(_, s)| s.into()),
230 |idx: LocalFunctionIndex| {
231 let func_idx = module.import_counts.function_index(idx);
232 let sig_idx = module.functions[func_idx];
233 (sig_idx, signatures[sig_idx])
234 },
235 )?;
236 let imports = module
237 .imports
238 .iter()
239 .map(|((module_name, field, idx), entity)| unc_vm_vm::VMImport {
240 module: String::from(module_name),
241 field: String::from(field),
242 import_no: *idx,
243 ty: match entity {
244 ImportIndex::Function(i) => {
245 let sig_idx = module.functions[*i];
246 VMImportType::Function {
247 sig: signatures[sig_idx],
248 static_trampoline: trampolines[sig_idx],
249 }
250 }
251 ImportIndex::Table(i) => VMImportType::Table(module.tables[*i]),
252 &ImportIndex::Memory(i) => {
253 let ty = module.memories[i];
254 VMImportType::Memory(ty, info.memory_styles[i].clone())
255 }
256 ImportIndex::Global(i) => VMImportType::Global(module.globals[*i]),
257 },
258 })
259 .collect();
260
261 let function_relocations = executable.function_relocations.iter();
262 let section_relocations = executable.custom_section_relocations.iter();
263 crate::universal::link_module(
264 &functions,
265 |func_idx, jt_idx| executable.function_jt_offsets[func_idx][jt_idx],
266 function_relocations.map(|(i, rs)| (i, rs.iter().cloned())),
267 &custom_sections,
268 section_relocations.map(|(i, rs)| (i, rs.iter().cloned())),
269 &executable.trampolines,
270 );
271
272 unsafe {
274 code_memory.publish()?;
278 }
279 let exports = module
280 .exports
281 .iter()
282 .map(|(s, i)| (s.clone(), i.clone()))
283 .collect::<BTreeMap<String, ExportIndex>>();
284
285 Ok(UniversalArtifact {
286 engine: self.clone(),
287 _code_memory: code_memory,
288 import_counts: module.import_counts,
289 start_function: module.start_function,
290 vmoffsets: VMOffsets::for_host().with_module_info(&*module),
291 imports,
292 dynamic_function_trampolines: dynamic_trampolines.into_boxed_slice(),
293 functions: functions.into_boxed_slice(),
294 exports,
295 signatures,
296 local_memories,
297 data_segments: executable.data_initializers.clone(),
298 passive_data: module.passive_data.clone(),
299 local_tables,
300 element_segments: module.table_initializers.clone(),
301 passive_elements: module.passive_elements.clone(),
302 local_globals,
303 })
304 }
305
306 pub fn load_universal_executable_ref(
308 &self,
309 executable: &UniversalExecutableRef,
310 ) -> Result<UniversalArtifact, CompileError> {
311 let info = &executable.compile_info;
312 let module = &info.module;
313 let import_counts: ImportCounts = unrkyv(&module.import_counts);
314 let local_memories = (import_counts.memories as usize..module.memories.len())
315 .map(|idx| {
316 let idx = MemoryIndex::new(idx);
317 let mty = &module.memories[&idx];
318 (unrkyv(mty), unrkyv(&info.memory_styles[&idx]))
319 })
320 .collect();
321 let local_tables = (import_counts.tables as usize..module.tables.len())
322 .map(|idx| {
323 let idx = TableIndex::new(idx);
324 let tty = &module.tables[&idx];
325 (unrkyv(tty), unrkyv(&info.table_styles[&idx]))
326 })
327 .collect();
328 let local_globals: Vec<(GlobalType, GlobalInit)> = module
329 .globals
330 .iter()
331 .skip(import_counts.globals as _)
332 .enumerate()
333 .map(|(idx, (_, t))| {
334 let init = unrkyv(&module.global_initializers[&LocalGlobalIndex::new(idx)]);
335 (*t, init)
336 })
337 .collect();
338
339 let passive_data =
340 rkyv::Deserialize::deserialize(&module.passive_data, &mut SharedDeserializeMap::new())
341 .map_err(|_| CompileError::Validate("could not deserialize passive data".into()))?;
342 let data_segments = executable.data_initializers.iter();
343 let data_segments = data_segments.map(|s| DataInitializer::from(s).into()).collect();
344 let element_segments = unrkyv(&module.table_initializers);
345 let passive_elements: BTreeMap<unc_vm_types::ElemIndex, Box<[FunctionIndex]>> =
346 unrkyv(&module.passive_elements);
347
348 let import_counts: ImportCounts = unrkyv(&module.import_counts);
349 let mut inner_engine = self.inner_mut();
350
351 let local_functions = executable.function_bodies.iter().map(|(_, b)| b.into());
352 let call_trampolines = executable.function_call_trampolines.iter();
353 let dynamic_trampolines = executable.dynamic_function_trampolines.iter();
354 let signatures = module
355 .signatures
356 .values()
357 .map(|sig| {
358 let sig_ref = FunctionTypeRef::from(sig);
359 inner_engine
360 .signatures
361 .register(FunctionType::new(sig_ref.params(), sig_ref.results()))
362 })
363 .collect::<PrimaryMap<SignatureIndex, _>>()
364 .into_boxed_slice();
365 let (functions, trampolines, dynamic_trampolines, custom_sections, mut code_memory) =
366 inner_engine.allocate(
367 local_functions,
368 call_trampolines.map(|(_, b)| b.into()),
369 dynamic_trampolines.map(|(_, b)| b.into()),
370 executable.custom_sections.iter().map(|(_, s)| s.into()),
371 |idx: LocalFunctionIndex| {
372 let func_idx = import_counts.function_index(idx);
373 let sig_idx = module.functions[&func_idx];
374 (sig_idx, signatures[sig_idx])
375 },
376 )?;
377 let imports = {
378 module
379 .imports
380 .iter()
381 .map(|((module_name, field, idx), entity)| unc_vm_vm::VMImport {
382 module: String::from(module_name.as_str()),
383 field: String::from(field.as_str()),
384 import_no: *idx,
385 ty: match entity {
386 ImportIndex::Function(i) => {
387 let sig_idx = module.functions[i];
388 VMImportType::Function {
389 sig: signatures[sig_idx],
390 static_trampoline: trampolines[sig_idx],
391 }
392 }
393 ImportIndex::Table(i) => VMImportType::Table(unrkyv(&module.tables[i])),
394 ImportIndex::Memory(i) => {
395 let ty = unrkyv(&module.memories[i]);
396 VMImportType::Memory(ty, unrkyv(&info.memory_styles[i]))
397 }
398 ImportIndex::Global(i) => VMImportType::Global(unrkyv(&module.globals[i])),
399 },
400 })
401 .collect()
402 };
403
404 let function_relocations = executable.function_relocations.iter();
405 let section_relocations = executable.custom_section_relocations.iter();
406 crate::universal::link_module(
407 &functions,
408 |func_idx, jt_idx| {
409 let func_idx = rkyv::Archived::<LocalFunctionIndex>::new(func_idx.index());
410 let jt_idx = rkyv::Archived::<JumpTable>::new(jt_idx.index());
411 executable.function_jt_offsets[&func_idx][&jt_idx]
412 },
413 function_relocations.map(|(i, r)| (i, r.iter().map(unrkyv))),
414 &custom_sections,
415 section_relocations.map(|(i, r)| (i, r.iter().map(unrkyv))),
416 &unrkyv(&executable.trampolines),
417 );
418
419 unsafe {
421 code_memory.publish()?;
425 }
426 let exports = module
427 .exports
428 .iter()
429 .map(|(s, i)| (unrkyv(s), unrkyv(i)))
430 .collect::<BTreeMap<String, ExportIndex>>();
431 Ok(UniversalArtifact {
432 engine: self.clone(),
433 _code_memory: code_memory,
434 import_counts,
435 start_function: unrkyv(&module.start_function),
436 vmoffsets: VMOffsets::for_host().with_archived_module_info(&*module),
437 imports,
438 dynamic_function_trampolines: dynamic_trampolines.into_boxed_slice(),
439 functions: functions.into_boxed_slice(),
440 exports,
441 signatures,
442 local_memories,
443 data_segments,
444 passive_data,
445 local_tables,
446 element_segments,
447 passive_elements,
448 local_globals,
449 })
450 }
451
452 pub fn target(&self) -> &Target {
454 &self.target
455 }
456
457 pub fn register_signature(&self, func_type: FunctionType) -> VMSharedSignatureIndex {
459 self.inner().signatures.register(func_type)
460 }
461
462 pub fn register_function_metadata(&self, func_data: VMCallerCheckedAnyfunc) -> VMFuncRef {
464 self.inner().func_data().register(func_data)
465 }
466
467 pub fn lookup_signature(&self, sig: VMSharedSignatureIndex) -> Option<FunctionType> {
469 self.inner().signatures.lookup(sig).cloned()
470 }
471
472 #[tracing::instrument(target = "unc_vm", level = "trace", skip_all)]
474 pub fn validate(&self, binary: &[u8]) -> Result<(), CompileError> {
475 self.inner().validate(binary)
476 }
477
478 pub fn id(&self) -> &EngineId {
480 &self.engine_id
481 }
482}
483
484pub struct UniversalEngineInner {
486 compiler: Option<Box<dyn Compiler>>,
488 code_memory_pool: super::LimitedMemoryPool,
490 features: Features,
492 pub(crate) signatures: SignatureRegistry,
495 func_data: Arc<FuncDataRegistry>,
499}
500
501impl UniversalEngineInner {
502 pub fn compiler(&self) -> Result<&dyn Compiler, CompileError> {
504 if self.compiler.is_none() {
505 return Err(CompileError::Codegen("The UniversalEngine is operating in headless mode, so it can only execute already compiled Modules.".to_string()));
506 }
507 Ok(&**self.compiler.as_ref().unwrap())
508 }
509
510 pub fn validate<'data>(&self, data: &'data [u8]) -> Result<(), CompileError> {
512 self.compiler()?.validate_module(self.features(), data)
513 }
514
515 pub fn features(&self) -> &Features {
517 &self.features
518 }
519
520 #[allow(clippy::type_complexity)]
522 pub(crate) fn allocate<'a>(
523 &mut self,
524 local_functions: impl ExactSizeIterator<Item = FunctionBodyRef<'a>>,
525 call_trampolines: impl ExactSizeIterator<Item = FunctionBodyRef<'a>>,
526 dynamic_trampolines: impl ExactSizeIterator<Item = FunctionBodyRef<'a>>,
527 custom_sections: impl ExactSizeIterator<Item = CustomSectionRef<'a>>,
528 function_signature: impl Fn(LocalFunctionIndex) -> (SignatureIndex, VMSharedSignatureIndex),
529 ) -> Result<
530 (
531 PrimaryMap<LocalFunctionIndex, VMLocalFunction>,
532 PrimaryMap<SignatureIndex, VMTrampoline>,
533 PrimaryMap<FunctionIndex, FunctionBodyPtr>,
534 PrimaryMap<SectionIndex, SectionBodyPtr>,
535 CodeMemory,
536 ),
537 CompileError,
538 > {
539 let code_memory_pool = &mut self.code_memory_pool;
540 let function_count = local_functions.len();
541 let call_trampoline_count = call_trampolines.len();
542 let function_bodies =
543 call_trampolines.chain(local_functions).chain(dynamic_trampolines).collect::<Vec<_>>();
544
545 let mut section_types = Vec::with_capacity(custom_sections.len());
547 let mut executable_sections = Vec::new();
548 let mut data_sections = Vec::new();
549 for section in custom_sections {
550 if let CustomSectionProtection::ReadExecute = section.protection {
551 executable_sections.push(section);
552 } else {
553 data_sections.push(section);
554 }
555 section_types.push(section.protection);
556 }
557
558 let page_size = rustix::param::page_size();
568 let total_len = 0;
569 let total_len = function_bodies.iter().fold(total_len, |acc, func| {
570 round_up(acc, ARCH_FUNCTION_ALIGNMENT.into()) + function_allocation_size(*func)
571 });
572 let total_len = executable_sections.iter().fold(total_len, |acc, exec| {
573 round_up(acc, ARCH_FUNCTION_ALIGNMENT.into()) + exec.bytes.len()
574 });
575 let total_len = round_up(total_len, page_size);
576 let total_len = data_sections.iter().fold(total_len, |acc, data| {
577 round_up(acc, DATA_SECTION_ALIGNMENT.into()) + data.bytes.len()
578 });
579
580 let mut code_memory = code_memory_pool.get(total_len).map_err(|e| {
581 CompileError::Resource(format!("could not allocate code memory: {}", e))
582 })?;
583 let mut code_writer = unsafe {
584 code_memory.writer()
586 };
587
588 let mut allocated_functions = vec![];
589 let mut allocated_data_sections = vec![];
590 let mut allocated_executable_sections = vec![];
591 for func in function_bodies {
592 let offset = code_writer
593 .write_executable(ARCH_FUNCTION_ALIGNMENT, func.body)
594 .expect("incorrectly computed code memory size");
595 allocated_functions.push((offset, func.body.len()));
596 }
597 for section in executable_sections {
598 let offset = code_writer.write_executable(ARCH_FUNCTION_ALIGNMENT, section.bytes)?;
599 allocated_executable_sections.push(offset);
600 }
601 if !data_sections.is_empty() {
602 for section in data_sections {
603 let offset = code_writer
604 .write_data(DATA_SECTION_ALIGNMENT, section.bytes)
605 .expect("incorrectly computed code memory size");
606 allocated_data_sections.push(offset);
607 }
608 }
609
610 let mut allocated_function_call_trampolines: PrimaryMap<SignatureIndex, VMTrampoline> =
611 PrimaryMap::new();
612
613 for (offset, _) in allocated_functions.drain(0..call_trampoline_count) {
614 let trampoline = unsafe {
615 std::mem::transmute::<_, VMTrampoline>(code_memory.executable_address(offset))
626 };
627 allocated_function_call_trampolines.push(trampoline);
628 }
629
630 let allocated_functions_result = allocated_functions
631 .drain(0..function_count)
632 .enumerate()
633 .map(|(index, (offset, length))| -> Result<_, CompileError> {
634 let index = LocalFunctionIndex::new(index);
635 let (sig_idx, sig) = function_signature(index);
636 Ok(VMLocalFunction {
637 body: FunctionBodyPtr(unsafe { code_memory.executable_address(offset).cast() }),
638 length: u32::try_from(length).map_err(|_| {
639 CompileError::Codegen("function body length exceeds 4GiB".into())
640 })?,
641 signature: sig,
642 trampoline: allocated_function_call_trampolines[sig_idx],
643 })
644 })
645 .collect::<Result<PrimaryMap<LocalFunctionIndex, _>, _>>()?;
646
647 let allocated_dynamic_function_trampolines = allocated_functions
648 .drain(..)
649 .map(|(offset, _)| {
650 FunctionBodyPtr(unsafe { code_memory.executable_address(offset).cast() })
651 })
652 .collect::<PrimaryMap<FunctionIndex, _>>();
653
654 let mut exec_iter = allocated_executable_sections.iter();
655 let mut data_iter = allocated_data_sections.iter();
656 let allocated_custom_sections = section_types
657 .into_iter()
658 .map(|protection| {
659 SectionBodyPtr(if protection == CustomSectionProtection::ReadExecute {
660 unsafe { code_memory.executable_address(*exec_iter.next().unwrap()).cast() }
661 } else {
662 unsafe { code_memory.writable_address(*data_iter.next().unwrap()).cast() }
663 })
664 })
665 .collect::<PrimaryMap<SectionIndex, _>>();
666
667 Ok((
668 allocated_functions_result,
669 allocated_function_call_trampolines,
670 allocated_dynamic_function_trampolines,
671 allocated_custom_sections,
672 code_memory,
673 ))
674 }
675
676 pub(crate) fn func_data(&self) -> &Arc<FuncDataRegistry> {
678 &self.func_data
679 }
680}
681
682fn round_up(size: usize, multiple: usize) -> usize {
683 debug_assert!(multiple.is_power_of_two());
684 (size + (multiple - 1)) & !(multiple - 1)
685}
686
687fn function_allocation_size(func: FunctionBodyRef<'_>) -> usize {
688 func.body.len()
689}