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