1use alloc::{collections::BTreeSet, sync::Arc, vec::Vec};
2
3use miden_assembly::{PathBuf as LibraryPath, ast::InvocationTarget};
4use miden_assembly_syntax::{ast::Attribute, parser::WordValue};
5use miden_core::operations::DebugVarLocation;
6use midenc_hir::{
7 FunctionIdent, Op, OpExt, SourceSpan, Span, Symbol, TraceTarget, Type, ValueRef,
8 diagnostics::IntoDiagnostic,
9 dialects::{
10 builtin,
11 debuginfo::attributes::{
12 SubprogramAttr, decode_frame_base_local_index, encode_frame_base_local_offset,
13 },
14 },
15 interner,
16 pass::AnalysisManager,
17};
18use midenc_hir_analysis::analyses::LivenessAnalysis;
19use midenc_session::diagnostics::{Report, Spanned, WrapErr};
20use smallvec::SmallVec;
21
22use crate::{
23 OperandStack, TraceEvent,
24 artifact::MasmComponent,
25 emitter::BlockEmitter,
26 linker::{LinkInfo, Linker},
27 masm,
28};
29
30pub trait ToMasmComponent {
32 fn to_masm_component(&self, analysis_manager: AnalysisManager)
33 -> Result<MasmComponent, Report>;
34}
35
36impl ToMasmComponent for builtin::World {
41 fn to_masm_component(
42 &self,
43 analysis_manager: AnalysisManager,
44 ) -> Result<MasmComponent, Report> {
45 let context = self.as_operation().context_rc();
47
48 let link_info = Linker::default().link(None, self.as_operation()).map_err(Report::msg)?;
50
51 let entrypoint = match context.session().options.entrypoint.as_deref() {
53 Some(entry) => {
54 let entry_id = entry.parse::<FunctionIdent>().map_err(|_| {
55 Report::msg(format!("invalid entrypoint identifier: '{entry}'"))
56 })?;
57 let name = masm::ProcedureName::from_raw_parts(masm::Ident::from_raw_parts(
58 Span::new(entry_id.function.span, entry_id.function.as_str().into()),
59 ));
60
61 let path = LibraryPath::new(entry_id.module.as_str()).into_diagnostic()?;
62 let qualified = masm::QualifiedProcedureName::new(path.as_path(), name);
63 Some(masm::InvocationTarget::Path(Span::new(
64 entry_id.function.span,
65 qualified.into_inner(),
66 )))
67 }
68 None => None,
69 };
70
71 let requires_init = link_info.has_globals() || link_info.has_data_segments();
74 let init = if requires_init {
75 let name = masm::ProcedureName::new("init").unwrap();
76 let qualified = masm::QualifiedProcedureName::new("::init", name);
77 Some(masm::InvocationTarget::Path(Span::new(
78 SourceSpan::default(),
79 qualified.into_inner(),
80 )))
81 } else {
82 None
83 };
84
85 let root =
89 Arc::<miden_assembly_syntax::Path>::from(miden_assembly_syntax::Path::new("::init"));
90 let init_module = Arc::new(masm::Module::new(masm::ModuleKind::Library, &root));
91 let modules = vec![init_module];
92
93 let rodata = data_segments_to_rodata(&link_info)?;
94
95 let kernel = if context.session().options.target_requires_protocol() {
96 Some(miden_protocol::transaction::TransactionKernel::kernel())
97 } else {
98 None
99 };
100
101 let heap_base = core::cmp::max(
104 link_info.reserved_memory_bytes(),
105 link_info.globals_layout().next_page_boundary() as usize,
106 );
107 let heap_base = u32::try_from(heap_base)
108 .expect("unable to allocate dynamic heap: global table too large");
109 let stack_pointer = link_info.globals_layout().stack_pointer_offset();
110 let mut masm_component = MasmComponent {
111 id: None,
112 root,
113 init,
114 entrypoint,
115 kernel,
116 rodata,
117 heap_base,
118 stack_pointer,
119 modules,
120 };
121 let builder = MasmComponentBuilder {
122 analysis_manager,
123 component: &mut masm_component,
124 link_info: &link_info,
125 source_manager: context.session().source_manager.clone(),
126 init_body: Default::default(),
127 invoked_from_init: Default::default(),
128 };
129
130 builder.build(self.as_operation())?;
131
132 Ok(masm_component)
133 }
134}
135
136impl ToMasmComponent for builtin::Component {
138 fn to_masm_component(
139 &self,
140 analysis_manager: AnalysisManager,
141 ) -> Result<MasmComponent, Report> {
142 let context = self.as_operation().context_rc();
144
145 let id = self.id();
147 let link_info = Linker::default()
148 .link(Some(id.clone()), self.as_operation())
149 .map_err(Report::msg)?;
150
151 let component_path = id.to_library_path();
153
154 let entrypoint = match context.session().options.entrypoint.as_deref() {
156 Some(entry) => {
157 let entry_id = entry.parse::<FunctionIdent>().map_err(|_| {
158 Report::msg(format!("invalid entrypoint identifier: '{entry}'"))
159 })?;
160 let name = masm::ProcedureName::from_raw_parts(masm::Ident::from_raw_parts(
161 Span::new(entry_id.function.span, entry_id.function.as_str().into()),
162 ));
163
164 let is_wrapper = id.is_synthetic_wrapper();
173 let path = if is_wrapper {
174 let mut path = component_path.clone();
175 path.push(entry_id.module.as_str());
176 path
177 } else {
178 LibraryPath::new(entry_id.module.as_str()).into_diagnostic()?
181 };
182 let qualified = masm::QualifiedProcedureName::new(path.as_path(), name);
183 Some(masm::InvocationTarget::Path(Span::new(
184 entry_id.function.span,
185 qualified.into_inner(),
186 )))
187 }
188 None => None,
189 };
190
191 let requires_init = link_info.has_globals() || link_info.has_data_segments();
194 let init = if requires_init {
195 let name = masm::ProcedureName::new("init").unwrap();
196 let qualified =
197 masm::QualifiedProcedureName::new(component_path.as_path().to_absolute(), name);
198 Some(masm::InvocationTarget::Path(Span::new(
199 SourceSpan::default(),
200 qualified.into_inner(),
201 )))
202 } else {
203 None
204 };
205
206 let root: Arc<miden_assembly_syntax::Path> =
210 id.to_library_path().to_absolute().into_owned().into();
211 let modules = vec![Arc::new(masm::Module::new(masm::ModuleKind::Library, &root))];
212
213 let rodata = data_segments_to_rodata(&link_info)?;
214
215 let kernel = if context.session().options.target_requires_protocol() {
216 Some(miden_protocol::transaction::TransactionKernel::kernel())
217 } else {
218 None
219 };
220
221 let heap_base = core::cmp::max(
224 link_info.reserved_memory_bytes(),
225 link_info.globals_layout().next_page_boundary() as usize,
226 );
227 let heap_base = u32::try_from(heap_base)
228 .expect("unable to allocate dynamic heap: global table too large");
229 let stack_pointer = link_info.globals_layout().stack_pointer_offset();
230 let mut masm_component = MasmComponent {
231 id: Some(id),
232 root,
233 init,
234 entrypoint,
235 kernel,
236 rodata,
237 heap_base,
238 stack_pointer,
239 modules,
240 };
241 let builder = MasmComponentBuilder {
242 analysis_manager,
243 component: &mut masm_component,
244 link_info: &link_info,
245 source_manager: context.session().source_manager.clone(),
246 init_body: Default::default(),
247 invoked_from_init: Default::default(),
248 };
249
250 builder.build(self.as_operation())?;
251
252 Ok(masm_component)
253 }
254}
255
256fn data_segments_to_rodata(link_info: &LinkInfo) -> Result<Vec<crate::Rodata>, Report> {
257 use midenc_hir::constants::ConstantData;
258
259 use crate::data_segments::{ResolvedDataSegment, merge_data_segments};
260 let mut resolved = SmallVec::<[ResolvedDataSegment; 2]>::new();
261 for sref in link_info.segment_layout().iter() {
262 let s = sref.borrow();
263 resolved.push(ResolvedDataSegment {
264 offset: *s.get_offset(),
265 data: s.initializer().as_slice().to_vec(),
266 readonly: *s.get_readonly(),
267 });
268 }
269 Ok(match merge_data_segments(resolved).map_err(Report::msg)? {
270 None => alloc::vec::Vec::new(),
271 Some(merged) => {
272 let data = alloc::sync::Arc::new(ConstantData::from(merged.data));
273 let felts = crate::Rodata::bytes_to_elements(data.as_slice());
274 let digest = miden_core::crypto::hash::Poseidon2::hash_elements(&felts);
275 alloc::vec![crate::Rodata {
276 component: link_info.component().cloned().unwrap_or(builtin::ComponentId {
277 namespace: interner::Symbol::intern("root_ns"),
278 name: interner::Symbol::intern("root"),
279 version: midenc_hir::version::Version::new(1, 0, 0)
280 }),
281 digest,
282 start: super::NativePtr::from_ptr(merged.offset),
283 data,
284 }]
285 }
286 })
287}
288
289struct MasmComponentBuilder<'a> {
290 component: &'a mut MasmComponent,
291 analysis_manager: AnalysisManager,
292 link_info: &'a LinkInfo,
293 source_manager: Arc<dyn midenc_session::SourceManager + Send + Sync>,
294 init_body: Vec<masm::Op>,
295 invoked_from_init: BTreeSet<masm::Invoke>,
296}
297
298impl MasmComponentBuilder<'_> {
299 pub fn build(mut self, component: &midenc_hir::Operation) -> Result<(), Report> {
301 use masm::{Instruction as Inst, InvocationTarget, Op};
302
303 if self.component.init.is_some() {
306 let span = component.span();
307
308 let heap_base = self.component.heap_base;
310 self.init_body.push(masm::Op::Inst(Span::new(
311 span,
312 Inst::Push(masm::Immediate::Value(Span::unknown(heap_base.into()))),
313 )));
314 let heap_init = {
315 let name = masm::ProcedureName::new("heap_init").unwrap();
316 let module = masm::LibraryPath::new("::intrinsics::mem").unwrap();
317 let qualified = masm::QualifiedProcedureName::new(module.as_path(), name);
318 InvocationTarget::Path(Span::new(span, qualified.into_inner()))
319 };
320 self.init_body.push(Op::Inst(Span::new(
321 span,
322 Inst::Trace(TraceEvent::FrameStart.as_u32().into()),
323 )));
324 self.init_body.push(Op::Inst(Span::new(span, Inst::Exec(heap_init))));
325 self.init_body
326 .push(Op::Inst(Span::new(span, Inst::Trace(TraceEvent::FrameEnd.as_u32().into()))));
327
328 self.emit_data_segment_initialization();
330 }
331
332 let region = component.region(0);
334 let block = region.entry();
335 for op in block.body() {
336 if let Some(module) = op.downcast_ref::<builtin::Module>() {
337 self.define_module(module)?;
338 } else if let Some(interface) = op.downcast_ref::<builtin::Interface>() {
339 self.define_interface(interface)?;
340 } else if let Some(function) = op.downcast_ref::<builtin::Function>() {
341 self.define_function(function)?;
342 } else {
343 panic!(
344 "invalid component-level operation: '{}' is not supported in a component body",
345 op.name()
346 )
347 }
348 }
349
350 if self.component.init.is_some() {
352 let module =
353 Arc::get_mut(&mut self.component.modules[0]).expect("expected unique reference");
354
355 let init_name = masm::ProcedureName::new("init").unwrap();
356 let init_body = core::mem::take(&mut self.init_body);
357 let init = masm::Procedure::new(
358 Default::default(),
359 masm::Visibility::Public,
360 init_name,
361 0,
362 masm::Block::new(component.span(), init_body),
363 )
364 .with_signature(masm::FunctionType::new(
365 midenc_hir::CallConv::Fast,
366 vec![],
367 vec![],
368 ));
369
370 module
371 .define_procedure(init, self.source_manager.clone())
372 .into_diagnostic()
373 .wrap_err("failed to define component `init` procedure")?;
374 } else {
375 assert!(
376 self.init_body.is_empty(),
377 "the need for an 'init' function was not expected, but code was generated for one"
378 );
379 }
380
381 Ok(())
382 }
383
384 fn define_interface(&mut self, interface: &builtin::Interface) -> Result<(), Report> {
385 let interface_path = if let Some(id) = self.component.id.as_ref() {
386 let mut path = id.to_library_path();
387 path.push(interface.name().as_str());
388 path
389 } else {
390 interface.path().to_library_path()
391 };
392 let mut masm_module =
393 Box::new(masm::Module::new(masm::ModuleKind::Library, interface_path));
394 let builder = MasmModuleBuilder {
395 module: &mut masm_module,
396 analysis_manager: self
397 .analysis_manager
398 .nest(interface.as_operation().as_operation_ref()),
399 link_info: self.link_info,
400 source_manager: self.source_manager.clone(),
401 init_body: &mut self.init_body,
402 invoked_from_init: &mut self.invoked_from_init,
403 };
404 builder.build_from_interface(interface)?;
405
406 self.component.modules.push(Arc::from(masm_module));
407
408 Ok(())
409 }
410
411 fn define_module(&mut self, module: &builtin::Module) -> Result<(), Report> {
412 let module_path = if let Some(id) = self.component.id.as_ref() {
413 let mut path = id.to_library_path();
414 path.push(module.name().as_str());
415 path
416 } else {
417 module.path().to_library_path()
418 };
419 let mut masm_module = Box::new(masm::Module::new(masm::ModuleKind::Library, module_path));
420 let builder = MasmModuleBuilder {
421 module: &mut masm_module,
422 analysis_manager: self.analysis_manager.nest(module.as_operation_ref()),
423 link_info: self.link_info,
424 source_manager: self.source_manager.clone(),
425 init_body: &mut self.init_body,
426 invoked_from_init: &mut self.invoked_from_init,
427 };
428 builder.build(module)?;
429
430 self.component.modules.push(Arc::from(masm_module));
431
432 Ok(())
433 }
434
435 fn define_function(&mut self, function: &builtin::Function) -> Result<(), Report> {
436 let builder = MasmFunctionBuilder::new(function)?;
437 let procedure = builder.build(
438 function,
439 self.analysis_manager.nest(function.as_operation_ref()),
440 self.link_info,
441 )?;
442
443 let module =
444 Arc::get_mut(&mut self.component.modules[0]).expect("expected unique reference");
445 let expected_path_len = if module.path().is_absolute() { 2 } else { 1 };
446 assert_eq!(
447 module.path().len(),
448 expected_path_len,
449 "expected top-level namespace module, but one has not been defined (in '{}' of '{}')",
450 module.path(),
451 function.path()
452 );
453 module
454 .define_procedure(procedure, self.source_manager.clone())
455 .into_diagnostic()
456 .wrap_err("failed to define MASM procedure")?;
457
458 Ok(())
459 }
460
461 fn emit_data_segment_initialization(&mut self) {
465 use masm::{Instruction as Inst, InvocationTarget, Op};
466
467 let span = SourceSpan::default();
473 let pipe_preimage_to_memory = {
474 let name = masm::ProcedureName::new("pipe_preimage_to_memory").unwrap();
475 let module = masm::LibraryPath::new("::miden::core::mem").unwrap();
476 let qualified = masm::QualifiedProcedureName::new(module.as_path(), name);
477 InvocationTarget::Path(Span::new(span, qualified.into_inner()))
478 };
479 for rodata in self.component.rodata.iter() {
480 let word = rodata.digest.as_elements();
484 let word_value = [word[0], word[1], word[2], word[3]];
485
486 self.init_body.push(Op::Inst(Span::new(
487 span,
488 Inst::Push(masm::Immediate::Value(Span::unknown(WordValue(word_value).into()))),
489 )));
490 self.init_body
492 .push(Op::Inst(Span::new(span, Inst::SysEvent(masm::SystemEventNode::PushMapVal))));
493 assert!(rodata.start.is_word_aligned(), "rodata segments must be word-aligned");
495 self.init_body.push(Op::Inst(Span::new(
496 span,
497 Inst::Push(masm::Immediate::Value(Span::unknown(rodata.start.addr.into()))),
498 )));
499 self.init_body.push(Op::Inst(Span::new(
501 span,
502 Inst::Push(masm::Immediate::Value(Span::unknown(
503 (rodata.size_in_words() as u32).into(),
504 ))),
505 )));
506 self.init_body.push(Op::Inst(Span::new(
508 span,
509 Inst::Trace(TraceEvent::FrameStart.as_u32().into()),
510 )));
511 self.init_body
512 .push(Op::Inst(Span::new(span, Inst::Exec(pipe_preimage_to_memory.clone()))));
513 self.init_body
514 .push(Op::Inst(Span::new(span, Inst::Trace(TraceEvent::FrameEnd.as_u32().into()))));
515 self.init_body.push(Op::Inst(Span::new(span, Inst::Drop)));
517 }
518 }
519}
520
521struct MasmModuleBuilder<'a> {
522 module: &'a mut masm::Module,
523 analysis_manager: AnalysisManager,
524 link_info: &'a LinkInfo,
525 source_manager: Arc<dyn midenc_session::SourceManager + Send + Sync>,
526 init_body: &'a mut Vec<masm::Op>,
527 invoked_from_init: &'a mut BTreeSet<masm::Invoke>,
528}
529
530impl MasmModuleBuilder<'_> {
531 pub fn build(mut self, module: &builtin::Module) -> Result<(), Report> {
532 let region = module.body();
533 let block = region.entry();
534 for op in block.body() {
535 if let Some(function) = op.downcast_ref::<builtin::Function>() {
536 self.define_function(function)?;
537 } else if let Some(gv) = op.downcast_ref::<builtin::GlobalVariable>() {
538 self.emit_global_variable_initializer(gv)?;
539 } else if op.is::<builtin::Segment>() {
540 continue;
541 } else {
542 panic!(
543 "invalid module-level operation: '{}' is not legal in a MASM module body",
544 op.name()
545 )
546 }
547 }
548
549 Ok(())
550 }
551
552 pub fn build_from_interface(mut self, interface: &builtin::Interface) -> Result<(), Report> {
553 let region = interface.body();
554 let block = region.entry();
555 for op in block.body() {
556 if let Some(function) = op.downcast_ref::<builtin::Function>() {
557 self.define_function(function)?;
558 } else {
559 panic!(
560 "invalid interface-level operation: '{}' is not legal in a MASM module body",
561 op.name()
562 )
563 }
564 }
565
566 Ok(())
567 }
568
569 fn define_function(&mut self, function: &builtin::Function) -> Result<(), Report> {
570 let builder = MasmFunctionBuilder::new(function)?;
571
572 let procedure = builder.build(
573 function,
574 self.analysis_manager.nest(function.as_operation_ref()),
575 self.link_info,
576 )?;
577
578 self.module
579 .define_procedure(procedure, self.source_manager.clone())
580 .map_err(|e| Report::msg(e.to_string()))?;
581
582 Ok(())
583 }
584
585 fn emit_global_variable_initializer(
586 &mut self,
587 gv: &builtin::GlobalVariable,
588 ) -> Result<(), Report> {
589 if gv.is_declaration() {
591 return Ok(());
592 }
593
594 let analysis_manager = self.analysis_manager.nest(gv.as_operation_ref());
596 let liveness = analysis_manager.get_analysis::<LivenessAnalysis>()?;
597
598 let initializer_region = gv.region(0);
600 let initializer_block = initializer_region.entry();
601
602 let mut block_emitter = BlockEmitter {
603 liveness: &liveness,
604 link_info: self.link_info,
605 invoked: self.invoked_from_init,
606 target: Default::default(),
607 stack: OperandStack::new(gv.as_operation().context_rc()),
608 trace_target: TraceTarget::category("codegen")
609 .with_relevant_symbol(gv.name().as_symbol()),
610 };
611 block_emitter.emit_inline(&initializer_block);
612
613 assert_eq!(block_emitter.stack.len(), 1, "expected only global variable value on stack");
615 let return_ty = block_emitter.stack.peek().unwrap().ty();
616 assert_eq!(
617 &return_ty,
618 &*gv.get_ty(),
619 "expected initializer to return value of same type as declaration"
620 );
621
622 let computed_addr = self
624 .link_info
625 .globals_layout()
626 .get_computed_addr(gv.as_global_var_ref())
627 .expect("undefined global variable");
628 block_emitter.emitter().store_imm(computed_addr, gv.span());
629
630 let mut body = core::mem::take(&mut block_emitter.target);
632 self.init_body.append(&mut body);
633
634 Ok(())
635 }
636}
637
638struct MasmFunctionBuilder {
639 span: midenc_hir::SourceSpan,
640 name: masm::ProcedureName,
641 signature: masm::FunctionType,
642 visibility: masm::Visibility,
643 num_locals: u16,
644}
645
646impl MasmFunctionBuilder {
647 pub fn new(function: &builtin::Function) -> Result<Self, Report> {
648 use midenc_hir::{Symbol, Visibility};
649
650 let name = *function.get_name();
651 let name = masm::ProcedureName::from_raw_parts(masm::Ident::from_raw_parts(Span::new(
652 name.span,
653 name.as_ref().into(),
654 )));
655 let visibility = match function.visibility() {
656 Visibility::Public => masm::Visibility::Public,
657 Visibility::Internal => masm::Visibility::Public,
659 Visibility::Private => masm::Visibility::Private,
660 };
661 let locals_required = function.locals().iter().map(|ty| ty.size_in_felts()).sum::<usize>();
662 let num_locals = u16::try_from(locals_required).map_err(|_| {
663 let context = function.as_operation().context();
664 context
665 .diagnostics()
666 .diagnostic(miden_assembly::diagnostics::Severity::Error)
667 .with_message("cannot emit masm for function")
668 .with_primary_label(
669 function.span(),
670 "local storage exceeds procedure limit: no more than u16::MAX elements are \
671 supported",
672 )
673 .into_report()
674 })?;
675
676 let signature =
677 semantic_debug_signature(function).unwrap_or_else(|| lowered_signature(function));
678
679 Ok(Self {
680 span: function.span(),
681 name,
682 signature,
683 visibility,
684 num_locals,
685 })
686 }
687
688 pub fn build(
689 self,
690 function: &builtin::Function,
691 analysis_manager: AnalysisManager,
692 link_info: &LinkInfo,
693 ) -> Result<masm::Procedure, Report> {
694 use alloc::collections::BTreeSet;
695
696 use midenc_hir_analysis::analyses::LivenessAnalysis;
697
698 let demangled_symbol_name = midenc_hir::demangle::demangle(function.get_name().as_str());
699 let trace_target = TraceTarget::category("codegen")
700 .with_relevant_symbol(midenc_hir::SymbolName::intern(demangled_symbol_name));
701
702 log::trace!(target: &trace_target, "lowering {}", function.as_operation());
703
704 let liveness = analysis_manager.get_analysis::<LivenessAnalysis>()?;
705
706 let mut invoked = BTreeSet::default();
707 let entry = function.entry_block();
708 let mut stack = crate::OperandStack::new(function.as_operation().context_rc());
709 {
710 let entry_block = entry.borrow();
711 for arg in entry_block.arguments().iter().rev().copied() {
712 stack.push(arg as ValueRef);
713 }
714 }
715 let mut emitter = BlockEmitter {
716 liveness: &liveness,
717 link_info,
718 invoked: &mut invoked,
719 target: Default::default(),
720 stack,
721 trace_target,
722 };
723
724 if function.signature().cc.is_wasm_canonical_abi()
727 && (link_info.has_globals() || link_info.has_data_segments())
728 {
729 let init = InvocationTarget::Symbol("init".parse().unwrap());
742 emitter.emitter().emit(masm::Instruction::Exec(init), SourceSpan::default());
745 }
746
747 let mut body = emitter.emit(&entry.borrow());
748
749 if function.signature().cc.is_wasm_canonical_abi() {
750 let truncate_stack = {
758 let name = masm::ProcedureName::new("truncate_stack").unwrap();
759 let module = masm::LibraryPath::new("::miden::core::sys").unwrap();
760 let qualified = masm::QualifiedProcedureName::new(module.as_path(), name);
761 InvocationTarget::Path(Span::new(SourceSpan::default(), qualified.into_inner()))
762 };
763 let span = SourceSpan::default();
764 invoked.insert(masm::Invoke::new(masm::InvokeKind::Exec, truncate_stack.clone()));
765 body.push(masm::Op::Inst(Span::new(span, masm::Instruction::Exec(truncate_stack))));
766 }
767 let Self {
768 span,
769 name,
770 signature,
771 visibility,
772 num_locals,
773 } = self;
774
775 let aligned_num_locals = num_locals.next_multiple_of(miden_core::WORD_SIZE as u16);
781
782 let stack_pointer_addr = link_info.globals_layout().stack_pointer_offset();
785
786 patch_debug_var_locals_in_block(&mut body, aligned_num_locals, stack_pointer_addr);
791
792 if !block_has_real_instructions(&body) {
798 body.push(masm::Op::Inst(Span::unknown(masm::Instruction::Nop)));
799 }
800
801 let mut procedure = masm::Procedure::new(span, visibility, name, num_locals, body);
802 procedure.set_signature(signature);
803 for attribute in ["auth_script", "note_script"] {
804 if function.has_attribute(attribute) {
805 procedure
806 .attributes_mut()
807 .insert(Attribute::Marker(masm::Ident::new(attribute).unwrap()));
808 }
809 }
810 procedure.extend_invoked(invoked);
811
812 Ok(procedure)
813 }
814}
815
816fn lowered_signature(function: &builtin::Function) -> masm::FunctionType {
817 let sig = function.signature();
818 let args = sig.params.iter().map(|param| masm::TypeExpr::from(param.ty.clone())).collect();
819 let results = sig
820 .results
821 .iter()
822 .map(|result| masm::TypeExpr::from(result.ty.clone()))
823 .collect();
824 masm::FunctionType::new(sig.cc, args, results)
825}
826
827fn semantic_debug_signature(function: &builtin::Function) -> Option<masm::FunctionType> {
828 let subprogram = function
829 .as_operation()
830 .get_attribute("di.subprogram")?
831 .try_downcast_attr::<SubprogramAttr>()
832 .ok()?;
833 let subprogram = subprogram.borrow();
834 let Type::Function(ty) = subprogram.ty.as_ref()? else {
835 return None;
836 };
837
838 let args = ty.params().iter().map(component_abi_type_expr_from_hir).collect();
839 let results = ty.results().iter().map(component_abi_type_expr_from_hir).collect();
840 Some(masm::FunctionType::new(ty.calling_convention(), args, results))
841}
842
843fn component_abi_type_expr_from_hir(ty: &Type) -> masm::TypeExpr {
853 match ty {
854 Type::Array(array) => masm::TypeExpr::Array(masm::ArrayType::new(
855 component_abi_type_expr_from_hir(array.element_type()),
856 array.len(),
857 )),
858 Type::Struct(struct_ty) => {
859 let name = struct_ty.name().and_then(|name| masm::Ident::new(name.as_ref()).ok());
860 let fields = struct_ty.fields().iter().enumerate().map(|(index, field)| {
861 let name = field
862 .name
863 .as_deref()
864 .map(masm::Ident::new)
865 .and_then(Result::ok)
866 .unwrap_or_else(|| masm::Ident::new(format!("field{index}")).unwrap());
867 masm::StructField {
868 span: SourceSpan::UNKNOWN,
869 name,
870 ty: component_abi_type_expr_from_hir(&field.ty),
871 }
872 });
873 masm::TypeExpr::Struct(
874 masm::StructType::new(name, fields)
875 .with_repr(Span::unknown(struct_ty.repr()))
876 .with_span(SourceSpan::UNKNOWN),
877 )
878 }
879 Type::Ptr(ptr) => masm::TypeExpr::Ptr(
880 masm::PointerType::new(component_abi_type_expr_from_hir(ptr.pointee()))
881 .with_address_space(ptr.addrspace()),
882 ),
883 Type::Function(_) => masm::TypeExpr::Ptr(masm::PointerType::new(
884 masm::TypeExpr::Primitive(Span::unknown(Type::Felt)),
885 )),
886 Type::List(element_ty) => masm::TypeExpr::Ptr(
887 masm::PointerType::new(component_abi_type_expr_from_hir(element_ty))
888 .with_address_space(masm::types::AddressSpace::Byte),
889 ),
890 Type::Unknown | Type::Never | Type::F64 => panic!("unrepresentable type value: {ty}"),
891 ty => masm::TypeExpr::Primitive(Span::unknown(ty.clone())),
892 }
893}
894
895fn block_has_real_instructions(block: &masm::Block) -> bool {
900 block.iter().any(|op| match op {
901 masm::Op::Inst(inst) => !matches!(
902 inst.inner(),
903 masm::Instruction::Debug(_)
904 | masm::Instruction::DebugVar(_)
905 | masm::Instruction::Trace(_)
906 ),
907 masm::Op::If {
908 then_blk, else_blk, ..
909 } => block_has_real_instructions(then_blk) || block_has_real_instructions(else_blk),
910 masm::Op::While { body, .. } => block_has_real_instructions(body),
911 masm::Op::Repeat { body, .. } => block_has_real_instructions(body),
912 })
913}
914
915fn patch_debug_var_locals_in_block(
924 block: &mut masm::Block,
925 aligned_num_locals: u16,
926 stack_pointer_addr: Option<u32>,
927) {
928 for op in block.iter_mut() {
929 match op {
930 masm::Op::Inst(span_inst) => {
931 if let masm::Instruction::DebugVar(info) = &mut **span_inst {
933 if let DebugVarLocation::Local(idx) = info.value_location() {
934 let fmp_offset = *idx - (aligned_num_locals as i16);
936 info.set_value_location(DebugVarLocation::Local(fmp_offset));
937 } else if let DebugVarLocation::FrameBase {
938 global_index,
939 byte_offset,
940 } = info.value_location()
941 {
942 let byte_offset = *byte_offset;
943 if let Some(local_index) = decode_frame_base_local_index(*global_index) {
944 if let Ok(local_index) = i16::try_from(local_index) {
945 let local_offset = local_index - (aligned_num_locals as i16);
946 info.set_value_location(DebugVarLocation::FrameBase {
947 global_index: encode_frame_base_local_offset(local_offset),
948 byte_offset,
949 });
950 }
951 } else {
952 if let Some(resolved_addr) = stack_pointer_addr {
955 info.set_value_location(DebugVarLocation::FrameBase {
956 global_index: resolved_addr,
957 byte_offset,
958 });
959 }
960 }
961 }
962 }
963 }
964 masm::Op::If {
965 then_blk, else_blk, ..
966 } => {
967 patch_debug_var_locals_in_block(then_blk, aligned_num_locals, stack_pointer_addr);
968 patch_debug_var_locals_in_block(else_blk, aligned_num_locals, stack_pointer_addr);
969 }
970 masm::Op::While {
971 body: while_body, ..
972 } => {
973 patch_debug_var_locals_in_block(while_body, aligned_num_locals, stack_pointer_addr);
974 }
975 masm::Op::Repeat {
976 body: repeat_body, ..
977 } => {
978 patch_debug_var_locals_in_block(
979 repeat_body,
980 aligned_num_locals,
981 stack_pointer_addr,
982 );
983 }
984 }
985 }
986}
987
988#[cfg(test)]
989mod tests {
990 use alloc::sync::Arc;
991
992 use midenc_hir::{PointerType, StructType, TypeRepr};
993
994 use super::*;
995
996 #[test]
997 fn type_expr_from_hir_pointer_conversion_preserves_address_space() {
998 for addrspace in [masm::types::AddressSpace::Byte, masm::types::AddressSpace::Element] {
999 let ty = Type::from(PointerType::new_with_address_space(Type::U32, addrspace));
1000
1001 let masm::TypeExpr::Ptr(ptr) = component_abi_type_expr_from_hir(&ty) else {
1002 panic!("expected pointer type expression");
1003 };
1004 assert_eq!(ptr.address_space(), addrspace);
1005
1006 let masm::TypeExpr::Ptr(ptr) = masm::TypeExpr::from(ty) else {
1007 panic!("expected pointer type expression");
1008 };
1009 assert_eq!(ptr.address_space(), addrspace);
1010 }
1011 }
1012
1013 #[test]
1014 fn component_abi_type_conversion_preserves_wide_primitives() {
1015 let masm::TypeExpr::Primitive(ty) = component_abi_type_expr_from_hir(&Type::U64) else {
1016 panic!("expected primitive component ABI type");
1017 };
1018 assert_eq!(ty.inner(), &Type::U64);
1019
1020 let masm::TypeExpr::Array(ty) = masm::TypeExpr::from(Type::U64) else {
1021 panic!("expected lowered MASM type");
1022 };
1023 assert_eq!(ty.arity, 2);
1024 let masm::TypeExpr::Primitive(element_ty) = ty.elem.as_ref() else {
1025 panic!("expected primitive array element type");
1026 };
1027 assert_eq!(element_ty.inner(), &Type::U32);
1028 }
1029
1030 #[test]
1031 fn component_abi_type_conversion_preserves_nominal_struct_metadata() {
1032 let ty = Type::Struct(Arc::new(StructType::from_parts(
1033 Some(Arc::from("miden:base/core-types@1.0.0/account-id")),
1034 TypeRepr::Default,
1035 [
1036 (Arc::<str>::from("prefix"), Type::Felt),
1037 (Arc::<str>::from("suffix"), Type::Felt),
1038 ],
1039 )));
1040
1041 let masm::TypeExpr::Struct(struct_ty) = component_abi_type_expr_from_hir(&ty) else {
1042 panic!("expected struct component ABI type");
1043 };
1044 assert_eq!(
1045 struct_ty.name.as_ref().map(|name| name.as_str()),
1046 Some("miden:base/core-types@1.0.0/account-id"),
1047 );
1048 assert_eq!(struct_ty.fields[0].name.as_str(), "prefix");
1049 assert_eq!(struct_ty.fields[1].name.as_str(), "suffix");
1050
1051 let masm::TypeExpr::Struct(struct_ty) = masm::TypeExpr::from(ty) else {
1052 panic!("expected lowered struct type");
1053 };
1054 assert!(struct_ty.name.is_none());
1055 assert_eq!(struct_ty.fields[0].name.as_str(), "field0");
1056 assert_eq!(struct_ty.fields[1].name.as_str(), "field1");
1057 }
1058}