Skip to content

Commit

Permalink
allow return values from ABI functions (#257)
Browse files Browse the repository at this point in the history
Co-authored-by: Alexander Hansen <[email protected]>
  • Loading branch information
sezna and Alexander Hansen authored Sep 22, 2021
1 parent 6a3c580 commit 083535a
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 71 deletions.
26 changes: 7 additions & 19 deletions core_lang/src/asm_generation/expression/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -461,26 +461,14 @@ pub(crate) fn convert_abi_fn_to_asm<'sc>(

asm_buf.append(&mut body);
// return the value from the abi function
asm_buf.push(Op {
// TODO we are just returning zero for now and not supporting return values from abi
// functions
opcode: Either::Left(VirtualOp::RET(VirtualRegister::Constant(
ConstantRegister::Zero,
))),
owning_span: None,
comment: format!("{} abi fn return", decl.name.primary_name),
});
asm_buf.append(&mut check!(
ret_or_retd_value(&decl, return_register, register_sequencer, &mut namespace),
return err(warnings, errors),
warnings,
errors
));

parent_namespace.data_section = namespace.data_section;
// because we are not supporting return values right now, throw an error if the function
// returns anything.
if decl.return_type != MaybeResolvedType::Resolved(ResolvedType::Unit)
&& decl.return_type != MaybeResolvedType::Resolved(ResolvedType::ErrorRecovery)
{
errors.push(CompileError::Unimplemented(
"ABI function return values are not yet implemented",
decl.return_type_span.clone(),
));
}

// the return value is already put in its proper register via the above statement, so the buf
// is done
Expand Down
125 changes: 78 additions & 47 deletions core_lang/src/asm_generation/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -633,53 +633,17 @@ pub(crate) fn compile_ast_to_asm<'sc>(
errors
);
asm_buf.append(&mut body);
if main_function.return_type == MaybeResolvedType::Resolved(ResolvedType::Unit) {
asm_buf.push(Op {
owning_span: None,
opcode: Either::Left(VirtualOp::RET(VirtualRegister::Constant(
ConstantRegister::Zero,
))),
comment: "main fn returns unit value".into(),
});
} else {
let size_of_main_func_return_bytes = check!(
main_function.return_type.force_resolution(
&MaybeResolvedType::Resolved(ResolvedType::Unit),
&main_function.return_type_span
),
return err(warnings, errors),
warnings,
errors
)
.stack_size_of()
* 8;
if size_of_main_func_return_bytes == 8 {
asm_buf.push(Op {
owning_span: None,
opcode: Either::Left(VirtualOp::RET(return_register)),
comment: "main fn return value".into(),
});
} else {
// if the type is larger than one word, then we use RETD to return data
// RB is the size_in_bytes
let rb_register = register_sequencer.next();
let size_bytes =
namespace.insert_data_value(&Literal::U64(size_of_main_func_return_bytes));
// `return_register` is $rA
asm_buf.push(Op {
opcode: Either::Left(VirtualOp::LWDataId(rb_register.clone(), size_bytes)),
owning_span: Some(main_function.return_type_span),
comment: "loading rB for RETD".into(),
});

// now $rB has the size of the type in bytes
asm_buf.push(Op {
owning_span: None,
opcode: Either::Left(VirtualOp::RETD(return_register, rb_register)),
comment: "main fn return value".into(),
});
}
}
asm_buf.append(&mut check!(
ret_or_retd_value(
&main_function,
return_register,
&mut register_sequencer,
&mut namespace
),
return err(warnings, errors),
warnings,
errors
));

HllAsmSet::ScriptMain {
program_section: AbstractInstructionSet { ops: asm_buf },
Expand Down Expand Up @@ -1258,3 +1222,70 @@ fn load_coin_color<'sc>(return_register: VirtualRegister) -> Op<'sc> {
owning_span: None,
}
}

/// Given a [TypedFunctionDeclaration] and a `return_register`, return
/// the return value of the function using either a `RET` or a `RETD` opcode.
fn ret_or_retd_value<'sc>(
func: &TypedFunctionDeclaration<'sc>,
return_register: VirtualRegister,
register_sequencer: &mut RegisterSequencer,
namespace: &mut AsmNamespace<'sc>,
) -> CompileResult<'sc, Vec<Op<'sc>>> {
let mut errors = vec![];
let mut warnings = vec![];
let mut asm_buf = vec![];
let main_func_ret_ty = check!(
func.return_type.force_resolution(
&MaybeResolvedType::Resolved(ResolvedType::Unit),
&func.return_type_span
),
return err(warnings, errors),
warnings,
errors
);

if main_func_ret_ty == ResolvedType::Unit {
// unit returns should always be zero, although because they can be
// omitted from functions, the register is sometimes uninitialized.
// Manually return zero in this case.
return ok(
vec![Op {
opcode: Either::Left(VirtualOp::RET(VirtualRegister::Constant(
ConstantRegister::Zero,
))),
owning_span: Some(func.return_type_span.clone()),
comment: format!("fn {} returns unit", func.name.primary_name),
}],
warnings,
errors,
);
}

let size_of_main_func_return_bytes = main_func_ret_ty.stack_size_of() * 8;
if size_of_main_func_return_bytes <= 8 {
asm_buf.push(Op {
owning_span: None,
opcode: Either::Left(VirtualOp::RET(return_register)),
comment: format!("{} fn return value", func.name.primary_name),
});
} else {
// if the type is larger than one word, then we use RETD to return data
// RB is the size_in_bytes
let rb_register = register_sequencer.next();
let size_bytes = namespace.insert_data_value(&Literal::U64(size_of_main_func_return_bytes));
// `return_register` is $rA
asm_buf.push(Op {
opcode: Either::Left(VirtualOp::LWDataId(rb_register.clone(), size_bytes)),
owning_span: Some(func.return_type_span.clone()),
comment: "loading rB for RETD".into(),
});

// now $rB has the size of the type in bytes
asm_buf.push(Op {
owning_span: None,
opcode: Either::Left(VirtualOp::RETD(return_register, rb_register)),
comment: format!("{} fn return value", func.name.primary_name),
});
}
ok(asm_buf, warnings, errors)
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,26 @@ contract;
struct InputStruct { field_1: bool, field_2: u64 }

abi MyContract {
fn foo(gas: u64, coin: u64, color: b256, input: InputStruct);
fn foo(gas: u64, coin: u64, color: b256, input: InputStruct) -> InputStruct;
} {
fn baz(gas: u64, coin: u64, color: b256, input: bool) { }
}


impl MyContract for Contract {
fn foo(gas: u64, coin: u64, color: b256, input: InputStruct) {
fn foo(gas: u64, coin: u64, color: b256, input: InputStruct) -> InputStruct {
let status_code = if input.field_1 { "okay" } else { "fail" };
calls_other_contract();
calls_other_contract()
}
}

fn calls_other_contract() {
fn calls_other_contract() -> InputStruct {
let x = abi(MyContract, 0x0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000);
// commenting this out for now since contract call asm generation is not yet implemented
let color = 0x0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000_0000;
let input = InputStruct {
field_1: true,
field_2: 3,
};
x.foo(5, 5, color, input);
x.foo(5, 5, color, input)
}

0 comments on commit 083535a

Please sign in to comment.