1use proc_macro::TokenStream;
9use proc_macro2::TokenStream as TS2;
10use quote::quote;
11use syn::{
12 parse_macro_input, spanned::Spanned, Attribute, BinOp, Expr, ExprCall, ExprMacro, Item, ItemFn,
13 ItemMod, ItemStatic, Pat, Stmt, Type, UnOp,
14};
15
16#[proc_macro_attribute]
17pub fn axiom_cell(_attr: TokenStream, item: TokenStream) -> TokenStream {
18 let module = parse_macro_input!(item as ItemMod);
19 match expand_cell(module) {
20 Ok(ts) => ts.into(),
21 Err(e) => e.to_compile_error().into(),
22 }
23}
24
25fn expand_cell(module: ItemMod) -> syn::Result<TS2> {
26 let mod_name = &module.ident;
27 let bytecode_fn = syn::Ident::new(&format!("{}_bytecode", mod_name), mod_name.span());
28 let selectors_fn = syn::Ident::new(&format!("{}_selectors", mod_name), mod_name.span());
29
30 let items = match &module.content {
31 Some((_, items)) => items,
32 None => {
33 return Err(syn::Error::new(
34 module.span(),
35 "#[axiom_cell] requires an inline module body",
36 ))
37 }
38 };
39
40 let mut storage_slots: Vec<StorageSlot> = vec![];
41 let mut selectors: Vec<SelectorFn> = vec![];
42 let mut helpers: Vec<HelperFn> = vec![];
43
44 for item in items {
45 match item {
46 Item::Static(s) if has_attr(&s.attrs, "storage") => {
47 storage_slots.push(parse_storage(s)?)
48 }
49 Item::Fn(f) if has_attr(&f.attrs, "selector") => selectors.push(parse_selector(f)?),
50 Item::Fn(f) if !has_attr(&f.attrs, "selector") && !has_attr(&f.attrs, "storage") => {
51 helpers.push(parse_helper(f)?);
52 }
53 _ => {}
54 }
55 }
56
57 let mut all_ir_code: Vec<TS2> = vec![];
58 let mut selector_entries: Vec<TS2> = vec![];
59
60 let mut next_vreg: u32 = 1; let mut next_label: u32 = 0;
64
65 for (_sel_idx, sel) in selectors.iter().enumerate() {
66 let sel_name = &sel.name;
67 selector_entries.push(quote! {
68 (#sel_name, truthlinked_axiom_sdk::abi::selector_of(#sel_name))
69 });
70
71 let handler_label = next_label;
72 next_label += 1;
73
74 let v_cd = next_vreg;
76 next_vreg += 1;
77 let v_sel = next_vreg;
78 next_vreg += 1;
79 let v_mask = next_vreg;
80 next_vreg += 1;
81 let v_mcd = next_vreg;
82 next_vreg += 1;
83 let v_cond = next_vreg;
84 next_vreg += 1;
85
86 let n_args = sel.args.len() as u32;
87 let arg_base = next_vreg;
88 next_vreg += n_args;
89 let off_base = next_vreg;
90 next_vreg += n_args;
91 let body_vreg_start = next_vreg;
92 next_vreg += 10_000; let label_start = next_label;
94 next_label += 10_000;
95
96 all_ir_code.push(quote! {
97 {
98 ir.push(Ir::LoadImm8(#v_cd, 0));
100 ir.push(Ir::GetCalldata(#v_cd, #v_cd));
101 let sel_bytes = truthlinked_axiom_sdk::abi::selector_of(#sel_name);
103 let mut sel32 = vec![0u8; 32];
104 sel32[..4].copy_from_slice(&sel_bytes);
105 ir.push(Ir::LoadConst(#v_sel, sel32));
106 let mut mask32 = vec![0u8; 32];
108 mask32[0]=0xFF; mask32[1]=0xFF; mask32[2]=0xFF; mask32[3]=0xFF;
109 ir.push(Ir::LoadConst(#v_mask, mask32));
110 ir.push(Ir::And(#v_mcd, #v_cd, #v_mask));
111 ir.push(Ir::Eq(#v_cond, #v_mcd, #v_sel));
112 ir.push(Ir::JumpIf(#v_cond, #handler_label));
113 }
114 });
115
116 let mut arg_loads: Vec<TS2> = vec![];
118 for i in 0..n_args {
119 let vreg = arg_base + i;
120 let off_vreg = off_base + i;
121 let offset = (4 + i * 32) as u64;
122 arg_loads.push(quote! {
123 ir.push(Ir::LoadImm64(#off_vreg, #offset));
124 ir.push(Ir::GetCalldata(#vreg, #off_vreg));
125 });
126 }
127
128 let arg_names: Vec<String> = sel.args.iter().map(|(n, _)| n.to_string()).collect();
130 let arg_vregs: Vec<u32> = (0..n_args).map(|i| arg_base + i).collect();
131
132 let body_code = lower_body_ir_full(
134 &sel.body,
135 &storage_slots,
136 &helpers,
137 &arg_names,
138 &arg_vregs,
139 body_vreg_start,
140 label_start,
141 );
142
143 let vreg_ceiling = body_vreg_start + 10_000;
144 let label_ceiling = label_start + 10_000;
145 all_ir_code.push(quote! {
146 ir.push(Ir::Label(#handler_label));
147 #(#arg_loads)*
148 {
149 let mut _vreg: u32 = #body_vreg_start;
150 let mut _label: u32 = #label_start + 1;
151 let mut _break_stack: Vec<u32> = Vec::new();
152 let mut _continue_stack: Vec<u32> = Vec::new();
153 #body_code
154 assert!(_vreg <= #vreg_ceiling,
155 "axiom_cell: selector '{}' used {} vregs, exceeds 500-slot limit (overflow into adjacent block)",
156 #sel_name, _vreg - #body_vreg_start);
157 assert!(_label <= #label_ceiling,
158 "axiom_cell: selector '{}' used {} labels, exceeds 500-slot limit",
159 #sel_name, _label - #label_start);
160 }
161 });
162 }
163
164 all_ir_code.push(quote! { ir.push(Ir::Trap(0x0001)); });
165
166 for helper in helpers.iter_mut() {
172 let fn_label_id = next_label;
173 next_label += 1;
174 let n_args = helper.args.len() as u32;
175 let h_arg_base = next_vreg;
176 next_vreg += n_args;
177 let ret_vreg = next_vreg;
178 next_vreg += 1;
179 next_vreg += 10_000; next_label += 10_000;
181 helper.label_id = fn_label_id;
182 helper.arg_vregs = (0..n_args).map(|i| h_arg_base + i).collect();
183 helper.ret_vreg = ret_vreg;
184 }
185
186 for helper in helpers.iter() {
188 let fn_label_id = helper.label_id;
189 let _fn_name = &helper.name;
190 let _n_args = helper.args.len() as u32;
191 let ret_vreg = helper.ret_vreg;
192 let body_vreg_start = helper.ret_vreg + 1;
193 let h_label_start = helper.label_id + 1;
194
195 let arg_names: Vec<String> = helper.args.iter().map(|(n, _)| n.to_string()).collect();
196 let arg_vregs = &helper.arg_vregs;
197
198 let body_code = lower_body_ir_full(
199 &helper.body,
200 &storage_slots,
201 &helpers,
202 &arg_names,
203 arg_vregs,
204 body_vreg_start,
205 h_label_start,
206 );
207
208 let h_vreg_ceiling = body_vreg_start + 10_000u32;
209 let h_label_ceiling = h_label_start + 10_000u32;
210 let fn_name_str = &helper.name;
211
212 all_ir_code.push(quote! {
213 ir.push(Ir::Label(#fn_label_id));
214 {
215 let mut _vreg: u32 = #body_vreg_start;
216 let mut _label: u32 = #h_label_start + 1;
217 let mut _break_stack: Vec<u32> = Vec::new();
218 let mut _continue_stack: Vec<u32> = Vec::new();
219 let _fn_ret_vreg: u32 = #ret_vreg;
220 #body_code
221 assert!(_vreg <= #h_vreg_ceiling,
222 "axiom_cell: helper fn '{}' used {} vregs, exceeds 500-slot limit",
223 #fn_name_str, _vreg - #body_vreg_start);
224 assert!(_label <= #h_label_ceiling,
225 "axiom_cell: helper fn '{}' used {} labels, exceeds 500-slot limit",
226 #fn_name_str, _label - #h_label_start);
227 }
228 ir.push(Ir::Return);
229 });
230 }
231
232 let clean_items: Vec<TS2> = items
233 .iter()
234 .filter_map(|item| match item {
235 Item::Static(s) if has_attr(&s.attrs, "storage") => None,
236 Item::Fn(f) if has_attr(&f.attrs, "selector") => None,
237 other => Some(quote! { #other }),
238 })
239 .collect();
240
241 let vis = &module.vis;
242 let mod_name2 = mod_name;
243
244 Ok(quote! {
245 #vis mod #mod_name2 {
246 #(#clean_items)*
247 }
248
249 pub fn #bytecode_fn() -> Vec<u8> {
250 use truthlinked_axiom_sdk::ir::Ir;
251 use truthlinked_axiom_sdk::regalloc;
252 use truthlinked_axiom_sdk::codegen;
253
254 let mut ir: Vec<Ir> = Vec::new();
255 #(#all_ir_code)*
256
257 let alloc = regalloc::allocate(&ir);
258 codegen::emit(&ir, &alloc)
259 }
260
261 pub fn #selectors_fn() -> Vec<(&'static str, [u8; 4])> {
262 vec![ #(#selector_entries),* ]
263 }
264 })
265}
266
267struct StorageSlot {
270 _name: syn::Ident,
271 _ty: Box<Type>,
272}
273struct SelectorFn {
274 name: String,
275 body: Vec<Stmt>,
276 args: Vec<(syn::Ident, Box<Type>)>,
277}
278struct HelperFn {
279 name: String,
280 body: Vec<Stmt>,
281 args: Vec<(syn::Ident, Box<Type>)>,
282 label_id: u32,
283 arg_vregs: Vec<u32>,
284 ret_vreg: u32,
285}
286
287fn has_attr(attrs: &[Attribute], name: &str) -> bool {
288 attrs.iter().any(|a| a.path().is_ident(name))
289}
290
291fn get_attr_str(attrs: &[Attribute], name: &str) -> Option<String> {
292 for attr in attrs {
293 if attr.path().is_ident(name) {
294 if let Ok(list) = attr.meta.require_list() {
295 if let Ok(lit) = syn::parse2::<syn::LitStr>(list.tokens.clone()) {
296 return Some(lit.value());
297 }
298 }
299 }
300 }
301 None
302}
303
304fn parse_storage(s: &ItemStatic) -> syn::Result<StorageSlot> {
305 Ok(StorageSlot {
306 _name: s.ident.clone(),
307 _ty: s.ty.clone(),
308 })
309}
310
311fn parse_selector(f: &ItemFn) -> syn::Result<SelectorFn> {
312 let name = get_attr_str(&f.attrs, "selector").ok_or_else(|| {
313 syn::Error::new(f.span(), "#[selector(\"name\")] requires a string literal")
314 })?;
315 let args = f
316 .sig
317 .inputs
318 .iter()
319 .filter_map(|arg| {
320 if let syn::FnArg::Typed(pt) = arg {
321 if let Pat::Ident(pi) = pt.pat.as_ref() {
322 return Some((pi.ident.clone(), pt.ty.clone()));
323 }
324 }
325 None
326 })
327 .collect();
328 Ok(SelectorFn {
329 name,
330 body: f.block.stmts.clone(),
331 args,
332 })
333}
334
335fn parse_helper(f: &ItemFn) -> syn::Result<HelperFn> {
336 let name = f.sig.ident.to_string();
337 let args = f
338 .sig
339 .inputs
340 .iter()
341 .filter_map(|arg| {
342 if let syn::FnArg::Typed(pt) = arg {
343 if let Pat::Ident(pi) = pt.pat.as_ref() {
344 return Some((pi.ident.clone(), pt.ty.clone()));
345 }
346 }
347 None
348 })
349 .collect();
350 Ok(HelperFn {
351 name,
352 body: f.block.stmts.clone(),
353 args,
354 label_id: 0,
355 arg_vregs: vec![],
356 ret_vreg: 0,
357 })
358}
359
360fn lower_body_ir_full(
364 stmts: &[Stmt],
365 slots: &[StorageSlot],
366 helpers: &[HelperFn],
367 arg_names: &[String],
368 arg_vregs: &[u32],
369 _vreg_start: u32,
370 _label_start: u32,
371) -> TS2 {
372 let arg_inserts: Vec<TS2> = arg_names
375 .iter()
376 .zip(arg_vregs.iter())
377 .map(|(name, vreg)| {
378 quote! { _vars.insert(#name.to_string(), #vreg); }
379 })
380 .collect();
381
382 let stmt_code: Vec<TS2> = stmts
383 .iter()
384 .map(|s| lower_stmt(s, slots, helpers))
385 .collect();
386
387 quote! {
388 let mut _vars: std::collections::HashMap<String, u32> = std::collections::HashMap::new();
389 #(#arg_inserts)*
390 #(#stmt_code)*
391 }
392}
393
394fn lower_stmt(stmt: &Stmt, slots: &[StorageSlot], helpers: &[HelperFn]) -> TS2 {
396 match stmt {
397 Stmt::Expr(Expr::Return(r), _) => {
399 if let Some(expr) = &r.expr {
400 if is_ok_unit(expr) {
401 return quote! { ir.push(Ir::Halt); };
402 }
403 if let Some(code) = is_err_code(expr) {
404 return quote! { ir.push(Ir::Trap(#code)); };
405 }
406 }
407 quote! { ir.push(Ir::Halt); }
408 }
409 Stmt::Expr(Expr::Macro(m), _) => lower_macro(m, helpers),
411 Stmt::Local(local) => lower_local(local, slots, helpers),
413 Stmt::Expr(expr, _) => lower_expr_stmt(expr, slots, helpers),
415 _ => quote! {},
416 }
417}
418
419fn lower_macro(m: &ExprMacro, helpers: &[HelperFn]) -> TS2 {
420 let path = m
421 .mac
422 .path
423 .segments
424 .last()
425 .map(|s| s.ident.to_string())
426 .unwrap_or_default();
427 match path.as_str() {
428 "require_owner" => quote! { ir.push(Ir::RequireOwner); },
429 "require" => {
430 if let Ok(expr) = syn::parse2::<Expr>(m.mac.tokens.clone()) {
431 let eval = lower_expr_to_vreg(&expr, quote! { _cond_r }, helpers);
432 quote! {
433 let _cond_r = { _vreg += 1; _vreg };
435 #eval
436 ir.push(Ir::RequireNonZero(_cond_r));
437 }
438 } else {
439 quote! { ir.push(Ir::RequireNonZero(3)); }
440 }
441 }
442 _ => quote! {},
443 }
444}
445
446fn lower_local(local: &syn::Local, _slots: &[StorageSlot], helpers: &[HelperFn]) -> TS2 {
448 let var_name = match &local.pat {
449 Pat::Ident(pi) => pi.ident.to_string(),
450 Pat::Type(pt) => match pt.pat.as_ref() {
451 Pat::Ident(pi) => pi.ident.to_string(),
452 _ => return quote! {},
453 },
454 _ => return quote! {},
455 };
456
457 if let Some(init) = &local.init {
458 let expr = &init.expr;
459 let eval = lower_expr_to_vreg(expr, quote! { _dst }, helpers);
460 quote! {
461 let _dst = { _vreg += 1; _vreg };
462 _vars.insert(#var_name.to_string(), _dst);
463 #eval
464 }
465 } else {
466 quote! {
467 let _dst = { _vreg += 1; _vreg };
468 _vars.insert(#var_name.to_string(), _dst);
469 }
470 }
471}
472
473fn lower_expr_stmt(expr: &Expr, slots: &[StorageSlot], helpers: &[HelperFn]) -> TS2 {
474 match expr {
475 Expr::Call(call) if is_path_call(call, &["storage", "set"]) => {
477 if let Some(key_name) = extract_ref_ident(&call.args) {
478 let key_str = key_name.to_string();
479 let val_vreg = if let Some(Expr::Path(p)) = call.args.iter().nth(1) {
481 if let Some(ident) = p.path.get_ident() {
482 let name = ident.to_string();
483 quote! { *_vars.get(#name).expect("undefined variable") }
484 } else {
485 quote! { 0u32 }
486 }
487 } else {
488 quote! { 0u32 }
489 };
490 return quote! {
491 let _kv = { _vreg += 1; _vreg };
492 let key = truthlinked_axiom_sdk::hashing::namespace(#key_str);
493 ir.push(Ir::LoadConst(_kv, key.to_vec()));
494 ir.push(Ir::SStore(_kv, #val_vreg));
495 };
496 }
497 quote! {}
498 }
499 Expr::If(ei) => lower_if_expr(ei, slots, helpers, None),
501 Expr::While(w) => {
503 let cond_eval = lower_expr_to_vreg(&w.cond, quote! { _while_cond }, helpers);
504 let body: Vec<TS2> = w
505 .body
506 .stmts
507 .iter()
508 .map(|s| lower_stmt(s, slots, helpers))
509 .collect();
510 quote! {{
511 let _while_cond = { _vreg += 1; _vreg };
512 let _loop_start = { _label += 1; _label };
513 let _loop_end = { _label += 1; _label };
514 _break_stack.push(_loop_end);
516 _continue_stack.push(_loop_start);
517 ir.push(Ir::Label(_loop_start));
518 #cond_eval
519 ir.push(Ir::JumpIfNot(_while_cond, _loop_end));
520 #(#body)*
521 ir.push(Ir::Jump(_loop_start));
522 ir.push(Ir::Label(_loop_end));
523 _break_stack.pop();
524 _continue_stack.pop();
525 }}
526 }
527 Expr::Loop(l) => {
529 let body: Vec<TS2> = l
530 .body
531 .stmts
532 .iter()
533 .map(|s| lower_stmt(s, slots, helpers))
534 .collect();
535 quote! {{
536 let _loop_start = { _label += 1; _label };
537 let _loop_end = { _label += 1; _label };
538 _break_stack.push(_loop_end);
539 _continue_stack.push(_loop_start);
540 ir.push(Ir::Label(_loop_start));
541 #(#body)*
542 ir.push(Ir::Jump(_loop_start));
543 ir.push(Ir::Label(_loop_end));
544 _break_stack.pop();
545 _continue_stack.pop();
546 }}
547 }
548 Expr::Break(_) => quote! {
550 ir.push(Ir::Jump(*_break_stack.last().expect("break outside loop")));
551 },
552 Expr::Continue(_) => quote! {
554 ir.push(Ir::Jump(*_continue_stack.last().expect("continue outside loop")));
555 },
556 Expr::Call(call) => {
558 let dst = quote! { { _vreg += 1; _vreg } };
559 lower_call_to_vreg(call, dst, slots, helpers)
560 }
561 _ => quote! {},
562 }
563}
564
565fn lower_expr_to_vreg(expr: &Expr, dst_expr: TS2, helpers: &[HelperFn]) -> TS2 {
567 match expr {
568 Expr::Path(p) if p.path.get_ident().is_some() => {
570 let name = p.path.get_ident().unwrap().to_string();
571 quote! {
572 {
573 let _src = *_vars.get(#name).expect(concat!("undefined variable: ", #name));
574 ir.push(Ir::Mov(#dst_expr, _src));
575 }
576 }
577 }
578 Expr::Lit(syn::ExprLit {
580 lit: syn::Lit::Int(i),
581 ..
582 }) => {
583 if let Ok(v) = i.base10_parse::<u64>() {
584 quote! { ir.push(Ir::LoadImm64(#dst_expr, #v)); }
585 } else {
586 quote! {}
587 }
588 }
589 Expr::Call(call) if is_path_call(call, &["context", "caller"]) => {
591 quote! { ir.push(Ir::GetCaller(#dst_expr)); }
592 }
593 Expr::Call(call) if is_path_call(call, &["context", "owner"]) => {
594 quote! { ir.push(Ir::GetOwner(#dst_expr)); }
595 }
596 Expr::Call(call) if is_path_call(call, &["context", "height"]) => {
597 quote! { ir.push(Ir::GetHeight(#dst_expr)); }
598 }
599 Expr::Call(call) if is_path_call(call, &["context", "value"]) => {
600 quote! { ir.push(Ir::GetValue(#dst_expr)); }
601 }
602 Expr::Call(call) if is_path_call(call, &["context", "timestamp"]) => {
603 quote! { ir.push(Ir::GetTimestamp(#dst_expr)); }
604 }
605 Expr::Call(call) if is_path_call(call, &["storage", "get"]) => {
607 if let Some(key_name) = extract_ref_ident(&call.args) {
608 let key_str = key_name.to_string();
609 quote! {
610 {
611 let _kv = { _vreg += 1; _vreg };
612 let key = truthlinked_axiom_sdk::hashing::namespace(#key_str);
613 ir.push(Ir::LoadConst(_kv, key.to_vec()));
614 ir.push(Ir::SLoad(#dst_expr, _kv));
615 }
616 }
617 } else {
618 quote! {}
619 }
620 }
621 Expr::Binary(bin) => {
623 let lhs_eval = lower_expr_to_vreg(&bin.left, quote! { _lhs_r }, helpers);
624 let rhs_eval = lower_expr_to_vreg(&bin.right, quote! { _rhs_r }, helpers);
625 let op_ir = match &bin.op {
626 BinOp::Add(_) => quote! { ir.push(Ir::Add(#dst_expr, _lhs_r, _rhs_r)); },
627 BinOp::Sub(_) => quote! { ir.push(Ir::Sub(#dst_expr, _lhs_r, _rhs_r)); },
628 BinOp::Mul(_) => quote! { ir.push(Ir::Mul(#dst_expr, _lhs_r, _rhs_r)); },
629 BinOp::Div(_) => quote! { ir.push(Ir::Div(#dst_expr, _lhs_r, _rhs_r)); },
630 BinOp::Rem(_) => quote! { ir.push(Ir::Mod(#dst_expr, _lhs_r, _rhs_r)); },
631 BinOp::BitAnd(_) => quote! { ir.push(Ir::And(#dst_expr, _lhs_r, _rhs_r)); },
632 BinOp::BitOr(_) => quote! { ir.push(Ir::Or(#dst_expr, _lhs_r, _rhs_r)); },
633 BinOp::BitXor(_) => quote! { ir.push(Ir::Xor(#dst_expr, _lhs_r, _rhs_r)); },
634 BinOp::Eq(_) => quote! { ir.push(Ir::Eq(#dst_expr, _lhs_r, _rhs_r)); },
635 BinOp::Ne(_) => quote! { ir.push(Ir::Ne(#dst_expr, _lhs_r, _rhs_r)); },
636 BinOp::Lt(_) => quote! { ir.push(Ir::Lt(#dst_expr, _lhs_r, _rhs_r)); },
637 BinOp::Le(_) => quote! { ir.push(Ir::Lte(#dst_expr, _lhs_r, _rhs_r)); },
638 BinOp::Gt(_) => quote! { ir.push(Ir::Gt(#dst_expr, _lhs_r, _rhs_r)); },
639 BinOp::Ge(_) => quote! { ir.push(Ir::Gte(#dst_expr, _lhs_r, _rhs_r)); },
640 _ => quote! {},
641 };
642 quote! {{
644 let _lhs_r = { _vreg += 1; _vreg };
645 let _rhs_r = { _vreg += 1; _vreg };
646 #lhs_eval
647 #rhs_eval
648 #op_ir
649 }}
650 }
651 Expr::Unary(u) if matches!(u.op, UnOp::Not(_)) => {
653 let inner_eval = lower_expr_to_vreg(&u.expr, quote! { _inner_r }, helpers);
654 quote! {{
655 let _inner_r = { _vreg += 1; _vreg };
656 #inner_eval
657 ir.push(Ir::IsZero(#dst_expr, _inner_r));
658 }}
659 }
660 Expr::If(ei) => lower_if_expr(ei, &[], helpers, Some(dst_expr)),
662 Expr::Paren(p) => lower_expr_to_vreg(&p.expr, dst_expr, helpers),
664 _ => quote! {},
665 }
666}
667
668fn lower_if_expr(
671 ei: &syn::ExprIf,
672 slots: &[StorageSlot],
673 helpers: &[HelperFn],
674 result_vreg: Option<TS2>,
675) -> TS2 {
676 let cond_eval = lower_expr_to_vreg(&ei.cond, quote! { _cond_if }, helpers);
677
678 let lower_branch = |stmts: &[Stmt], helpers: &[HelperFn]| -> TS2 {
682 if stmts.is_empty() {
683 return quote! {};
684 }
685 let (last, rest) = stmts.split_last().unwrap();
686 let rest_code: Vec<TS2> = rest.iter().map(|s| lower_stmt(s, slots, helpers)).collect();
687 let last_code = if let Some(ref rv) = result_vreg {
688 match last {
690 Stmt::Expr(expr, None) => {
691 let eval = lower_expr_to_vreg(expr, quote! { _branch_res }, helpers);
692 quote! {
693 let _branch_res = { _vreg += 1; _vreg };
694 #eval
695 ir.push(Ir::Mov(#rv, _branch_res));
696 }
697 }
698 _ => lower_stmt(last, slots, helpers),
699 }
700 } else {
701 lower_stmt(last, slots, helpers)
702 };
703 quote! { #(#rest_code)* #last_code }
704 };
705
706 let then_code = lower_branch(&ei.then_branch.stmts, helpers);
707
708 let else_code = if let Some((_, else_expr)) = &ei.else_branch {
709 match else_expr.as_ref() {
710 Expr::Block(b) => lower_branch(&b.block.stmts, helpers),
711 Expr::If(nested) => lower_if_expr(nested, slots, helpers, result_vreg.clone()),
712 _ => quote! {},
713 }
714 } else {
715 quote! {}
716 };
717
718 quote! {{
719 let _cond_if = { _vreg += 1; _vreg };
720 #cond_eval
721 let _else_lbl = { _label += 1; _label };
722 let _end_lbl = { _label += 1; _label };
723 ir.push(Ir::JumpIfNot(_cond_if, _else_lbl));
724 #then_code
725 ir.push(Ir::Jump(_end_lbl));
726 ir.push(Ir::Label(_else_lbl));
727 #else_code
728 ir.push(Ir::Label(_end_lbl));
729 }}
730}
731
732fn lower_call_to_vreg(
733 call: &ExprCall,
734 dst: TS2,
735 _slots: &[StorageSlot],
736 helpers: &[HelperFn],
737) -> TS2 {
738 if is_path_call(call, &["context", "caller"]) {
739 return quote! { let _d=#dst; ir.push(Ir::GetCaller(_d)); };
740 }
741 if is_path_call(call, &["context", "owner"]) {
742 return quote! { let _d=#dst; ir.push(Ir::GetOwner(_d)); };
743 }
744 if is_path_call(call, &["context", "height"]) {
745 return quote! { let _d=#dst; ir.push(Ir::GetHeight(_d)); };
746 }
747 if is_path_call(call, &["context", "value"]) {
748 return quote! { let _d=#dst; ir.push(Ir::GetValue(_d)); };
749 }
750 if is_path_call(call, &["storage", "get"]) {
751 if let Some(key_name) = extract_ref_ident(&call.args) {
752 let key_str = key_name.to_string();
753 return quote! {
754 let _d = #dst;
755 let _kv = { _vreg += 1; _vreg };
756 let key = truthlinked_axiom_sdk::hashing::namespace(#key_str);
757 ir.push(Ir::LoadConst(_kv, key.to_vec()));
758 ir.push(Ir::SLoad(_d, _kv));
759 };
760 }
761 }
762 if let Expr::Path(p) = call.func.as_ref() {
764 if let Some(fn_ident) = p.path.get_ident() {
765 let fn_name = fn_ident.to_string();
766 if let Some(helper) = helpers.iter().find(|h| h.name == fn_name) {
767 let fn_label_id = helper.label_id;
768 let ret_vreg = helper.ret_vreg;
769 let arg_loads: Vec<TS2> = call
770 .args
771 .iter()
772 .zip(helper.arg_vregs.iter())
773 .map(|(arg_expr, &arg_vreg)| {
774 let eval = lower_expr_to_vreg(arg_expr, quote! { #arg_vreg }, helpers);
775 quote! { #eval }
776 })
777 .collect();
778 return quote! {
779 #(#arg_loads)*
780 ir.push(Ir::Call(#fn_label_id));
781 let _call_dst = #dst;
782 ir.push(Ir::Mov(_call_dst, #ret_vreg));
783 };
784 }
785 }
786 }
787 quote! {}
788}
789
790fn is_path_call(call: &ExprCall, path: &[&str]) -> bool {
793 if let Expr::Path(p) = call.func.as_ref() {
794 let segs: Vec<_> = p
795 .path
796 .segments
797 .iter()
798 .map(|s| s.ident.to_string())
799 .collect();
800 let want: Vec<String> = path.iter().map(|s| s.to_string()).collect();
801 return segs.ends_with(&want);
802 }
803 false
804}
805
806fn extract_ref_ident(
807 args: &syn::punctuated::Punctuated<Expr, syn::Token![,]>,
808) -> Option<&syn::Ident> {
809 if let Some(Expr::Reference(r)) = args.first() {
810 if let Expr::Path(p) = r.expr.as_ref() {
811 return p.path.get_ident();
812 }
813 }
814 None
815}
816
817fn is_ok_unit(expr: &Expr) -> bool {
818 if let Expr::Call(c) = expr {
819 if let Expr::Path(p) = c.func.as_ref() {
820 if p.path.is_ident("Ok") {
821 if let Some(Expr::Tuple(t)) = c.args.first() {
822 return t.elems.is_empty();
823 }
824 }
825 }
826 }
827 false
828}
829
830fn is_err_code(expr: &Expr) -> Option<u16> {
831 if let Expr::Call(c) = expr {
832 if let Expr::Path(p) = c.func.as_ref() {
833 if p.path.is_ident("Err") {
834 if let Some(Expr::Lit(syn::ExprLit {
835 lit: syn::Lit::Int(i),
836 ..
837 })) = c.args.first()
838 {
839 let s = i.to_string();
841 let v: Option<u16> = if s.starts_with("0x") || s.starts_with("0X") {
842 u16::from_str_radix(&s[2..], 16).ok()
843 } else {
844 s.parse().ok()
845 };
846 if let Some(code) = v {
847 return Some(code);
848 }
849 }
850 return Some(0x0002); }
852 }
853 }
854 None
855}
856
857#[proc_macro_attribute]
858pub fn storage(_attr: TokenStream, item: TokenStream) -> TokenStream {
859 item
860}
861
862#[proc_macro_attribute]
863pub fn selector(_attr: TokenStream, item: TokenStream) -> TokenStream {
864 item
865}