diff --git a/cranelift/filetests/isa/riscv/legalize-abi.cton b/cranelift/filetests/isa/riscv/legalize-abi.cton index bf93596128..eada8ebda9 100644 --- a/cranelift/filetests/isa/riscv/legalize-abi.cton +++ b/cranelift/filetests/isa/riscv/legalize-abi.cton @@ -14,6 +14,50 @@ ebb0(v0: i64): return v1 } +function split_call_arg(i32) { + fn1 = function foo(i64) + fn2 = function foo(i32, i64) +ebb0(v0: i32): + v1 = uextend.i64 v0 + call fn1(v1) + ; check: $(v1l=$V), $(v1h=$V) = isplit_lohi $v1 + ; check: call $fn1($v1l, $v1h) + call fn2(v0, v1) + ; check: call $fn2($v0, $V, $V) + return +} + +function split_ret_val() { + fn1 = function foo() -> i64 +ebb0: + v1 = call fn1() + ; check: $ebb0: + ; nextln: $(v1l=$V), $(v1h=$V) = call $fn1() + ; check: $(v1new=$V) = iconcat_lohi $v1l, $v1h + ; check: $v1 = copy $v1new + jump ebb1(v1) + ; check: jump $ebb1($v1) + +ebb1(v10: i64): + jump ebb1(v10) +} + +; First return value is fine, second one is expanded. +function split_ret_val2() { + fn1 = function foo() -> i32, i64 +ebb0: + v1, v2 = call fn1() + ; check: $ebb0: + ; nextln: $v1, $(v2l=$V), $(v2h=$V) = call $fn1() + ; check: $(v2new=$V) = iconcat_lohi $v2l, $v2h + ; check: $v2 -> $v2new + jump ebb1(v1, v2) + ; check: jump $ebb1($v1, $v2) + +ebb1(v9: i32, v10: i64): + jump ebb1(v9, v10) +} + function int_ext(i8, i8 sext, i8 uext) -> i8 uext { ebb0(v1: i8, v2: i8, v3: i8): ; check: $ebb0($v1: i8, $(v2x=$V): i32, $(v3x=$V): i32): @@ -24,6 +68,22 @@ ebb0(v1: i8, v2: i8, v3: i8): return v1 } +; Function produces single return value, still need to copy. +function ext_ret_val() { + fn1 = function foo() -> i8 sext +ebb0: + v1 = call fn1() + ; check: $ebb0: + ; nextln: $(rv=$V) = call $fn1() + ; check: $(v1new=$V) = ireduce.i8 $rv + ; check: $v1 = copy $v1new + jump ebb1(v1) + ; check: jump $ebb1($v1) + +ebb1(v10: i8): + jump ebb1(v10) +} + function vector_split_args(i64x4) -> i64x4 { ebb0(v0: i64x4): ; check: $ebb0($(v0al=$V): i32, $(v0ah=$V): i32, $(v0bl=$V): i32, $(v0bh=$V): i32, $(v0cl=$V): i32, $(v0ch=$V): i32, $(v0dl=$V): i32, $(v0dh=$V): i32): diff --git a/lib/cretonne/src/legalizer.rs b/lib/cretonne/src/legalizer.rs index 6814e3fe2a..49651491fd 100644 --- a/lib/cretonne/src/legalizer.rs +++ b/lib/cretonne/src/legalizer.rs @@ -163,6 +163,89 @@ fn legalize_entry_arguments(func: &mut Function, entry: Ebb) { } } +/// Legalize the results returned from a call instruction to match the ABI signature. +/// +/// The cursor `pos` points to a call instruction with at least one return value. The cursor will +/// be left pointing after the instructions inserted to convert the return values. +/// +/// This function is very similar to the `legalize_entry_arguments` function above. +fn legalize_inst_results(dfg: &mut DataFlowGraph, + pos: &mut Cursor, + mut get_abi_type: ResType) + where ResType: FnMut(&DataFlowGraph, usize) -> ArgumentType +{ + let mut call = pos.current_inst().expect("Cursor must point to a call instruction"); + + // We theoretically allow for call instructions that return a number of fixed results before + // the call return values. In practice, it doesn't happen. + let fixed_results = dfg[call].opcode().constraints().fixed_results(); + assert_eq!(fixed_results, 0, "Fixed results on calls not supported"); + + let mut next_res = dfg.detach_secondary_results(call); + // The currently last result on the call instruction. + let mut last_res = dfg.first_result(call); + let mut abi_res = 0; + + // The first result requires special handling. + let first_ty = dfg.value_type(last_res); + if first_ty != get_abi_type(dfg, abi_res).value_type { + // Move the call out of the way, so we can redefine the first result. + let copy = call; + call = dfg.redefine_first_value(pos); + last_res = dfg.first_result(call); + // Set up a closure that can attach new results to `call`. + let mut get_res = |dfg: &mut DataFlowGraph, ty| { + let abi_type = get_abi_type(dfg, abi_res); + if ty == abi_type.value_type { + // Don't append the first result - it's not detachable. + if fixed_results + abi_res == 0 { + *dfg[call].first_type_mut() = ty; + debug_assert_eq!(last_res, dfg.first_result(call)); + } else { + last_res = dfg.append_secondary_result(last_res, ty); + } + abi_res += 1; + Ok(last_res) + } else { + Err(abi_type) + } + }; + + let v = convert_from_abi(dfg, pos, first_ty, &mut get_res); + dfg.replace(copy).copy(v); + } + + // Point immediately after the call and any instructions dealing with the first result. + pos.next_inst(); + + // Now do the secondary results. + while let Some(res) = next_res { + next_res = dfg.next_secondary_result(res); + + let res_type = dfg.value_type(res); + if res_type == get_abi_type(dfg, abi_res).value_type { + // No value translation is necessary, this result matches the ABI type. + dfg.attach_secondary_result(last_res, res); + last_res = res; + abi_res += 1; + } else { + let mut get_res = |dfg: &mut DataFlowGraph, ty| { + let abi_type = get_abi_type(dfg, abi_res); + if ty == abi_type.value_type { + last_res = dfg.append_secondary_result(last_res, ty); + abi_res += 1; + Ok(last_res) + } else { + Err(abi_type) + } + }; + let v = convert_from_abi(dfg, pos, res_type, &mut get_res); + // The old `res` is no longer an attached result. + dfg.change_to_alias(res, v); + } + } +} + /// Compute original value of type `ty` from the legalized ABI arguments. /// /// The conversion is recursive, controlled by the `get_arg` closure which is called to retrieve an @@ -423,7 +506,11 @@ fn handle_call_abi(dfg: &mut DataFlowGraph, pos: &mut Cursor) -> bool { abi_args, |dfg, abi_arg| dfg.signatures[sig_ref].argument_types[abi_arg]); - // TODO: Convert return values. + if !dfg.signatures[sig_ref].return_types.is_empty() { + legalize_inst_results(dfg, + pos, + |dfg, abi_res| dfg.signatures[sig_ref].return_types[abi_res]); + } // Yes, we changed stuff. true