| author | |
| committer | |
| log | 539bb8a2d7de08a9f7b63f136cb1ec8e5d88568a |
| tree | 515c8565f8cb54e40b3addef8293b107f93c26b1 |
| parent | e86a89d3f0e911b99fa6432425a2799aada6f801 |
| parent | 59df39e949907183e76ac78d7ace07e96f72e8a8 |
| signature |
stage2,x64: implement integer division5 files changed, 435 insertions(+), 24 deletions(-)
src/arch/x86_64/CodeGen.zig+234-20| ... | ... | @@ -1252,30 +1252,244 @@ fn airShlWithOverflow(self: *Self, inst: Air.Inst.Index) !void { |
| 1252 | 1252 | return self.fail("TODO implement airShlWithOverflow for {}", .{self.target.cpu.arch}); |
| 1253 | 1253 | } |
| 1254 | 1254 | |
| 1255 | /// Generates signed or unsigned integer division. | |
| 1256 | /// Requires use of .rax and .rdx registers. Spills them if necessary. | |
| 1257 | /// Quotient is saved in .rax and remainder in .rdx. | |
| 1258 | fn genIntDivOpMir( | |
| 1259 | self: *Self, | |
| 1260 | ty: Type, | |
| 1261 | signedness: std.builtin.Signedness, | |
| 1262 | lhs: MCValue, | |
| 1263 | rhs: MCValue, | |
| 1264 | ) !void { | |
| 1265 | const abi_size = @intCast(u32, ty.abiSize(self.target.*)); | |
| 1266 | if (abi_size > 8) { | |
| 1267 | return self.fail("TODO implement genIntDivOpMir for ABI size larger than 8", .{}); | |
| 1268 | } | |
| 1269 | ||
| 1270 | try self.register_manager.getReg(.rax, null); | |
| 1271 | try self.register_manager.getReg(.rdx, null); | |
| 1272 | self.register_manager.freezeRegs(&.{ .rax, .rdx }); | |
| 1273 | defer self.register_manager.unfreezeRegs(&.{ .rax, .rdx }); | |
| 1274 | ||
| 1275 | const dividend = switch (lhs) { | |
| 1276 | .register => lhs, | |
| 1277 | else => blk: { | |
| 1278 | const reg = try self.copyToTmpRegister(ty, lhs); | |
| 1279 | break :blk MCValue{ .register = reg }; | |
| 1280 | }, | |
| 1281 | }; | |
| 1282 | try self.genSetReg(ty, .rax, dividend); | |
| 1283 | ||
| 1284 | self.register_manager.freezeRegs(&.{dividend.register}); | |
| 1285 | defer self.register_manager.unfreezeRegs(&.{dividend.register}); | |
| 1286 | ||
| 1287 | switch (signedness) { | |
| 1288 | .signed => { | |
| 1289 | _ = try self.addInst(.{ | |
| 1290 | .tag = .cwd, | |
| 1291 | .ops = (Mir.Ops{ | |
| 1292 | .flags = 0b11, | |
| 1293 | }).encode(), | |
| 1294 | .data = undefined, | |
| 1295 | }); | |
| 1296 | }, | |
| 1297 | .unsigned => { | |
| 1298 | _ = try self.addInst(.{ | |
| 1299 | .tag = .xor, | |
| 1300 | .ops = (Mir.Ops{ | |
| 1301 | .reg1 = .rdx, | |
| 1302 | .reg2 = .rdx, | |
| 1303 | }).encode(), | |
| 1304 | .data = undefined, | |
| 1305 | }); | |
| 1306 | }, | |
| 1307 | } | |
| 1308 | ||
| 1309 | const divisor = switch (rhs) { | |
| 1310 | .register => rhs, | |
| 1311 | else => blk: { | |
| 1312 | const reg = try self.copyToTmpRegister(ty, rhs); | |
| 1313 | break :blk MCValue{ .register = reg }; | |
| 1314 | }, | |
| 1315 | }; | |
| 1316 | const op_tag: Mir.Inst.Tag = switch (signedness) { | |
| 1317 | .signed => .idiv, | |
| 1318 | .unsigned => .div, | |
| 1319 | }; | |
| 1320 | ||
| 1321 | switch (divisor) { | |
| 1322 | .register => |reg| { | |
| 1323 | _ = try self.addInst(.{ | |
| 1324 | .tag = op_tag, | |
| 1325 | .ops = (Mir.Ops{ | |
| 1326 | .reg1 = reg, | |
| 1327 | }).encode(), | |
| 1328 | .data = undefined, | |
| 1329 | }); | |
| 1330 | }, | |
| 1331 | .stack_offset => |off| { | |
| 1332 | _ = try self.addInst(.{ | |
| 1333 | .tag = op_tag, | |
| 1334 | .ops = (Mir.Ops{ | |
| 1335 | .reg2 = .rbp, | |
| 1336 | .flags = switch (abi_size) { | |
| 1337 | 1 => 0b00, | |
| 1338 | 2 => 0b01, | |
| 1339 | 4 => 0b10, | |
| 1340 | 8 => 0b11, | |
| 1341 | else => unreachable, | |
| 1342 | }, | |
| 1343 | }).encode(), | |
| 1344 | .data = .{ .imm = @bitCast(u32, -off) }, | |
| 1345 | }); | |
| 1346 | }, | |
| 1347 | else => unreachable, | |
| 1348 | } | |
| 1349 | } | |
| 1350 | ||
| 1351 | fn genInlineIntDivFloor(self: *Self, ty: Type, lhs: MCValue, rhs: MCValue) !MCValue { | |
| 1352 | const signedness = ty.intInfo(self.target.*).signedness; | |
| 1353 | const dividend = switch (lhs) { | |
| 1354 | .register => |reg| reg, | |
| 1355 | else => try self.copyToTmpRegister(ty, lhs), | |
| 1356 | }; | |
| 1357 | self.register_manager.freezeRegs(&.{dividend}); | |
| 1358 | ||
| 1359 | const divisor = switch (rhs) { | |
| 1360 | .register => |reg| reg, | |
| 1361 | else => try self.copyToTmpRegister(ty, rhs), | |
| 1362 | }; | |
| 1363 | self.register_manager.freezeRegs(&.{divisor}); | |
| 1364 | defer self.register_manager.unfreezeRegs(&.{ dividend, divisor }); | |
| 1365 | ||
| 1366 | try self.genIntDivOpMir(Type.isize, signedness, .{ .register = dividend }, .{ .register = divisor }); | |
| 1367 | ||
| 1368 | _ = try self.addInst(.{ | |
| 1369 | .tag = .xor, | |
| 1370 | .ops = (Mir.Ops{ | |
| 1371 | .reg1 = divisor.to64(), | |
| 1372 | .reg2 = dividend.to64(), | |
| 1373 | }).encode(), | |
| 1374 | .data = undefined, | |
| 1375 | }); | |
| 1376 | _ = try self.addInst(.{ | |
| 1377 | .tag = .sar, | |
| 1378 | .ops = (Mir.Ops{ | |
| 1379 | .reg1 = divisor.to64(), | |
| 1380 | .flags = 0b10, | |
| 1381 | }).encode(), | |
| 1382 | .data = .{ .imm = 63 }, | |
| 1383 | }); | |
| 1384 | _ = try self.addInst(.{ | |
| 1385 | .tag = .@"test", | |
| 1386 | .ops = (Mir.Ops{ | |
| 1387 | .reg1 = .rdx, | |
| 1388 | .reg2 = .rdx, | |
| 1389 | }).encode(), | |
| 1390 | .data = undefined, | |
| 1391 | }); | |
| 1392 | _ = try self.addInst(.{ | |
| 1393 | .tag = .cond_mov_eq, | |
| 1394 | .ops = (Mir.Ops{ | |
| 1395 | .reg1 = divisor.to64(), | |
| 1396 | .reg2 = .rdx, | |
| 1397 | }).encode(), | |
| 1398 | .data = undefined, | |
| 1399 | }); | |
| 1400 | try self.genBinMathOpMir(.add, Type.isize, .{ .register = divisor.to64() }, .{ .register = .rax }); | |
| 1401 | return MCValue{ .register = divisor }; | |
| 1402 | } | |
| 1403 | ||
| 1255 | 1404 | fn airDiv(self: *Self, inst: Air.Inst.Index) !void { |
| 1256 | 1405 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; |
| 1257 | const result: MCValue = if (self.liveness.isUnused(inst)) | |
| 1258 | .dead | |
| 1259 | else | |
| 1260 | return self.fail("TODO implement div for {}", .{self.target.cpu.arch}); | |
| 1406 | const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { | |
| 1407 | const tag = self.air.instructions.items(.tag)[inst]; | |
| 1408 | const ty = self.air.typeOfIndex(inst); | |
| 1409 | ||
| 1410 | if (ty.zigTypeTag() != .Int) { | |
| 1411 | return self.fail("TODO implement {} for operands of dst type {}", .{ tag, ty.zigTypeTag() }); | |
| 1412 | } | |
| 1413 | ||
| 1414 | if (tag == .div_float) { | |
| 1415 | return self.fail("TODO implement {}", .{tag}); | |
| 1416 | } | |
| 1417 | ||
| 1418 | // Spill .rax and .rdx upfront to ensure we don't spill the operands too late. | |
| 1419 | try self.register_manager.getReg(.rax, null); | |
| 1420 | try self.register_manager.getReg(.rdx, null); | |
| 1421 | ||
| 1422 | const lhs = try self.resolveInst(bin_op.lhs); | |
| 1423 | const rhs = try self.resolveInst(bin_op.rhs); | |
| 1424 | ||
| 1425 | const signedness = ty.intInfo(self.target.*).signedness; | |
| 1426 | if (signedness == .unsigned) { | |
| 1427 | try self.genIntDivOpMir(ty, signedness, lhs, rhs); | |
| 1428 | break :result MCValue{ .register = .rax }; | |
| 1429 | } | |
| 1430 | ||
| 1431 | switch (tag) { | |
| 1432 | .div_exact, .div_trunc => { | |
| 1433 | try self.genIntDivOpMir(ty, signedness, lhs, rhs); | |
| 1434 | break :result MCValue{ .register = .rax }; | |
| 1435 | }, | |
| 1436 | .div_floor => { | |
| 1437 | break :result try self.genInlineIntDivFloor(ty, lhs, rhs); | |
| 1438 | }, | |
| 1439 | else => unreachable, | |
| 1440 | } | |
| 1441 | }; | |
| 1261 | 1442 | return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); |
| 1262 | 1443 | } |
| 1263 | 1444 | |
| 1264 | 1445 | fn airRem(self: *Self, inst: Air.Inst.Index) !void { |
| 1265 | 1446 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; |
| 1266 | const result: MCValue = if (self.liveness.isUnused(inst)) | |
| 1267 | .dead | |
| 1268 | else | |
| 1269 | return self.fail("TODO implement rem for {}", .{self.target.cpu.arch}); | |
| 1447 | const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { | |
| 1448 | const ty = self.air.typeOfIndex(inst); | |
| 1449 | if (ty.zigTypeTag() != .Int) { | |
| 1450 | return self.fail("TODO implement .rem for operands of dst type {}", .{ty.zigTypeTag()}); | |
| 1451 | } | |
| 1452 | // Spill .rax and .rdx upfront to ensure we don't spill the operands too late. | |
| 1453 | try self.register_manager.getReg(.rax, null); | |
| 1454 | try self.register_manager.getReg(.rdx, null); | |
| 1455 | const lhs = try self.resolveInst(bin_op.lhs); | |
| 1456 | const rhs = try self.resolveInst(bin_op.rhs); | |
| 1457 | const signedness = ty.intInfo(self.target.*).signedness; | |
| 1458 | try self.genIntDivOpMir(ty, signedness, lhs, rhs); | |
| 1459 | break :result MCValue{ .register = .rdx }; | |
| 1460 | }; | |
| 1270 | 1461 | return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); |
| 1271 | 1462 | } |
| 1272 | 1463 | |
| 1273 | 1464 | fn airMod(self: *Self, inst: Air.Inst.Index) !void { |
| 1274 | 1465 | const bin_op = self.air.instructions.items(.data)[inst].bin_op; |
| 1275 | const result: MCValue = if (self.liveness.isUnused(inst)) | |
| 1276 | .dead | |
| 1277 | else | |
| 1278 | return self.fail("TODO implement mod for {}", .{self.target.cpu.arch}); | |
| 1466 | const result: MCValue = if (self.liveness.isUnused(inst)) .dead else result: { | |
| 1467 | const ty = self.air.typeOfIndex(inst); | |
| 1468 | if (ty.zigTypeTag() != .Int) { | |
| 1469 | return self.fail("TODO implement .mod for operands of dst type {}", .{ty.zigTypeTag()}); | |
| 1470 | } | |
| 1471 | // Spill .rax and .rdx upfront to ensure we don't spill the operands too late. | |
| 1472 | try self.register_manager.getReg(.rax, null); | |
| 1473 | try self.register_manager.getReg(.rdx, null); | |
| 1474 | const lhs = try self.resolveInst(bin_op.lhs); | |
| 1475 | const rhs = try self.resolveInst(bin_op.rhs); | |
| 1476 | const signedness = ty.intInfo(self.target.*).signedness; | |
| 1477 | switch (signedness) { | |
| 1478 | .unsigned => { | |
| 1479 | try self.genIntDivOpMir(ty, signedness, lhs, rhs); | |
| 1480 | break :result MCValue{ .register = .rdx }; | |
| 1481 | }, | |
| 1482 | .signed => { | |
| 1483 | const div_floor = try self.genInlineIntDivFloor(ty, lhs, rhs); | |
| 1484 | try self.genIMulOpMir(ty, div_floor, rhs); | |
| 1485 | ||
| 1486 | const reg = try self.copyToTmpRegister(ty, lhs); | |
| 1487 | try self.genBinMathOpMir(.sub, ty, .{ .register = reg }, div_floor); | |
| 1488 | ||
| 1489 | break :result MCValue{ .register = reg }; | |
| 1490 | }, | |
| 1491 | } | |
| 1492 | }; | |
| 1279 | 1493 | return self.finishAir(inst, result, .{ bin_op.lhs, bin_op.rhs, .none }); |
| 1280 | 1494 | } |
| 1281 | 1495 | |
| ... | ... | @@ -4126,7 +4340,7 @@ fn genInlineMemset( |
| 4126 | 4340 | } |
| 4127 | 4341 | |
| 4128 | 4342 | fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void { |
| 4129 | const abi_size = ty.abiSize(self.target.*); | |
| 4343 | const abi_size = @intCast(u32, ty.abiSize(self.target.*)); | |
| 4130 | 4344 | switch (mcv) { |
| 4131 | 4345 | .dead => unreachable, |
| 4132 | 4346 | .ptr_stack_offset => |off| { |
| ... | ... | @@ -4136,7 +4350,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void |
| 4136 | 4350 | _ = try self.addInst(.{ |
| 4137 | 4351 | .tag = .lea, |
| 4138 | 4352 | .ops = (Mir.Ops{ |
| 4139 | .reg1 = registerAlias(reg, @intCast(u32, abi_size)), | |
| 4353 | .reg1 = registerAlias(reg, abi_size), | |
| 4140 | 4354 | .reg2 = .rbp, |
| 4141 | 4355 | }).encode(), |
| 4142 | 4356 | .data = .{ .imm = @bitCast(u32, -off) }, |
| ... | ... | @@ -4202,7 +4416,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void |
| 4202 | 4416 | _ = try self.addInst(.{ |
| 4203 | 4417 | .tag = .mov, |
| 4204 | 4418 | .ops = (Mir.Ops{ |
| 4205 | .reg1 = registerAlias(reg, @intCast(u32, abi_size)), | |
| 4419 | .reg1 = registerAlias(reg, abi_size), | |
| 4206 | 4420 | }).encode(), |
| 4207 | 4421 | .data = .{ .imm = @truncate(u32, x) }, |
| 4208 | 4422 | }); |
| ... | ... | @@ -4249,7 +4463,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void |
| 4249 | 4463 | .tag = .mov_sign_extend, |
| 4250 | 4464 | .ops = (Mir.Ops{ |
| 4251 | 4465 | .reg1 = reg.to64(), |
| 4252 | .reg2 = src_reg, | |
| 4466 | .reg2 = registerAlias(src_reg, abi_size), | |
| 4253 | 4467 | }).encode(), |
| 4254 | 4468 | .data = undefined, |
| 4255 | 4469 | }); |
| ... | ... | @@ -4260,7 +4474,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void |
| 4260 | 4474 | .tag = .mov_zero_extend, |
| 4261 | 4475 | .ops = (Mir.Ops{ |
| 4262 | 4476 | .reg1 = reg.to64(), |
| 4263 | .reg2 = src_reg, | |
| 4477 | .reg2 = registerAlias(src_reg, abi_size), | |
| 4264 | 4478 | }).encode(), |
| 4265 | 4479 | .data = undefined, |
| 4266 | 4480 | }); |
| ... | ... | @@ -4272,8 +4486,8 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void |
| 4272 | 4486 | _ = try self.addInst(.{ |
| 4273 | 4487 | .tag = .mov, |
| 4274 | 4488 | .ops = (Mir.Ops{ |
| 4275 | .reg1 = registerAlias(reg, @divExact(src_reg.size(), 8)), | |
| 4276 | .reg2 = src_reg, | |
| 4489 | .reg1 = registerAlias(reg, abi_size), | |
| 4490 | .reg2 = registerAlias(src_reg, abi_size), | |
| 4277 | 4491 | }).encode(), |
| 4278 | 4492 | .data = undefined, |
| 4279 | 4493 | }); |
| ... | ... | @@ -4399,7 +4613,7 @@ fn genSetReg(self: *Self, ty: Type, reg: Register, mcv: MCValue) InnerError!void |
| 4399 | 4613 | _ = try self.addInst(.{ |
| 4400 | 4614 | .tag = .mov, |
| 4401 | 4615 | .ops = (Mir.Ops{ |
| 4402 | .reg1 = registerAlias(reg, @intCast(u32, abi_size)), | |
| 4616 | .reg1 = registerAlias(reg, abi_size), | |
| 4403 | 4617 | .reg2 = .rbp, |
| 4404 | 4618 | .flags = 0b01, |
| 4405 | 4619 | }).encode(), |
src/arch/x86_64/Emit.zig+87-2| ... | ... | @@ -138,8 +138,13 @@ pub fn lowerMir(emit: *Emit) InnerError!void { |
| 138 | 138 | .shr => try emit.mirShift(.shr, inst), |
| 139 | 139 | .sar => try emit.mirShift(.sar, inst), |
| 140 | 140 | |
| 141 | .imul => try emit.mirMulDiv(.imul, inst), | |
| 142 | .idiv => try emit.mirMulDiv(.idiv, inst), | |
| 143 | .div => try emit.mirMulDiv(.div, inst), | |
| 141 | 144 | .imul_complex => try emit.mirIMulComplex(inst), |
| 142 | 145 | |
| 146 | .cwd => try emit.mirCwd(inst), | |
| 147 | ||
| 143 | 148 | .push => try emit.mirPushPop(.push, inst), |
| 144 | 149 | .pop => try emit.mirPushPop(.pop, inst), |
| 145 | 150 | |
| ... | ... | @@ -156,6 +161,8 @@ pub fn lowerMir(emit: *Emit) InnerError!void { |
| 156 | 161 | .cond_set_byte_eq_ne, |
| 157 | 162 | => try emit.mirCondSetByte(tag, inst), |
| 158 | 163 | |
| 164 | .cond_mov_eq => try emit.mirCondMov(.cmove, inst), | |
| 165 | ||
| 159 | 166 | .ret => try emit.mirRet(inst), |
| 160 | 167 | |
| 161 | 168 | .syscall => try emit.mirSyscall(), |
| ... | ... | @@ -368,6 +375,24 @@ fn mirCondSetByte(emit: *Emit, mir_tag: Mir.Inst.Tag, inst: Mir.Inst.Index) Inne |
| 368 | 375 | return lowerToMEnc(tag, RegisterOrMemory.reg(ops.reg1.to8()), emit.code); |
| 369 | 376 | } |
| 370 | 377 | |
| 378 | fn mirCondMov(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { | |
| 379 | const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); | |
| 380 | if (ops.flags == 0b00) { | |
| 381 | return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.reg(ops.reg2), emit.code); | |
| 382 | } | |
| 383 | const imm = emit.mir.instructions.items(.data)[inst].imm; | |
| 384 | const ptr_size: Memory.PtrSize = switch (ops.flags) { | |
| 385 | 0b00 => unreachable, | |
| 386 | 0b01 => .word_ptr, | |
| 387 | 0b10 => .dword_ptr, | |
| 388 | 0b11 => .qword_ptr, | |
| 389 | }; | |
| 390 | return lowerToRmEnc(tag, ops.reg1, RegisterOrMemory.mem(ptr_size, .{ | |
| 391 | .disp = imm, | |
| 392 | .base = ops.reg2, | |
| 393 | }), emit.code); | |
| 394 | } | |
| 395 | ||
| 371 | 396 | fn mirTest(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { |
| 372 | 397 | const tag = emit.mir.instructions.items(.tag)[inst]; |
| 373 | 398 | assert(tag == .@"test"); |
| ... | ... | @@ -386,7 +411,7 @@ fn mirTest(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { |
| 386 | 411 | return lowerToMiEnc(.@"test", RegisterOrMemory.reg(ops.reg1), imm, emit.code); |
| 387 | 412 | } |
| 388 | 413 | // TEST r/m64, r64 |
| 389 | return emit.fail("TODO TEST r/m64, r64", .{}); | |
| 414 | return lowerToMrEnc(.@"test", RegisterOrMemory.reg(ops.reg1), ops.reg2, emit.code); | |
| 390 | 415 | }, |
| 391 | 416 | else => return emit.fail("TODO more TEST alternatives", .{}), |
| 392 | 417 | } |
| ... | ... | @@ -683,6 +708,27 @@ fn mirShift(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { |
| 683 | 708 | } |
| 684 | 709 | } |
| 685 | 710 | |
| 711 | fn mirMulDiv(emit: *Emit, tag: Tag, inst: Mir.Inst.Index) InnerError!void { | |
| 712 | const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); | |
| 713 | if (ops.reg1 != .none) { | |
| 714 | assert(ops.reg2 == .none); | |
| 715 | return lowerToMEnc(tag, RegisterOrMemory.reg(ops.reg1), emit.code); | |
| 716 | } | |
| 717 | assert(ops.reg1 == .none); | |
| 718 | assert(ops.reg2 != .none); | |
| 719 | const imm = emit.mir.instructions.items(.data)[inst].imm; | |
| 720 | const ptr_size: Memory.PtrSize = switch (ops.flags) { | |
| 721 | 0b00 => .byte_ptr, | |
| 722 | 0b01 => .word_ptr, | |
| 723 | 0b10 => .dword_ptr, | |
| 724 | 0b11 => .qword_ptr, | |
| 725 | }; | |
| 726 | return lowerToMEnc(tag, RegisterOrMemory.mem(ptr_size, .{ | |
| 727 | .disp = imm, | |
| 728 | .base = ops.reg2, | |
| 729 | }), emit.code); | |
| 730 | } | |
| 731 | ||
| 686 | 732 | fn mirIMulComplex(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { |
| 687 | 733 | const tag = emit.mir.instructions.items(.tag)[inst]; |
| 688 | 734 | assert(tag == .imul_complex); |
| ... | ... | @@ -714,6 +760,17 @@ fn mirIMulComplex(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { |
| 714 | 760 | } |
| 715 | 761 | } |
| 716 | 762 | |
| 763 | fn mirCwd(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { | |
| 764 | const ops = Mir.Ops.decode(emit.mir.instructions.items(.ops)[inst]); | |
| 765 | const tag: Tag = switch (ops.flags) { | |
| 766 | 0b00 => .cbw, | |
| 767 | 0b01 => .cwd, | |
| 768 | 0b10 => .cdq, | |
| 769 | 0b11 => .cqo, | |
| 770 | }; | |
| 771 | return lowerToZoEnc(tag, emit.code); | |
| 772 | } | |
| 773 | ||
| 717 | 774 | fn mirLea(emit: *Emit, inst: Mir.Inst.Index) InnerError!void { |
| 718 | 775 | const tag = emit.mir.instructions.items(.tag)[inst]; |
| 719 | 776 | assert(tag == .lea); |
| ... | ... | @@ -1048,6 +1105,8 @@ const Tag = enum { |
| 1048 | 1105 | brk, |
| 1049 | 1106 | nop, |
| 1050 | 1107 | imul, |
| 1108 | idiv, | |
| 1109 | div, | |
| 1051 | 1110 | syscall, |
| 1052 | 1111 | ret_near, |
| 1053 | 1112 | ret_far, |
| ... | ... | @@ -1115,6 +1174,12 @@ const Tag = enum { |
| 1115 | 1174 | sal, |
| 1116 | 1175 | shr, |
| 1117 | 1176 | sar, |
| 1177 | cbw, | |
| 1178 | cwd, | |
| 1179 | cdq, | |
| 1180 | cqo, | |
| 1181 | cmove, | |
| 1182 | cmovz, | |
| 1118 | 1183 | |
| 1119 | 1184 | fn isSetCC(tag: Tag) bool { |
| 1120 | 1185 | return switch (tag) { |
| ... | ... | @@ -1234,6 +1299,8 @@ inline fn getOpCode(tag: Tag, enc: Encoding, is_one_byte: bool) ?OpCode { |
| 1234 | 1299 | .brk => OpCode.oneByte(0xcc), |
| 1235 | 1300 | .nop => OpCode.oneByte(0x90), |
| 1236 | 1301 | .syscall => OpCode.twoByte(0x0f, 0x05), |
| 1302 | .cbw => OpCode.oneByte(0x98), | |
| 1303 | .cwd, .cdq, .cqo => OpCode.oneByte(0x99), | |
| 1237 | 1304 | else => null, |
| 1238 | 1305 | }, |
| 1239 | 1306 | .d => return switch (tag) { |
| ... | ... | @@ -1276,6 +1343,7 @@ inline fn getOpCode(tag: Tag, enc: Encoding, is_one_byte: bool) ?OpCode { |
| 1276 | 1343 | .setnl, .setge => OpCode.twoByte(0x0f, 0x9d), |
| 1277 | 1344 | .setle, .setng => OpCode.twoByte(0x0f, 0x9e), |
| 1278 | 1345 | .setnle, .setg => OpCode.twoByte(0x0f, 0x9f), |
| 1346 | .idiv, .div, .imul => OpCode.oneByte(if (is_one_byte) 0xf6 else 0xf7), | |
| 1279 | 1347 | else => null, |
| 1280 | 1348 | }, |
| 1281 | 1349 | .o => return switch (tag) { |
| ... | ... | @@ -1319,6 +1387,7 @@ inline fn getOpCode(tag: Tag, enc: Encoding, is_one_byte: bool) ?OpCode { |
| 1319 | 1387 | .sbb => OpCode.oneByte(if (is_one_byte) 0x18 else 0x19), |
| 1320 | 1388 | .cmp => OpCode.oneByte(if (is_one_byte) 0x38 else 0x39), |
| 1321 | 1389 | .mov => OpCode.oneByte(if (is_one_byte) 0x88 else 0x89), |
| 1390 | .@"test" => OpCode.oneByte(if (is_one_byte) 0x84 else 0x85), | |
| 1322 | 1391 | else => null, |
| 1323 | 1392 | }, |
| 1324 | 1393 | .rm => return switch (tag) { |
| ... | ... | @@ -1336,6 +1405,7 @@ inline fn getOpCode(tag: Tag, enc: Encoding, is_one_byte: bool) ?OpCode { |
| 1336 | 1405 | .movzx => OpCode.twoByte(0x0f, if (is_one_byte) 0xb6 else 0xb7), |
| 1337 | 1406 | .lea => OpCode.oneByte(if (is_one_byte) 0x8c else 0x8d), |
| 1338 | 1407 | .imul => OpCode.twoByte(0x0f, 0xaf), |
| 1408 | .cmove, .cmovz => OpCode.twoByte(0x0f, 0x44), | |
| 1339 | 1409 | else => null, |
| 1340 | 1410 | }, |
| 1341 | 1411 | .oi => return switch (tag) { |
| ... | ... | @@ -1409,6 +1479,9 @@ inline fn getModRmExt(tag: Tag) ?u3 { |
| 1409 | 1479 | => 0x4, |
| 1410 | 1480 | .shr => 0x5, |
| 1411 | 1481 | .sar => 0x7, |
| 1482 | .imul => 0x5, | |
| 1483 | .idiv => 0x7, | |
| 1484 | .div => 0x6, | |
| 1412 | 1485 | else => null, |
| 1413 | 1486 | }; |
| 1414 | 1487 | } |
| ... | ... | @@ -1565,7 +1638,15 @@ const RegisterOrMemory = union(enum) { |
| 1565 | 1638 | |
| 1566 | 1639 | fn lowerToZoEnc(tag: Tag, code: *std.ArrayList(u8)) InnerError!void { |
| 1567 | 1640 | const opc = getOpCode(tag, .zo, false).?; |
| 1568 | const encoder = try Encoder.init(code, 1); | |
| 1641 | const encoder = try Encoder.init(code, 2); | |
| 1642 | switch (tag) { | |
| 1643 | .cqo => { | |
| 1644 | encoder.rex(.{ | |
| 1645 | .w = true, | |
| 1646 | }); | |
| 1647 | }, | |
| 1648 | else => {}, | |
| 1649 | } | |
| 1569 | 1650 | opc.encode(encoder); |
| 1570 | 1651 | } |
| 1571 | 1652 | |
| ... | ... | @@ -2204,6 +2285,10 @@ test "lower M encoding" { |
| 2204 | 2285 | try expectEqualHexStrings("\xFF\x24\x25\x10\x00\x00\x00", emit.lowered(), "jmp qword ptr [ds:0x10]"); |
| 2205 | 2286 | try lowerToMEnc(.seta, RegisterOrMemory.reg(.r11b), emit.code()); |
| 2206 | 2287 | try expectEqualHexStrings("\x41\x0F\x97\xC3", emit.lowered(), "seta r11b"); |
| 2288 | try lowerToMEnc(.idiv, RegisterOrMemory.reg(.rax), emit.code()); | |
| 2289 | try expectEqualHexStrings("\x48\xF7\xF8", emit.lowered(), "idiv rax"); | |
| 2290 | try lowerToMEnc(.imul, RegisterOrMemory.reg(.al), emit.code()); | |
| 2291 | try expectEqualHexStrings("\xF6\xE8", emit.lowered(), "imul al"); | |
| 2207 | 2292 | } |
| 2208 | 2293 | |
| 2209 | 2294 | test "lower M1 and MC encodings" { |
src/arch/x86_64/Mir.zig+20-2| ... | ... | @@ -220,10 +220,21 @@ pub const Inst = struct { |
| 220 | 220 | sar_mem_index_imm, |
| 221 | 221 | |
| 222 | 222 | /// ops flags: form: |
| 223 | /// 0bX0 reg1 | |
| 224 | /// 0bX1 [reg1 + imm32] | |
| 223 | /// 0b00 reg1 | |
| 224 | /// 0b00 byte ptr [reg2 + imm32] | |
| 225 | /// 0b01 word ptr [reg2 + imm32] | |
| 226 | /// 0b10 dword ptr [reg2 + imm32] | |
| 227 | /// 0b11 qword ptr [reg2 + imm32] | |
| 225 | 228 | imul, |
| 226 | 229 | idiv, |
| 230 | div, | |
| 231 | ||
| 232 | /// ops flags: form: | |
| 233 | /// 0b00 AX <- AL | |
| 234 | /// 0b01 DX:AX <- AX | |
| 235 | /// 0b10 EDX:EAX <- EAX | |
| 236 | /// 0b11 RDX:RAX <- RAX | |
| 237 | cwd, | |
| 227 | 238 | |
| 228 | 239 | /// ops flags: form: |
| 229 | 240 | /// 0b00 reg1, reg2 |
| ... | ... | @@ -275,6 +286,13 @@ pub const Inst = struct { |
| 275 | 286 | cond_jmp_eq_ne, |
| 276 | 287 | cond_set_byte_eq_ne, |
| 277 | 288 | |
| 289 | /// ops flags: | |
| 290 | /// 0b00 reg1, reg2, | |
| 291 | /// 0b01 reg1, word ptr [reg2 + imm] | |
| 292 | /// 0b10 reg1, dword ptr [reg2 + imm] | |
| 293 | /// 0b11 reg1, qword ptr [reg2 + imm] | |
| 294 | cond_mov_eq, | |
| 295 | ||
| 278 | 296 | /// ops flags: form: |
| 279 | 297 | /// 0b00 reg1 |
| 280 | 298 | /// 0b01 [reg1 + imm32] |
test/behavior.zig+1| ... | ... | @@ -50,6 +50,7 @@ test { |
| 50 | 50 | _ = @import("behavior/tuple.zig"); |
| 51 | 51 | _ = @import("behavior/type.zig"); |
| 52 | 52 | _ = @import("behavior/var_args.zig"); |
| 53 | _ = @import("behavior/int_div.zig"); | |
| 53 | 54 | |
| 54 | 55 | // tests that don't pass for stage1 |
| 55 | 56 | if (builtin.zig_backend != .stage1) { |
test/behavior/int_div.zig created+93| ... | ... | @@ -0,0 +1,93 @@ |
| 1 | const std = @import("std"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const expect = std.testing.expect; | |
| 4 | ||
| 5 | test "integer division" { | |
| 6 | if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; | |
| 7 | if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; | |
| 8 | if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; | |
| 9 | if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; | |
| 10 | ||
| 11 | try testDivision(); | |
| 12 | comptime try testDivision(); | |
| 13 | } | |
| 14 | fn testDivision() !void { | |
| 15 | try expect(div(u32, 13, 3) == 4); | |
| 16 | try expect(div(u64, 13, 3) == 4); | |
| 17 | try expect(div(u8, 13, 3) == 4); | |
| 18 | ||
| 19 | try expect(divExact(u32, 55, 11) == 5); | |
| 20 | try expect(divExact(i32, -55, 11) == -5); | |
| 21 | try expect(divExact(i64, -55, 11) == -5); | |
| 22 | try expect(divExact(i16, -55, 11) == -5); | |
| 23 | ||
| 24 | try expect(divFloor(i8, 5, 3) == 1); | |
| 25 | try expect(divFloor(i16, -5, 3) == -2); | |
| 26 | try expect(divFloor(i64, -0x80000000, -2) == 0x40000000); | |
| 27 | try expect(divFloor(i32, 0, -0x80000000) == 0); | |
| 28 | try expect(divFloor(i64, -0x40000001, 0x40000000) == -2); | |
| 29 | try expect(divFloor(i32, -0x80000000, 1) == -0x80000000); | |
| 30 | try expect(divFloor(i32, 10, 12) == 0); | |
| 31 | try expect(divFloor(i32, -14, 12) == -2); | |
| 32 | try expect(divFloor(i32, -2, 12) == -1); | |
| 33 | ||
| 34 | try expect(divTrunc(i32, 5, 3) == 1); | |
| 35 | try expect(divTrunc(i32, -5, 3) == -1); | |
| 36 | try expect(divTrunc(i32, 9, -10) == 0); | |
| 37 | try expect(divTrunc(i32, -9, 10) == 0); | |
| 38 | try expect(divTrunc(i32, 10, 12) == 0); | |
| 39 | try expect(divTrunc(i32, -14, 12) == -1); | |
| 40 | try expect(divTrunc(i32, -2, 12) == 0); | |
| 41 | ||
| 42 | try expect(mod(u32, 10, 12) == 10); | |
| 43 | try expect(mod(i32, 10, 12) == 10); | |
| 44 | try expect(mod(i64, -14, 12) == 10); | |
| 45 | try expect(mod(i16, -2, 12) == 10); | |
| 46 | try expect(mod(i8, -2, 12) == 10); | |
| 47 | ||
| 48 | try expect(rem(i32, 10, 12) == 10); | |
| 49 | try expect(rem(i32, -14, 12) == -2); | |
| 50 | try expect(rem(i32, -2, 12) == -2); | |
| 51 | ||
| 52 | comptime { | |
| 53 | try expect( | |
| 54 | 1194735857077236777412821811143690633098347576 % 508740759824825164163191790951174292733114988 == 177254337427586449086438229241342047632117600, | |
| 55 | ); | |
| 56 | try expect( | |
| 57 | @rem(-1194735857077236777412821811143690633098347576, 508740759824825164163191790951174292733114988) == -177254337427586449086438229241342047632117600, | |
| 58 | ); | |
| 59 | try expect( | |
| 60 | 1194735857077236777412821811143690633098347576 / 508740759824825164163191790951174292733114988 == 2, | |
| 61 | ); | |
| 62 | try expect( | |
| 63 | @divTrunc(-1194735857077236777412821811143690633098347576, 508740759824825164163191790951174292733114988) == -2, | |
| 64 | ); | |
| 65 | try expect( | |
| 66 | @divTrunc(1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == -2, | |
| 67 | ); | |
| 68 | try expect( | |
| 69 | @divTrunc(-1194735857077236777412821811143690633098347576, -508740759824825164163191790951174292733114988) == 2, | |
| 70 | ); | |
| 71 | try expect( | |
| 72 | 4126227191251978491697987544882340798050766755606969681711 % 10 == 1, | |
| 73 | ); | |
| 74 | } | |
| 75 | } | |
| 76 | fn div(comptime T: type, a: T, b: T) T { | |
| 77 | return a / b; | |
| 78 | } | |
| 79 | fn divExact(comptime T: type, a: T, b: T) T { | |
| 80 | return @divExact(a, b); | |
| 81 | } | |
| 82 | fn divFloor(comptime T: type, a: T, b: T) T { | |
| 83 | return @divFloor(a, b); | |
| 84 | } | |
| 85 | fn divTrunc(comptime T: type, a: T, b: T) T { | |
| 86 | return @divTrunc(a, b); | |
| 87 | } | |
| 88 | fn mod(comptime T: type, a: T, b: T) T { | |
| 89 | return @mod(a, b); | |
| 90 | } | |
| 91 | fn rem(comptime T: type, a: T, b: T) T { | |
| 92 | return @rem(a, b); | |
| 93 | } |