authorgravatar for 81774659+gracefuu@users.noreply.github.comgracefu <81774659+gracefuu@users.noreply.github.com> 2021-04-09 13:51:00+08:00
committergravatar for 81774659+gracefuu@users.noreply.github.comgracefu <81774659+gracefuu@users.noreply.github.com> 2021-04-16 15:21:17+08:00
logc4b83ea02102611a85f75b189f0803d9b6a335c2
tree5460625c3766085c5f1210f577e3144296c6a986
parent5bd464e386df35bfe38b062190074ce3c2689001
signaturelock-open Commit is signed but in an unrecognized format.

stage2 x86_64: implement integer mul

This was also an experiment to see if it were easier to implement a new feature when using the instruction encoder. Verdict: It's not that much easier, but I think it's certainly much more readable, because the description of the Instruction annotates what each field means. Right now, precise knowledge of x86_64 instructions is still required because things like when to set the 64-bit flag, how to read x86_64 instruction references, etc. are still not automatically done for you. In the future, this interface might make it sligtly easier to write an assembler for x86_64, by abstracting the bit-fiddling aspects of instruction encoding.

4 files changed, 318 insertions(+), 0 deletions(-)

src/Module.zig+60
......@@ -4330,6 +4330,33 @@ pub fn intSub(allocator: *Allocator, lhs: Value, rhs: Value) !Value {
43304330 }
43314331}
43324332
4333pub fn intMul(allocator: *Allocator, lhs: Value, rhs: Value) !Value {
4334 // TODO is this a performance issue? maybe we should try the operation without
4335 // resorting to BigInt first.
4336 var lhs_space: Value.BigIntSpace = undefined;
4337 var rhs_space: Value.BigIntSpace = undefined;
4338 const lhs_bigint = lhs.toBigInt(&lhs_space);
4339 const rhs_bigint = rhs.toBigInt(&rhs_space);
4340 const limbs = try allocator.alloc(
4341 std.math.big.Limb,
4342 lhs_bigint.limbs.len + rhs_bigint.limbs.len + 1,
4343 );
4344 var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
4345 var limbs_buffer = try allocator.alloc(
4346 std.math.big.Limb,
4347 std.math.big.int.calcMulLimbsBufferLen(lhs_bigint.limbs.len, rhs_bigint.limbs.len, 1),
4348 );
4349 defer allocator.free(limbs_buffer);
4350 result_bigint.mul(lhs_bigint, rhs_bigint, limbs_buffer, allocator);
4351 const result_limbs = result_bigint.limbs[0..result_bigint.len];
4352
4353 if (result_bigint.positive) {
4354 return Value.Tag.int_big_positive.create(allocator, result_limbs);
4355 } else {
4356 return Value.Tag.int_big_negative.create(allocator, result_limbs);
4357 }
4358}
4359
43334360pub fn floatAdd(
43344361 arena: *Allocator,
43354362 float_type: Type,
......@@ -4396,6 +4423,39 @@ pub fn floatSub(
43964423 }
43974424}
43984425
4426pub fn floatMul(
4427 arena: *Allocator,
4428 float_type: Type,
4429 src: LazySrcLoc,
4430 lhs: Value,
4431 rhs: Value,
4432) !Value {
4433 switch (float_type.tag()) {
4434 .f16 => {
4435 @panic("TODO add __trunctfhf2 to compiler-rt");
4436 //const lhs_val = lhs.toFloat(f16);
4437 //const rhs_val = rhs.toFloat(f16);
4438 //return Value.Tag.float_16.create(arena, lhs_val * rhs_val);
4439 },
4440 .f32 => {
4441 const lhs_val = lhs.toFloat(f32);
4442 const rhs_val = rhs.toFloat(f32);
4443 return Value.Tag.float_32.create(arena, lhs_val * rhs_val);
4444 },
4445 .f64 => {
4446 const lhs_val = lhs.toFloat(f64);
4447 const rhs_val = rhs.toFloat(f64);
4448 return Value.Tag.float_64.create(arena, lhs_val * rhs_val);
4449 },
4450 .f128, .comptime_float, .c_longdouble => {
4451 const lhs_val = lhs.toFloat(f128);
4452 const rhs_val = rhs.toFloat(f128);
4453 return Value.Tag.float_128.create(arena, lhs_val * rhs_val);
4454 },
4455 else => unreachable,
4456 }
4457}
4458
43994459pub fn simplePtrType(
44004460 mod: *Module,
44014461 arena: *Allocator,
src/Sema.zig+7
......@@ -3885,6 +3885,13 @@ fn analyzeArithmetic(
38853885 try Module.floatSub(sema.arena, scalar_type, src, lhs_val, rhs_val);
38863886 break :blk val;
38873887 },
3888 .mul => blk: {
3889 const val = if (is_int)
3890 try Module.intMul(sema.arena, lhs_val, rhs_val)
3891 else
3892 try Module.floatMul(sema.arena, scalar_type, src, lhs_val, rhs_val);
3893 break :blk val;
3894 },
38883895 else => return sema.mod.fail(&block.base, src, "TODO Implement arithmetic operand '{s}'", .{@tagName(zir_tag)}),
38893896 };
38903897
src/codegen.zig+149
......@@ -1079,6 +1079,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
10791079 if (inst.base.isUnused())
10801080 return MCValue.dead;
10811081 switch (arch) {
1082 .x86_64 => return try self.genX8664BinMath(&inst.base, inst.lhs, inst.rhs),
10821083 .arm, .armeb => return try self.genArmMul(&inst.base, inst.lhs, inst.rhs),
10831084 else => return self.fail(inst.base.src, "TODO implement mul for {}", .{self.target.cpu.arch}),
10841085 }
......@@ -1574,6 +1575,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
15741575 .sub, .subwrap => try self.genX8664BinMathCode(inst.src, inst.ty, dst_mcv, src_mcv, 5, 0x28),
15751576 .xor, .not => try self.genX8664BinMathCode(inst.src, inst.ty, dst_mcv, src_mcv, 6, 0x30),
15761577
1578 .mul, .mulwrap => try self.genX8664Imul(inst.src, inst.ty, dst_mcv, src_mcv),
15771579 else => unreachable,
15781580 }
15791581
......@@ -1795,6 +1797,153 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
17951797 }
17961798 }
17971799
1800 /// Performs integer multiplication between dst_mcv and src_mcv, storing the result in dst_mcv.
1801 fn genX8664Imul(
1802 self: *Self,
1803 src: LazySrcLoc,
1804 dst_ty: Type,
1805 dst_mcv: MCValue,
1806 src_mcv: MCValue,
1807 ) !void {
1808 switch (dst_mcv) {
1809 .none => unreachable,
1810 .undef => unreachable,
1811 .dead, .unreach, .immediate => unreachable,
1812 .compare_flags_unsigned => unreachable,
1813 .compare_flags_signed => unreachable,
1814 .ptr_stack_offset => unreachable,
1815 .ptr_embedded_in_code => unreachable,
1816 .register => |dst_reg| {
1817 switch (src_mcv) {
1818 .none => unreachable,
1819 .undef => try self.genSetReg(src, dst_ty, dst_reg, .undef),
1820 .dead, .unreach => unreachable,
1821 .ptr_stack_offset => unreachable,
1822 .ptr_embedded_in_code => unreachable,
1823 .register => |src_reg| {
1824 // register, register
1825 //
1826 // Use the following imul opcode
1827 // 0F AF /r: IMUL r32/64, r/m32/64
1828 try self.encodeX8664Instruction(src, Instruction{
1829 .operand_size_64 = dst_ty.abiSize(self.target.*) == 64,
1830 .primary_opcode_2b = 0xaf,
1831 // TODO: Explicit optional wrap due to stage 1 miscompilation :(
1832 // https://github.com/ziglang/zig/issues/6515
1833 .modrm = @as(
1834 ?Instruction.ModrmEffectiveAddress,
1835 Instruction.ModrmEffectiveAddress{ .reg = src_reg },
1836 ),
1837 .reg = dst_reg,
1838 });
1839 },
1840 .immediate => |imm| {
1841 // register, immediate:
1842 // depends on size of immediate.
1843 //
1844 // immediate fits in i8:
1845 // 6B /r ib: IMUL r32/64, r/m32/64, imm8
1846 //
1847 // immediate fits in i32:
1848 // 69 /r id: IMUL r32/64, r/m32/64, imm32
1849 //
1850 // immediate is huge:
1851 // split into 2 instructions
1852 // 1) copy the 64 bit immediate into a tmp register
1853 // 2) perform register,register mul
1854 // 0F AF /r: IMUL r32/64, r/m32/64
1855 if (math.minInt(i8) <= imm and imm <= math.maxInt(i8)) {
1856 try self.encodeX8664Instruction(src, Instruction{
1857 .operand_size_64 = dst_ty.abiSize(self.target.*) == 64,
1858 .primary_opcode_1b = 0x6B,
1859 .reg = dst_reg,
1860 // TODO: Explicit optional wrap due to stage 1 miscompilation :(
1861 // https://github.com/ziglang/zig/issues/6515
1862 .modrm = @as(
1863 ?Instruction.ModrmEffectiveAddress,
1864 Instruction.ModrmEffectiveAddress{ .reg = dst_reg },
1865 ),
1866 .immediate_bytes = 1,
1867 .immediate = imm,
1868 });
1869 } else if (math.minInt(i32) <= imm and imm <= math.maxInt(i32)) {
1870 try self.encodeX8664Instruction(src, Instruction{
1871 .operand_size_64 = dst_ty.abiSize(self.target.*) == 64,
1872 .primary_opcode_1b = 0x69,
1873 .reg = dst_reg,
1874 // TODO: Explicit optional wrap due to stage 1 miscompilation :(
1875 // https://github.com/ziglang/zig/issues/6515
1876 .modrm = @as(
1877 ?Instruction.ModrmEffectiveAddress,
1878 Instruction.ModrmEffectiveAddress{ .reg = dst_reg },
1879 ),
1880 .immediate_bytes = 4,
1881 .immediate = imm,
1882 });
1883 } else {
1884 const src_reg = try self.copyToTmpRegister(src, dst_ty, src_mcv);
1885 return self.genX8664Imul(src, dst_ty, dst_mcv, MCValue{ .register = src_reg });
1886 }
1887 },
1888 .embedded_in_code, .memory, .stack_offset => {
1889 return self.fail(src, "TODO implement x86 multiply source memory", .{});
1890 },
1891 .compare_flags_unsigned => {
1892 return self.fail(src, "TODO implement x86 multiply source compare flag (unsigned)", .{});
1893 },
1894 .compare_flags_signed => {
1895 return self.fail(src, "TODO implement x86 multiply source compare flag (signed)", .{});
1896 },
1897 }
1898 },
1899 .stack_offset => |off| {
1900 switch (src_mcv) {
1901 .none => unreachable,
1902 .undef => return self.genSetStack(src, dst_ty, off, .undef),
1903 .dead, .unreach => unreachable,
1904 .ptr_stack_offset => unreachable,
1905 .ptr_embedded_in_code => unreachable,
1906 .register => |src_reg| {
1907 // copy dst to a register
1908 const dst_reg = try self.copyToTmpRegister(src, dst_ty, dst_mcv);
1909 // multiply into dst_reg
1910 // register, register
1911 // Use the following imul opcode
1912 // 0F AF /r: IMUL r32/64, r/m32/64
1913 try self.encodeX8664Instruction(src, Instruction{
1914 .operand_size_64 = dst_ty.abiSize(self.target.*) == 64,
1915 .primary_opcode_2b = 0xaf,
1916 // TODO: Explicit optional wrap due to stage 1 miscompilation :(
1917 // https://github.com/ziglang/zig/issues/6515
1918 .modrm = @as(
1919 ?Instruction.ModrmEffectiveAddress,
1920 Instruction.ModrmEffectiveAddress{ .reg = src_reg },
1921 ),
1922 .reg = dst_reg,
1923 });
1924 // copy dst_reg back out
1925 return self.genSetStack(src, dst_ty, off, MCValue{ .register = dst_reg });
1926 },
1927 .immediate => |imm| {
1928 return self.fail(src, "TODO implement x86 multiply source immediate", .{});
1929 },
1930 .embedded_in_code, .memory, .stack_offset => {
1931 return self.fail(src, "TODO implement x86 multiply source memory", .{});
1932 },
1933 .compare_flags_unsigned => {
1934 return self.fail(src, "TODO implement x86 multiply source compare flag (unsigned)", .{});
1935 },
1936 .compare_flags_signed => {
1937 return self.fail(src, "TODO implement x86 multiply source compare flag (signed)", .{});
1938 },
1939 }
1940 },
1941 .embedded_in_code, .memory => {
1942 return self.fail(src, "TODO implement x86 multiply destination memory", .{});
1943 },
1944 }
1945 }
1946
17981947 fn genX8664ModRMRegToStack(self: *Self, src: LazySrcLoc, ty: Type, off: u32, reg: Register, opcode: u8) !void {
17991948 const abi_size = ty.abiSize(self.target.*);
18001949 const adj_off = off + abi_size;
test/stage2/test.zig+102
......@@ -358,6 +358,81 @@ pub fn addCases(ctx: *TestContext) !void {
358358 , &[_][]const u8{":2:15: error: incompatible types: 'bool' and 'comptime_int'"});
359359 }
360360
361 {
362 var case = ctx.exe("multiplying numbers at runtime and comptime", linux_x64);
363 case.addCompareOutput(
364 \\export fn _start() noreturn {
365 \\ mul(3, 4);
366 \\
367 \\ exit();
368 \\}
369 \\
370 \\fn mul(a: u32, b: u32) void {
371 \\ if (a * b != 12) unreachable;
372 \\}
373 \\
374 \\fn exit() noreturn {
375 \\ asm volatile ("syscall"
376 \\ :
377 \\ : [number] "{rax}" (231),
378 \\ [arg1] "{rdi}" (0)
379 \\ : "rcx", "r11", "memory"
380 \\ );
381 \\ unreachable;
382 \\}
383 ,
384 "",
385 );
386 // comptime function call
387 case.addCompareOutput(
388 \\export fn _start() noreturn {
389 \\ exit();
390 \\}
391 \\
392 \\fn mul(a: u32, b: u32) u32 {
393 \\ return a * b;
394 \\}
395 \\
396 \\const x = mul(3, 4);
397 \\
398 \\fn exit() noreturn {
399 \\ asm volatile ("syscall"
400 \\ :
401 \\ : [number] "{rax}" (231),
402 \\ [arg1] "{rdi}" (x - 12)
403 \\ : "rcx", "r11", "memory"
404 \\ );
405 \\ unreachable;
406 \\}
407 ,
408 "",
409 );
410 // Inline function call
411 case.addCompareOutput(
412 \\export fn _start() noreturn {
413 \\ var x: usize = 5;
414 \\ const y = mul(2, 3, x);
415 \\ exit(y - 30);
416 \\}
417 \\
418 \\fn mul(a: usize, b: usize, c: usize) callconv(.Inline) usize {
419 \\ return a * b * c;
420 \\}
421 \\
422 \\fn exit(code: usize) noreturn {
423 \\ asm volatile ("syscall"
424 \\ :
425 \\ : [number] "{rax}" (231),
426 \\ [arg1] "{rdi}" (code)
427 \\ : "rcx", "r11", "memory"
428 \\ );
429 \\ unreachable;
430 \\}
431 ,
432 "",
433 );
434 }
435
361436 {
362437 var case = ctx.exe("assert function", linux_x64);
363438 case.addCompareOutput(
......@@ -741,6 +816,7 @@ pub fn addCases(ctx: *TestContext) !void {
741816 case.addCompareOutput(
742817 \\export fn _start() noreturn {
743818 \\ assert(add(3, 4) == 1221);
819 \\ assert(mul(3, 4) == 21609);
744820 \\
745821 \\ exit();
746822 \\}
......@@ -774,6 +850,32 @@ pub fn addCases(ctx: *TestContext) !void {
774850 \\ return z;
775851 \\}
776852 \\
853 \\fn mul(a: u32, b: u32) u32 {
854 \\ const x: u32 = blk: {
855 \\ const c = a * a * a * a; // 81
856 \\ const d = a * a * a * b; // 108
857 \\ const e = a * a * b * a; // 108
858 \\ const f = a * a * b * b; // 144
859 \\ const g = a * b * a * a; // 108
860 \\ const h = a * b * a * b; // 144
861 \\ const i = a * b * b * a; // 144
862 \\ const j = a * b * b * b; // 192
863 \\ const k = b * a * a * a; // 108
864 \\ const l = b * a * a * b; // 144
865 \\ const m = b * a * b * a; // 144
866 \\ const n = b * a * b * b; // 192
867 \\ const o = b * b * a * a; // 144
868 \\ const p = b * b * a * b; // 192
869 \\ const q = b * b * b * a; // 192
870 \\ const r = b * b * b * b; // 256
871 \\ const s = c + d + e + f + g + h + i + j + k + l + m + n + o + p + q + r; // 2401
872 \\ break :blk s;
873 \\ };
874 \\ const y = x * a; // 7203
875 \\ const z = y * a; // 21609
876 \\ return z;
877 \\}
878 \\
777879 \\pub fn assert(ok: bool) void {
778880 \\ if (!ok) unreachable; // assertion failure
779881 \\}