1use std::borrow::Cow;
16use std::collections::HashMap;
17
18use crate::{
19 address_index::{AddressIndex, AddressTarget},
20 builder::Builder,
21 context::Context,
22 types::AggregateField,
23 value::{
24 BasicBlock, BlockParam, BlockParamId, FunctionBody, FunctionId, InstructionId, QCodeView,
25 Renameable, TempId, TempRef, Value, ValueId, ValueRef, Varnode, VarnodeId,
26 block::BlockId,
27 insn::{Callee, IntrinsicId},
28 },
29};
30use wazabin_qcode_parser::ast::{
31 Atom, Callee as ParsedCallee, CastOp, ExprNode, ExtractField, FnDecl, FnKind, GepField, Label,
32 Program, ProgramKind, Statement, StructDecl, StructFieldType, TypedAtom,
33};
34
35#[derive(Debug, Default, Clone)]
39pub struct Symbols {
40 pub functions: HashMap<String, FunctionId>,
41 pub blocks: HashMap<String, BlockId>,
42 pub ssa: HashMap<String, InstructionId>,
43 pub varnodes: HashMap<String, VarnodeId>,
44 pub temps: HashMap<String, TempId>,
45 pub block_params: HashMap<String, BlockParamId>,
46}
47
48impl Symbols {
49 pub fn function(&self, name: &str) -> FunctionId {
50 self.functions[name]
51 }
52 pub fn block(&self, name: &str) -> BlockId {
53 self.blocks[name]
54 }
55 pub fn ssa(&self, name: &str) -> InstructionId {
56 self.ssa[name]
57 }
58 pub fn varnode(&self, name: &str) -> VarnodeId {
59 self.varnodes[name]
60 }
61 pub fn temp(&self, name: &str) -> TempId {
62 self.temps[name]
63 }
64 pub fn block_param(&self, name: &str) -> BlockParamId {
65 self.block_params[name]
66 }
67}
68
69pub fn lower_str(ctx: &mut Context, source: &str) -> Result<Symbols, String> {
71 lower_str_with_externals(ctx, source, HashMap::new())
72}
73
74pub fn lower_str_with_externals(
77 ctx: &mut Context,
78 source: &str,
79 externals: HashMap<String, ValueId>,
80) -> Result<Symbols, String> {
81 let program = wazabin_qcode_parser::qcode_from_str(source).map_err(|e| e.to_string())?;
82 lower_program_with_externals(ctx, &program, externals)
83}
84
85pub fn lower_program(ctx: &mut Context, program: &Program) -> Result<Symbols, String> {
87 lower_program_with_externals(ctx, program, HashMap::new())
88}
89
90pub fn lower_program_with_externals(
92 ctx: &mut Context,
93 program: &Program,
94 externals: HashMap<String, ValueId>,
95) -> Result<Symbols, String> {
96 let mut symbols = Symbols::default();
97 register_structs(ctx, &program.structs);
98 let mut addresses = AddressIndex::analyze(ctx);
99
100 match &program.kind {
101 ProgramKind::Statements(statements) => {
102 let mut locals = HashMap::new();
103 lower_statement_block(
104 ctx,
105 &mut addresses,
106 statements,
107 &mut locals,
108 &mut symbols,
109 &externals,
110 )?;
111 }
112 ProgramKind::Functions { varnodes, fns } => {
113 let mut globals = HashMap::new();
114 for stmt in varnodes {
115 let Statement::LocalDecl {
116 name, size_bytes, ..
117 } = stmt.inner()
118 else {
119 return Err("top-level list may only contain varnode declarations".into());
120 };
121 let id = make_global_varnode(ctx, name, *size_bytes);
122 globals.insert(name.clone(), Local::Varnode(id));
123 symbols.varnodes.insert(name.clone(), id);
124 }
125
126 for fn_decl in fns {
129 let fid = make_function(ctx, fn_decl)?;
130 symbols.functions.insert(fn_decl.name.clone(), fid);
131 }
132 for fn_decl in fns {
133 prepare_named_callees(ctx, &fn_decl.statements, &mut symbols);
134 }
135 for fn_decl in fns {
136 let fid = symbols.functions[&fn_decl.name];
137 lower_fn_body(
138 ctx,
139 &mut addresses,
140 fid,
141 fn_decl,
142 &globals,
143 &mut symbols,
144 &externals,
145 )?;
146 }
147 }
148 }
149 Ok(symbols)
150}
151
152#[derive(Clone, Copy)]
153enum Local {
154 Varnode(VarnodeId),
155 Temp(TempId),
156 Instruction(InstructionId),
157 BlockParam(BlockParamId),
158}
159
160impl Local {
161 fn value_id(self) -> ValueId {
162 match self {
163 Local::Varnode(id) => id.into(),
164 Local::Temp(id) => id.into(),
165 Local::Instruction(id) => id.into(),
166 Local::BlockParam(id) => id.into(),
167 }
168 }
169}
170
171fn register_structs(ctx: &mut Context, structs: &[StructDecl]) {
172 for s in structs {
173 let mut offset = 0usize;
174 let mut fields = Vec::new();
175 for field in &s.fields {
176 let (size, ty) = match &field.ty {
177 StructFieldType::Int(n) => (*n, ctx.shared.types.get_or_make_int(*n)),
178 StructFieldType::StructPtr(target) => {
179 let pointee = ctx.shared.types.get_or_make_struct(target, 0, Vec::new());
180 (
181 8usize,
182 ctx.shared.types.get_or_make_struct_pointer(8, pointee),
183 )
184 }
185 };
186 if !field.is_padding() {
187 fields.push(AggregateField::new_at(&field.name, ty, offset));
188 }
189 offset += size;
190 }
191 ctx.shared.types.get_or_make_struct(&s.name, offset, fields);
192 }
193}
194
195fn make_global_varnode(ctx: &mut Context, name: &str, size: usize) -> VarnodeId {
196 let unique = ctx.get_unique_name(Cow::Owned(name.to_owned()));
197 let space = ctx.get_or_make_named_space(unique.as_ref());
198 let id = Varnode::make(ctx, 0, size, space).id;
199 let _ = Varnode::from_id_mut(ctx, id).rename(unique);
200 id
201}
202
203fn make_function(ctx: &mut Context, fn_decl: &FnDecl) -> Result<FunctionId, String> {
204 let name = Cow::Owned(fn_decl.name.clone());
205 let f = match fn_decl.kind {
206 FnKind::Machine => FunctionBody::make(ctx, name),
207 FnKind::Lambda => FunctionBody::make_lambda(ctx, name),
208 };
209 f.map(|r| r.id).map_err(|e| e.to_string())
210}
211
212fn collect_block_param_names(statements: &[Statement]) -> HashMap<String, Vec<String>> {
214 let mut out = HashMap::new();
215 for s in statements {
216 if let Statement::LabelDecl {
217 label: Label::Named { name, params, .. },
218 ..
219 } = s.inner()
220 {
221 out.insert(
222 name.clone(),
223 params.iter().map(|p| p.name.clone()).collect(),
224 );
225 }
226 }
227 out
228}
229
230fn prepare_named_callees(ctx: &mut Context, statements: &[Statement], symbols: &mut Symbols) {
231 for statement in statements {
232 let Statement::Call {
233 target: ParsedCallee::Named(name),
234 ..
235 } = statement.inner()
236 else {
237 continue;
238 };
239 if symbols.functions.contains_key(name) {
240 continue;
241 }
242 let id = FunctionBody::from_name(ctx, name)
243 .map(|function| function.id)
244 .unwrap_or_else(|| {
245 FunctionBody::make(ctx, Cow::Owned(name.clone()))
246 .expect("callee name absence was checked")
247 .id
248 });
249 symbols.functions.insert(name.clone(), id);
250 }
251}
252
253fn prepare_named_spaces(ctx: &mut Context, statements: &[Statement]) {
256 let mut names = Vec::new();
257 for statement in statements {
258 let expression = match statement.inner() {
259 Statement::Assign { expr, .. } | Statement::Expr(expr) => Some(expr),
260 _ => None,
261 };
262 let Some(ExprNode::Load { space, .. } | ExprNode::Store { space, .. }) = expression else {
263 continue;
264 };
265 if !space.starts_with('$') {
266 names.push(space.as_str());
267 }
268 }
269 names.sort_unstable();
270 names.dedup();
271 for name in names {
272 ctx.get_or_make_named_space(name);
273 }
274}
275
276fn lower_fn_body(
277 ctx: &mut Context,
278 addresses: &mut AddressIndex,
279 fid: FunctionId,
280 fn_decl: &FnDecl,
281 globals: &HashMap<String, Local>,
282 symbols: &mut Symbols,
283 externals: &HashMap<String, ValueId>,
284) -> Result<(), String> {
285 let statements = &fn_decl.statements;
286 prepare_named_spaces(ctx, statements);
287 let first = statements
288 .first()
289 .ok_or_else(|| format!("fn `{}`: body cannot be empty", fn_decl.name))?;
290 let Statement::LabelDecl {
291 label: Label::Named { name: entry, .. },
292 ..
293 } = first.inner()
294 else {
295 return Err(format!(
296 "fn `{}`: first statement must be a named label like `<entry>`",
297 fn_decl.name
298 ));
299 };
300 let entry = entry.clone();
301
302 let entry_id = BasicBlock::make(ctx, fid)
304 .with_name(Cow::Owned(entry.clone()))
305 .map_err(|e| e.to_string())?
306 .id;
307 FunctionBody::from_id_mut(ctx, fid)
308 .set_root(entry_id)
309 .map_err(|e| e.to_string())?;
310 symbols.blocks.insert(entry.clone(), entry_id);
311
312 let mut block_ids: HashMap<String, BlockId> = HashMap::new();
313 block_ids.insert(entry.clone(), entry_id);
314 create_blocks_and_params(ctx, statements, Some(fid), &entry, &mut block_ids, symbols);
315 let address_blocks = prepare_address_blocks(ctx, addresses, statements, fid)?;
316
317 lower_body(
318 ctx,
319 statements,
320 &entry,
321 &block_ids,
322 &address_blocks,
323 globals,
324 symbols,
325 externals,
326 )
327}
328
329fn lower_statement_block(
331 ctx: &mut Context,
332 addresses: &mut AddressIndex,
333 statements: &[Statement],
334 globals: &mut HashMap<String, Local>,
335 symbols: &mut Symbols,
336 externals: &HashMap<String, ValueId>,
337) -> Result<(), String> {
338 let split = statements
340 .iter()
341 .position(|s| !matches!(s.inner(), Statement::LocalDecl { .. }))
342 .unwrap_or(statements.len());
343 let (preamble, body) = statements.split_at(split);
344 for stmt in preamble {
345 let Statement::LocalDecl {
346 name, size_bytes, ..
347 } = stmt.inner()
348 else {
349 unreachable!()
350 };
351 let id = make_global_varnode(ctx, name, *size_bytes);
352 globals.insert(name.clone(), Local::Varnode(id));
353 symbols.varnodes.insert(name.clone(), id);
354 }
355 if body.is_empty() {
356 return Err("qcode program cannot be empty".into());
357 }
358 let Statement::LabelDecl {
359 label: Label::Named { name: entry, .. },
360 ..
361 } = body[0].inner()
362 else {
363 return Err("statement program must start with a named label, e.g. `<block>`".into());
364 };
365 let entry = entry.clone();
366
367 prepare_named_callees(ctx, body, symbols);
368 prepare_named_spaces(ctx, body);
369
370 let mut block_ids: HashMap<String, BlockId> = HashMap::new();
371 let host = ctx.anon_function();
374 create_blocks_and_params(ctx, body, Some(host), "", &mut block_ids, symbols);
375 let address_blocks = prepare_address_blocks(ctx, addresses, body, host)?;
376
377 lower_body(
378 ctx,
379 body,
380 &entry,
381 &block_ids,
382 &address_blocks,
383 globals,
384 symbols,
385 externals,
386 )
387}
388
389fn prepare_address_blocks(
390 ctx: &mut Context,
391 addresses: &mut AddressIndex,
392 statements: &[Statement],
393 function: FunctionId,
394) -> Result<HashMap<u64, BlockId>, String> {
395 let mut values = Vec::new();
396 let mut push = |label: &Label| {
397 if let Label::Address { value, .. } = label {
398 values.push(*value);
399 }
400 };
401 for statement in statements {
402 match statement.inner() {
403 Statement::LabelDecl { label, .. } | Statement::Branch { target: label, .. } => {
404 push(label)
405 }
406 Statement::BranchInd { targets, .. }
407 | Statement::Call { targets, .. }
408 | Statement::CallInd { targets, .. } => targets.iter().for_each(&mut push),
409 Statement::CBranch {
410 target,
411 fallthrough,
412 ..
413 } => {
414 push(target);
415 push(fallthrough);
416 }
417 Statement::Switch { cases, default, .. } => {
418 cases.iter().for_each(|(_, target, _)| push(target));
419 if let Some((target, _)) = default {
420 push(target);
421 }
422 }
423 _ => {}
424 }
425 }
426 values.sort_unstable();
427 values.dedup();
428
429 let mut blocks = HashMap::new();
430 for value in values {
431 let foreign = match addresses.get(value) {
432 Some(AddressTarget::Function(owner)) if owner != function => Some(owner),
433 Some(AddressTarget::Block(block)) if block.func != function => Some(block.func),
434 _ => None,
435 };
436 if let Some(owner) = foreign {
437 return Err(format!(
438 "control-flow target <{value:#x}> resolves to storage owned by {owner:?}, \
439 but the branch is in {function:?}; cross-function control flow must be a \
440 call/tail call, not a foreign block target"
441 ));
442 }
443 blocks.insert(
444 value,
445 ctx.get_or_make_block_indexed(addresses, value, function),
446 );
447 }
448 Ok(blocks)
449}
450
451fn create_blocks_and_params(
454 ctx: &mut Context,
455 statements: &[Statement],
456 func: Option<FunctionId>,
457 skip_entry: &str,
458 block_ids: &mut HashMap<String, BlockId>,
459 symbols: &mut Symbols,
460) {
461 for stmt in statements {
462 let Statement::LabelDecl {
463 label: Label::Named { name, params, .. },
464 ..
465 } = stmt.inner()
466 else {
467 continue;
468 };
469 let block_id = if name == skip_entry {
470 block_ids[name]
471 } else {
472 let fid = func.unwrap_or_else(|| ctx.anon_function());
476 let id = BasicBlock::make(ctx, fid)
477 .with_name(Cow::Owned(name.clone()))
478 .expect("qcode: block name conflict")
479 .id;
480 block_ids.insert(name.clone(), id);
481 symbols.blocks.insert(name.clone(), id);
482 id
483 };
484 for param in params {
485 let pid = BasicBlock::from_id_mut(ctx, block_id)
486 .push_param(param.size_bytes.unwrap_or(0))
487 .id;
488 let _ = BlockParam::from_id_mut(ctx, pid).rename(Cow::Owned(param.name.clone()));
489 symbols.block_params.insert(param.name.clone(), pid);
490 }
491 }
492}
493
494#[allow(clippy::too_many_arguments)] fn lower_body(
496 ctx: &mut Context,
497 statements: &[Statement],
498 entry: &str,
499 block_ids: &HashMap<String, BlockId>,
500 address_blocks: &HashMap<u64, BlockId>,
501 globals: &HashMap<String, Local>,
502 symbols: &mut Symbols,
503 externals: &HashMap<String, ValueId>,
504) -> Result<(), String> {
505 let block_param_names = collect_block_param_names(statements);
506 let mut locals = globals.clone();
507 if let Some(Statement::LabelDecl {
509 label: Label::Named { params, .. },
510 ..
511 }) = statements.first().map(|s| s.inner())
512 {
513 for param in params {
514 locals.insert(
515 param.name.clone(),
516 Local::BlockParam(symbols.block_params[¶m.name]),
517 );
518 }
519 }
520
521 let entry_id = block_ids[entry];
522 let mut b = (ctx).builder(entry_id);
523 let mut lw = Lowerer {
524 b: &mut b,
525 locals: &mut locals,
526 block_ids,
527 address_blocks,
528 block_param_names: &block_param_names,
529 symbols,
530 externals,
531 };
532 for stmt in statements.iter().skip(1) {
533 lw.statement(stmt.inner())?;
534 }
535 Ok(())
536}
537
538struct Lowerer<'a, 'str, 'ctx> {
539 b: &'a mut Builder<'str, 'ctx>,
540 locals: &'a mut HashMap<String, Local>,
541 block_ids: &'a HashMap<String, BlockId>,
542 address_blocks: &'a HashMap<u64, BlockId>,
543 block_param_names: &'a HashMap<String, Vec<String>>,
544 symbols: &'a mut Symbols,
545 externals: &'a HashMap<String, ValueId>,
546}
547
548impl Lowerer<'_, '_, '_> {
549 fn prepared_space(&self, name: &str) -> Result<crate::space::SpaceId, String> {
550 let shared = self.b.shr();
551 shared
552 .named_spaces
553 .get(name)
554 .copied()
555 .or_else(|| {
556 (shared.spaces[shared.default_space].name.as_deref() == Some(name))
557 .then_some(shared.default_space)
558 })
559 .ok_or_else(|| format!("unprepared memory space `{name}`"))
560 }
561
562 fn block(&mut self, label: &Label) -> Result<BlockId, String> {
563 let resolved = match label {
564 Label::Named { name, .. } => self
565 .block_ids
566 .get(name)
567 .copied()
568 .ok_or_else(|| format!("unknown block <{name}>"))?,
569 Label::Address { value, .. } => self
570 .address_blocks
571 .get(value)
572 .copied()
573 .ok_or_else(|| format!("unprepared address block <{value:#x}>"))?,
574 };
575 let current = self.b.current_block().func;
583 let owner = self.b.view().block_ref(resolved).parent().map(|f| f.id);
584 if let Some(owner) = owner
585 && owner != current
586 {
587 return Err(format!(
588 "control-flow target {label:?} resolves to a block owned by {owner:?}, \
589 but the branch is in {current:?}; cross-function control flow must be a \
590 call/tail call, not a foreign block target",
591 ));
592 }
593 Ok(resolved)
594 }
595
596 fn add_edge_hints(&mut self, targets: &[Label]) -> Result<(), String> {
601 if targets.is_empty() {
602 return Ok(());
603 }
604 let from = self.b.current_block();
605 for target in targets {
606 let to = self.block(target)?;
607 self.b.add_cfg_edge(from, to);
608 }
609 Ok(())
610 }
611
612 fn statement(&mut self, stmt: &Statement) -> Result<(), String> {
613 match stmt {
614 Statement::LocalDecl {
615 name, size_bytes, ..
616 } => {
617 let id = self
618 .b
619 .make_named_temp(Cow::Owned(name.clone()), *size_bytes);
620 self.locals.insert(name.clone(), Local::Temp(id));
621 self.symbols.temps.insert(name.clone(), id);
622 }
623
624 Statement::Assign {
625 name,
626 expr,
627 decl_struct_ptr,
628 ..
629 } => {
630 let value = self.expr(expr)?;
631 let ValueId::Instruction(id) = value else {
632 return Err(format!("`%{name}` must be bound to an instruction result"));
633 };
634 let _ = self.b.rename_insn(id, Cow::Owned(name.clone()));
635 if let Some(struct_name) = decl_struct_ptr {
636 let pointee = self
637 .b
638 .shr()
639 .types
640 .get_or_make_struct(struct_name, 0, Vec::new());
641 let sp = self.b.shr().types.get_or_make_struct_pointer(8, pointee);
642 self.b.set_insn_type(id, sp);
643 }
644 self.locals.insert(name.clone(), Local::Instruction(id));
645 self.symbols.ssa.insert(name.clone(), id);
646 }
647
648 Statement::Expr(expr) => {
649 self.expr(expr)?;
650 }
651
652 Statement::LabelDecl {
653 label: Label::Named { name, params, .. },
654 ..
655 } => {
656 let id = self.block_ids[name];
657 self.b.switch_to_block(id);
658 for param in params {
659 self.locals.insert(
660 param.name.clone(),
661 Local::BlockParam(self.symbols.block_params[¶m.name]),
662 );
663 }
664 }
665 Statement::LabelDecl {
666 label: Label::Address { value, .. },
667 ..
668 } => {
669 let blk = self.address_blocks[value];
670 self.b.switch_to_block(blk);
671 }
672
673 Statement::Branch { target, args, .. } => {
674 if args.is_empty() {
675 let t = self.block(target)?;
676 self.b.push_branch(t);
677 } else {
678 let t = self.block(target)?;
679 let argv = self.branch_args(target, args)?;
680 self.b.push_branch_with_args(t, argv);
681 }
682 }
683
684 Statement::BranchInd { ptr, targets, .. } => {
685 let p = self.ptr_atom(ptr)?;
686 self.b.push_branchind(p);
687 self.add_edge_hints(targets)?;
688 }
689
690 Statement::Switch {
691 scrutinee,
692 cases,
693 default,
694 ..
695 } => {
696 let value = self.atom(scrutinee, None)?;
697 let mut arms = Vec::with_capacity(cases.len());
698 for (case, target, args) in cases {
699 let block = self.block(target)?;
700 let args = self.branch_args(target, args)?;
701 arms.push((*case, block, args));
702 }
703 let default = match default {
704 Some((target, args)) => {
705 let block = self.block(target)?;
706 Some((block, self.branch_args(target, args)?))
707 }
708 None => None,
709 };
710 self.b.push_switch(value, arms, default);
711 }
712
713 Statement::CBranch {
714 condition,
715 target,
716 target_args,
717 fallthrough,
718 fallthrough_args,
719 ..
720 } => {
721 let cond = self.atom(condition, None)?;
722 let t = self.block(target)?;
723 let f = self.block(fallthrough)?;
724 let ta = self.branch_args(target, target_args)?;
725 let fa = self.branch_args(fallthrough, fallthrough_args)?;
726 self.b.push_cbranch_with_args(cond, t, ta, f, fa);
727 self.b.switch_to_block(f);
728 }
729
730 Statement::Call {
731 target,
732 tail,
733 args,
734 targets,
735 ..
736 } => {
737 let t = self.call_callee(target);
738 let argv = args
741 .iter()
742 .map(|(_, atom)| self.atom(atom, None))
743 .collect::<Result<Vec<_>, _>>()?;
744 if *tail {
745 self.b.push_tail_call_with_args(t, argv);
746 } else {
747 self.b.push_call_with_args(t, argv);
748 self.add_edge_hints(targets)?;
749 }
750 }
751
752 Statement::CallInd {
753 ptr, args, targets, ..
754 } => {
755 let p = self.ptr_atom(ptr)?;
756 let argv = self.atoms(args)?;
757 self.b.push_call_ind_with_args(p, argv);
758 self.add_edge_hints(targets)?;
759 }
760
761 Statement::Return { ptr, value, .. } => {
762 let p = self.ptr_atom(ptr)?;
763 if let Some(value) = value {
764 let v = self.atom(value, None)?;
765 self.b.push_return_with_value(v, p);
766 } else {
767 self.b.push_return(p);
768 }
769 }
770
771 Statement::ReturnValue { value, .. } => {
772 let v = self.atom(value, None)?;
773 self.b.push_return_value(v);
774 }
775
776 Statement::BadInsn { .. } => {
777 self.b.push_bad_insn();
778 }
779
780 Statement::Assert { condition, .. } => {
781 let c = self.atom(condition, None)?;
782 self.b.push_assert(c);
783 }
784
785 Statement::Commented { inner, .. } => self.statement(inner)?,
786 }
787 Ok(())
788 }
789
790 fn branch_args(
791 &mut self,
792 target: &Label,
793 args: &[(String, TypedAtom)],
794 ) -> Result<Vec<ValueId>, String> {
795 if args.is_empty() {
796 return Ok(Vec::new());
797 }
798 let Label::Named { name, .. } = target else {
799 return Err("block arguments can only be passed to named labels".into());
800 };
801 let params = self
802 .block_param_names
803 .get(name)
804 .ok_or_else(|| format!("unknown branch target <{name}>"))?
805 .clone();
806 if args.len() != params.len() {
807 return Err(format!(
808 "branch to <{name}> passes {} args but target declares {} params",
809 args.len(),
810 params.len()
811 ));
812 }
813 let mut out = Vec::with_capacity(params.len());
814 for param_name in ¶ms {
815 let value = args
816 .iter()
817 .find(|(arg, _)| arg == param_name)
818 .map(|(_, v)| v)
819 .ok_or_else(|| format!("branch to <{name}> missing argument @{param_name}"))?;
820 let v = self.atom(value, None)?;
821 let size = ValueRef::from_view(self.b.view(), v).size();
824 let pid = self.symbols.block_params[param_name];
825 self.b.constrain_param_size(pid, size);
826 out.push(v);
827 }
828 Ok(out)
829 }
830
831 fn expr(&mut self, expr: &ExprNode) -> Result<ValueId, String> {
832 match expr {
833 ExprNode::Atom(atom) => self.atom(atom, None),
834
835 ExprNode::Unop { op, src } => {
836 let s = self.atom(src, None)?;
837 Ok(match op.as_str() {
838 "~" => self.b.push_bit_negate(s).id(),
839 "-" => self.b.push_neg(s).id(),
840 "f-" => self.b.push_fneg(s).id(),
841 "abs" => self.b.push_abs(s).id(),
842 "sqrt" => self.b.push_sqrt(s).id(),
843 "floor" => self.b.push_floor(s).id(),
844 "ceil" => self.b.push_ceil(s).id(),
845 "round" => self.b.push_round(s).id(),
846 _ => return Err(format!("unsupported unary operator: {op}")),
847 })
848 }
849
850 ExprNode::Binary { lhs, op, rhs } => {
851 let lhs_hint = self.size_hint(lhs);
852 let rhs_hint = self.size_hint(rhs);
853 let l = self.atom(lhs, rhs_hint)?;
854 let r = self.atom(rhs, lhs_hint)?;
855 Ok(self.binop(op, l, r)?)
856 }
857
858 ExprNode::Cast {
859 op,
860 size_bytes,
861 src,
862 } => {
863 let s = self.atom(src, None)?;
864 let n = *size_bytes;
865 Ok(match op {
866 CastOp::Zext => self.b.push_zext(s, n).id(),
867 CastOp::Sext => self.b.push_sext(s, n).id(),
868 CastOp::IntToFloat => self.b.push_int_to_float(s, n).id(),
869 CastOp::FloatToFloat => self.b.push_float_to_float(s, n).id(),
870 CastOp::Trunc => self.b.push_trunc(s, n).id(),
871 })
872 }
873
874 ExprNode::Load {
875 space,
876 size_bytes,
877 ptr,
878 } => {
879 let p = self.ptr_atom(ptr)?;
880 let space = if let Some(name) = space.strip_prefix('$') {
881 self.b.get_or_make_local_temp_space(name)
882 } else {
883 self.prepared_space(space)?.into()
884 };
885 Ok(self.b.push_load::<false>(p, *size_bytes, space).id())
886 }
887
888 ExprNode::Store {
889 space,
890 size_bytes,
891 ptr,
892 src,
893 } => {
894 let p = self.ptr_atom(ptr)?;
895 let s = self.atom(src, Some(*size_bytes))?;
896 let space = if let Some(name) = space.strip_prefix('$') {
897 self.b.get_or_make_local_temp_space(name)
898 } else {
899 self.prepared_space(space)?.into()
900 };
901 Ok(self.b.push_store(s, p, space).id())
902 }
903
904 ExprNode::FuncCall { op, args } => self.func_call(op, args),
905
906 ExprNode::Intrinsic { name, args } => {
907 let id = IntrinsicId::from_name(name)
908 .ok_or_else(|| format!("unknown intrinsic `{name}`"))?;
909 let argv = self.atoms(args)?;
910 Ok(self.b.push_intrinsic(id, argv).id())
911 }
912
913 ExprNode::Apply { target, args } => {
914 let target = self.existing_callee(target, "apply")?;
915 let argv = self.atoms(args)?;
916 Ok(self.b.push_apply(target, argv).id())
917 }
918
919 ExprNode::Map {
920 body,
921 src,
922 captures,
923 } => {
924 let body = self.existing_callee(body, "map")?;
925 let s = self.atom(src, None)?;
926 let caps = self.atoms(captures)?;
927 Ok(self.b.push_map(body, s, caps).id())
928 }
929
930 ExprNode::Scan {
931 body,
932 init,
933 src,
934 captures,
935 } => {
936 let body = self.existing_callee(body, "scan")?;
937 let i = self.atom(init, None)?;
938 let s = self.atom(src, None)?;
939 let caps = self.atoms(captures)?;
940 Ok(self.b.push_scan(body, i, s, caps).id())
941 }
942
943 ExprNode::Tuple { fields } => {
944 let mut named = Vec::with_capacity(fields.len());
945 for (i, f) in fields.iter().enumerate() {
946 let name = f.name.clone().unwrap_or_else(|| format!("field{}", i + 1));
947 let v = self.atom(&f.value, None)?;
948 named.push((name, v));
949 }
950 Ok(self.b.push_named_tuple(named).id())
951 }
952
953 ExprNode::Extract { agg, field } => {
954 let a = self.atom(agg, None)?;
955 let index = match field {
956 ExtractField::Index(i) => *i as usize,
957 ExtractField::Name(name) => {
958 let ty = self
959 .b
960 .stored_type_of(a)
961 .ok_or("extract: aggregate has no stored type")?;
962 self.b
963 .shr()
964 .types
965 .field_index(ty, name)
966 .ok_or_else(|| format!("extract: no field `{name}`"))?
967 }
968 };
969 Ok(self.b.push_extract(a, index).id())
970 }
971
972 ExprNode::Gep { base, field } => {
973 let base_v = self.atom(base, None)?;
974 Ok(match field {
975 GepField::Offset(off) => self.b.push_gep(base_v, *off as usize).id(),
976 GepField::Name(name) => self.b.push_gep_field(base_v, name).id(),
977 })
978 }
979
980 ExprNode::Range { src, start, end } => {
981 let s = self.atom(src, None)?;
982 let src_size = ValueRef::from_view(self.b.view(), s).size();
983 let start = start.map(|v| v as usize).unwrap_or(0);
984 let end = end.map(|v| v as usize).unwrap_or(src_size);
985 Ok(self.b.push_range(s, start, end - start).id())
986 }
987 }
988 }
989
990 fn binop(&mut self, op: &str, l: ValueId, r: ValueId) -> Result<ValueId, String> {
991 Ok(match op {
992 "+" => self.b.push_add(l, r).id(),
993 "-" => self.b.push_sub(l, r).id(),
994 "*" => self.b.push_mul(l, r).id(),
995 "/" => self.b.push_div(l, r).id(),
996 "&" => self.b.push_bit_and(l, r).id(),
997 "|" => self.b.push_bit_or(l, r).id(),
998 "^" => self.b.push_bit_xor(l, r).id(),
999 "<<" => self.b.push_shl(l, r).id(),
1000 ">>" => self.b.push_shr(l, r).id(),
1001 "s>>" => self.b.push_sshr(l, r).id(),
1002 "==" => self.b.push_eq(l, r).id(),
1003 "!=" => self.b.push_ne(l, r).id(),
1004 "<" => self.b.push_lt(l, r).id(),
1005 "<=" => self.b.push_le(l, r).id(),
1006 ">" => self.b.push_gt(l, r).id(),
1007 ">=" => self.b.push_ge(l, r).id(),
1008 "s<" => self.b.push_slt(l, r).id(),
1009 "s<=" => self.b.push_sle(l, r).id(),
1010 "s>" => self.b.push_sgt(l, r).id(),
1011 "s>=" => self.b.push_sge(l, r).id(),
1012 "%" => self.b.push_mod(l, r).id(),
1013 "s/" => self.b.push_sdiv(l, r).id(),
1014 "s%" => self.b.push_smod(l, r).id(),
1015 "f+" => self.b.push_fadd(l, r).id(),
1016 "f-" => self.b.push_fsub(l, r).id(),
1017 "f*" => self.b.push_fmul(l, r).id(),
1018 "f/" => self.b.push_fdiv(l, r).id(),
1019 "f==" => self.b.push_feq(l, r).id(),
1020 "f!=" => self.b.push_fne(l, r).id(),
1021 "f<" => self.b.push_flt(l, r).id(),
1022 "f<=" => self.b.push_fle(l, r).id(),
1023 "f>" => self.b.push_fgt(l, r).id(),
1024 "f>=" => self.b.push_fge(l, r).id(),
1025 _ => return Err(format!("unsupported operator: {op}")),
1026 })
1027 }
1028
1029 fn func_call(&mut self, op: &str, args: &[TypedAtom]) -> Result<ValueId, String> {
1030 let expect = |n: usize| -> Result<(), String> {
1031 if args.len() == n {
1032 Ok(())
1033 } else {
1034 Err(format!("{op} expects {n} argument(s)"))
1035 }
1036 };
1037 Ok(match op {
1038 "nan" => {
1039 expect(1)?;
1040 let s = self.atom(&args[0], None)?;
1041 self.b.push_is_nan(s).id()
1042 }
1043 "popcount" => {
1044 expect(1)?;
1045 let s = self.atom(&args[0], None)?;
1046 self.b.push_popcount(s, 1).id()
1047 }
1048 "lzcount" => {
1049 expect(1)?;
1050 let s = self.atom(&args[0], None)?;
1051 self.b.push_lzcount(s, 1).id()
1052 }
1053 "carry" => {
1054 expect(2)?;
1055 let l = self.atom(&args[0], None)?;
1056 let r = self.atom(&args[1], None)?;
1057 self.b.push_carry(l, r).id()
1058 }
1059 "scarry" => {
1060 expect(2)?;
1061 let l = self.atom(&args[0], None)?;
1062 let r = self.atom(&args[1], None)?;
1063 self.b.push_scarry(l, r).id()
1064 }
1065 "sborrow" => {
1066 expect(2)?;
1067 let l = self.atom(&args[0], None)?;
1068 let r = self.atom(&args[1], None)?;
1069 self.b.push_sborrow(l, r).id()
1070 }
1071 _ => return Err(format!("unsupported function call: {op}")),
1072 })
1073 }
1074
1075 fn atoms(&mut self, atoms: &[TypedAtom]) -> Result<Vec<ValueId>, String> {
1076 atoms.iter().map(|a| self.atom(a, None)).collect()
1077 }
1078
1079 fn atom(&mut self, typed: &TypedAtom, size_hint: Option<usize>) -> Result<ValueId, String> {
1081 match &typed.atom {
1082 Atom::External(name) => {
1083 if let Some(local) = self.locals.get(name).copied() {
1086 if matches!(local, Local::BlockParam(_)) {
1087 return Ok(self.coerce_block_param(local, typed.size_bytes, size_hint));
1088 }
1089 let v = local.value_id();
1090 self.check_size(v, typed.size_bytes, name)?;
1091 Ok(v)
1092 } else if let Some(&value) = self.externals.get(name) {
1093 self.check_size(value, typed.size_bytes, name)?;
1094 Ok(value)
1095 } else {
1096 Err(format!("unknown external capture `{{{name}}}`"))
1097 }
1098 }
1099 Atom::Ssa(name) => {
1100 let local = self
1101 .locals
1102 .get(name)
1103 .copied()
1104 .ok_or_else(|| format!("unknown SSA value `%{name}`"))?;
1105 let Local::Instruction(_) = local else {
1106 return Err(format!("`%{name}` is not an SSA value"));
1107 };
1108 let v = local.value_id();
1109 self.check_size(v, typed.size_bytes, name)?;
1110 Ok(v)
1111 }
1112 Atom::BlockParam(name) => {
1113 let local = self
1114 .locals
1115 .get(name)
1116 .copied()
1117 .ok_or_else(|| format!("unknown block param `@{name}`"))?;
1118 let Local::BlockParam(_) = local else {
1119 return Err(format!("`@{name}` is not a block param"));
1120 };
1121 Ok(self.coerce_block_param(local, typed.size_bytes, size_hint))
1122 }
1123 Atom::Varnode(name) => Err(format!(
1124 "`{name}` is a varnode; use `&{name}` or `load(sz, {name})` in a value position"
1125 )),
1126 Atom::AddressOf(name) => {
1127 let local = self
1128 .locals
1129 .get(name)
1130 .copied()
1131 .ok_or_else(|| format!("unknown varnode `{name}` in addressof"))?;
1132 let (addr_size, value) = match local {
1133 Local::Varnode(vid) => (
1134 Varnode::from_id(self.b.shr(), vid).space().addr_size,
1135 vid.into(),
1136 ),
1137 Local::Temp(id) => (
1138 TempRef::new(self.b.view(), id).space().addr_size(),
1139 id.into(),
1140 ),
1141 _ => {
1142 return Err(format!(
1143 "`&{name}`: addressof applies only to memory values"
1144 ));
1145 }
1146 };
1147 if let Some(expected) = typed.size_bytes
1150 && addr_size != expected
1151 {
1152 return Err(format!(
1153 "qcode size mismatch for `&{name}`: expected {expected} bytes, got {addr_size}"
1154 ));
1155 }
1156 Ok(value)
1157 }
1158 Atom::Int(value) => {
1159 let size = typed.size_bytes.or(size_hint).unwrap_or(8);
1160 Ok(self.b.shr().get_const(*value, size))
1161 }
1162 Atom::Bool(value) => Ok(self.b.shr().get_bool_const(*value)),
1163 }
1164 }
1165
1166 fn check_size(
1170 &self,
1171 value: ValueId,
1172 explicit: Option<usize>,
1173 name: &str,
1174 ) -> Result<(), String> {
1175 if let Some(expected) = explicit {
1176 let actual = ValueRef::from_view(self.b.view(), value).size();
1177 if actual != expected {
1178 return Err(format!(
1179 "qcode size mismatch for `{name}`: expected {expected} bytes, got {actual}"
1180 ));
1181 }
1182 }
1183 Ok(())
1184 }
1185
1186 fn coerce_block_param(
1188 &mut self,
1189 local: Local,
1190 explicit: Option<usize>,
1191 hint: Option<usize>,
1192 ) -> ValueId {
1193 if let (Local::BlockParam(pid), Some(size)) = (local, explicit.or(hint)) {
1194 self.b.constrain_param_size(pid, size);
1195 }
1196 local.value_id()
1197 }
1198
1199 fn ptr_atom(&mut self, typed: &TypedAtom) -> Result<ValueId, String> {
1201 if let Atom::Varnode(name) = &typed.atom {
1202 let local = self
1203 .locals
1204 .get(name)
1205 .copied()
1206 .ok_or_else(|| format!("unknown varnode `{name}`"))?;
1207 if !matches!(local, Local::Varnode(_) | Local::Temp(_)) {
1208 return Err(format!("`{name}` is not a varnode"));
1209 }
1210 Ok(local.value_id())
1211 } else {
1212 self.atom(typed, None)
1213 }
1214 }
1215
1216 fn call_callee(&mut self, callee: &ParsedCallee) -> Callee {
1217 match callee {
1218 ParsedCallee::Named(name) => Callee::Real(self.symbols.functions[name]),
1219 ParsedCallee::Minted(slot) => Callee::Minted(*slot),
1220 }
1221 }
1222
1223 fn existing_callee(&self, callee: &ParsedCallee, operation: &str) -> Result<Callee, String> {
1224 match callee {
1225 ParsedCallee::Named(name) => self
1226 .symbols
1227 .functions
1228 .get(name)
1229 .copied()
1230 .map(Callee::Real)
1231 .ok_or_else(|| format!("{operation}: unknown function `{name}`")),
1232 ParsedCallee::Minted(slot) => Ok(Callee::Minted(*slot)),
1233 }
1234 }
1235
1236 fn size_hint(&self, typed: &TypedAtom) -> Option<usize> {
1238 if let Some(explicit) = typed.size_bytes {
1239 return Some(explicit);
1240 }
1241 let local = match &typed.atom {
1242 Atom::External(name) | Atom::Ssa(name) | Atom::BlockParam(name) => {
1243 self.locals.get(name).copied()?
1244 }
1245 _ => return None,
1246 };
1247 Some(ValueRef::from_view(self.b.view(), local.value_id()).size())
1248 }
1249}
1250
1251#[cfg(test)]
1252mod tests {
1253 use super::*;
1254
1255 #[test]
1256 fn minted_callees_render_parse_and_lower_in_all_direct_forms() {
1257 use crate::value::insn::Mnemonic;
1258
1259 fn host_block(ctx: &mut Context<'_>, name: &str) -> BlockId {
1260 let function = FunctionBody::make(ctx, Cow::Owned(name.to_owned()))
1261 .unwrap()
1262 .id;
1263 let block = BasicBlock::make(ctx, function).id;
1264 let mut function = FunctionBody::from_id_mut(ctx, function);
1265 function.add_block(block);
1266 function.set_root(block).unwrap();
1267 block
1268 }
1269
1270 let mut rendered_ctx = Context::new();
1271 let block = host_block(&mut rendered_ctx, "rendered");
1272 let arg = rendered_ctx.get_const(1, 8).id();
1273 let rendered_ids = {
1274 let mut builder = rendered_ctx.builder(block);
1275 let apply = builder.push_apply(Callee::Minted(1), vec![arg]).id();
1276 let map = builder.push_map(Callee::Minted(2), arg, Vec::new()).id();
1277 let scan = builder
1278 .push_scan(Callee::Minted(3), arg, arg, Vec::new())
1279 .id();
1280 let call = builder
1281 .push_call_with_args(Callee::Minted(4), vec![arg])
1282 .id();
1283 [apply, map, scan, call].map(|value| match value {
1284 ValueId::Instruction(id) => id,
1285 _ => unreachable!(),
1286 })
1287 };
1288 let rendered = rendered_ids.map(|id| rendered_ctx.get_insn(id).as_statement().to_string());
1289
1290 let tail_block = host_block(&mut rendered_ctx, "rendered_tail");
1291 let tail_id = {
1292 let mut builder = rendered_ctx.builder(tail_block);
1293 let value = builder
1294 .push_tail_call_with_args(Callee::Minted(5), vec![arg])
1295 .id();
1296 let ValueId::Instruction(id) = value else {
1297 unreachable!()
1298 };
1299 id
1300 };
1301 let tail = rendered_ctx.get_insn(tail_id).as_statement().to_string();
1302
1303 let mut forms = rendered.into_iter().collect::<Vec<_>>();
1304 forms.push(tail);
1305 for (index, statement) in forms.iter().enumerate() {
1306 let slot = index as u32 + 1;
1307 assert!(
1308 statement.contains(&format!("<minted:{slot}>")),
1309 "renderer must emit the canonical placeholder: {statement}"
1310 );
1311 let mut lowered = Context::new();
1312 let source = format!("fn host:\n<entry>\n{statement}");
1313 let symbols = lower_str(&mut lowered, &source).expect("rendered form must lower");
1314 let instruction = FunctionBody::from_id(&lowered, symbols.function("host"))
1315 .root()
1316 .unwrap()
1317 .iter()
1318 .next()
1319 .unwrap();
1320 let actual = match instruction.mnemonic() {
1321 Mnemonic::Apply(value) => value.target,
1322 Mnemonic::Map(value) => value.body,
1323 Mnemonic::Scan(value) => value.body,
1324 Mnemonic::Call(value) => value.target,
1325 Mnemonic::TailCall(value) => value.target,
1326 other => panic!("unexpected lowered mnemonic: {other:?}"),
1327 };
1328 assert_eq!(actual, Callee::Minted(slot));
1329 }
1330 }
1331
1332 #[test]
1333 fn lowers_fib_lambda_roundtrips() {
1334 let mut ctx = Context::new();
1335 let syms = lower_str(
1336 &mut ctx,
1337 "
1338 lambda fib_loop:
1339 <entry @input:i64>
1340 goto <head @hn=@input @a=0 @b=1>;
1341 <head @hn:i64 @a:i64 @b:i64>
1342 %done = @hn == 0;
1343 if %done goto <exit @r=@a> else goto <body @m=@hn @x=@a @y=@b>;
1344 <body @m:i64 @x:i64 @y:i64>
1345 %next = @x + @y;
1346 %m1 = @m - 1;
1347 goto <head @hn=%m1 @a=@y @b=%next>;
1348 <exit @r:i64>
1349 return @r;
1350 ",
1351 )
1352 .expect("lowers");
1353
1354 let fid = syms.function("fib_loop");
1355 let f = FunctionBody::from_id(&ctx, fid);
1356 assert!(f.is_lambda());
1357 let text = f.to_string();
1358 assert!(text.contains("lambda fib_loop"));
1359 assert!(text.contains("i64 @hn == i64 0x0"));
1360 assert!(text.contains("i64 @x + i64 @y"));
1361 }
1362
1363 #[test]
1369 fn rejects_cross_function_address_branch() {
1370 use std::borrow::Cow;
1371
1372 let mut ctx = Context::new();
1373 let g = FunctionBody::make_at_addr(&mut ctx, 0x2000, Some(Cow::Borrowed("g"))).id;
1375 let g_blk = BasicBlock::make(&mut ctx, g).with_address(0x2000).id;
1376 FunctionBody::from_id_mut(&mut ctx, g)
1377 .set_root(g_blk)
1378 .unwrap();
1379
1380 let err = lower_str(
1383 &mut ctx,
1384 "
1385 fn f:
1386 <entry>
1387 goto <0x2000>;
1388 ",
1389 )
1390 .expect_err("cross-function address goto must be rejected");
1391 assert!(
1392 err.contains("cross-function control flow"),
1393 "unexpected error: {err}"
1394 );
1395 }
1396
1397 #[test]
1398 fn rejects_cross_function_rootless_function_address_branch() {
1399 use std::borrow::Cow;
1400
1401 let mut ctx = Context::new();
1402 let g = FunctionBody::make_at_addr(&mut ctx, 0x2000, Some(Cow::Borrowed("g"))).id;
1403 assert!(FunctionBody::from_id(&ctx, g).root().is_none());
1404
1405 let err = lower_str(
1406 &mut ctx,
1407 "
1408 fn f:
1409 <entry>
1410 goto <0x2000>;
1411 ",
1412 )
1413 .expect_err("rootless foreign function address goto must be rejected");
1414 assert!(
1415 err.contains("cross-function control flow"),
1416 "unexpected error: {err}"
1417 );
1418 }
1419}