@@ -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 ) ]
72115pub 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 ) ?
0 commit comments