use super::riscv_register_info::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum RiscVCallingConvention {
LP64,
LP64D,
LP64F,
ILP32,
ILP32D,
ILP32F,
}
impl RiscVCallingConvention {
pub fn name(&self) -> &'static str {
match self {
RiscVCallingConvention::LP64 => "LP64",
RiscVCallingConvention::LP64D => "LP64D",
RiscVCallingConvention::LP64F => "LP64F",
RiscVCallingConvention::ILP32 => "ILP32",
RiscVCallingConvention::ILP32D => "ILP32D",
RiscVCallingConvention::ILP32F => "ILP32F",
}
}
pub fn is_64bit(&self) -> bool {
matches!(
self,
RiscVCallingConvention::LP64
| RiscVCallingConvention::LP64D
| RiscVCallingConvention::LP64F
)
}
pub fn uses_fp_regs(&self) -> bool {
matches!(
self,
RiscVCallingConvention::LP64D
| RiscVCallingConvention::LP64F
| RiscVCallingConvention::ILP32D
| RiscVCallingConvention::ILP32F
)
}
pub fn has_hardware_double(&self) -> bool {
matches!(
self,
RiscVCallingConvention::LP64D | RiscVCallingConvention::ILP32D
)
}
pub fn xlen_bits(&self) -> u32 {
if self.is_64bit() {
64
} else {
32
}
}
pub fn xlen_bytes(&self) -> u32 {
self.xlen_bits() / 8
}
pub fn get_num_int_param_regs(&self) -> usize {
8
}
pub fn get_num_fp_param_regs(&self) -> usize {
if self.uses_fp_regs() {
8
} else {
0
}
}
pub fn get_int_param_regs(&self) -> Vec<u16> {
vec![A0, A1, A2, A3, A4, A5, A6, A7]
}
pub fn get_fp_param_regs(&self) -> Vec<u16> {
if self.uses_fp_regs() {
vec![FA0, FA1, FA2, FA3, FA4, FA5, FA6, FA7]
} else {
vec![]
}
}
pub fn get_stack_alignment(&self) -> u32 {
16
}
pub fn get_red_zone_size(&self) -> u32 {
0
}
pub fn get_frame_pointer_reg(&self) -> u16 {
S0
}
pub fn get_return_address_reg(&self) -> u16 {
RA
}
pub fn get_stack_pointer_reg(&self) -> u16 {
SP
}
pub fn get_return_regs(&self, size_bytes: u32, is_fp: bool) -> Vec<u16> {
let xlen = self.xlen_bytes();
if is_fp {
if size_bytes <= 8 {
vec![FA0]
} else {
vec![FA0, FA1]
}
} else {
if size_bytes <= xlen {
vec![A0]
} else if size_bytes <= 2 * xlen {
vec![A0, A1]
} else {
vec![A0]
}
}
}
pub fn needs_indirect_return(&self, size_bytes: u32, is_aggregate: bool) -> bool {
if !is_aggregate {
return false;
}
let xlen = self.xlen_bytes();
size_bytes > 2 * xlen
}
pub fn uses_register_params(&self) -> bool {
true
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum RiscVArgClass {
Integer,
Float,
Memory,
NoClass,
}
impl RiscVArgClass {
pub fn is_register_class(&self) -> bool {
matches!(self, RiscVArgClass::Integer | RiscVArgClass::Float)
}
pub fn needs_fp_reg(&self) -> bool {
matches!(self, RiscVArgClass::Float)
}
}
#[derive(Debug, Clone)]
pub struct RiscVArgInfo {
pub in_reg: bool,
pub regs: Vec<u16>,
pub stack_offset: i64,
pub size: u32,
pub alignment: u32,
pub is_indirect: bool,
pub indirect_reg: Option<u16>,
}
impl Default for RiscVArgInfo {
fn default() -> Self {
RiscVArgInfo {
in_reg: true,
regs: Vec::new(),
stack_offset: 0,
size: 0,
alignment: 1,
is_indirect: false,
indirect_reg: None,
}
}
}
#[derive(Debug, Clone)]
pub struct RiscVCallFrame {
pub stack_size: i64,
pub arg_offsets: Vec<i64>,
pub saved_ra_offset: i64,
pub saved_fp_offset: i64,
pub callee_saved_size: i64,
}
impl Default for RiscVCallFrame {
fn default() -> Self {
RiscVCallFrame {
stack_size: 0,
arg_offsets: Vec::new(),
saved_ra_offset: 0,
saved_fp_offset: 0,
callee_saved_size: 0,
}
}
}
#[derive(Debug, Clone)]
pub struct RiscVTypeInfo {
pub size: u32,
pub alignment: u32,
pub is_fp: bool,
pub is_aggregate: bool,
pub members: Vec<RiscVTypeInfo>,
}
impl RiscVTypeInfo {
pub fn integer(size: u32) -> Self {
RiscVTypeInfo {
size,
alignment: size,
is_fp: false,
is_aggregate: false,
members: Vec::new(),
}
}
pub fn pointer(xlen: u32) -> Self {
RiscVTypeInfo {
size: xlen,
alignment: xlen,
is_fp: false,
is_aggregate: false,
members: Vec::new(),
}
}
pub fn float(size: u32) -> Self {
RiscVTypeInfo {
size,
alignment: size,
is_fp: true,
is_aggregate: false,
members: Vec::new(),
}
}
pub fn aggregate(size: u32, alignment: u32, members: Vec<RiscVTypeInfo>) -> Self {
RiscVTypeInfo {
size,
alignment,
is_fp: false,
is_aggregate: true,
members,
}
}
}
fn align_to(value: i64, alignment: u32) -> i64 {
let align = alignment as i64;
(value + align - 1) & !(align - 1)
}
pub fn classify_arg_type(ty: &RiscVTypeInfo, cc: &RiscVCallingConvention) -> Vec<RiscVArgClass> {
let xlen = cc.xlen_bytes() as u32;
if ty.is_fp {
let num_chunks = (ty.size + xlen - 1) / xlen;
return vec![RiscVArgClass::Float; num_chunks as usize];
}
if !ty.is_aggregate {
let num_chunks = (ty.size + xlen - 1) / xlen;
return vec![RiscVArgClass::Integer; num_chunks as usize];
}
if ty.size > 2 * xlen {
return vec![RiscVArgClass::Memory];
}
classify_aggregate(ty, xlen)
}
fn classify_aggregate(ty: &RiscVTypeInfo, xlen: u32) -> Vec<RiscVArgClass> {
let num_chunks = ((ty.size + xlen - 1) / xlen) as usize;
let mut classes = vec![RiscVArgClass::NoClass; num_chunks];
let mut field_offset: u32 = 0;
for member in &ty.members {
let aligned_offset = align_to(field_offset as i64, member.alignment) as u32;
field_offset = aligned_offset;
let member_classes = if member.is_fp {
let num_member_chunks = ((member.size + xlen - 1) / xlen) as usize;
vec![RiscVArgClass::Float; num_member_chunks]
} else if member.is_aggregate {
classify_aggregate(member, xlen)
} else {
let num_member_chunks = ((member.size + xlen - 1) / xlen) as usize;
vec![RiscVArgClass::Integer; num_member_chunks]
};
let start_chunk = (field_offset / xlen) as usize;
for (i, cls) in member_classes.iter().enumerate() {
let chunk_idx = start_chunk + i;
if chunk_idx < classes.len() {
classes[chunk_idx] = merge_classes(classes[chunk_idx], *cls);
}
}
field_offset += member.size;
}
for cls in &mut classes {
if *cls == RiscVArgClass::NoClass {
*cls = RiscVArgClass::Integer;
}
}
classes
}
fn merge_classes(a: RiscVArgClass, b: RiscVArgClass) -> RiscVArgClass {
use RiscVArgClass::*;
match (a, b) {
(Memory, _) | (_, Memory) => Memory,
(Integer, _) | (_, Integer) => Integer,
(Float, Float) => Float,
(NoClass, other) | (other, NoClass) => other,
}
}
pub fn assign_args(
types: &[RiscVTypeInfo],
cc: &RiscVCallingConvention,
) -> (Vec<RiscVArgInfo>, RiscVCallFrame) {
let xlen = cc.xlen_bytes() as u32;
let mut arg_infos = Vec::with_capacity(types.len());
let mut arg_offsets = Vec::with_capacity(types.len());
let mut int_regs_used: usize = 0;
let mut fp_regs_used: usize = 0;
let max_int_regs = cc.get_num_int_param_regs();
let max_fp_regs = cc.get_num_fp_param_regs();
let int_regs = cc.get_int_param_regs();
let fp_regs = cc.get_fp_param_regs();
let mut stack_offset: i64 = 0;
let stack_align = cc.get_stack_alignment() as i64;
for ty in types {
let classes = classify_arg_type(ty, cc);
if ty.is_aggregate && ty.size > 2 * xlen {
let mut info = RiscVArgInfo {
in_reg: true,
regs: Vec::new(),
stack_offset: 0,
size: xlen,
alignment: xlen,
is_indirect: true,
indirect_reg: None,
};
if int_regs_used < max_int_regs {
let reg = int_regs[int_regs_used];
info.regs.push(reg);
info.indirect_reg = Some(reg);
int_regs_used += 1;
} else {
stack_offset = align_to(stack_offset, xlen);
info.in_reg = false;
info.stack_offset = stack_offset;
info.indirect_reg = None;
stack_offset += xlen as i64;
}
arg_offsets.push(info.stack_offset);
arg_infos.push(info);
continue;
}
let mut regs_used: Vec<u16> = Vec::new();
let mut needs_stack = false;
let mut first_stack_offset: i64 = 0;
for (i, cls) in classes.iter().enumerate() {
match cls {
RiscVArgClass::Integer => {
if int_regs_used < max_int_regs {
regs_used.push(int_regs[int_regs_used]);
int_regs_used += 1;
} else {
if !needs_stack {
stack_offset = align_to(stack_offset, xlen);
first_stack_offset = stack_offset;
needs_stack = true;
}
stack_offset += xlen as i64;
}
}
RiscVArgClass::Float => {
if fp_regs_used < max_fp_regs {
regs_used.push(fp_regs[fp_regs_used]);
fp_regs_used += 1;
} else {
if int_regs_used < max_int_regs {
regs_used.push(int_regs[int_regs_used]);
int_regs_used += 1;
} else {
if !needs_stack {
stack_offset = align_to(stack_offset, xlen);
first_stack_offset = stack_offset;
needs_stack = true;
}
stack_offset += xlen as i64;
}
}
}
RiscVArgClass::Memory => {
panic!("Memory class should not appear in chunk classification after top-level check");
}
RiscVArgClass::NoClass => {
}
}
}
let info = RiscVArgInfo {
in_reg: !needs_stack,
regs: regs_used,
stack_offset: if needs_stack { first_stack_offset } else { 0 },
size: ty.size,
alignment: ty.alignment,
is_indirect: false,
indirect_reg: None,
};
arg_offsets.push(info.stack_offset);
arg_infos.push(info);
}
stack_offset = align_to(stack_offset, stack_align as u32);
let frame = RiscVCallFrame {
stack_size: stack_offset,
arg_offsets,
saved_ra_offset: -(xlen as i64), saved_fp_offset: -(2 * xlen as i64), callee_saved_size: 0, };
(arg_infos, frame)
}
#[cfg(test)]
mod tests {
use super::*;
fn make_lp64d() -> RiscVCallingConvention {
RiscVCallingConvention::LP64D
}
fn make_ilp32() -> RiscVCallingConvention {
RiscVCallingConvention::ILP32
}
fn make_lp64() -> RiscVCallingConvention {
RiscVCallingConvention::LP64
}
#[test]
fn test_cc_name() {
assert_eq!(RiscVCallingConvention::LP64.name(), "LP64");
assert_eq!(RiscVCallingConvention::LP64D.name(), "LP64D");
assert_eq!(RiscVCallingConvention::LP64F.name(), "LP64F");
assert_eq!(RiscVCallingConvention::ILP32.name(), "ILP32");
assert_eq!(RiscVCallingConvention::ILP32D.name(), "ILP32D");
assert_eq!(RiscVCallingConvention::ILP32F.name(), "ILP32F");
}
#[test]
fn test_cc_is_64bit() {
assert!(RiscVCallingConvention::LP64.is_64bit());
assert!(RiscVCallingConvention::LP64D.is_64bit());
assert!(RiscVCallingConvention::LP64F.is_64bit());
assert!(!RiscVCallingConvention::ILP32.is_64bit());
assert!(!RiscVCallingConvention::ILP32D.is_64bit());
assert!(!RiscVCallingConvention::ILP32F.is_64bit());
}
#[test]
fn test_cc_uses_fp_regs() {
assert!(!RiscVCallingConvention::LP64.uses_fp_regs());
assert!(RiscVCallingConvention::LP64D.uses_fp_regs());
assert!(RiscVCallingConvention::LP64F.uses_fp_regs());
assert!(!RiscVCallingConvention::ILP32.uses_fp_regs());
assert!(RiscVCallingConvention::ILP32D.uses_fp_regs());
assert!(RiscVCallingConvention::ILP32F.uses_fp_regs());
}
#[test]
fn test_cc_has_hardware_double() {
assert!(!RiscVCallingConvention::LP64.has_hardware_double());
assert!(RiscVCallingConvention::LP64D.has_hardware_double());
assert!(!RiscVCallingConvention::LP64F.has_hardware_double());
assert!(!RiscVCallingConvention::ILP32.has_hardware_double());
assert!(RiscVCallingConvention::ILP32D.has_hardware_double());
assert!(!RiscVCallingConvention::ILP32F.has_hardware_double());
}
#[test]
fn test_cc_xlen() {
assert_eq!(RiscVCallingConvention::LP64.xlen_bits(), 64);
assert_eq!(RiscVCallingConvention::LP64.xlen_bytes(), 8);
assert_eq!(RiscVCallingConvention::ILP32.xlen_bits(), 32);
assert_eq!(RiscVCallingConvention::ILP32.xlen_bytes(), 4);
}
#[test]
fn test_cc_num_param_regs() {
assert_eq!(make_lp64d().get_num_int_param_regs(), 8);
assert_eq!(make_ilp32().get_num_int_param_regs(), 8);
assert_eq!(make_lp64d().get_num_fp_param_regs(), 8);
assert_eq!(make_lp64().get_num_fp_param_regs(), 0);
assert_eq!(make_ilp32().get_num_fp_param_regs(), 0);
}
#[test]
fn test_cc_get_int_param_regs() {
let regs = make_lp64d().get_int_param_regs();
assert_eq!(regs.len(), 8);
assert_eq!(regs[0], A0);
assert_eq!(regs[7], A7);
}
#[test]
fn test_cc_get_fp_param_regs() {
let regs = make_lp64d().get_fp_param_regs();
assert_eq!(regs.len(), 8);
assert_eq!(regs[0], FA0);
assert_eq!(regs[7], FA7);
let regs_lp64 = make_lp64().get_fp_param_regs();
assert_eq!(regs_lp64.len(), 0);
}
#[test]
fn test_cc_get_stack_alignment() {
assert_eq!(make_lp64d().get_stack_alignment(), 16);
assert_eq!(make_ilp32().get_stack_alignment(), 16);
}
#[test]
fn test_cc_get_red_zone_size() {
assert_eq!(make_lp64d().get_red_zone_size(), 0);
}
#[test]
fn test_cc_special_regs() {
let cc = make_lp64d();
assert_eq!(cc.get_frame_pointer_reg(), S0);
assert_eq!(cc.get_return_address_reg(), RA);
assert_eq!(cc.get_stack_pointer_reg(), SP);
}
#[test]
fn test_cc_get_return_regs() {
let cc = make_lp64d();
assert_eq!(cc.get_return_regs(8, false), vec![A0]);
assert_eq!(cc.get_return_regs(16, false), vec![A0, A1]);
assert_eq!(cc.get_return_regs(4, true), vec![FA0]);
assert_eq!(cc.get_return_regs(8, true), vec![FA0]);
assert_eq!(cc.get_return_regs(16, true), vec![FA0, FA1]);
}
#[test]
fn test_cc_needs_indirect_return() {
let cc = make_lp64d();
assert!(!cc.needs_indirect_return(8, true));
assert!(!cc.needs_indirect_return(16, true));
assert!(cc.needs_indirect_return(24, true));
assert!(cc.needs_indirect_return(32, true));
assert!(!cc.needs_indirect_return(64, false));
}
#[test]
fn test_cc_uses_register_params() {
assert!(make_lp64d().uses_register_params());
assert!(make_ilp32().uses_register_params());
}
#[test]
fn test_arg_class_is_register_class() {
assert!(RiscVArgClass::Integer.is_register_class());
assert!(RiscVArgClass::Float.is_register_class());
assert!(!RiscVArgClass::Memory.is_register_class());
assert!(!RiscVArgClass::NoClass.is_register_class());
}
#[test]
fn test_arg_class_needs_fp_reg() {
assert!(!RiscVArgClass::Integer.needs_fp_reg());
assert!(RiscVArgClass::Float.needs_fp_reg());
assert!(!RiscVArgClass::Memory.needs_fp_reg());
assert!(!RiscVArgClass::NoClass.needs_fp_reg());
}
#[test]
fn test_classify_int_primitives() {
let cc = make_lp64d();
let ty = RiscVTypeInfo::integer(8);
let classes = classify_arg_type(&ty, &cc);
assert_eq!(classes.len(), 1);
assert_eq!(classes[0], RiscVArgClass::Integer);
}
#[test]
fn test_classify_pointer() {
let cc = make_lp64d();
let ty = RiscVTypeInfo::pointer(8);
let classes = classify_arg_type(&ty, &cc);
assert_eq!(classes.len(), 1);
assert_eq!(classes[0], RiscVArgClass::Integer);
}
#[test]
fn test_classify_fp_primitives() {
let cc = make_lp64d();
let ty_f32 = RiscVTypeInfo::float(4);
let classes = classify_arg_type(&ty_f32, &cc);
assert_eq!(classes.len(), 1);
assert_eq!(classes[0], RiscVArgClass::Float);
let ty_f64 = RiscVTypeInfo::float(8);
let classes = classify_arg_type(&ty_f64, &cc);
assert_eq!(classes.len(), 1);
assert_eq!(classes[0], RiscVArgClass::Float);
}
#[test]
fn test_classify_simple_struct() {
let cc = make_lp64d();
let s = RiscVTypeInfo::aggregate(
8,
4,
vec![RiscVTypeInfo::integer(4), RiscVTypeInfo::integer(4)],
);
let classes = classify_arg_type(&s, &cc);
assert_eq!(classes.len(), 1);
assert_eq!(classes[0], RiscVArgClass::Integer);
}
#[test]
fn test_classify_struct_with_float() {
let cc = make_lp64d();
let s =
RiscVTypeInfo::aggregate(8, 4, vec![RiscVTypeInfo::float(4), RiscVTypeInfo::float(4)]);
let classes = classify_arg_type(&s, &cc);
assert_eq!(classes.len(), 1);
assert_eq!(classes[0], RiscVArgClass::Float);
}
#[test]
fn test_classify_struct_mixed_int_fp() {
let cc = make_lp64d();
let s = RiscVTypeInfo::aggregate(
8,
4,
vec![RiscVTypeInfo::integer(4), RiscVTypeInfo::float(4)],
);
let classes = classify_arg_type(&s, &cc);
assert_eq!(classes.len(), 1);
assert_eq!(classes[0], RiscVArgClass::Integer);
}
#[test]
fn test_classify_large_struct() {
let cc = make_lp64d();
let s = RiscVTypeInfo::aggregate(24, 8, vec![]);
let classes = classify_arg_type(&s, &cc);
assert_eq!(classes.len(), 1);
assert_eq!(classes[0], RiscVArgClass::Memory);
}
#[test]
fn test_assign_simple_int_args() {
let cc = make_lp64d();
let types = vec![RiscVTypeInfo::integer(8), RiscVTypeInfo::integer(8)];
let (infos, _frame) = assign_args(&types, &cc);
assert_eq!(infos.len(), 2);
assert!(infos[0].in_reg);
assert_eq!(infos[0].regs, vec![A0]);
assert!(infos[1].in_reg);
assert_eq!(infos[1].regs, vec![A1]);
}
#[test]
fn test_assign_fp_args() {
let cc = make_lp64d();
let types = vec![RiscVTypeInfo::float(4), RiscVTypeInfo::float(8)];
let (infos, _frame) = assign_args(&types, &cc);
assert_eq!(infos.len(), 2);
assert!(infos[0].in_reg);
assert_eq!(infos[0].regs, vec![FA0]);
assert!(infos[1].in_reg);
assert_eq!(infos[1].regs, vec![FA1]);
}
#[test]
fn test_assign_many_int_args_exhaust_regs() {
let cc = make_lp64d();
let types: Vec<RiscVTypeInfo> = (0..10).map(|_| RiscVTypeInfo::integer(8)).collect();
let (infos, frame) = assign_args(&types, &cc);
assert_eq!(infos.len(), 10);
for i in 0..8 {
assert!(infos[i].in_reg);
assert_eq!(infos[i].regs.len(), 1);
}
assert!(!infos[8].in_reg);
assert!(!infos[9].in_reg);
assert!(frame.stack_size > 0);
}
#[test]
fn test_assign_indirect_large_struct() {
let cc = make_lp64d();
let s = RiscVTypeInfo::aggregate(24, 8, vec![]);
let types = vec![s];
let (infos, _frame) = assign_args(&types, &cc);
assert_eq!(infos.len(), 1);
assert!(infos[0].is_indirect);
assert!(infos[0].indirect_reg.is_some());
}
#[test]
fn test_assign_small_struct_in_regs() {
let cc = make_lp64d();
let s = RiscVTypeInfo::aggregate(
8,
4,
vec![RiscVTypeInfo::integer(4), RiscVTypeInfo::integer(4)],
);
let types = vec![s];
let (infos, _frame) = assign_args(&types, &cc);
assert_eq!(infos.len(), 1);
assert!(!infos[0].is_indirect);
assert!(infos[0].in_reg);
}
#[test]
fn test_merge_classes_basic() {
use RiscVArgClass::*;
assert_eq!(merge_classes(Integer, Integer), Integer);
assert_eq!(merge_classes(Float, Float), Float);
assert_eq!(merge_classes(Integer, Float), Integer);
assert_eq!(merge_classes(Float, Integer), Integer);
assert_eq!(merge_classes(Memory, Integer), Memory);
assert_eq!(merge_classes(Float, Memory), Memory);
assert_eq!(merge_classes(NoClass, Integer), Integer);
assert_eq!(merge_classes(Float, NoClass), Float);
}
#[test]
fn test_arg_info_default() {
let info = RiscVArgInfo::default();
assert!(info.in_reg);
assert!(info.regs.is_empty());
assert_eq!(info.stack_offset, 0);
assert_eq!(info.size, 0);
assert_eq!(info.alignment, 1);
assert!(!info.is_indirect);
assert_eq!(info.indirect_reg, None);
}
#[test]
fn test_call_frame_default() {
let frame = RiscVCallFrame::default();
assert_eq!(frame.stack_size, 0);
assert!(frame.arg_offsets.is_empty());
assert_eq!(frame.saved_ra_offset, 0);
assert_eq!(frame.saved_fp_offset, 0);
assert_eq!(frame.callee_saved_size, 0);
}
#[test]
fn test_assign_mixed_int_fp_args() {
let cc = make_lp64d();
let types = vec![
RiscVTypeInfo::integer(8),
RiscVTypeInfo::float(8),
RiscVTypeInfo::integer(8),
RiscVTypeInfo::float(4),
];
let (infos, _frame) = assign_args(&types, &cc);
assert_eq!(infos.len(), 4);
assert_eq!(infos[0].regs, vec![A0]);
assert_eq!(infos[1].regs, vec![FA0]);
assert_eq!(infos[2].regs, vec![A1]);
assert_eq!(infos[3].regs, vec![FA1]);
}
#[test]
fn test_assign_ilp32_scalar() {
let cc = make_ilp32();
let types = vec![RiscVTypeInfo::integer(4)];
let (infos, _frame) = assign_args(&types, &cc);
assert_eq!(infos.len(), 1);
assert_eq!(infos[0].regs, vec![A0]);
}
#[test]
fn test_classify_ilp32_int() {
let cc = make_ilp32();
let ty = RiscVTypeInfo::integer(4);
let classes = classify_arg_type(&ty, &cc);
assert_eq!(classes.len(), 1);
assert_eq!(classes[0], RiscVArgClass::Integer);
}
#[test]
fn test_frame_stack_alignment() {
let cc = make_lp64d();
let types: Vec<RiscVTypeInfo> = (0..20).map(|_| RiscVTypeInfo::integer(8)).collect();
let (_infos, frame) = assign_args(&types, &cc);
assert_eq!(frame.stack_size % 16, 0);
}
}