Skip to content

Commit 90fff8d

Browse files
committed
feat: implement Display trait support for DynamicBox opaque types
Enables proper Display trait formatting for opaque types (like Tensor) by storing and calling Display trait implementation function pointers in DynamicBox. Key changes: - Fix Display trait lookup in cranelift_backend to handle both HirType::Opaque and HirType::Ptr(Opaque) types when creating DynamicBox - Add display_fn field initialization in zrtl.rs for primitive DynamicBox creation - Extend ZRTL plugin macro to support 'opaque' return type syntax in signatures - Add opaque return type signatures to tensor plugin functions - Fix format_dynamic_box in zrtl_io to properly call display_fn function pointer - Add arange shorthand to ZynML grammar @Builtin and @types directives - Change ZynPEG runtime defaults from I32 to Type::Any for proper type inference Result: Tensors now print as "tensor([1.0, 2.0, 3.0], shape=[3], dtype=f32)" instead of "<opaque ...>" hex dumps when @types directive properly declares return types. Note: Type::Any resolution still needs proper implementation in the type resolver/checker. Currently works for cases where @types directive provides explicit type information.
1 parent 5387788 commit 90fff8d

9 files changed

Lines changed: 218 additions & 52 deletions

File tree

crates/compiler/src/cranelift_backend.rs

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -979,15 +979,21 @@ impl CraneliftBackend {
979979
.copied()
980980
.unwrap_or(false);
981981
log::debug!("[DynamicBox] Symbol: {}, param {}: needs_boxing = {}", symbol_name, param_index, needs_boxing);
982+
eprintln!("[DEBUG Boxing] Symbol: {}, param {}: needs_boxing = {}", symbol_name, param_index, needs_boxing);
983+
if let Some(sig) = self.symbol_signatures.get(symbol_name) {
984+
eprintln!("[DEBUG Signature] Symbol: {}, returns_dynamic = {}", symbol_name, sig.returns_dynamic());
985+
} else {
986+
eprintln!("[DEBUG Signature] Symbol: {} has no signature", symbol_name);
987+
}
982988
if needs_boxing {
983989
// This parameter expects DynamicBox - wrap it
984990
// For opaque types (i64 pointer), we need to create a DynamicBox struct
985-
// DynamicBox layout: { tag: u32, size: u32, data: i64, dropper: i64 }
991+
// DynamicBox layout: { tag: u32, size: u32, data: i64, dropper: i64, display_fn: i64 }
986992

987-
// Allocate stack space for DynamicBox (24 bytes on 64-bit)
993+
// Allocate stack space for DynamicBox (32 bytes on 64-bit)
988994
let slot = builder.create_sized_stack_slot(cranelift_codegen::ir::StackSlotData::new(
989995
cranelift_codegen::ir::StackSlotKind::ExplicitSlot,
990-
24,
996+
32,
991997
));
992998
let box_addr = builder.ins().stack_addr(types::I64, slot, 0);
993999

@@ -1006,6 +1012,74 @@ impl CraneliftBackend {
10061012
let null_dropper = builder.ins().iconst(types::I64, 0);
10071013
builder.ins().store(cranelift_codegen::ir::MemFlags::new(), null_dropper, box_addr, 16);
10081014

1015+
// Set display_fn - check if this opaque type implements Display trait
1016+
// Convention: Display::to_string is at symbol {type_name}$to_string
1017+
let display_fn_value = {
1018+
// Get the HIR value to check if it's an opaque type
1019+
let arg_hir_id = args[param_index];
1020+
eprintln!("[DEBUG DynamicBox] Checking arg_hir_id: {:?}", arg_hir_id);
1021+
eprintln!("[DEBUG DynamicBox] args list: {:?}", args);
1022+
if let Some(hir_value) = function.values.get(&arg_hir_id) {
1023+
eprintln!("[DEBUG DynamicBox] HIR value: {:?}", hir_value);
1024+
eprintln!("[DEBUG DynamicBox] HIR value type: {:?}", hir_value.ty);
1025+
1026+
// Extract opaque type name, handling both Opaque and Ptr(Opaque(...))
1027+
let opaque_name = match &hir_value.ty {
1028+
HirType::Opaque(type_name) => Some(type_name),
1029+
HirType::Ptr(inner) => {
1030+
if let HirType::Opaque(type_name) = inner.as_ref() {
1031+
Some(type_name)
1032+
} else {
1033+
None
1034+
}
1035+
}
1036+
_ => None,
1037+
};
1038+
1039+
if let Some(type_name) = opaque_name {
1040+
// Extract the type name string
1041+
let type_name_str = type_name.resolve_global()
1042+
.unwrap_or_else(|| String::new());
1043+
eprintln!("[DEBUG DynamicBox] Opaque type name: {}", type_name_str);
1044+
1045+
if !type_name_str.is_empty() {
1046+
// Construct Display method symbol: {type_name}$to_string
1047+
let display_symbol = format!("{}$to_string", type_name_str);
1048+
eprintln!("[DEBUG DynamicBox] Looking for Display symbol: {}", display_symbol);
1049+
1050+
// Look up in runtime symbols
1051+
if let Some((_, func_ptr)) = self.runtime_symbols
1052+
.iter()
1053+
.find(|(name, _)| name == &display_symbol)
1054+
{
1055+
eprintln!("[DEBUG DynamicBox] Found Display impl: {} -> {:p}", display_symbol, *func_ptr);
1056+
// Found Display implementation!
1057+
builder.ins().iconst(types::I64, *func_ptr as i64)
1058+
} else {
1059+
eprintln!("[DEBUG DynamicBox] No Display impl found for: {}", display_symbol);
1060+
// No Display implementation
1061+
builder.ins().iconst(types::I64, 0)
1062+
}
1063+
} else {
1064+
eprintln!("[DEBUG DynamicBox] Empty type name");
1065+
// Empty type name
1066+
builder.ins().iconst(types::I64, 0)
1067+
}
1068+
} else {
1069+
eprintln!("[DEBUG DynamicBox] Not an opaque type (or Ptr to opaque)");
1070+
// Not an opaque type
1071+
builder.ins().iconst(types::I64, 0)
1072+
}
1073+
} else {
1074+
eprintln!("[DEBUG DynamicBox] HIR value not found");
1075+
// HIR value not found
1076+
builder.ins().iconst(types::I64, 0)
1077+
}
1078+
};
1079+
eprintln!("[DEBUG DynamicBox] Storing display_fn at offset 24");
1080+
builder.ins().store(cranelift_codegen::ir::MemFlags::new(), display_fn_value, box_addr, 24);
1081+
eprintln!("[DEBUG DynamicBox] DynamicBox complete, passing to function");
1082+
10091083
// Pass the box by value (load struct fields and pass as args)
10101084
// Actually, DynamicBox is passed by value, so we need to load the struct
10111085
// For now, pass the pointer to the struct (this might need ABI adjustment)

crates/compiler/src/zrtl.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1259,6 +1259,7 @@ pub struct DynamicBoxRepr {
12591259
pub size: u32, // Size in bytes
12601260
pub data: *mut u8,
12611261
pub dropper: Option<extern "C" fn(*mut u8)>,
1262+
pub display_fn: Option<extern "C" fn(*const u8) -> *const u8>,
12621263
}
12631264

12641265
/// Convert compiler closure to ZrtlClosure
@@ -1414,6 +1415,7 @@ pub unsafe extern "C" fn zyntax_primitive_to_box(
14141415
size,
14151416
data,
14161417
dropper: Some(default_box_dropper),
1418+
display_fn: None,
14171419
});
14181420

14191421
Box::into_raw(boxed)
@@ -1428,6 +1430,7 @@ pub unsafe extern "C" fn zyntax_box_i32(value: i32) -> *mut DynamicBoxRepr {
14281430
size: 4,
14291431
data,
14301432
dropper: Some(drop_box_i32),
1433+
display_fn: None,
14311434
});
14321435
Box::into_raw(boxed)
14331436
}
@@ -1441,6 +1444,7 @@ pub unsafe extern "C" fn zyntax_box_i64(value: i64) -> *mut DynamicBoxRepr {
14411444
size: 8,
14421445
data,
14431446
dropper: Some(drop_box_i64),
1447+
display_fn: None,
14441448
});
14451449
Box::into_raw(boxed)
14461450
}
@@ -1454,6 +1458,7 @@ pub unsafe extern "C" fn zyntax_box_f32(value: f32) -> *mut DynamicBoxRepr {
14541458
size: 4,
14551459
data,
14561460
dropper: Some(drop_box_f32),
1461+
display_fn: None,
14571462
});
14581463
Box::into_raw(boxed)
14591464
}
@@ -1467,6 +1472,7 @@ pub unsafe extern "C" fn zyntax_box_f64(value: f64) -> *mut DynamicBoxRepr {
14671472
size: 8,
14681473
data,
14691474
dropper: Some(drop_box_f64),
1475+
display_fn: None,
14701476
});
14711477
Box::into_raw(boxed)
14721478
}
@@ -1480,6 +1486,7 @@ pub unsafe extern "C" fn zyntax_box_bool(value: i32) -> *mut DynamicBoxRepr {
14801486
size: 1,
14811487
data,
14821488
dropper: Some(drop_box_u8),
1489+
display_fn: None,
14831490
});
14841491
Box::into_raw(boxed)
14851492
}

crates/zyn_peg/src/runtime.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1638,8 +1638,8 @@ impl AstHostFunctions for TypedAstBuilder {
16381638
fn create_param(&mut self, name: &str, ty: NodeHandle) -> NodeHandle {
16391639
// Store parameter name and type for later use in create_function
16401640
let handle = self.alloc_handle();
1641-
// Get the type from the type handle, default to i32 if not found
1642-
let param_type = self.get_type_from_handle(ty).unwrap_or(Type::Primitive(PrimitiveType::I32));
1641+
// Get the type from the type handle, default to Any if not found
1642+
let param_type = self.get_type_from_handle(ty).unwrap_or(Type::Any);
16431643
// IMPORTANT: Register parameter type IMMEDIATELY so that variable references
16441644
// in the function body (which is parsed after params but before create_function is called)
16451645
// can find the correct type
@@ -1731,10 +1731,10 @@ impl AstHostFunctions for TypedAstBuilder {
17311731
let span = self.default_span();
17321732

17331733
// Look up the variable's actual type from our tracking map
1734-
// If not found, default to I32
1734+
// If not found, default to Any (will be resolved during type inference)
17351735
let var_type = self.variable_types.get(name)
17361736
.cloned()
1737-
.unwrap_or(Type::Primitive(PrimitiveType::I32));
1737+
.unwrap_or(Type::Any);
17381738

17391739
eprintln!("[DEBUG create_identifier] Variable '{}' has type {:?}", name, var_type);
17401740
let expr = self.inner.variable(name, var_type, span);
@@ -1777,10 +1777,10 @@ impl AstHostFunctions for TypedAstBuilder {
17771777
"f64" | "F64" => Type::Primitive(PrimitiveType::F64),
17781778
"bool" | "Bool" => Type::Primitive(PrimitiveType::Bool),
17791779
"void" | "Void" | "()" => Type::Primitive(PrimitiveType::Unit),
1780-
_ => Type::Primitive(PrimitiveType::I32), // Default for unknown types
1780+
_ => Type::Any, // Use Any for unknown types - will be resolved during type inference
17811781
}
17821782
}
1783-
None => Type::Primitive(PrimitiveType::I32), // Default when no return type specified
1783+
None => Type::Any, // Use Any when no return type specified - will be resolved during type inference
17841784
};
17851785

17861786
let expr = self.inner.call_positional(callee_expr, arg_exprs, ty, span);
@@ -1850,7 +1850,7 @@ impl AstHostFunctions for TypedAstBuilder {
18501850
// Infer type from initializer expression if available
18511851
let var_type = init_expr.as_ref()
18521852
.map(|expr| expr.ty.clone())
1853-
.unwrap_or(Type::Primitive(PrimitiveType::I32));
1853+
.unwrap_or(Type::Any);
18541854

18551855
// Register the variable type for later lookup
18561856
self.variable_types.insert(name.to_string(), var_type.clone());

crates/zynml/ml.zyn

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
tensor_zeros: "$Tensor$zeros",
3636
tensor_ones: "$Tensor$ones",
3737
tensor_full: "$Tensor$full_f32",
38+
arange: "$Tensor$arange_f32", // arange(1.0, 10.0, 1.0) shorthand
3839
tensor_arange: "$Tensor$arange_f32",
3940
tensor_linspace: "$Tensor$linspace_f32",
4041
tensor_rand: "$Tensor$rand_f32",
@@ -202,6 +203,7 @@
202203
tensor_zeros: $Tensor,
203204
tensor_ones: $Tensor,
204205
tensor_full: $Tensor,
206+
arange: $Tensor, // Shorthand for tensor_arange
205207
tensor_arange: $Tensor,
206208
tensor_linspace: $Tensor,
207209
tensor_rand: $Tensor,

plugins/zrtl_io/src/lib.rs

Lines changed: 31 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -514,7 +514,7 @@ unsafe fn format_dynamic_box(value: &zrtl::DynamicBox, output: &mut String) {
514514

515515
TypeCategory::Tuple => {
516516
// Tuple is stored as consecutive DynamicBox values
517-
// Size tells us total bytes, each DynamicBox is 24 bytes (on 64-bit)
517+
// Size tells us total bytes, each DynamicBox is 32 bytes (on 64-bit)
518518
let num_elements = value.size as usize / std::mem::size_of::<zrtl::DynamicBox>();
519519
output.push('(');
520520
let elements = value.data as *const zrtl::DynamicBox;
@@ -602,17 +602,37 @@ unsafe fn format_dynamic_box(value: &zrtl::DynamicBox, output: &mut String) {
602602
}
603603

604604
TypeCategory::Opaque | TypeCategory::Custom => {
605-
// For opaque/custom types, show hex dump of first few bytes
606-
output.push_str("<opaque ");
607-
let num_bytes = (value.size as usize).min(16);
608-
for i in 0..num_bytes {
609-
let byte = *value.data.add(i);
610-
let _ = write!(output, "{:02x}", byte);
611-
}
612-
if value.size > 16 {
613-
let _ = write!(output, "... ({} bytes total)", value.size);
605+
// Check if this opaque type has a Display trait implementation
606+
if let Some(display_fn) = value.display_fn {
607+
// Call the display function to format the value
608+
let formatted_ptr = display_fn(value.data as *const u8);
609+
if !formatted_ptr.is_null() {
610+
// Display function returned a ZRTL string pointer
611+
// Cast from *const u8 to StringConstPtr (*const i32)
612+
let str_ptr = formatted_ptr as StringConstPtr;
613+
if let Some(formatted_str) = zrtl::string_as_str(str_ptr) {
614+
output.push_str(formatted_str);
615+
} else {
616+
output.push_str("<opaque (display invalid utf8)>");
617+
}
618+
// The string is owned by the display function, don't free it here
619+
} else {
620+
// Display function returned null - fall back to hex dump
621+
output.push_str("<opaque (display failed)>");
622+
}
623+
} else {
624+
// No Display trait - show hex dump of first few bytes
625+
output.push_str("<opaque ");
626+
let num_bytes = (value.size as usize).min(16);
627+
for i in 0..num_bytes {
628+
let byte = *value.data.add(i);
629+
let _ = write!(output, "{:02x}", byte);
630+
}
631+
if value.size > 16 {
632+
let _ = write!(output, "... ({} bytes total)", value.size);
633+
}
634+
output.push('>');
614635
}
615-
output.push('>');
616636
}
617637
}
618638
}

0 commit comments

Comments
 (0)