Legalize return values from call instructions.
Like the entry block arguments, the return values from a call instruction need to be converted back from their ABI representation. Add tests of call instruction legalization.
This commit is contained in:
@@ -14,6 +14,50 @@ ebb0(v0: i64):
|
|||||||
return v1
|
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 {
|
function int_ext(i8, i8 sext, i8 uext) -> i8 uext {
|
||||||
ebb0(v1: i8, v2: i8, v3: i8):
|
ebb0(v1: i8, v2: i8, v3: i8):
|
||||||
; check: $ebb0($v1: i8, $(v2x=$V): i32, $(v3x=$V): i32):
|
; check: $ebb0($v1: i8, $(v2x=$V): i32, $(v3x=$V): i32):
|
||||||
@@ -24,6 +68,22 @@ ebb0(v1: i8, v2: i8, v3: i8):
|
|||||||
return v1
|
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 {
|
function vector_split_args(i64x4) -> i64x4 {
|
||||||
ebb0(v0: 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):
|
; 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):
|
||||||
|
|||||||
@@ -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<ResType>(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.
|
/// 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
|
/// 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,
|
abi_args,
|
||||||
|dfg, abi_arg| dfg.signatures[sig_ref].argument_types[abi_arg]);
|
|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.
|
// Yes, we changed stuff.
|
||||||
true
|
true
|
||||||
|
|||||||
Reference in New Issue
Block a user