Skip to content

Commit 1ea16a2

Browse files
MarcIlungaclaude
andcommitted
Add translator support for local word memory ops, locaddr, and u32div
Unblocks translation of sha256.masm (and other files using local word memory). Adds LocLoadWBe, LocLoadWLe, LocStoreWBe, LocStoreWLe, Locaddr, U32Div, and U32DivImm to the instruction translator, stack effect metadata, and multi-instruction count list. Includes 17 new tests (positive translations, negative constant errors, stack effects, and instruction count verification). Note: The Lean model (Instruction.lean, Semantics.lean) needs matching constructors and handlers added separately for the generated Lean to compile. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 37124ea commit 1ea16a2

File tree

3 files changed

+192
-1
lines changed

3 files changed

+192
-1
lines changed

masm-to-lean/src/instruction.rs

Lines changed: 102 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -311,6 +311,11 @@ pub fn translate_instruction(inst: &Instruction) -> Result<String> {
311311
U32WideningMadd => ".u32WidenMadd".into(),
312312
U32WrappingMadd => ".u32WrappingMadd".into(),
313313

314+
U32Div => ".u32Div".into(),
315+
U32DivImm(imm) => {
316+
let v = u32_imm(imm)?;
317+
return Ok(format!(".inst (.push {v}), .inst .u32Div"));
318+
}
314319
U32DivMod => ".u32DivMod".into(),
315320
U32DivModImm(imm) => {
316321
let v = u32_imm(imm)?;
@@ -366,6 +371,11 @@ pub fn translate_instruction(inst: &Instruction) -> Result<String> {
366371
// Procedure locals
367372
LocLoad(idx) => format!(".locLoad {}", u16_imm(idx)?),
368373
LocStore(idx) => format!(".locStore {}", u16_imm(idx)?),
374+
LocLoadWBe(idx) => format!(".locLoadwBe {}", u16_imm(idx)?),
375+
LocLoadWLe(idx) => format!(".locLoadwLe {}", u16_imm(idx)?),
376+
LocStoreWBe(idx) => format!(".locStorewBe {}", u16_imm(idx)?),
377+
LocStoreWLe(idx) => format!(".locStorewLe {}", u16_imm(idx)?),
378+
Locaddr(idx) => format!(".locaddr {}", u16_imm(idx)?),
369379

370380
// Advice stack
371381
AdvPush(imm) => match imm {
@@ -400,11 +410,102 @@ pub fn translate_instruction(inst: &Instruction) -> Result<String> {
400410
#[cfg(test)]
401411
mod tests {
402412
use super::translate_instruction;
403-
use miden_assembly_syntax::ast::Instruction;
413+
use miden_assembly_syntax::ast::{ImmU16, ImmU32, Instruction};
414+
use miden_assembly_syntax::debuginfo::Span;
404415

405416
#[test]
406417
fn translates_u32_wrapping_madd() {
407418
let lean = translate_instruction(&Instruction::U32WrappingMadd).unwrap();
408419
assert_eq!(lean, ".u32WrappingMadd");
409420
}
421+
422+
// --- Local word memory instructions ---
423+
424+
#[test]
425+
fn translates_loc_loadw_be() {
426+
let imm = ImmU16::Value(Span::unknown(4u16));
427+
let lean = translate_instruction(&Instruction::LocLoadWBe(imm)).unwrap();
428+
assert_eq!(lean, ".locLoadwBe 4");
429+
}
430+
431+
#[test]
432+
fn translates_loc_loadw_le() {
433+
let imm = ImmU16::Value(Span::unknown(8u16));
434+
let lean = translate_instruction(&Instruction::LocLoadWLe(imm)).unwrap();
435+
assert_eq!(lean, ".locLoadwLe 8");
436+
}
437+
438+
#[test]
439+
fn translates_loc_storew_be() {
440+
let imm = ImmU16::Value(Span::unknown(0u16));
441+
let lean = translate_instruction(&Instruction::LocStoreWBe(imm)).unwrap();
442+
assert_eq!(lean, ".locStorewBe 0");
443+
}
444+
445+
#[test]
446+
fn translates_loc_storew_le() {
447+
let imm = ImmU16::Value(Span::unknown(12u16));
448+
let lean = translate_instruction(&Instruction::LocStoreWLe(imm)).unwrap();
449+
assert_eq!(lean, ".locStorewLe 12");
450+
}
451+
452+
#[test]
453+
fn translates_locaddr() {
454+
let imm = ImmU16::Value(Span::unknown(32u16));
455+
let lean = translate_instruction(&Instruction::Locaddr(imm)).unwrap();
456+
assert_eq!(lean, ".locaddr 32");
457+
}
458+
459+
#[test]
460+
fn translates_loc_loadw_be_zero_index() {
461+
let imm = ImmU16::Value(Span::unknown(0u16));
462+
let lean = translate_instruction(&Instruction::LocLoadWBe(imm)).unwrap();
463+
assert_eq!(lean, ".locLoadwBe 0");
464+
}
465+
466+
// --- U32Div instructions ---
467+
468+
#[test]
469+
fn translates_u32_div() {
470+
let lean = translate_instruction(&Instruction::U32Div).unwrap();
471+
assert_eq!(lean, ".u32Div");
472+
}
473+
474+
#[test]
475+
fn translates_u32_div_imm() {
476+
let imm = ImmU32::Value(Span::unknown(4u32));
477+
let lean = translate_instruction(&Instruction::U32DivImm(imm)).unwrap();
478+
// Should expand to push + op (multi-instruction)
479+
assert_eq!(lean, ".inst (.push 4), .inst .u32Div");
480+
}
481+
482+
// --- Negative: constant immediates should error ---
483+
484+
#[test]
485+
fn loc_loadw_be_constant_errors() {
486+
let imm = ImmU16::Constant(
487+
miden_assembly_syntax::ast::Ident::new("SOME_CONST").unwrap(),
488+
);
489+
let result = translate_instruction(&Instruction::LocLoadWBe(imm));
490+
assert!(result.is_err());
491+
assert!(result.unwrap_err().to_string().contains("unresolved constant"));
492+
}
493+
494+
#[test]
495+
fn locaddr_constant_errors() {
496+
let imm = ImmU16::Constant(
497+
miden_assembly_syntax::ast::Ident::new("OFFSET").unwrap(),
498+
);
499+
let result = translate_instruction(&Instruction::Locaddr(imm));
500+
assert!(result.is_err());
501+
}
502+
503+
#[test]
504+
fn u32_div_imm_constant_errors() {
505+
let imm = ImmU32::Constant(
506+
miden_assembly_syntax::ast::Ident::new("DIVISOR").unwrap(),
507+
);
508+
let result = translate_instruction(&Instruction::U32DivImm(imm));
509+
assert!(result.is_err());
510+
}
410511
}

masm-to-lean/src/instruction_info.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -990,6 +990,11 @@ pub fn instruction_info(inst: &Instruction) -> InstructionInfo {
990990
info.comment_name = "u32Div".into();
991991
info.is_known = true;
992992
}
993+
U32DivImm(_) => {
994+
info.stack_effect = Some(StackEffect::new(1, 1));
995+
info.comment_name = "u32Div (imm)".into();
996+
info.is_known = true;
997+
}
993998
U32Mod => {
994999
info.stack_effect = Some(StackEffect::new(2, 1));
9951000
info.comment_name = "u32Mod".into();
@@ -1199,6 +1204,31 @@ pub fn instruction_info(inst: &Instruction) -> InstructionInfo {
11991204
info.comment_name = "locStore".into();
12001205
info.is_known = true;
12011206
}
1207+
LocLoadWBe(_) | LocLoadWLe(_) => {
1208+
// Overwrites top 4 elements with word from local memory
1209+
info.stack_effect = Some(StackEffect::with_depth(4, 4, 4));
1210+
info.comment_name = if matches!(inst, LocLoadWBe(_)) {
1211+
"locLoadwBe".into()
1212+
} else {
1213+
"locLoadwLe".into()
1214+
};
1215+
info.is_known = true;
1216+
}
1217+
LocStoreWBe(_) | LocStoreWLe(_) => {
1218+
// Stores top 4 elements to local memory (keeps them on stack)
1219+
info.stack_effect = Some(StackEffect::with_depth(0, 0, 4));
1220+
info.comment_name = if matches!(inst, LocStoreWBe(_)) {
1221+
"locStorewBe".into()
1222+
} else {
1223+
"locStorewLe".into()
1224+
};
1225+
info.is_known = true;
1226+
}
1227+
Locaddr(_) => {
1228+
info.stack_effect = Some(StackEffect::new(0, 1));
1229+
info.comment_name = "locaddr".into();
1230+
info.is_known = true;
1231+
}
12021232

12031233
// === Advice stack ===
12041234
AdvPush(imm) => {

masm-to-lean/src/stack_effect.rs

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ impl<'a> StackSimulator<'a> {
191191
| Instruction::U32WrappingSubImm(_)
192192
| Instruction::U32WideningMulImm(_)
193193
| Instruction::U32WrappingMulImm(_)
194+
| Instruction::U32DivImm(_)
194195
| Instruction::U32DivModImm(_)
195196
| Instruction::U32ModImm(_)
196197
) {
@@ -466,6 +467,65 @@ mod tests {
466467
assert_eq!(effect.net_effect, -1);
467468
}
468469

470+
#[test]
471+
fn test_instruction_effect_loc_loadw_be() {
472+
use miden_assembly_syntax::ast::ImmU16;
473+
let imm = ImmU16::Value(Span::unknown(4u16));
474+
let effect = instruction_effect(&Instruction::LocLoadWBe(imm)).unwrap();
475+
// Overwrites top 4 elements with word from local memory
476+
assert_eq!(effect.pops, 4);
477+
assert_eq!(effect.pushes, 4);
478+
assert_eq!(effect.required_depth, 4);
479+
}
480+
481+
#[test]
482+
fn test_instruction_effect_loc_storew_be() {
483+
use miden_assembly_syntax::ast::ImmU16;
484+
let imm = ImmU16::Value(Span::unknown(0u16));
485+
let effect = instruction_effect(&Instruction::LocStoreWBe(imm)).unwrap();
486+
// Stores top 4 elements, keeps them on stack
487+
assert_eq!(effect.pops, 0);
488+
assert_eq!(effect.pushes, 0);
489+
assert_eq!(effect.required_depth, 4);
490+
}
491+
492+
#[test]
493+
fn test_instruction_effect_locaddr() {
494+
use miden_assembly_syntax::ast::ImmU16;
495+
let imm = ImmU16::Value(Span::unknown(32u16));
496+
let effect = instruction_effect(&Instruction::Locaddr(imm)).unwrap();
497+
// Pushes one address onto the stack
498+
assert_eq!(effect.pops, 0);
499+
assert_eq!(effect.pushes, 1);
500+
}
501+
502+
#[test]
503+
fn test_instruction_effect_u32_div() {
504+
let effect = instruction_effect(&Instruction::U32Div).unwrap();
505+
assert_eq!(effect.pops, 2);
506+
assert_eq!(effect.pushes, 1);
507+
}
508+
509+
#[test]
510+
fn test_instruction_effect_u32_div_imm() {
511+
use miden_assembly_syntax::ast::ImmU32;
512+
let imm = ImmU32::Value(Span::unknown(4u32));
513+
let effect = instruction_effect(&Instruction::U32DivImm(imm)).unwrap();
514+
// Imm variant: consumes 1 (the dividend), produces 1 (quotient)
515+
assert_eq!(effect.pops, 1);
516+
assert_eq!(effect.pushes, 1);
517+
}
518+
519+
#[test]
520+
fn test_u32_div_imm_counts_as_two_instructions() {
521+
use miden_assembly_syntax::ast::ImmU32;
522+
let imm = ImmU32::Value(Span::unknown(4u32));
523+
let block = make_block(vec![Instruction::U32DivImm(imm)]);
524+
let effect = analyze_block(&block);
525+
// U32DivImm expands to push + u32Div = 2 instructions
526+
assert_eq!(effect.instruction_count, 2);
527+
}
528+
469529
#[test]
470530
fn test_analyze_block_with_callees() {
471531
// Block with exec that calls a known procedure

0 commit comments

Comments
 (0)