Skip to content

Commit 6bbba27

Browse files
0xrinegadeclaude
andcommitted
feat(compiler): Extend IR with struct operations and debug info
Add compiler IR extensions: - debug.rs: Enhanced debug information generation - ir.rs: Struct field access, memory operations, type annotations - regalloc_analyzer.rs: Register allocation analysis improvements These support upcoming struct-aware code generation for OVSM programs. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent 48f2d2b commit 6bbba27

File tree

3 files changed

+312
-0
lines changed

3 files changed

+312
-0
lines changed

crates/ovsm/src/compiler/debug.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,13 @@ pub fn format_ir_instr(instr: &IrInstruction) -> String {
5151
IrInstruction::Move(dst, src) => format!("r{} = r{}", dst.0, src.0),
5252

5353
IrInstruction::Load(dst, base, off) => format!("r{} = [r{} + {}]", dst.0, base.0, off),
54+
IrInstruction::Load1(dst, base, off) => format!("r{} = (u8)[r{} + {}]", dst.0, base.0, off),
55+
IrInstruction::Load2(dst, base, off) => format!("r{} = (u16)[r{} + {}]", dst.0, base.0, off),
56+
IrInstruction::Load4(dst, base, off) => format!("r{} = (u32)[r{} + {}]", dst.0, base.0, off),
5457
IrInstruction::Store(base, src, off) => format!("[r{} + {}] = r{}", base.0, off, src.0),
58+
IrInstruction::Store1(base, src, off) => format!("(u8)[r{} + {}] = r{}", base.0, off, src.0),
59+
IrInstruction::Store2(base, src, off) => format!("(u16)[r{} + {}] = r{}", base.0, off, src.0),
60+
IrInstruction::Store4(base, src, off) => format!("(u32)[r{} + {}] = r{}", base.0, off, src.0),
5561

5662
IrInstruction::Label(name) => format!("{}:", name),
5763
IrInstruction::Jump(target) => format!("jmp {}", target),

crates/ovsm/src/compiler/ir.rs

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,20 @@ impl FieldType {
4949
_ => None,
5050
}
5151
}
52+
53+
/// Convert to Anchor IDL type string
54+
pub fn to_idl_type(&self) -> &'static str {
55+
match self {
56+
FieldType::U8 => "u8",
57+
FieldType::U16 => "u16",
58+
FieldType::U32 => "u32",
59+
FieldType::U64 => "u64",
60+
FieldType::I8 => "i8",
61+
FieldType::I16 => "i16",
62+
FieldType::I32 => "i32",
63+
FieldType::I64 => "i64",
64+
}
65+
}
5266
}
5367

5468
/// A field in a struct definition
@@ -67,6 +81,35 @@ pub struct StructDef {
6781
pub total_size: i64,
6882
}
6983

84+
impl StructDef {
85+
/// Generate Anchor IDL JSON for this struct
86+
/// This enables TypeScript clients to interact with OVSM programs
87+
pub fn to_anchor_idl(&self) -> String {
88+
let mut fields_json = Vec::new();
89+
for field in &self.fields {
90+
fields_json.push(format!(
91+
r#" {{ "name": "{}", "type": "{}" }}"#,
92+
field.name,
93+
field.field_type.to_idl_type()
94+
));
95+
}
96+
97+
format!(
98+
r#"{{
99+
"name": "{}",
100+
"type": {{
101+
"kind": "struct",
102+
"fields": [
103+
{}
104+
]
105+
}}
106+
}}"#,
107+
self.name,
108+
fields_json.join(",\n")
109+
)
110+
}
111+
}
112+
70113
/// Virtual register (infinite supply, mapped to physical during codegen)
71114
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
72115
pub struct IrReg(pub u32);
@@ -704,6 +747,239 @@ impl IrGenerator {
704747
}
705748
}
706749

750+
// Handle (struct-ptr StructName base_ptr field_name)
751+
// Returns a pointer to a field, useful for nested structs or arrays
752+
// Example: (struct-ptr MyState state_ptr inner_struct)
753+
if name == "struct-ptr" && args.len() == 3 {
754+
if let (Expression::Variable(struct_name), Expression::Variable(field_name)) =
755+
(&args[0].value, &args[2].value)
756+
{
757+
let struct_def = self.struct_defs.get(struct_name)
758+
.ok_or_else(|| Error::runtime(format!("Unknown struct '{}'", struct_name)))?
759+
.clone();
760+
761+
let field = struct_def.fields.iter()
762+
.find(|f| &f.name == field_name)
763+
.ok_or_else(|| Error::runtime(format!(
764+
"Unknown field '{}' in struct '{}'", field_name, struct_name
765+
)))?;
766+
767+
let base_reg = self.generate_expr(&args[1].value)?
768+
.ok_or_else(|| Error::runtime("struct-ptr base_ptr has no result"))?;
769+
770+
let dst = self.alloc_reg();
771+
let offset_reg = self.alloc_reg();
772+
self.emit(IrInstruction::ConstI64(offset_reg, field.offset));
773+
self.emit(IrInstruction::Add(dst, base_reg, offset_reg));
774+
775+
return Ok(Some(dst));
776+
}
777+
}
778+
779+
// Handle (struct-offset StructName field_name)
780+
// Returns the compile-time offset of a field (no base pointer needed)
781+
// Example: (struct-offset MyState counter) => 0
782+
if name == "struct-offset" && args.len() == 2 {
783+
if let (Expression::Variable(struct_name), Expression::Variable(field_name)) =
784+
(&args[0].value, &args[1].value)
785+
{
786+
let struct_def = self.struct_defs.get(struct_name)
787+
.ok_or_else(|| Error::runtime(format!("Unknown struct '{}'", struct_name)))?
788+
.clone();
789+
790+
let field = struct_def.fields.iter()
791+
.find(|f| &f.name == field_name)
792+
.ok_or_else(|| Error::runtime(format!(
793+
"Unknown field '{}' in struct '{}'", field_name, struct_name
794+
)))?;
795+
796+
let dst = self.alloc_reg();
797+
self.emit(IrInstruction::ConstI64(dst, field.offset));
798+
return Ok(Some(dst));
799+
}
800+
}
801+
802+
// Handle (struct-field-size StructName field_name)
803+
// Returns the size of a specific field
804+
// Example: (struct-field-size MyState counter) => 4
805+
if name == "struct-field-size" && args.len() == 2 {
806+
if let (Expression::Variable(struct_name), Expression::Variable(field_name)) =
807+
(&args[0].value, &args[1].value)
808+
{
809+
let struct_def = self.struct_defs.get(struct_name)
810+
.ok_or_else(|| Error::runtime(format!("Unknown struct '{}'", struct_name)))?
811+
.clone();
812+
813+
let field = struct_def.fields.iter()
814+
.find(|f| &f.name == field_name)
815+
.ok_or_else(|| Error::runtime(format!(
816+
"Unknown field '{}' in struct '{}'", field_name, struct_name
817+
)))?;
818+
819+
let dst = self.alloc_reg();
820+
self.emit(IrInstruction::ConstI64(dst, field.field_type.size()));
821+
return Ok(Some(dst));
822+
}
823+
}
824+
825+
// Handle (struct-idl StructName)
826+
// Prints the Anchor IDL JSON for a struct at compile time
827+
// Example: (struct-idl MyState) => prints JSON and returns 0
828+
if name == "struct-idl" && args.len() == 1 {
829+
if let Expression::Variable(struct_name) = &args[0].value {
830+
let struct_def = self.struct_defs.get(struct_name)
831+
.ok_or_else(|| Error::runtime(format!("Unknown struct '{}'", struct_name)))?
832+
.clone();
833+
834+
// Print the IDL at compile time
835+
eprintln!("📋 Anchor IDL for struct '{}':", struct_name);
836+
eprintln!("{}", struct_def.to_anchor_idl());
837+
838+
// Return 0 at runtime (this is a compile-time-only operation)
839+
let dst = self.alloc_reg();
840+
self.emit(IrInstruction::ConstI64(dst, 0));
841+
return Ok(Some(dst));
842+
}
843+
}
844+
845+
// =============================================================
846+
// BORSH SERIALIZATION HELPERS
847+
// =============================================================
848+
// Borsh uses little-endian format which is what x86/sBPF uses natively
849+
// Our struct-get/set already produce the correct Borsh-compatible layout
850+
851+
// Handle (borsh-serialize StructName src_ptr dst_buffer offset)
852+
// Serializes struct fields to a buffer in Borsh format
853+
// Returns the number of bytes written
854+
if name == "borsh-serialize" && args.len() >= 3 {
855+
if let Expression::Variable(struct_name) = &args[0].value {
856+
let struct_def = self.struct_defs.get(struct_name)
857+
.ok_or_else(|| Error::runtime(format!("Unknown struct '{}'", struct_name)))?
858+
.clone();
859+
860+
let src_ptr = self.generate_expr(&args[1].value)?
861+
.ok_or_else(|| Error::runtime("borsh-serialize src_ptr has no result"))?;
862+
863+
let dst_buffer = self.generate_expr(&args[2].value)?
864+
.ok_or_else(|| Error::runtime("borsh-serialize dst_buffer has no result"))?;
865+
866+
// Optional offset argument (defaults to 0)
867+
let base_offset = if args.len() >= 4 {
868+
match &args[3].value {
869+
Expression::IntLiteral(n) => *n,
870+
_ => 0,
871+
}
872+
} else {
873+
0
874+
};
875+
876+
// Copy each field from struct to buffer using native endianness (LE)
877+
for field in &struct_def.fields {
878+
let field_offset = field.offset;
879+
let dst_offset = base_offset + field_offset;
880+
881+
// Load from source struct
882+
let temp_reg = self.alloc_reg();
883+
match field.field_type {
884+
FieldType::U8 | FieldType::I8 => {
885+
self.emit(IrInstruction::Load1(temp_reg, src_ptr, field_offset));
886+
self.emit(IrInstruction::Store1(dst_buffer, temp_reg, dst_offset));
887+
}
888+
FieldType::U16 | FieldType::I16 => {
889+
self.emit(IrInstruction::Load2(temp_reg, src_ptr, field_offset));
890+
self.emit(IrInstruction::Store2(dst_buffer, temp_reg, dst_offset));
891+
}
892+
FieldType::U32 | FieldType::I32 => {
893+
self.emit(IrInstruction::Load4(temp_reg, src_ptr, field_offset));
894+
self.emit(IrInstruction::Store4(dst_buffer, temp_reg, dst_offset));
895+
}
896+
FieldType::U64 | FieldType::I64 => {
897+
self.emit(IrInstruction::Load(temp_reg, src_ptr, field_offset));
898+
self.emit(IrInstruction::Store(dst_buffer, temp_reg, dst_offset));
899+
}
900+
}
901+
}
902+
903+
// Return the number of bytes written (total struct size)
904+
let dst = self.alloc_reg();
905+
self.emit(IrInstruction::ConstI64(dst, struct_def.total_size));
906+
return Ok(Some(dst));
907+
}
908+
}
909+
910+
// Handle (borsh-deserialize StructName src_buffer dst_ptr offset)
911+
// Deserializes buffer to struct fields in Borsh format
912+
// Returns the number of bytes read
913+
if name == "borsh-deserialize" && args.len() >= 3 {
914+
if let Expression::Variable(struct_name) = &args[0].value {
915+
let struct_def = self.struct_defs.get(struct_name)
916+
.ok_or_else(|| Error::runtime(format!("Unknown struct '{}'", struct_name)))?
917+
.clone();
918+
919+
let src_buffer = self.generate_expr(&args[1].value)?
920+
.ok_or_else(|| Error::runtime("borsh-deserialize src_buffer has no result"))?;
921+
922+
let dst_ptr = self.generate_expr(&args[2].value)?
923+
.ok_or_else(|| Error::runtime("borsh-deserialize dst_ptr has no result"))?;
924+
925+
// Optional offset argument (defaults to 0)
926+
let base_offset = if args.len() >= 4 {
927+
match &args[3].value {
928+
Expression::IntLiteral(n) => *n,
929+
_ => 0,
930+
}
931+
} else {
932+
0
933+
};
934+
935+
// Copy each field from buffer to struct using native endianness (LE)
936+
for field in &struct_def.fields {
937+
let field_offset = field.offset;
938+
let src_offset = base_offset + field_offset;
939+
940+
// Load from source buffer
941+
let temp_reg = self.alloc_reg();
942+
match field.field_type {
943+
FieldType::U8 | FieldType::I8 => {
944+
self.emit(IrInstruction::Load1(temp_reg, src_buffer, src_offset));
945+
self.emit(IrInstruction::Store1(dst_ptr, temp_reg, field_offset));
946+
}
947+
FieldType::U16 | FieldType::I16 => {
948+
self.emit(IrInstruction::Load2(temp_reg, src_buffer, src_offset));
949+
self.emit(IrInstruction::Store2(dst_ptr, temp_reg, field_offset));
950+
}
951+
FieldType::U32 | FieldType::I32 => {
952+
self.emit(IrInstruction::Load4(temp_reg, src_buffer, src_offset));
953+
self.emit(IrInstruction::Store4(dst_ptr, temp_reg, field_offset));
954+
}
955+
FieldType::U64 | FieldType::I64 => {
956+
self.emit(IrInstruction::Load(temp_reg, src_buffer, src_offset));
957+
self.emit(IrInstruction::Store(dst_ptr, temp_reg, field_offset));
958+
}
959+
}
960+
}
961+
962+
// Return the number of bytes read (total struct size)
963+
let dst = self.alloc_reg();
964+
self.emit(IrInstruction::ConstI64(dst, struct_def.total_size));
965+
return Ok(Some(dst));
966+
}
967+
}
968+
969+
// Handle (borsh-size StructName)
970+
// Returns the serialized size of a struct (same as struct-size for fixed-size structs)
971+
if name == "borsh-size" && args.len() == 1 {
972+
if let Expression::Variable(struct_name) = &args[0].value {
973+
let total_size = self.struct_defs.get(struct_name)
974+
.ok_or_else(|| Error::runtime(format!("Unknown struct '{}'", struct_name)))?
975+
.total_size;
976+
977+
let dst = self.alloc_reg();
978+
self.emit(IrInstruction::ConstI64(dst, total_size));
979+
return Ok(Some(dst));
980+
}
981+
}
982+
707983
// Handle (get array index) - array/object access
708984
if name == "get" && args.len() == 2 {
709985
let base_reg = self.generate_expr(&args[0].value)?

crates/ovsm/src/compiler/regalloc_analyzer.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,11 +465,41 @@ impl RegAllocAnalyzer {
465465
uses.push(*base);
466466
desc = format!("Load R{} = [R{}+{}]", dst.0, base.0, offset);
467467
}
468+
IrInstruction::Load1(dst, base, offset) => {
469+
defs.push(*dst);
470+
uses.push(*base);
471+
desc = format!("Load1 R{} = (u8)[R{}+{}]", dst.0, base.0, offset);
472+
}
473+
IrInstruction::Load2(dst, base, offset) => {
474+
defs.push(*dst);
475+
uses.push(*base);
476+
desc = format!("Load2 R{} = (u16)[R{}+{}]", dst.0, base.0, offset);
477+
}
478+
IrInstruction::Load4(dst, base, offset) => {
479+
defs.push(*dst);
480+
uses.push(*base);
481+
desc = format!("Load4 R{} = (u32)[R{}+{}]", dst.0, base.0, offset);
482+
}
468483
IrInstruction::Store(base, src, offset) => {
469484
uses.push(*base);
470485
uses.push(*src);
471486
desc = format!("Store [R{}+{}] = R{}", base.0, offset, src.0);
472487
}
488+
IrInstruction::Store1(base, src, offset) => {
489+
uses.push(*base);
490+
uses.push(*src);
491+
desc = format!("Store1 (u8)[R{}+{}] = R{}", base.0, offset, src.0);
492+
}
493+
IrInstruction::Store2(base, src, offset) => {
494+
uses.push(*base);
495+
uses.push(*src);
496+
desc = format!("Store2 (u16)[R{}+{}] = R{}", base.0, offset, src.0);
497+
}
498+
IrInstruction::Store4(base, src, offset) => {
499+
uses.push(*base);
500+
uses.push(*src);
501+
desc = format!("Store4 (u32)[R{}+{}] = R{}", base.0, offset, src.0);
502+
}
473503
IrInstruction::Alloc(dst, size) => {
474504
defs.push(*dst);
475505
uses.push(*size);

0 commit comments

Comments
 (0)