Aarch64: fix narrow integer-register extension with Baldrdash ABI.
In the Baldrdash (SpiderMonkey) embedding, we must take care to zero-extend all function arguments to callees in integer registers when the types are narrower than 64 bits. This is because, unlike the native SysV ABI, the Baldrdash ABI expects high bits to be cleared. Not doing so leads to difficult-to-trace errors where high bits falsely tag an int32 as e.g. an object pointer, leading to potential security issues.
This commit is contained in:
@@ -113,9 +113,9 @@ use log::{debug, trace};
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum ABIArg {
|
||||
/// In a real register.
|
||||
Reg(RealReg, ir::Type),
|
||||
Reg(RealReg, ir::Type, ir::ArgumentExtension),
|
||||
/// Arguments only: on stack, at given offset from SP at entry.
|
||||
Stack(i64, ir::Type),
|
||||
Stack(i64, ir::Type, ir::ArgumentExtension),
|
||||
}
|
||||
|
||||
/// AArch64 ABI information shared between body (callee) and caller.
|
||||
@@ -187,6 +187,7 @@ fn try_fill_baldrdash_reg(call_conv: isa::CallConv, param: &ir::AbiParam) -> Opt
|
||||
Some(ABIArg::Reg(
|
||||
xreg(BALDRDASH_TLS_REG).to_real_reg(),
|
||||
ir::types::I64,
|
||||
param.extension,
|
||||
))
|
||||
}
|
||||
&ir::ArgumentPurpose::SignatureId => {
|
||||
@@ -194,6 +195,7 @@ fn try_fill_baldrdash_reg(call_conv: isa::CallConv, param: &ir::AbiParam) -> Opt
|
||||
Some(ABIArg::Reg(
|
||||
xreg(BALDRDASH_SIG_REG).to_real_reg(),
|
||||
ir::types::I64,
|
||||
param.extension,
|
||||
))
|
||||
}
|
||||
_ => None,
|
||||
@@ -279,7 +281,11 @@ fn compute_arg_locs(
|
||||
} else {
|
||||
vreg(*next_reg)
|
||||
};
|
||||
ret.push(ABIArg::Reg(reg.to_real_reg(), param.value_type));
|
||||
ret.push(ABIArg::Reg(
|
||||
reg.to_real_reg(),
|
||||
param.value_type,
|
||||
param.extension,
|
||||
));
|
||||
*next_reg += 1;
|
||||
} else {
|
||||
// Compute size. Every arg takes a minimum slot of 8 bytes. (16-byte
|
||||
@@ -289,7 +295,11 @@ fn compute_arg_locs(
|
||||
// Align.
|
||||
debug_assert!(size.is_power_of_two());
|
||||
next_stack = (next_stack + size - 1) & !(size - 1);
|
||||
ret.push(ABIArg::Stack(next_stack as i64, param.value_type));
|
||||
ret.push(ABIArg::Stack(
|
||||
next_stack as i64,
|
||||
param.value_type,
|
||||
param.extension,
|
||||
));
|
||||
next_stack += size;
|
||||
}
|
||||
}
|
||||
@@ -301,9 +311,17 @@ fn compute_arg_locs(
|
||||
let extra_arg = if add_ret_area_ptr {
|
||||
debug_assert!(args_or_rets == ArgsOrRets::Args);
|
||||
if next_xreg < max_reg_vals {
|
||||
ret.push(ABIArg::Reg(xreg(next_xreg).to_real_reg(), I64));
|
||||
ret.push(ABIArg::Reg(
|
||||
xreg(next_xreg).to_real_reg(),
|
||||
I64,
|
||||
ir::ArgumentExtension::None,
|
||||
));
|
||||
} else {
|
||||
ret.push(ABIArg::Stack(next_stack as i64, I64));
|
||||
ret.push(ABIArg::Stack(
|
||||
next_stack as i64,
|
||||
I64,
|
||||
ir::ArgumentExtension::None,
|
||||
));
|
||||
next_stack += 8;
|
||||
}
|
||||
Some(ret.len() - 1)
|
||||
@@ -491,7 +509,7 @@ fn get_special_purpose_param_register(
|
||||
) -> Option<Reg> {
|
||||
let idx = f.signature.special_param_index(purpose)?;
|
||||
match abi.args[idx] {
|
||||
ABIArg::Reg(reg, _) => Some(reg.to_reg()),
|
||||
ABIArg::Reg(reg, ..) => Some(reg.to_reg()),
|
||||
ABIArg::Stack(..) => None,
|
||||
}
|
||||
}
|
||||
@@ -866,7 +884,7 @@ impl ABIBody for AArch64ABIBody {
|
||||
fn liveins(&self) -> Set<RealReg> {
|
||||
let mut set: Set<RealReg> = Set::empty();
|
||||
for &arg in &self.sig.args {
|
||||
if let ABIArg::Reg(r, _) = arg {
|
||||
if let ABIArg::Reg(r, ..) = arg {
|
||||
set.insert(r);
|
||||
}
|
||||
}
|
||||
@@ -876,7 +894,7 @@ impl ABIBody for AArch64ABIBody {
|
||||
fn liveouts(&self) -> Set<RealReg> {
|
||||
let mut set: Set<RealReg> = Set::empty();
|
||||
for &ret in &self.sig.rets {
|
||||
if let ABIArg::Reg(r, _) = ret {
|
||||
if let ABIArg::Reg(r, ..) = ret {
|
||||
set.insert(r);
|
||||
}
|
||||
}
|
||||
@@ -897,8 +915,10 @@ impl ABIBody for AArch64ABIBody {
|
||||
|
||||
fn gen_copy_arg_to_reg(&self, idx: usize, into_reg: Writable<Reg>) -> Inst {
|
||||
match &self.sig.args[idx] {
|
||||
&ABIArg::Reg(r, ty) => Inst::gen_move(into_reg, r.to_reg(), ty),
|
||||
&ABIArg::Stack(off, ty) => load_stack(
|
||||
// Extension mode doesn't matter (we're copying out, not in; we
|
||||
// ignore high bits by convention).
|
||||
&ABIArg::Reg(r, ty, _) => Inst::gen_move(into_reg, r.to_reg(), ty),
|
||||
&ABIArg::Stack(off, ty, _) => load_stack(
|
||||
MemArg::FPOffset(self.fp_to_arg_offset() + off, ty),
|
||||
into_reg,
|
||||
ty,
|
||||
@@ -921,15 +941,10 @@ impl ABIBody for AArch64ABIBody {
|
||||
}
|
||||
}
|
||||
|
||||
fn gen_copy_reg_to_retval(
|
||||
&self,
|
||||
idx: usize,
|
||||
from_reg: Writable<Reg>,
|
||||
ext: ArgumentExtension,
|
||||
) -> Vec<Inst> {
|
||||
fn gen_copy_reg_to_retval(&self, idx: usize, from_reg: Writable<Reg>) -> Vec<Inst> {
|
||||
let mut ret = Vec::new();
|
||||
match &self.sig.rets[idx] {
|
||||
&ABIArg::Reg(r, ty) => {
|
||||
&ABIArg::Reg(r, ty, ext) => {
|
||||
let from_bits = ty_bits(ty) as u8;
|
||||
let dest_reg = Writable::from_reg(r.to_reg());
|
||||
match (ext, from_bits) {
|
||||
@@ -954,7 +969,7 @@ impl ABIBody for AArch64ABIBody {
|
||||
_ => ret.push(Inst::gen_move(dest_reg, from_reg.to_reg(), ty)),
|
||||
};
|
||||
}
|
||||
&ABIArg::Stack(off, ty) => {
|
||||
&ABIArg::Stack(off, ty, ext) => {
|
||||
let from_bits = ty_bits(ty) as u8;
|
||||
// Trash the from_reg; it should be its last use.
|
||||
match (ext, from_bits) {
|
||||
@@ -1364,7 +1379,7 @@ fn abisig_to_uses_and_defs(sig: &ABISig) -> (Vec<Reg>, Vec<Writable<Reg>>) {
|
||||
let mut uses = Vec::new();
|
||||
for arg in &sig.args {
|
||||
match arg {
|
||||
&ABIArg::Reg(reg, _) => uses.push(reg.to_reg()),
|
||||
&ABIArg::Reg(reg, ..) => uses.push(reg.to_reg()),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -1373,7 +1388,7 @@ fn abisig_to_uses_and_defs(sig: &ABISig) -> (Vec<Reg>, Vec<Writable<Reg>>) {
|
||||
let mut defs = get_caller_saves(sig.call_conv);
|
||||
for ret in &sig.rets {
|
||||
match ret {
|
||||
&ABIArg::Reg(reg, _) => defs.push(Writable::from_reg(reg.to_reg())),
|
||||
&ABIArg::Reg(reg, ..) => defs.push(Writable::from_reg(reg.to_reg())),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
@@ -1469,12 +1484,49 @@ impl ABICall for AArch64ABICall {
|
||||
from_reg: Reg,
|
||||
) {
|
||||
match &self.sig.args[idx] {
|
||||
&ABIArg::Reg(reg, ty) => ctx.emit(Inst::gen_move(
|
||||
Writable::from_reg(reg.to_reg()),
|
||||
from_reg,
|
||||
ty,
|
||||
)),
|
||||
&ABIArg::Stack(off, ty) => {
|
||||
&ABIArg::Reg(reg, ty, ext)
|
||||
if ext != ir::ArgumentExtension::None && ty_bits(ty) < 64 =>
|
||||
{
|
||||
assert_eq!(RegClass::I64, reg.get_class());
|
||||
let signed = match ext {
|
||||
ir::ArgumentExtension::Uext => false,
|
||||
ir::ArgumentExtension::Sext => true,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
ctx.emit(Inst::Extend {
|
||||
rd: Writable::from_reg(reg.to_reg()),
|
||||
rn: from_reg,
|
||||
signed,
|
||||
from_bits: ty_bits(ty) as u8,
|
||||
to_bits: 64,
|
||||
});
|
||||
}
|
||||
&ABIArg::Reg(reg, ty, _) => {
|
||||
ctx.emit(Inst::gen_move(
|
||||
Writable::from_reg(reg.to_reg()),
|
||||
from_reg,
|
||||
ty,
|
||||
));
|
||||
}
|
||||
&ABIArg::Stack(off, ty, ext) => {
|
||||
if ext != ir::ArgumentExtension::None && ty_bits(ty) < 64 {
|
||||
assert_eq!(RegClass::I64, from_reg.get_class());
|
||||
let signed = match ext {
|
||||
ir::ArgumentExtension::Uext => false,
|
||||
ir::ArgumentExtension::Sext => true,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
// Extend in place in the source register. Our convention is to
|
||||
// treat high bits as undefined for values in registers, so this
|
||||
// is safe, even for an argument that is nominally read-only.
|
||||
ctx.emit(Inst::Extend {
|
||||
rd: Writable::from_reg(from_reg),
|
||||
rn: from_reg,
|
||||
signed,
|
||||
from_bits: ty_bits(ty) as u8,
|
||||
to_bits: 64,
|
||||
});
|
||||
}
|
||||
ctx.emit(store_stack(MemArg::SPOffset(off, ty), from_reg, ty))
|
||||
}
|
||||
}
|
||||
@@ -1487,8 +1539,10 @@ impl ABICall for AArch64ABICall {
|
||||
into_reg: Writable<Reg>,
|
||||
) {
|
||||
match &self.sig.rets[idx] {
|
||||
&ABIArg::Reg(reg, ty) => ctx.emit(Inst::gen_move(into_reg, reg.to_reg(), ty)),
|
||||
&ABIArg::Stack(off, ty) => {
|
||||
// Extension mode doesn't matter because we're copying out, not in,
|
||||
// and we ignore high bits in our own registers by convention.
|
||||
&ABIArg::Reg(reg, ty, _) => ctx.emit(Inst::gen_move(into_reg, reg.to_reg(), ty)),
|
||||
&ABIArg::Stack(off, ty, _) => {
|
||||
let ret_area_base = self.sig.stack_arg_space;
|
||||
ctx.emit(load_stack(
|
||||
MemArg::SPOffset(off + ret_area_base, ty),
|
||||
|
||||
Reference in New Issue
Block a user