Skip to content

Commit e9a47be

Browse files
committed
array return values
Functions returning pointer types (arrays, structs, tuples) receive an output pointer as their first parameter (after globals if main) The callee copies the result to the output pointer using memcpy and returns void The caller allocates stack space and passes the address, then uses that address as the result This prevents dangling pointers since the result lives in the caller's stack frame
1 parent 8421da2 commit e9a47be

File tree

2 files changed

+131
-32
lines changed

2 files changed

+131
-32
lines changed

src/jit.rs

Lines changed: 112 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -206,32 +206,56 @@ impl JIT {
206206
// Get the block parameters (function arguments).
207207
let block_params: Vec<_> = builder.block_params(entry_block).to_vec();
208208

209+
// Track current parameter index.
210+
let mut param_idx = 0;
211+
209212
// If this function has globals, the first block param is the globals base pointer.
210-
let (globals_base, param_offset) = if has_globals {
211-
(Some(block_params[0]), 1)
213+
let globals_base = if has_globals {
214+
let base = block_params[param_idx];
215+
param_idx += 1;
216+
Some(base)
217+
} else {
218+
None
219+
};
220+
221+
// If return type is a pointer type, first param (after globals) is output pointer.
222+
let output_ptr = if returns_via_pointer(decl.ret) {
223+
let ptr = block_params[param_idx];
224+
param_idx += 1;
225+
Some(ptr)
212226
} else {
213-
(None, 0)
227+
None
214228
};
215229

216-
let mut trans = FunctionTranslator::new(builder, &mut self.module, &self.globals, globals_base);
230+
let mut trans = FunctionTranslator::new(builder, &mut self.module, &self.globals, globals_base, output_ptr);
217231

218232
// Add variables for the function parameters and define them with block param values.
219233
for (i, param) in decl.params.iter().enumerate() {
220234
let ty = param.ty.expect("expected type").cranelift_type();
221235
let var = trans.declare_variable(&param.name, ty);
222-
trans.builder.def_var(var, block_params[i + param_offset]);
236+
trans.builder.def_var(var, block_params[i + param_idx]);
223237
// Function parameters are like let bindings - they hold values directly.
224238
trans.let_bindings.insert(param.name.to_string());
225239
}
226240

227241
let result = trans.translate_fn(decl, decls);
228242

229243
// Need a return instruction at the end of the function's block.
230-
// If the function returns void, return nothing; otherwise return the body's result.
231-
if *decl.ret == crate::Type::Void {
232-
trans.builder.ins().return_(&[]);
233-
} else {
234-
trans.builder.ins().return_(&[result]);
244+
// Skip if the block is already unreachable (e.g., body ended with explicit return).
245+
if !trans.builder.is_unreachable() {
246+
if returns_via_pointer(decl.ret) {
247+
// Copy result to output pointer and return void.
248+
let output = trans.output_ptr.unwrap();
249+
let size = decl.ret.size(decls) as i64;
250+
let size_val = trans.builder.ins().iconst(I64, size);
251+
// Use memcpy to copy the data.
252+
trans.builder.call_memcpy(trans.module.target_config(), output, result, size_val);
253+
trans.builder.ins().return_(&[]);
254+
} else if *decl.ret == crate::Type::Void {
255+
trans.builder.ins().return_(&[]);
256+
} else {
257+
trans.builder.ins().return_(&[result]);
258+
}
235259
}
236260

237261
// Indicate we're finished with the function.
@@ -292,18 +316,29 @@ impl crate::Type {
292316
}
293317
}
294318

319+
/// Returns true if this type is returned via an output pointer parameter.
320+
fn returns_via_pointer(ty: crate::TypeID) -> bool {
321+
ty.is_ptr()
322+
}
323+
295324
fn fn_sig(module: &JITModule, from: crate::TypeID, to: crate::TypeID) -> Signature {
296325
let mut sig = module.make_signature();
326+
327+
// If return type is a pointer type (array, struct, tuple), add output pointer as first param.
328+
if returns_via_pointer(to) {
329+
sig.params.push(AbiParam::new(I64));
330+
}
331+
297332
if let crate::Type::Tuple(args) = &*from {
298-
sig.params = args
299-
.iter()
300-
.map(|t| AbiParam::new(t.cranelift_type()))
301-
.collect();
333+
for t in args.iter() {
334+
sig.params.push(AbiParam::new(t.cranelift_type()));
335+
}
302336
} else {
303337
panic!();
304338
}
305339

306-
if *to != crate::Type::Void {
340+
// Only add return value if not void and not returned via pointer.
341+
if *to != crate::Type::Void && !returns_via_pointer(to) {
307342
sig.returns = vec![AbiParam::new(to.cranelift_type())];
308343
}
309344
sig
@@ -331,10 +366,19 @@ struct FunctionTranslator<'a> {
331366

332367
/// Base pointer for globals (passed as first param to main).
333368
globals_base: Option<Value>,
369+
370+
/// Output pointer for functions returning via pointer (arrays, structs, tuples).
371+
output_ptr: Option<Value>,
334372
}
335373

336374
impl<'a> FunctionTranslator<'a> {
337-
fn new(builder: FunctionBuilder<'a>, module: &'a mut JITModule, globals: &'a HashMap<Name, i32>, globals_base: Option<Value>) -> Self {
375+
fn new(
376+
builder: FunctionBuilder<'a>,
377+
module: &'a mut JITModule,
378+
globals: &'a HashMap<Name, i32>,
379+
globals_base: Option<Value>,
380+
output_ptr: Option<Value>,
381+
) -> Self {
338382
Self {
339383
builder,
340384
variables: HashMap::new(),
@@ -345,6 +389,7 @@ impl<'a> FunctionTranslator<'a> {
345389
let_bindings: HashSet::new(),
346390
globals,
347391
globals_base,
392+
output_ptr,
348393
}
349394
}
350395

@@ -438,16 +483,37 @@ impl<'a> FunctionTranslator<'a> {
438483
Expr::Call(fn_id, arg_ids) => {
439484
let f = self.translate_expr(*fn_id, decl, decls);
440485

441-
let mut args = vec![];
442-
for arg_id in arg_ids {
443-
args.push(self.translate_expr(*arg_id, decl, decls))
444-
}
445-
446486
if let crate::Type::Func(from, to) = *(decl.types[*fn_id]) {
487+
let mut args = vec![];
488+
489+
// If return type is pointer, allocate stack space and pass as first arg.
490+
let output_slot = if returns_via_pointer(to) {
491+
let size = to.size(decls) as u32;
492+
let slot = self.builder.create_sized_stack_slot(StackSlotData {
493+
kind: StackSlotKind::ExplicitSlot,
494+
size,
495+
align_shift: 0,
496+
key: None,
497+
});
498+
let addr = self.builder.ins().stack_addr(I64, slot, 0);
499+
args.push(addr);
500+
Some(addr)
501+
} else {
502+
None
503+
};
504+
505+
for arg_id in arg_ids {
506+
args.push(self.translate_expr(*arg_id, decl, decls))
507+
}
508+
447509
let sig = fn_sig(&self.module, from, to);
448510
let sref = self.builder.import_signature(sig);
449511
let call = self.builder.ins().call_indirect(sref, f, &args);
450-
if let Some(result) = self.builder.inst_results(call).first() {
512+
513+
if let Some(addr) = output_slot {
514+
// Return the address of the output buffer.
515+
addr
516+
} else if let Some(result) = self.builder.inst_results(call).first() {
451517
*result
452518
} else {
453519
self.builder.ins().iconst(I32, 0)
@@ -538,18 +604,17 @@ impl<'a> FunctionTranslator<'a> {
538604
.iter()
539605
.map(|e| self.translate_expr(*e, decl, decls))
540606
.collect();
541-
542-
let ty = decl.types[expr];
543607

544-
if let crate::Type::Array(ty, _) = &*ty {
608+
let ty = decl.types[expr];
545609

546-
let sz = ty.size(decls) as u32;
547-
let element_size = sz / elements.len() as u32;
610+
if let crate::Type::Array(elem_ty, _) = &*ty {
611+
let element_size = elem_ty.size(decls) as u32;
612+
let total_size = element_size * elements.len() as u32;
548613

549-
// Allocate a new stack slot with a size of the variable.
614+
// Allocate a new stack slot with a size of the array.
550615
let slot = self.builder.create_sized_stack_slot(StackSlotData {
551616
kind: StackSlotKind::ExplicitSlot,
552-
size: sz,
617+
size: total_size,
553618
align_shift: 0,
554619
key: None,
555620
});
@@ -662,13 +727,28 @@ impl<'a> FunctionTranslator<'a> {
662727
}
663728
Expr::Return(expr_id) => {
664729
let result = self.translate_expr(*expr_id, decl, decls);
665-
self.builder.ins().return_(&[result]);
730+
let ret_ty = decl.types[*expr_id];
731+
732+
if returns_via_pointer(ret_ty) {
733+
// Copy result to output pointer and return void.
734+
let output = self.output_ptr.expect("output_ptr not set for pointer return");
735+
let size = ret_ty.size(decls) as i64;
736+
let size_val = self.builder.ins().iconst(I64, size);
737+
self.builder.call_memcpy(self.module.target_config(), output, result, size_val);
738+
self.builder.ins().return_(&[]);
739+
} else {
740+
self.builder.ins().return_(&[result]);
741+
}
742+
666743
// Create an unreachable block for any code after return.
667744
let unreachable_block = self.builder.create_block();
668745
self.builder.switch_to_block(unreachable_block);
669746
self.builder.seal_block(unreachable_block);
670-
// Return a dummy value - this code is unreachable.
671-
self.builder.ins().iconst(I32, 0)
747+
// Create a dummy value before adding trap (trap terminates the block).
748+
let dummy = self.builder.ins().iconst(I32, 0);
749+
// Add a trap - this code is unreachable.
750+
self.builder.ins().trap(TrapCode::user(1).unwrap());
751+
dummy
672752
}
673753
Expr::While(cond_id, body_id) => {
674754
// Create blocks for header, body, and exit.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
// args: -c
2+
// expected stdout:
3+
// compilation successful
4+
// assert(true)
5+
// assert(true)
6+
// assert(true)
7+
8+
assert(cond: bool) → void
9+
10+
ret_array_literal() → [i32; 3] {
11+
return [1,2,3]
12+
}
13+
14+
main {
15+
var a = ret_array_literal()
16+
assert(a[0] == 1)
17+
assert(a[1] == 2)
18+
assert(a[2] == 3)
19+
}

0 commit comments

Comments
 (0)