Commit bbe4a9fa99

Andrew Kelley <andrew@ziglang.org>
2021-10-29 03:33:13
C backend: implement trunc for unsigned non-pow2 ints
1 parent 98009a2
Changed files (3)
src
codegen
test
src/codegen/c.zig
@@ -501,14 +501,9 @@ pub const DeclGen = struct {
                             .signed => "",
                             .unsigned => "u",
                         };
-                        inline for (.{ 8, 16, 32, 64, 128 }) |nbits| {
-                            if (info.bits <= nbits) {
-                                try w.print("{s}int{d}_t", .{ sign_prefix, nbits });
-                                break;
-                            }
-                        } else {
+                        const c_bits = toCIntBits(info.bits) orelse
                             return dg.fail("TODO: C backend: implement integer types larger than 128 bits", .{});
-                        }
+                        try w.print("{s}int{d}_t", .{ sign_prefix, c_bits });
                     },
                     else => unreachable,
                 }
@@ -1089,6 +1084,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
             .call             => try airCall(f, inst),
             .dbg_stmt         => try airDbgStmt(f, inst),
             .intcast          => try airIntCast(f, inst),
+            .trunc            => try airTrunc(f, inst),
             .bool_to_int      => try airBoolToInt(f, inst),
             .load             => try airLoad(f, inst),
             .ret              => try airRet(f, inst),
@@ -1116,7 +1112,6 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
             .float_to_int,
             .fptrunc,
             .fpext,
-            .trunc,
             => try airSimpleCast(f, inst),
 
             .ptrtoint => try airPtrToInt(f, inst),
@@ -1366,6 +1361,39 @@ fn airIntCast(f: *Function, inst: Air.Inst.Index) !CValue {
     return local;
 }
 
+fn airTrunc(f: *Function, inst: Air.Inst.Index) !CValue {
+    if (f.liveness.isUnused(inst)) return CValue.none;
+
+    const inst_ty = f.air.typeOfIndex(inst);
+    const local = try f.allocLocal(inst_ty, .Const);
+    const ty_op = f.air.instructions.items(.data)[inst].ty_op;
+    const writer = f.object.writer();
+    const operand = try f.resolveInst(ty_op.operand);
+    const target = f.object.dg.module.getTarget();
+    const dest_int_info = inst_ty.intInfo(target);
+    const dest_bits = dest_int_info.bits;
+
+    try writer.writeAll(" = ");
+
+    if (dest_bits >= 8 and std.math.isPowerOfTwo(dest_bits)) {
+        try f.writeCValue(writer, operand);
+        try writer.writeAll(";\n");
+        return local;
+    }
+
+    switch (dest_int_info.signedness) {
+        .unsigned => {
+            try f.writeCValue(writer, operand);
+            const mask = (@as(u65, 1) << @intCast(u7, dest_bits)) - 1;
+            try writer.print(" & {d}ULL;\n", .{mask});
+            return local;
+        },
+        .signed => {
+            return f.fail("TODO: C backend: implement trunc for signed integers", .{});
+        },
+    }
+}
+
 fn airBoolToInt(f: *Function, inst: Air.Inst.Index) !CValue {
     if (f.liveness.isUnused(inst))
         return CValue.none;
@@ -2615,3 +2643,12 @@ fn IndentWriter(comptime UnderlyingWriter: type) type {
         }
     };
 }
+
+fn toCIntBits(zig_bits: u32) ?u32 {
+    for (&[_]u8{ 8, 16, 32, 64, 128 }) |c_bits| {
+        if (zig_bits <= c_bits) {
+            return c_bits;
+        }
+    }
+    return null;
+}
test/behavior/basic.zig
@@ -21,3 +21,229 @@ test "truncate" {
 fn testTruncate(x: u32) u8 {
     return @truncate(u8, x);
 }
+
+test "truncate to non-power-of-two integers" {
+    try testTrunc(u32, u1, 0b10101, 0b1);
+    try testTrunc(u32, u1, 0b10110, 0b0);
+    try testTrunc(u32, u2, 0b10101, 0b01);
+    try testTrunc(u32, u2, 0b10110, 0b10);
+    // TODO add test coverage for this!
+    // try testTrunc(i32, i3, -4, -4);
+}
+
+fn testTrunc(comptime Big: type, comptime Little: type, big: Big, little: Little) !void {
+    try expect(@truncate(Little, big) == little);
+}
+
+const g1: i32 = 1233 + 1;
+var g2: i32 = 0;
+
+test "global variables" {
+    try expect(g2 == 0);
+    g2 = g1;
+    try expect(g2 == 1234);
+}
+
+test "comptime keyword on expressions" {
+    const x: i32 = comptime x: {
+        break :x 1 + 2 + 3;
+    };
+    try expect(x == comptime 6);
+}
+
+test "type equality" {
+    try expect(*const u8 != *u8);
+}
+
+test "pointer dereferencing" {
+    var x = @as(i32, 3);
+    const y = &x;
+
+    y.* += 1;
+
+    try expect(x == 4);
+    try expect(y.* == 4);
+}
+
+test "const expression eval handling of variables" {
+    var x = true;
+    while (x) {
+        x = false;
+    }
+}
+
+test "character literals" {
+    try expect('\'' == single_quote);
+}
+const single_quote = '\'';
+
+test "non const ptr to aliased type" {
+    const int = i32;
+    try expect(?*int == ?*i32);
+}
+
+test "cold function" {
+    thisIsAColdFn();
+    comptime thisIsAColdFn();
+}
+
+fn thisIsAColdFn() void {
+    @setCold(true);
+}
+
+test "unicode escape in character literal" {
+    var a: u24 = '\u{01f4a9}';
+    try expect(a == 128169);
+}
+
+test "unicode character in character literal" {
+    try expect('💩' == 128169);
+}
+
+fn first4KeysOfHomeRow() []const u8 {
+    return "aoeu";
+}
+
+test "return string from function" {
+    try expect(mem.eql(u8, first4KeysOfHomeRow(), "aoeu"));
+}
+
+test "hex escape" {
+    try expect(mem.eql(u8, "\x68\x65\x6c\x6c\x6f", "hello"));
+}
+
+test "multiline string" {
+    const s1 =
+        \\one
+        \\two)
+        \\three
+    ;
+    const s2 = "one\ntwo)\nthree";
+    try expect(mem.eql(u8, s1, s2));
+}
+
+test "multiline string comments at start" {
+    const s1 =
+        //\\one
+        \\two)
+        \\three
+    ;
+    const s2 = "two)\nthree";
+    try expect(mem.eql(u8, s1, s2));
+}
+
+test "multiline string comments at end" {
+    const s1 =
+        \\one
+        \\two)
+        //\\three
+    ;
+    const s2 = "one\ntwo)";
+    try expect(mem.eql(u8, s1, s2));
+}
+
+test "multiline string comments in middle" {
+    const s1 =
+        \\one
+        //\\two)
+        \\three
+    ;
+    const s2 = "one\nthree";
+    try expect(mem.eql(u8, s1, s2));
+}
+
+test "multiline string comments at multiple places" {
+    const s1 =
+        \\one
+        //\\two
+        \\three
+        //\\four
+        \\five
+    ;
+    const s2 = "one\nthree\nfive";
+    try expect(mem.eql(u8, s1, s2));
+}
+
+test "string concatenation" {
+    try expect(mem.eql(u8, "OK" ++ " IT " ++ "WORKED", "OK IT WORKED"));
+}
+
+test "array mult operator" {
+    try expect(mem.eql(u8, "ab" ** 5, "ababababab"));
+}
+
+const OpaqueA = opaque {};
+const OpaqueB = opaque {};
+
+test "opaque types" {
+    try expect(*OpaqueA != *OpaqueB);
+    if (!builtin.zig_is_stage2) {
+        try expect(mem.eql(u8, @typeName(OpaqueA), "OpaqueA"));
+        try expect(mem.eql(u8, @typeName(OpaqueB), "OpaqueB"));
+    }
+}
+
+const global_a: i32 = 1234;
+const global_b: *const i32 = &global_a;
+const global_c: *const f32 = @ptrCast(*const f32, global_b);
+test "compile time global reinterpret" {
+    const d = @ptrCast(*const i32, global_c);
+    try expect(d.* == 1234);
+}
+
+test "cast undefined" {
+    const array: [100]u8 = undefined;
+    const slice = @as([]const u8, &array);
+    testCastUndefined(slice);
+}
+fn testCastUndefined(x: []const u8) void {
+    _ = x;
+}
+
+test "implicit cast after unreachable" {
+    try expect(outer() == 1234);
+}
+fn inner() i32 {
+    return 1234;
+}
+fn outer() i64 {
+    return inner();
+}
+
+test "comptime if inside runtime while which unconditionally breaks" {
+    testComptimeIfInsideRuntimeWhileWhichUnconditionallyBreaks(true);
+    comptime testComptimeIfInsideRuntimeWhileWhichUnconditionallyBreaks(true);
+}
+fn testComptimeIfInsideRuntimeWhileWhichUnconditionallyBreaks(cond: bool) void {
+    while (cond) {
+        if (false) {}
+        break;
+    }
+}
+
+test "implicit comptime while" {
+    while (false) {
+        @compileError("bad");
+    }
+}
+
+fn fnThatClosesOverLocalConst() type {
+    const c = 1;
+    return struct {
+        fn g() i32 {
+            return c;
+        }
+    };
+}
+
+test "function closes over local const" {
+    const x = fnThatClosesOverLocalConst().g();
+    try expect(x == 1);
+}
+
+test "volatile load and store" {
+    var number: i32 = 1234;
+    const ptr = @as(*volatile i32, &number);
+    ptr.* += 1;
+    try expect(ptr.* == 1235);
+}
test/behavior/basic_llvm.zig
@@ -4,135 +4,6 @@ const mem = std.mem;
 const expect = std.testing.expect;
 const expectEqualStrings = std.testing.expectEqualStrings;
 
-const g1: i32 = 1233 + 1;
-var g2: i32 = 0;
-
-test "global variables" {
-    try expect(g2 == 0);
-    g2 = g1;
-    try expect(g2 == 1234);
-}
-
-test "comptime keyword on expressions" {
-    const x: i32 = comptime x: {
-        break :x 1 + 2 + 3;
-    };
-    try expect(x == comptime 6);
-}
-
-test "type equality" {
-    try expect(*const u8 != *u8);
-}
-
-test "pointer dereferencing" {
-    var x = @as(i32, 3);
-    const y = &x;
-
-    y.* += 1;
-
-    try expect(x == 4);
-    try expect(y.* == 4);
-}
-
-test "const expression eval handling of variables" {
-    var x = true;
-    while (x) {
-        x = false;
-    }
-}
-
-test "character literals" {
-    try expect('\'' == single_quote);
-}
-const single_quote = '\'';
-
-test "non const ptr to aliased type" {
-    const int = i32;
-    try expect(?*int == ?*i32);
-}
-
-test "cold function" {
-    thisIsAColdFn();
-    comptime thisIsAColdFn();
-}
-
-fn thisIsAColdFn() void {
-    @setCold(true);
-}
-
-test "unicode escape in character literal" {
-    var a: u24 = '\u{01f4a9}';
-    try expect(a == 128169);
-}
-
-test "unicode character in character literal" {
-    try expect('💩' == 128169);
-}
-
-fn first4KeysOfHomeRow() []const u8 {
-    return "aoeu";
-}
-
-test "return string from function" {
-    try expect(mem.eql(u8, first4KeysOfHomeRow(), "aoeu"));
-}
-
-test "hex escape" {
-    try expect(mem.eql(u8, "\x68\x65\x6c\x6c\x6f", "hello"));
-}
-
-test "multiline string" {
-    const s1 =
-        \\one
-        \\two)
-        \\three
-    ;
-    const s2 = "one\ntwo)\nthree";
-    try expect(mem.eql(u8, s1, s2));
-}
-
-test "multiline string comments at start" {
-    const s1 =
-        //\\one
-        \\two)
-        \\three
-    ;
-    const s2 = "two)\nthree";
-    try expect(mem.eql(u8, s1, s2));
-}
-
-test "multiline string comments at end" {
-    const s1 =
-        \\one
-        \\two)
-        //\\three
-    ;
-    const s2 = "one\ntwo)";
-    try expect(mem.eql(u8, s1, s2));
-}
-
-test "multiline string comments in middle" {
-    const s1 =
-        \\one
-        //\\two)
-        \\three
-    ;
-    const s2 = "one\nthree";
-    try expect(mem.eql(u8, s1, s2));
-}
-
-test "multiline string comments at multiple places" {
-    const s1 =
-        \\one
-        //\\two
-        \\three
-        //\\four
-        \\five
-    ;
-    const s2 = "one\nthree\nfive";
-    try expect(mem.eql(u8, s1, s2));
-}
-
 test "call result of if else expression" {
     try expect(mem.eql(u8, f2(true), "a"));
     try expect(mem.eql(u8, f2(false), "b"));
@@ -147,14 +18,6 @@ fn fB() []const u8 {
     return "b";
 }
 
-test "string concatenation" {
-    try expect(mem.eql(u8, "OK" ++ " IT " ++ "WORKED", "OK IT WORKED"));
-}
-
-test "array mult operator" {
-    try expect(mem.eql(u8, "ab" ** 5, "ababababab"));
-}
-
 test "memcpy and memset intrinsics" {
     try testMemcpyMemset();
     // TODO add comptime test coverage
@@ -176,14 +39,6 @@ fn testMemcpyMemset() !void {
 const OpaqueA = opaque {};
 const OpaqueB = opaque {};
 
-test "opaque types" {
-    try expect(*OpaqueA != *OpaqueB);
-    if (!builtin.zig_is_stage2) {
-        try expect(mem.eql(u8, @typeName(OpaqueA), "OpaqueA"));
-        try expect(mem.eql(u8, @typeName(OpaqueB), "OpaqueB"));
-    }
-}
-
 test "variable is allowed to be a pointer to an opaque type" {
     var x: i32 = 1234;
     _ = hereIsAnOpaqueType(@ptrCast(*OpaqueA, &x));
@@ -193,33 +48,6 @@ fn hereIsAnOpaqueType(ptr: *OpaqueA) *OpaqueA {
     return a;
 }
 
-const global_a: i32 = 1234;
-const global_b: *const i32 = &global_a;
-const global_c: *const f32 = @ptrCast(*const f32, global_b);
-test "compile time global reinterpret" {
-    const d = @ptrCast(*const i32, global_c);
-    try expect(d.* == 1234);
-}
-
-test "cast undefined" {
-    const array: [100]u8 = undefined;
-    const slice = @as([]const u8, &array);
-    testCastUndefined(slice);
-}
-fn testCastUndefined(x: []const u8) void {
-    _ = x;
-}
-
-test "implicit cast after unreachable" {
-    try expect(outer() == 1234);
-}
-fn inner() i32 {
-    return 1234;
-}
-fn outer() i64 {
-    return inner();
-}
-
 test "take address of parameter" {
     try testTakeAddressOfParameter(12.34);
 }
@@ -262,44 +90,6 @@ fn nine() u8 {
     return 9;
 }
 
-test "comptime if inside runtime while which unconditionally breaks" {
-    testComptimeIfInsideRuntimeWhileWhichUnconditionallyBreaks(true);
-    comptime testComptimeIfInsideRuntimeWhileWhichUnconditionallyBreaks(true);
-}
-fn testComptimeIfInsideRuntimeWhileWhichUnconditionallyBreaks(cond: bool) void {
-    while (cond) {
-        if (false) {}
-        break;
-    }
-}
-
-test "implicit comptime while" {
-    while (false) {
-        @compileError("bad");
-    }
-}
-
-fn fnThatClosesOverLocalConst() type {
-    const c = 1;
-    return struct {
-        fn g() i32 {
-            return c;
-        }
-    };
-}
-
-test "function closes over local const" {
-    const x = fnThatClosesOverLocalConst().g();
-    try expect(x == 1);
-}
-
-test "volatile load and store" {
-    var number: i32 = 1234;
-    const ptr = @as(*volatile i32, &number);
-    ptr.* += 1;
-    try expect(ptr.* == 1235);
-}
-
 test "struct inside function" {
     try testStructInFn();
     comptime try testStructInFn();