authorgravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-05-19 17:36:04+02:00
committergravatar for kubkon@jakubkonka.comJakub Konka <kubkon@jakubkonka.com> 2022-05-19 19:39:34+02:00
log5cbfd5819e423cdc6b092d1eb687189fb204b075
tree813b04e2f28f59af65d3d69709def69f6dd7ce08
parent283f40e4e9c44986353ba8abcc760684e9adf6cc

x64: check for floating-point intrinsics in codegen


4 files changed, 179 insertions(+), 134 deletions(-)

src/arch/x86_64/CodeGen.zig+164-119
...@@ -39,7 +39,7 @@ const RegisterLock = RegisterManager.RegisterLock;...@@ -39,7 +39,7 @@ const RegisterLock = RegisterManager.RegisterLock;
39const Register = bits.Register;39const Register = bits.Register;
4040
41const gp = abi.RegisterClass.gp;41const gp = abi.RegisterClass.gp;
42const avx = abi.RegisterClass.avx;42const sse = abi.RegisterClass.sse;
4343
44const InnerError = error{44const InnerError = error{
45 OutOfMemory,45 OutOfMemory,
...@@ -881,15 +881,18 @@ fn allocRegOrMem(self: *Self, inst: Air.Inst.Index, reg_ok: bool) !MCValue {...@@ -881,15 +881,18 @@ fn allocRegOrMem(self: *Self, inst: Air.Inst.Index, reg_ok: bool) !MCValue {
881 switch (elem_ty.zigTypeTag()) {881 switch (elem_ty.zigTypeTag()) {
882 .Vector => return self.fail("TODO allocRegOrMem for Vector type", .{}),882 .Vector => return self.fail("TODO allocRegOrMem for Vector type", .{}),
883 .Float => {883 .Float => {
884 // TODO check if AVX available884 if (self.intrinsicsAllowed(elem_ty)) {
885 const ptr_bytes: u64 = 32;885 const ptr_bytes: u64 = 32;
886 if (abi_size <= ptr_bytes) {886 if (abi_size <= ptr_bytes) {
887 if (self.register_manager.tryAllocReg(inst, .{887 if (self.register_manager.tryAllocReg(inst, .{
888 .selector_mask = avx,888 .selector_mask = sse,
889 })) |reg| {889 })) |reg| {
890 return MCValue{ .register = registerAlias(reg, abi_size) };890 return MCValue{ .register = registerAlias(reg, abi_size) };
891 }
891 }892 }
892 }893 }
894
895 return self.fail("TODO allocRegOrMem for Float type without SSE/AVX support", .{});
893 },896 },
894 else => {897 else => {
895 // Make sure the type can fit in a register before we try to allocate one.898 // Make sure the type can fit in a register before we try to allocate one.
...@@ -969,8 +972,11 @@ pub fn spillRegisters(self: *Self, comptime count: comptime_int, registers: [cou...@@ -969,8 +972,11 @@ pub fn spillRegisters(self: *Self, comptime count: comptime_int, registers: [cou
969/// allocated. A second call to `copyToTmpRegister` may return the same register.972/// allocated. A second call to `copyToTmpRegister` may return the same register.
970/// This can have a side effect of spilling instructions to the stack to free up a register.973/// This can have a side effect of spilling instructions to the stack to free up a register.
971fn copyToTmpRegister(self: *Self, ty: Type, mcv: MCValue) !Register {974fn copyToTmpRegister(self: *Self, ty: Type, mcv: MCValue) !Register {
972 const mask = switch (ty.zigTypeTag()) {975 const mask: RegisterManager.RegisterBitSet = switch (ty.zigTypeTag()) {
973 .Float => avx,976 .Float => blk: {
977 if (self.intrinsicsAllowed(ty)) break :blk sse;
978 return self.fail("TODO copy {} to register", .{ty.fmtDebug()});
979 },
974 else => gp,980 else => gp,
975 };981 };
976 const reg: Register = try self.register_manager.allocReg(null, .{982 const reg: Register = try self.register_manager.allocReg(null, .{
...@@ -985,8 +991,11 @@ fn copyToTmpRegister(self: *Self, ty: Type, mcv: MCValue) !Register {...@@ -985,8 +991,11 @@ fn copyToTmpRegister(self: *Self, ty: Type, mcv: MCValue) !Register {
985/// This can have a side effect of spilling instructions to the stack to free up a register.991/// This can have a side effect of spilling instructions to the stack to free up a register.
986/// WARNING make sure that the allocated register matches the returned MCValue from an instruction!992/// WARNING make sure that the allocated register matches the returned MCValue from an instruction!
987fn copyToRegisterWithInstTracking(self: *Self, reg_owner: Air.Inst.Index, ty: Type, mcv: MCValue) !MCValue {993fn copyToRegisterWithInstTracking(self: *Self, reg_owner: Air.Inst.Index, ty: Type, mcv: MCValue) !MCValue {
988 const mask = switch (ty.zigTypeTag()) {994 const mask: RegisterManager.RegisterBitSet = switch (ty.zigTypeTag()) {
989 .Float => avx,995 .Float => blk: {
996 if (self.intrinsicsAllowed(ty)) break :blk sse;
997 return self.fail("TODO copy {} to register", .{ty.fmtDebug()});
998 },
990 else => gp,999 else => gp,
991 };1000 };
992 const reg: Register = try self.register_manager.allocReg(reg_owner, .{1001 const reg: Register = try self.register_manager.allocReg(reg_owner, .{
...@@ -3469,27 +3478,32 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu...@@ -3469,27 +3478,32 @@ fn genBinOpMir(self: *Self, mir_tag: Mir.Inst.Tag, dst_ty: Type, dst_mcv: MCValu
3469 },3478 },
3470 .register => |src_reg| switch (dst_ty.zigTypeTag()) {3479 .register => |src_reg| switch (dst_ty.zigTypeTag()) {
3471 .Float => {3480 .Float => {
3472 const actual_tag: Mir.Inst.Tag = switch (dst_ty.tag()) {3481 if (self.intrinsicsAllowed(dst_ty)) {
3473 .f32 => switch (mir_tag) {3482 const actual_tag: Mir.Inst.Tag = switch (dst_ty.tag()) {
3474 .add => Mir.Inst.Tag.add_f32,3483 .f32 => switch (mir_tag) {
3475 .cmp => Mir.Inst.Tag.cmp_f32,3484 .add => Mir.Inst.Tag.add_f32_avx,
3476 else => return self.fail("TODO genBinOpMir for f32 register-register with MIR tag {}", .{mir_tag}),3485 .cmp => Mir.Inst.Tag.cmp_f32_avx,
3477 },3486 else => return self.fail("TODO genBinOpMir for f32 register-register with MIR tag {}", .{mir_tag}),
3478 .f64 => switch (mir_tag) {3487 },
3479 .add => Mir.Inst.Tag.add_f64,3488 .f64 => switch (mir_tag) {
3480 .cmp => Mir.Inst.Tag.cmp_f64,3489 .add => Mir.Inst.Tag.add_f64_avx,
3481 else => return self.fail("TODO genBinOpMir for f64 register-register with MIR tag {}", .{mir_tag}),3490 .cmp => Mir.Inst.Tag.cmp_f64_avx,
3482 },3491 else => return self.fail("TODO genBinOpMir for f64 register-register with MIR tag {}", .{mir_tag}),
3483 else => return self.fail("TODO genBinOpMir for float register-register and type {}", .{dst_ty.fmtDebug()}),3492 },
3484 };3493 else => return self.fail("TODO genBinOpMir for float register-register and type {}", .{dst_ty.fmtDebug()}),
3485 _ = try self.addInst(.{3494 };
3486 .tag = actual_tag,3495 _ = try self.addInst(.{
3487 .ops = Mir.Inst.Ops.encode(.{3496 .tag = actual_tag,
3488 .reg1 = dst_reg.to128(),3497 .ops = Mir.Inst.Ops.encode(.{
3489 .reg2 = src_reg.to128(),3498 .reg1 = dst_reg.to128(),
3490 }),3499 .reg2 = src_reg.to128(),
3491 .data = undefined,3500 }),
3492 });3501 .data = undefined,
3502 });
3503 return;
3504 }
3505
3506 return self.fail("TODO genBinOpMir for float register-register and no intrinsics", .{});
3493 },3507 },
3494 else => {3508 else => {
3495 _ = try self.addInst(.{3509 _ = try self.addInst(.{
...@@ -5326,24 +5340,29 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE...@@ -5326,24 +5340,29 @@ fn genSetStackArg(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue) InnerE
5326 .register => |reg| {5340 .register => |reg| {
5327 switch (ty.zigTypeTag()) {5341 switch (ty.zigTypeTag()) {
5328 .Float => {5342 .Float => {
5329 const tag: Mir.Inst.Tag = switch (ty.tag()) {5343 if (self.intrinsicsAllowed(ty)) {
5330 .f32 => .mov_f32,5344 const tag: Mir.Inst.Tag = switch (ty.tag()) {
5331 .f64 => .mov_f64,5345 .f32 => .mov_f32_avx,
5332 else => return self.fail("TODO genSetStackArg for register for type {}", .{ty.fmtDebug()}),5346 .f64 => .mov_f64_avx,
5333 };5347 else => return self.fail("TODO genSetStackArg for register for type {}", .{ty.fmtDebug()}),
5334 _ = try self.addInst(.{5348 };
5335 .tag = tag,5349 _ = try self.addInst(.{
5336 .ops = Mir.Inst.Ops.encode(.{5350 .tag = tag,
5337 .reg1 = switch (ty.tag()) {5351 .ops = Mir.Inst.Ops.encode(.{
5338 .f32 => .esp,5352 .reg1 = switch (ty.tag()) {
5339 .f64 => .rsp,5353 .f32 => .esp,
5340 else => unreachable,5354 .f64 => .rsp,
5341 },5355 else => unreachable,
5342 .reg2 = reg.to128(),5356 },
5343 .flags = 0b01,5357 .reg2 = reg.to128(),
5344 }),5358 .flags = 0b01,
5345 .data = .{ .imm = @bitCast(u32, -stack_offset) },5359 }),
5346 });5360 .data = .{ .imm = @bitCast(u32, -stack_offset) },
5361 });
5362 return;
5363 }
5364
5365 return self.fail("TODO genSetStackArg for register with no intrinsics", .{});
5347 },5366 },
5348 else => {5367 else => {
5349 _ = try self.addInst(.{5368 _ = try self.addInst(.{
...@@ -5505,24 +5524,29 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl...@@ -5505,24 +5524,29 @@ fn genSetStack(self: *Self, ty: Type, stack_offset: i32, mcv: MCValue, opts: Inl
55055524
5506 switch (ty.zigTypeTag()) {5525 switch (ty.zigTypeTag()) {
5507 .Float => {5526 .Float => {
5508 const tag: Mir.Inst.Tag = switch (ty.tag()) {5527 if (self.intrinsicsAllowed(ty)) {
5509 .f32 => .mov_f32,5528 const tag: Mir.Inst.Tag = switch (ty.tag()) {
5510 .f64 => .mov_f64,5529 .f32 => .mov_f32_avx,
5511 else => return self.fail("TODO genSetStack for register for type {}", .{ty.fmtDebug()}),5530 .f64 => .mov_f64_avx,
5512 };5531 else => return self.fail("TODO genSetStack for register for type {}", .{ty.fmtDebug()}),
5513 _ = try self.addInst(.{5532 };
5514 .tag = tag,5533 _ = try self.addInst(.{
5515 .ops = Mir.Inst.Ops.encode(.{5534 .tag = tag,
5516 .reg1 = switch (ty.tag()) {5535 .ops = Mir.Inst.Ops.encode(.{
5517 .f32 => base_reg.to32(),5536 .reg1 = switch (ty.tag()) {
5518 .f64 => base_reg.to64(),5537 .f32 => base_reg.to32(),
5519 else => unreachable,5538 .f64 => base_reg.to64(),
5520 },5539 else => unreachable,
5521 .reg2 = reg.to128(),5540 },
5522 .flags = 0b01,5541 .reg2 = reg.to128(),
5523 }),5542 .flags = 0b01,
5524 .data = .{ .imm = @bitCast(u32, -stack_offset) },5543 }),
5525 });5544 .data = .{ .imm = @bitCast(u32, -stack_offset) },
5545 });
5546 return;
5547 }
5548
5549 return self.fail("TODO genSetStack for register for type float with no intrinsics", .{});
5526 },5550 },
5527 else => {5551 else => {
5528 if (!math.isPowerOfTwo(abi_size)) {5552 if (!math.isPowerOfTwo(abi_size)) {
...@@ -6026,21 +6050,25 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void...@@ -6026,21 +6050,25 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
6026 },6050 },
6027 },6051 },
6028 .Float => {6052 .Float => {
6029 const tag: Mir.Inst.Tag = switch (ty.tag()) {6053 if (self.intrinsicsAllowed(ty)) {
6030 .f32 => .mov_f32,6054 const tag: Mir.Inst.Tag = switch (ty.tag()) {
6031 .f64 => .mov_f64,6055 .f32 => .mov_f32_avx,
6032 else => return self.fail("TODO genSetReg from register for {}", .{ty.fmtDebug()}),6056 .f64 => .mov_f64_avx,
6033 };6057 else => return self.fail("TODO genSetReg from register for {}", .{ty.fmtDebug()}),
6034 _ = try self.addInst(.{6058 };
6035 .tag = tag,6059 _ = try self.addInst(.{
6036 .ops = Mir.Inst.Ops.encode(.{6060 .tag = tag,
6037 .reg1 = reg.to128(),6061 .ops = Mir.Inst.Ops.encode(.{
6038 .reg2 = src_reg.to128(),6062 .reg1 = reg.to128(),
6039 .flags = 0b10,6063 .reg2 = src_reg.to128(),
6040 }),6064 .flags = 0b10,
6041 .data = undefined,6065 }),
6042 });6066 .data = undefined,
6043 return;6067 });
6068 return;
6069 }
6070
6071 return self.fail("TODO genSetReg from register for float with no intrinsics", .{});
6044 },6072 },
6045 else => {},6073 else => {},
6046 }6074 }
...@@ -6073,24 +6101,29 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void...@@ -6073,24 +6101,29 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
6073 const base_reg = try self.register_manager.allocReg(null, .{ .selector_mask = gp });6101 const base_reg = try self.register_manager.allocReg(null, .{ .selector_mask = gp });
6074 try self.loadMemPtrIntoRegister(base_reg, Type.usize, mcv);6102 try self.loadMemPtrIntoRegister(base_reg, Type.usize, mcv);
60756103
6076 const tag: Mir.Inst.Tag = switch (ty.tag()) {6104 if (self.intrinsicsAllowed(ty)) {
6077 .f32 => .mov_f32,6105 const tag: Mir.Inst.Tag = switch (ty.tag()) {
6078 .f64 => .mov_f64,6106 .f32 => .mov_f32_avx,
6079 else => return self.fail("TODO genSetReg from memory for {}", .{ty.fmtDebug()}),6107 .f64 => .mov_f64_avx,
6080 };6108 else => return self.fail("TODO genSetReg from memory for {}", .{ty.fmtDebug()}),
6109 };
60816110
6082 _ = try self.addInst(.{6111 _ = try self.addInst(.{
6083 .tag = tag,6112 .tag = tag,
6084 .ops = Mir.Inst.Ops.encode(.{6113 .ops = Mir.Inst.Ops.encode(.{
6085 .reg1 = reg.to128(),6114 .reg1 = reg.to128(),
6086 .reg2 = switch (ty.tag()) {6115 .reg2 = switch (ty.tag()) {
6087 .f32 => base_reg.to32(),6116 .f32 => base_reg.to32(),
6088 .f64 => base_reg.to64(),6117 .f64 => base_reg.to64(),
6089 else => unreachable,6118 else => unreachable,
6090 },6119 },
6091 }),6120 }),
6092 .data = .{ .imm = 0 },6121 .data = .{ .imm = 0 },
6093 });6122 });
6123 return;
6124 }
6125
6126 return self.fail("TODO genSetReg from memory for float with no intrinsics", .{});
6094 },6127 },
6095 else => {6128 else => {
6096 if (x <= math.maxInt(i32)) {6129 if (x <= math.maxInt(i32)) {
...@@ -6183,24 +6216,27 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void...@@ -6183,24 +6216,27 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void
6183 },6216 },
6184 },6217 },
6185 .Float => {6218 .Float => {
6186 const tag: Mir.Inst.Tag = switch (ty.tag()) {6219 if (self.intrinsicsAllowed(ty)) {
6187 .f32 => .mov_f32,6220 const tag: Mir.Inst.Tag = switch (ty.tag()) {
6188 .f64 => .mov_f64,6221 .f32 => .mov_f32_avx,
6189 else => return self.fail("TODO genSetReg from stack offset for {}", .{ty.fmtDebug()}),6222 .f64 => .mov_f64_avx,
6190 };6223 else => return self.fail("TODO genSetReg from stack offset for {}", .{ty.fmtDebug()}),
6191 _ = try self.addInst(.{6224 };
6192 .tag = tag,6225 _ = try self.addInst(.{
6193 .ops = Mir.Inst.Ops.encode(.{6226 .tag = tag,
6194 .reg1 = reg.to128(),6227 .ops = Mir.Inst.Ops.encode(.{
6195 .reg2 = switch (ty.tag()) {6228 .reg1 = reg.to128(),
6196 .f32 => .ebp,6229 .reg2 = switch (ty.tag()) {
6197 .f64 => .rbp,6230 .f32 => .ebp,
6198 else => unreachable,6231 .f64 => .rbp,
6199 },6232 else => unreachable,
6200 }),6233 },
6201 .data = .{ .imm = @bitCast(u32, -off) },6234 }),
6202 });6235 .data = .{ .imm = @bitCast(u32, -off) },
6203 return;6236 });
6237 return;
6238 }
6239 return self.fail("TODO genSetReg from stack offset for float with no intrinsics", .{});
6204 },6240 },
6205 else => {},6241 else => {},
6206 }6242 }
...@@ -6995,3 +7031,12 @@ fn truncateRegister(self: *Self, ty: Type, reg: Register) !void {...@@ -6995,3 +7031,12 @@ fn truncateRegister(self: *Self, ty: Type, reg: Register) !void {
6995 },7031 },
6996 }7032 }
6997}7033}
7034
7035fn intrinsicsAllowed(self: *Self, ty: Type) bool {
7036 return switch (ty.tag()) {
7037 .f32,
7038 .f64,
7039 => Target.x86.featureSetHasAny(self.target.cpu.features, .{ .avx, .avx2 }),
7040 else => unreachable, // TODO finish this off
7041 };
7042}
src/arch/x86_64/Emit.zig+6-6
...@@ -183,14 +183,14 @@ pub fn lowerMir(emit: *Emit) InnerError!void {...@@ -183,14 +183,14 @@ pub fn lowerMir(emit: *Emit) InnerError!void {
183 .nop => try emit.mirNop(),183 .nop => try emit.mirNop(),
184184
185 // AVX instructions185 // AVX instructions
186 .mov_f64 => try emit.mirMovFloatAvx(.vmovsd, inst),186 .mov_f64_avx => try emit.mirMovFloatAvx(.vmovsd, inst),
187 .mov_f32 => try emit.mirMovFloatAvx(.vmovss, inst),187 .mov_f32_avx => try emit.mirMovFloatAvx(.vmovss, inst),
188188
189 .add_f64 => try emit.mirAddFloatAvx(.vaddsd, inst),189 .add_f64_avx => try emit.mirAddFloatAvx(.vaddsd, inst),
190 .add_f32 => try emit.mirAddFloatAvx(.vaddss, inst),190 .add_f32_avx => try emit.mirAddFloatAvx(.vaddss, inst),
191191
192 .cmp_f64 => try emit.mirCmpFloatAvx(.vucomisd, inst),192 .cmp_f64_avx => try emit.mirCmpFloatAvx(.vucomisd, inst),
193 .cmp_f32 => try emit.mirCmpFloatAvx(.vucomiss, inst),193 .cmp_f32_avx => try emit.mirCmpFloatAvx(.vucomiss, inst),
194194
195 // Pseudo-instructions195 // Pseudo-instructions
196 .call_extern => try emit.mirCallExtern(inst),196 .call_extern => try emit.mirCallExtern(inst),
src/arch/x86_64/Mir.zig+6-6
...@@ -350,18 +350,18 @@ pub const Inst = struct {...@@ -350,18 +350,18 @@ pub const Inst = struct {
350 /// 0b00 reg1, qword ptr [reg2 + imm32]350 /// 0b00 reg1, qword ptr [reg2 + imm32]
351 /// 0b01 qword ptr [reg1 + imm32], reg2351 /// 0b01 qword ptr [reg1 + imm32], reg2
352 /// 0b10 reg1, reg2352 /// 0b10 reg1, reg2
353 mov_f64,353 mov_f64_avx,
354 mov_f32,354 mov_f32_avx,
355355
356 /// ops flags: form:356 /// ops flags: form:
357 /// 0b00 reg1, reg1, reg2357 /// 0b00 reg1, reg1, reg2
358 add_f64,358 add_f64_avx,
359 add_f32,359 add_f32_avx,
360360
361 /// ops flags: form:361 /// ops flags: form:
362 ///362 ///
363 cmp_f64,363 cmp_f64_avx,
364 cmp_f32,364 cmp_f32_avx,
365365
366 /// Pseudo-instructions366 /// Pseudo-instructions
367 /// call extern function367 /// call extern function
src/arch/x86_64/abi.zig+3-3
...@@ -383,11 +383,11 @@ pub const caller_preserved_regs = [_]Register{ .rax, .rcx, .rdx, .rsi, .rdi, .r8...@@ -383,11 +383,11 @@ pub const caller_preserved_regs = [_]Register{ .rax, .rcx, .rdx, .rsi, .rdi, .r8
383pub const c_abi_int_param_regs = [_]Register{ .rdi, .rsi, .rdx, .rcx, .r8, .r9 };383pub const c_abi_int_param_regs = [_]Register{ .rdi, .rsi, .rdx, .rcx, .r8, .r9 };
384pub const c_abi_int_return_regs = [_]Register{ .rax, .rdx };384pub const c_abi_int_return_regs = [_]Register{ .rax, .rdx };
385385
386const avx_regs = [_]Register{386const sse_avx_regs = [_]Register{
387 .ymm0, .ymm1, .ymm2, .ymm3, .ymm4, .ymm5, .ymm6, .ymm7,387 .ymm0, .ymm1, .ymm2, .ymm3, .ymm4, .ymm5, .ymm6, .ymm7,
388 .ymm8, .ymm9, .ymm10, .ymm11, .ymm12, .ymm13, .ymm14, .ymm15,388 .ymm8, .ymm9, .ymm10, .ymm11, .ymm12, .ymm13, .ymm14, .ymm15,
389};389};
390const allocatable_registers = callee_preserved_regs ++ caller_preserved_regs ++ avx_regs;390const allocatable_registers = callee_preserved_regs ++ caller_preserved_regs ++ sse_avx_regs;
391pub const RegisterManager = RegisterManagerFn(@import("CodeGen.zig"), Register, &allocatable_registers);391pub const RegisterManager = RegisterManagerFn(@import("CodeGen.zig"), Register, &allocatable_registers);
392392
393// Register classes393// Register classes
...@@ -401,7 +401,7 @@ pub const RegisterClass = struct {...@@ -401,7 +401,7 @@ pub const RegisterClass = struct {
401 }, true);401 }, true);
402 break :blk set;402 break :blk set;
403 };403 };
404 pub const avx: RegisterBitSet = blk: {404 pub const sse: RegisterBitSet = blk: {
405 var set = RegisterBitSet.initEmpty();405 var set = RegisterBitSet.initEmpty();
406 set.setRangeValue(.{406 set.setRangeValue(.{
407 .start = caller_preserved_regs.len + callee_preserved_regs.len,407 .start = caller_preserved_regs.len + callee_preserved_regs.len,