Commit 9bf1681990

drew <reserveblue@protonmail.com>
2021-11-15 03:28:44
C backend: basic big ints, fix airPtrToInt, array references, pointer arithmetic UB with NULL, implement airPtrElemPtr/Val, fix redundant indirection/references with arrays
-add additional test cases that were found to be passing -add basic int128 test cases which previously did not pass but weren't covered -most test cases in cast.zig now pass -i128/u128 or smaller int constants can now be rendered -unsigned int constants are now always suffixed with 'u' to prevent random compile errors -pointers with a val tag of 'zero' now just emit a 0 constant which coerces to the pointer type and fixes some warnings with ordered comparisons -pointers with a val tag of 'one' are now casted back to the pointer type -support pointers with a u64 val -fix bug where rendering an array's type will emit more indirection than is needed -render uint128_t/int128_t manually when needed -implement ptr_add/sub AIR handlers manually so they manually cast to int types which avoids UB if the result or ptr operand is NULL -implement airPtrElemVal/Ptr -airAlloc for arrays will not allocate a ref as the local for the array is already a reference/pointer to the array itself -fix airPtrToInt by casting to the int type
1 parent 952d865
Changed files (6)
src/codegen/c.zig
@@ -226,6 +226,36 @@ pub const DeclGen = struct {
         try dg.renderDeclName(decl, writer);
     }
 
+    /// Assumes that int_val is an int greater than maxInt(u64) and has > 64 and <= 128 bits.
+    fn renderBigInt(
+        writer: anytype,
+        int_val: anytype,
+    ) error{ OutOfMemory, AnalysisFail }!void {
+        const int_info = @typeInfo(@TypeOf(int_val)).Int;
+        const is_signed = int_info.signedness == .signed;
+        const is_neg = int_val < 0;
+        comptime assert(int_info.bits > 64 and int_info.bits <= 128);
+
+        // Clang and GCC don't support 128-bit integer constants but will hopefully unfold them
+        // if we construct one manually.
+        const magnitude = std.math.absCast(int_val);
+
+        const high = @truncate(u64, magnitude >> 64);
+        const low = @truncate(u64, magnitude);
+
+        // (int128_t)/<->( ( (uint128_t)( val_high << 64 )u ) + (uint128_t)val_low/u )
+        if (is_signed) try writer.writeAll("(int128_t)");
+        if (is_neg) try writer.writeByte('-');
+
+        assert(high > 0);
+        try writer.print("(((uint128_t)0x{x}u<<64)", .{ high });
+
+        if (low > 0)
+            try writer.print("+(uint128_t)0x{x}u", .{ low });
+
+        return writer.writeByte(')');
+    }
+
     fn renderValue(
         dg: *DeclGen,
         writer: anytype,
@@ -240,18 +270,18 @@ pub const DeclGen = struct {
                     const c_bits = toCIntBits(ty.intInfo(dg.module.getTarget()).bits) orelse
                         return dg.fail("TODO: C backend: implement integer types larger than 128 bits", .{});
                     switch (c_bits) {
-                        8 => return writer.writeAll("0xaaU"),
-                        16 => return writer.writeAll("0xaaaaU"),
-                        32 => return writer.writeAll("0xaaaaaaaaU"),
-                        64 => return writer.writeAll("0xaaaaaaaaaaaaaaaaUL"),
-                        128 => return writer.writeAll("0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaULL"),
+                        8 => return writer.writeAll("0xaau"),
+                        16 => return writer.writeAll("0xaaaau"),
+                        32 => return writer.writeAll("0xaaaaaaaau"),
+                        64 => return writer.writeAll("0xaaaaaaaaaaaaaaaau"),
+                        128 => return renderBigInt(writer, @as(u128, 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)),
                         else => unreachable,
                     }
                 },
                 .Float => {
                     switch (ty.floatBits(dg.module.getTarget())) {
-                        32 => return writer.writeAll("zig_bitcast_f32_u32(0xaaaaaaaa)"),
-                        64 => return writer.writeAll("zig_bitcast_f64_u64(0xaaaaaaaaaaaaaaaa)"),
+                        32 => return writer.writeAll("zig_bitcast_f32_u32(0xaaaaaaaau)"),
+                        64 => return writer.writeAll("zig_bitcast_f64_u64(0xaaaaaaaaaaaaaaaau)"),
                         else => return dg.fail("TODO float types > 64 bits are not support in renderValue() as of now", .{}),
                     }
                 },
@@ -265,10 +295,18 @@ pub const DeclGen = struct {
             }
         }
         switch (ty.zigTypeTag()) {
-            .Int => {
-                if (ty.isSignedInt())
-                    return writer.print("{d}", .{val.toSignedInt()});
-                return writer.print("{d}", .{val.toUnsignedInt()});
+            .Int => switch (val.tag()) {
+                .int_big_positive => try renderBigInt(writer, val.castTag(.int_big_positive).?.asBigInt().to(u128) catch {
+                    return dg.fail("TODO implement integer constants larger than 128 bits", .{});
+                }),
+                .int_big_negative => try renderBigInt(writer, val.castTag(.int_big_negative).?.asBigInt().to(i128) catch {
+                    return dg.fail("TODO implement integer constants larger than 128 bits", .{});
+                }),
+                else => {
+                    if (ty.isSignedInt())
+                        return writer.print("{d}", .{val.toSignedInt()});
+                    return writer.print("{d}u", .{val.toUnsignedInt()});
+                }
             },
             .Float => {
                 if (ty.floatBits(dg.module.getTarget()) <= 64) {
@@ -286,8 +324,17 @@ pub const DeclGen = struct {
                 return dg.fail("TODO: C backend: implement lowering large float values", .{});
             },
             .Pointer => switch (val.tag()) {
-                .null_value, .zero => try writer.writeAll("NULL"),
-                .one => try writer.writeAll("1"),
+                .null_value => try writer.writeAll("NULL"),
+                // Technically this should produce NULL but the integer literal 0 will always coerce
+                // to the assigned pointer type. Note this is just a hack to fix warnings from ordered comparisons (<, >, etc)
+                // between pointers and 0, which is an extension to begin with.
+                .zero => try writer.writeByte('0'),
+                .one => {
+                    // int constants like 1 will not cast to the pointer however.
+                    try writer.writeAll("((");
+                    try dg.renderType(writer, ty);
+                    return writer.writeAll(")1)");
+                },
                 .decl_ref => {
                     const decl = val.castTag(.decl_ref).?.data;
                     return dg.renderDeclValue(writer, ty, val, decl);
@@ -316,6 +363,11 @@ pub const DeclGen = struct {
                     const decl = val.castTag(.extern_fn).?.data;
                     try dg.renderDeclName(decl, writer);
                 },
+                .int_u64 => {
+                    try writer.writeAll("((");
+                    try dg.renderType(writer, ty);
+                    try writer.print(")0x{x}u)", .{val.toUnsignedInt()});
+                },
                 else => unreachable,
             },
             .Array => {
@@ -728,6 +780,8 @@ pub const DeclGen = struct {
                     .i32 => try w.writeAll("int32_t"),
                     .u64 => try w.writeAll("uint64_t"),
                     .i64 => try w.writeAll("int64_t"),
+                    .u128 => try w.writeAll("uint128_t"),
+                    .i128 => try w.writeAll("int128_t"),
                     .usize => try w.writeAll("uintptr_t"),
                     .isize => try w.writeAll("intptr_t"),
                     .c_short => try w.writeAll("short"),
@@ -787,8 +841,9 @@ pub const DeclGen = struct {
             },
             .Array => {
                 // We are referencing the array so it will decay to a C pointer.
-                try dg.renderType(w, t.elemType());
-                return w.writeAll(" *");
+                // NB: arrays are not really types in C so they are either specified in the declaration
+                // or are already pointed to; our only job is to render the element's type.
+                return dg.renderType(w, t.elemType());
             },
             .Optional => {
                 var opt_buf: Type.Payload.ElemType = undefined;
@@ -1068,12 +1123,15 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
             .unreach    => try airUnreach(f),
             .fence      => try airFence(f, inst),
 
+            .ptr_add => try airPtrAddSub (f, inst, " + "),
+            .ptr_sub => try airPtrAddSub (f, inst, " - "),
+
             // TODO use a different strategy for add that communicates to the optimizer
             // that wrapping is UB.
-            .add, .ptr_add => try airBinOp (f, inst, " + "),
+            .add => try airBinOp (f, inst, " + "),
             // TODO use a different strategy for sub that communicates to the optimizer
             // that wrapping is UB.
-            .sub, .ptr_sub => try airBinOp (f, inst, " - "),
+            .sub => try airBinOp (f, inst, " - "),
             // TODO use a different strategy for mul that communicates to the optimizer
             // that wrapping is UB.
             .mul           => try airBinOp (f, inst, " * "),
@@ -1187,7 +1245,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
             .ptr_slice_len_ptr => try airPtrSliceFieldPtr(f, inst, ".len;\n"),
             .ptr_slice_ptr_ptr => try airPtrSliceFieldPtr(f, inst, ".ptr;\n"),
 
-            .ptr_elem_val       => try airPtrElemVal(f, inst, "["),
+            .ptr_elem_val       => try airPtrElemVal(f, inst),
             .ptr_elem_ptr       => try airPtrElemPtr(f, inst),
             .slice_elem_val     => try airSliceElemVal(f, inst),
             .slice_elem_ptr     => try airSliceElemPtr(f, inst),
@@ -1240,20 +1298,39 @@ fn airPtrSliceFieldPtr(f: *Function, inst: Air.Inst.Index, suffix: []const u8) !
     return f.fail("TODO: C backend: airPtrSliceFieldPtr", .{});
 }
 
-fn airPtrElemVal(f: *Function, inst: Air.Inst.Index, prefix: []const u8) !CValue {
-    const is_volatile = false; // TODO
-    if (!is_volatile and f.liveness.isUnused(inst))
-        return CValue.none;
+fn airPtrElemVal(f: *Function, inst: Air.Inst.Index) !CValue {
+    const bin_op = f.air.instructions.items(.data)[inst].bin_op;
+    const slice_ty = f.air.typeOf(bin_op.lhs);
+    if (!slice_ty.isVolatilePtr() and f.liveness.isUnused(inst)) return CValue.none;
 
-    _ = prefix;
-    return f.fail("TODO: C backend: airPtrElemVal", .{});
+    const arr = try f.resolveInst(bin_op.lhs);
+    const index = try f.resolveInst(bin_op.rhs);
+    const writer = f.object.writer();
+    const local = try f.allocLocal(f.air.typeOfIndex(inst), .Const);
+    try writer.writeAll(" = ");
+    try f.writeCValue(writer, arr);
+    try writer.writeByte('[');
+    try f.writeCValue(writer, index);
+    try writer.writeAll("];\n");
+    return local;
 }
 
 fn airPtrElemPtr(f: *Function, inst: Air.Inst.Index) !CValue {
-    if (f.liveness.isUnused(inst))
-        return CValue.none;
+    if (f.liveness.isUnused(inst)) return CValue.none;
 
-    return f.fail("TODO: C backend: airPtrElemPtr", .{});
+    const ty_pl = f.air.instructions.items(.data)[inst].ty_pl;
+    const bin_op = f.air.extraData(Air.Bin, ty_pl.payload).data;
+
+    const arr = try f.resolveInst(bin_op.lhs);
+    const index = try f.resolveInst(bin_op.rhs);
+    const writer = f.object.writer();
+    const local = try f.allocLocal(f.air.typeOfIndex(inst), .Const);
+    try writer.writeAll(" = &");
+    try f.writeCValue(writer, arr);
+    try writer.writeByte('[');
+    try f.writeCValue(writer, index);
+    try writer.writeAll("];\n");
+    return local;
 }
 
 fn airSliceElemVal(f: *Function, inst: Air.Inst.Index) !CValue {
@@ -1317,6 +1394,10 @@ fn airAlloc(f: *Function, inst: Air.Inst.Index) !CValue {
     const local = try f.allocLocal(elem_type, mutability);
     try writer.writeAll(";\n");
 
+    // Arrays are already pointers so they don't need to be referenced.
+    if (elem_type.zigTypeTag() == .Array)
+        return CValue{ .local = local.local };
+
     return CValue{ .local_ref = local.local };
 }
 
@@ -1810,6 +1891,33 @@ fn airBinOp(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue
     return local;
 }
 
+fn airPtrAddSub(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue {
+    if (f.liveness.isUnused(inst))
+        return CValue.none;
+
+    const bin_op = f.air.instructions.items(.data)[inst].bin_op;
+    const lhs = try f.resolveInst(bin_op.lhs);
+    const rhs = try f.resolveInst(bin_op.rhs);
+
+    const writer = f.object.writer();
+    const inst_ty = f.air.typeOfIndex(inst);
+    const local = try f.allocLocal(inst_ty, .Const);
+
+    // We must convert to and from integer types to prevent UB if the operation results in a NULL pointer,
+    // or if LHS is NULL. The operation is only UB if the result is NULL and then dereferenced.
+    try writer.writeAll(" = (");
+    try f.renderType(writer, inst_ty);
+    try writer.writeAll(")(((uintptr_t)");
+    try f.writeCValue(writer, lhs);
+    try writer.print("){s}(", .{operator});
+    try f.writeCValue(writer, rhs);
+    try writer.writeAll("*sizeof(");
+    try f.renderType(writer, inst_ty.childType());
+    try writer.print(")));\n", .{});
+
+    return local;
+}
+
 fn airMinMax(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CValue {
     if (f.liveness.isUnused(inst)) return CValue.none;
 
@@ -2529,7 +2637,9 @@ fn airPtrToInt(f: *Function, inst: Air.Inst.Index) !CValue {
     const writer = f.object.writer();
     const operand = try f.resolveInst(un_op);
 
-    try writer.writeAll(" = ");
+    try writer.writeAll(" = (");
+    try f.renderType(writer, inst_ty);
+    try writer.writeAll(")");
     try f.writeCValue(writer, operand);
     try writer.writeAll(";\n");
     return local;
test/behavior/cast.zig
@@ -2,70 +2,8 @@ const std = @import("std");
 const expect = std.testing.expect;
 const mem = std.mem;
 const maxInt = std.math.maxInt;
-const Vector = std.meta.Vector;
 const native_endian = @import("builtin").target.cpu.arch.endian();
 
-test "int to ptr cast" {
-    const x = @as(usize, 13);
-    const y = @intToPtr(*u8, x);
-    const z = @ptrToInt(y);
-    try expect(z == 13);
-}
-
-test "integer literal to pointer cast" {
-    const vga_mem = @intToPtr(*u16, 0xB8000);
-    try expect(@ptrToInt(vga_mem) == 0xB8000);
-}
-
-test "peer type resolution: ?T and T" {
-    try expect(peerTypeTAndOptionalT(true, false).? == 0);
-    try expect(peerTypeTAndOptionalT(false, false).? == 3);
-    comptime {
-        try expect(peerTypeTAndOptionalT(true, false).? == 0);
-        try expect(peerTypeTAndOptionalT(false, false).? == 3);
-    }
-}
-fn peerTypeTAndOptionalT(c: bool, b: bool) ?usize {
-    if (c) {
-        return if (b) null else @as(usize, 0);
-    }
-
-    return @as(usize, 3);
-}
-
-test "resolve undefined with integer" {
-    try testResolveUndefWithInt(true, 1234);
-    comptime try testResolveUndefWithInt(true, 1234);
-}
-fn testResolveUndefWithInt(b: bool, x: i32) !void {
-    const value = if (b) x else undefined;
-    if (b) {
-        try expect(value == x);
-    }
-}
-
-test "@intCast i32 to u7" {
-    var x: u128 = maxInt(u128);
-    var y: i32 = 120;
-    var z = x >> @intCast(u7, y);
-    try expect(z == 0xff);
-}
-
-test "@intCast to comptime_int" {
-    try expect(@intCast(comptime_int, 0) == 0);
-}
-
-test "implicit cast comptime numbers to any type when the value fits" {
-    const a: u64 = 255;
-    var b: u8 = a;
-    try expect(b == 255);
-}
-
-test "implicit cast comptime_int to comptime_float" {
-    comptime try expect(@as(comptime_float, 10) == @as(f32, 10));
-    try expect(2 == 2.0);
-}
-
 test "pointer reinterpret const float to int" {
     // The hex representation is 0x3fe3333333333303.
     const float: f64 = 5.99999999999994648725e-01;
@@ -78,51 +16,15 @@ test "pointer reinterpret const float to int" {
         try expect(int_val == 0x3fe33333);
 }
 
-test "comptime_int @intToFloat" {
-    {
-        const result = @intToFloat(f16, 1234);
-        try expect(@TypeOf(result) == f16);
-        try expect(result == 1234.0);
-    }
-    {
-        const result = @intToFloat(f32, 1234);
-        try expect(@TypeOf(result) == f32);
-        try expect(result == 1234.0);
-    }
-    {
-        const result = @intToFloat(f64, 1234);
-        try expect(@TypeOf(result) == f64);
-        try expect(result == 1234.0);
-    }
-    {
-        const result = @intToFloat(f128, 1234);
-        try expect(@TypeOf(result) == f128);
-        try expect(result == 1234.0);
-    }
-    // big comptime_int (> 64 bits) to f128 conversion
-    {
-        const result = @intToFloat(f128, 0x1_0000_0000_0000_0000);
-        try expect(@TypeOf(result) == f128);
-        try expect(result == 0x1_0000_0000_0000_0000.0);
-    }
-}
-
 test "@floatToInt" {
     try testFloatToInts();
     comptime try testFloatToInts();
 }
 
 fn testFloatToInts() !void {
-    const x = @as(i32, 1e4);
-    try expect(x == 10000);
-    const y = @floatToInt(i32, @as(f32, 1e4));
-    try expect(y == 10000);
     try expectFloatToInt(f16, 255.1, u8, 255);
     try expectFloatToInt(f16, 127.2, i8, 127);
     try expectFloatToInt(f16, -128.2, i8, -128);
-    try expectFloatToInt(f32, 255.1, u8, 255);
-    try expectFloatToInt(f32, 127.2, i8, 127);
-    try expectFloatToInt(f32, -128.2, i8, -128);
 }
 
 fn expectFloatToInt(comptime F: type, f: F, comptime I: type, i: I) !void {
@@ -143,95 +45,6 @@ fn incrementVoidPtrArray(array: ?*c_void, len: usize) void {
     }
 }
 
-test "implicitly cast indirect pointer to maybe-indirect pointer" {
-    const S = struct {
-        const Self = @This();
-        x: u8,
-        fn constConst(p: *const *const Self) u8 {
-            return p.*.x;
-        }
-        fn maybeConstConst(p: ?*const *const Self) u8 {
-            return p.?.*.x;
-        }
-        fn constConstConst(p: *const *const *const Self) u8 {
-            return p.*.*.x;
-        }
-        fn maybeConstConstConst(p: ?*const *const *const Self) u8 {
-            return p.?.*.*.x;
-        }
-    };
-    const s = S{ .x = 42 };
-    const p = &s;
-    const q = &p;
-    const r = &q;
-    try expect(42 == S.constConst(q));
-    try expect(42 == S.maybeConstConst(q));
-    try expect(42 == S.constConstConst(r));
-    try expect(42 == S.maybeConstConstConst(r));
-}
-
-test "@intCast comptime_int" {
-    const result = @intCast(i32, 1234);
-    try expect(@TypeOf(result) == i32);
-    try expect(result == 1234);
-}
-
-test "@floatCast comptime_int and comptime_float" {
-    {
-        const result = @floatCast(f16, 1234);
-        try expect(@TypeOf(result) == f16);
-        try expect(result == 1234.0);
-    }
-    {
-        const result = @floatCast(f16, 1234.0);
-        try expect(@TypeOf(result) == f16);
-        try expect(result == 1234.0);
-    }
-    {
-        const result = @floatCast(f32, 1234);
-        try expect(@TypeOf(result) == f32);
-        try expect(result == 1234.0);
-    }
-    {
-        const result = @floatCast(f32, 1234.0);
-        try expect(@TypeOf(result) == f32);
-        try expect(result == 1234.0);
-    }
-}
-
-test "coerce undefined to optional" {
-    try expect(MakeType(void).getNull() == null);
-    try expect(MakeType(void).getNonNull() != null);
-}
-
-fn MakeType(comptime T: type) type {
-    return struct {
-        fn getNull() ?T {
-            return null;
-        }
-
-        fn getNonNull() ?T {
-            return @as(T, undefined);
-        }
-    };
-}
-
-test "implicit cast from *[N]T to [*c]T" {
-    var x: [4]u16 = [4]u16{ 0, 1, 2, 3 };
-    var y: [*c]u16 = &x;
-
-    try expect(std.mem.eql(u16, x[0..4], y[0..4]));
-    x[0] = 8;
-    y[3] = 6;
-    try expect(std.mem.eql(u16, x[0..4], y[0..4]));
-}
-
-test "*usize to *void" {
-    var i = @as(usize, 0);
-    var v = @ptrCast(*void, &i);
-    v.* = {};
-}
-
 test "compile time int to ptr of function" {
     try foobar(FUNCTION_CONSTANT);
 }
@@ -252,50 +65,3 @@ test "implicit ptr to *c_void" {
     var c: *u32 = @ptrCast(*u32, ptr2.?);
     try expect(c.* == 1);
 }
-
-test "@intToEnum passed a comptime_int to an enum with one item" {
-    const E = enum { A };
-    const x = @intToEnum(E, 0);
-    try expect(x == E.A);
-}
-
-test "@intCast to u0 and use the result" {
-    const S = struct {
-        fn doTheTest(zero: u1, one: u1, bigzero: i32) !void {
-            try expect((one << @intCast(u0, bigzero)) == 1);
-            try expect((zero << @intCast(u0, bigzero)) == 0);
-        }
-    };
-    try S.doTheTest(0, 1, 0);
-    comptime try S.doTheTest(0, 1, 0);
-}
-
-test "peer result null and comptime_int" {
-    const S = struct {
-        fn blah(n: i32) ?i32 {
-            if (n == 0) {
-                return null;
-            } else if (n < 0) {
-                return -1;
-            } else {
-                return 1;
-            }
-        }
-    };
-
-    try expect(S.blah(0) == null);
-    comptime try expect(S.blah(0) == null);
-    try expect(S.blah(10).? == 1);
-    comptime try expect(S.blah(10).? == 1);
-    try expect(S.blah(-10).? == -1);
-    comptime try expect(S.blah(-10).? == -1);
-}
-
-test "*const ?[*]const T to [*c]const [*c]const T" {
-    var array = [_]u8{ 'o', 'k' };
-    const opt_array_ptr: ?[*]const u8 = &array;
-    const a: *const ?[*]const u8 = &opt_array_ptr;
-    const b: [*c]const [*c]const u8 = a;
-    try expect(b.*[0] == 'o');
-    try expect(b[0][1] == 'k');
-}
test/behavior/cast_c.zig
@@ -0,0 +1,249 @@
+const std = @import("std");
+const expect = std.testing.expect;
+const mem = std.mem;
+const maxInt = std.math.maxInt;
+
+test "int to ptr cast" {
+    const x = @as(usize, 13);
+    const y = @intToPtr(*u8, x);
+    const z = @ptrToInt(y);
+    try expect(z == 13);
+}
+
+test "integer literal to pointer cast" {
+    const vga_mem = @intToPtr(*u16, 0xB8000);
+    try expect(@ptrToInt(vga_mem) == 0xB8000);
+}
+
+test "peer type resolution: ?T and T" {
+    try expect(peerTypeTAndOptionalT(true, false).? == 0);
+    try expect(peerTypeTAndOptionalT(false, false).? == 3);
+    comptime {
+        try expect(peerTypeTAndOptionalT(true, false).? == 0);
+        try expect(peerTypeTAndOptionalT(false, false).? == 3);
+    }
+}
+fn peerTypeTAndOptionalT(c: bool, b: bool) ?usize {
+    if (c) {
+        return if (b) null else @as(usize, 0);
+    }
+
+    return @as(usize, 3);
+}
+
+test "resolve undefined with integer" {
+    try testResolveUndefWithInt(true, 1234);
+    comptime try testResolveUndefWithInt(true, 1234);
+}
+fn testResolveUndefWithInt(b: bool, x: i32) !void {
+    const value = if (b) x else undefined;
+    if (b) {
+        try expect(value == x);
+    }
+}
+
+test "@intCast i32 to u7" {
+    var x: u128 = maxInt(u128);
+    var y: i32 = 120;
+    var z = x >> @intCast(u7, y);
+    try expect(z == 0xff);
+}
+
+test "@intCast to comptime_int" {
+    try expect(@intCast(comptime_int, 0) == 0);
+}
+
+test "implicit cast comptime numbers to any type when the value fits" {
+    const a: u64 = 255;
+    var b: u8 = a;
+    try expect(b == 255);
+}
+
+test "implicit cast comptime_int to comptime_float" {
+    comptime try expect(@as(comptime_float, 10) == @as(f32, 10));
+    try expect(2 == 2.0);
+}
+
+test "comptime_int @intToFloat" {
+    {
+        const result = @intToFloat(f16, 1234);
+        try expect(@TypeOf(result) == f16);
+        try expect(result == 1234.0);
+    }
+    {
+        const result = @intToFloat(f32, 1234);
+        try expect(@TypeOf(result) == f32);
+        try expect(result == 1234.0);
+    }
+    {
+        const result = @intToFloat(f64, 1234);
+        try expect(@TypeOf(result) == f64);
+        try expect(result == 1234.0);
+    }
+    {
+        const result = @intToFloat(f128, 1234);
+        try expect(@TypeOf(result) == f128);
+        try expect(result == 1234.0);
+    }
+    // big comptime_int (> 64 bits) to f128 conversion
+    {
+        const result = @intToFloat(f128, 0x1_0000_0000_0000_0000);
+        try expect(@TypeOf(result) == f128);
+        try expect(result == 0x1_0000_0000_0000_0000.0);
+    }
+}
+
+test "@floatToInt" {
+    try testFloatToInts();
+    comptime try testFloatToInts();
+}
+
+fn testFloatToInts() !void {
+    const x = @as(i32, 1e4);
+    try expect(x == 10000);
+    const y = @floatToInt(i32, @as(f32, 1e4));
+    try expect(y == 10000);
+    try expectFloatToInt(f32, 255.1, u8, 255);
+    try expectFloatToInt(f32, 127.2, i8, 127);
+    try expectFloatToInt(f32, -128.2, i8, -128);
+}
+
+fn expectFloatToInt(comptime F: type, f: F, comptime I: type, i: I) !void {
+    try expect(@floatToInt(I, f) == i);
+}
+
+test "implicitly cast indirect pointer to maybe-indirect pointer" {
+    const S = struct {
+        const Self = @This();
+        x: u8,
+        fn constConst(p: *const *const Self) u8 {
+            return p.*.x;
+        }
+        fn maybeConstConst(p: ?*const *const Self) u8 {
+            return p.?.*.x;
+        }
+        fn constConstConst(p: *const *const *const Self) u8 {
+            return p.*.*.x;
+        }
+        fn maybeConstConstConst(p: ?*const *const *const Self) u8 {
+            return p.?.*.*.x;
+        }
+    };
+    const s = S{ .x = 42 };
+    const p = &s;
+    const q = &p;
+    const r = &q;
+    try expect(42 == S.constConst(q));
+    try expect(42 == S.maybeConstConst(q));
+    try expect(42 == S.constConstConst(r));
+    try expect(42 == S.maybeConstConstConst(r));
+}
+
+test "@intCast comptime_int" {
+    const result = @intCast(i32, 1234);
+    try expect(@TypeOf(result) == i32);
+    try expect(result == 1234);
+}
+
+test "@floatCast comptime_int and comptime_float" {
+    {
+        const result = @floatCast(f16, 1234);
+        try expect(@TypeOf(result) == f16);
+        try expect(result == 1234.0);
+    }
+    {
+        const result = @floatCast(f16, 1234.0);
+        try expect(@TypeOf(result) == f16);
+        try expect(result == 1234.0);
+    }
+    {
+        const result = @floatCast(f32, 1234);
+        try expect(@TypeOf(result) == f32);
+        try expect(result == 1234.0);
+    }
+    {
+        const result = @floatCast(f32, 1234.0);
+        try expect(@TypeOf(result) == f32);
+        try expect(result == 1234.0);
+    }
+}
+
+test "coerce undefined to optional" {
+    try expect(MakeType(void).getNull() == null);
+    try expect(MakeType(void).getNonNull() != null);
+}
+
+fn MakeType(comptime T: type) type {
+    return struct {
+        fn getNull() ?T {
+            return null;
+        }
+
+        fn getNonNull() ?T {
+            return @as(T, undefined);
+        }
+    };
+}
+
+test "implicit cast from *[N]T to [*c]T" {
+    var x: [4]u16 = [4]u16{ 0, 1, 2, 3 };
+    var y: [*c]u16 = &x;
+
+    try expect(std.mem.eql(u16, x[0..4], y[0..4]));
+    x[0] = 8;
+    y[3] = 6;
+    try expect(std.mem.eql(u16, x[0..4], y[0..4]));
+}
+
+test "*usize to *void" {
+    var i = @as(usize, 0);
+    var v = @ptrCast(*void, &i);
+    v.* = {};
+}
+
+test "@intToEnum passed a comptime_int to an enum with one item" {
+    const E = enum { A };
+    const x = @intToEnum(E, 0);
+    try expect(x == E.A);
+}
+
+test "@intCast to u0 and use the result" {
+    const S = struct {
+        fn doTheTest(zero: u1, one: u1, bigzero: i32) !void {
+            try expect((one << @intCast(u0, bigzero)) == 1);
+            try expect((zero << @intCast(u0, bigzero)) == 0);
+        }
+    };
+    try S.doTheTest(0, 1, 0);
+    comptime try S.doTheTest(0, 1, 0);
+}
+
+test "peer result null and comptime_int" {
+    const S = struct {
+        fn blah(n: i32) ?i32 {
+            if (n == 0) {
+                return null;
+            } else if (n < 0) {
+                return -1;
+            } else {
+                return 1;
+            }
+        }
+    };
+
+    try expect(S.blah(0) == null);
+    comptime try expect(S.blah(0) == null);
+    try expect(S.blah(10).? == 1);
+    comptime try expect(S.blah(10).? == 1);
+    try expect(S.blah(-10).? == -1);
+    comptime try expect(S.blah(-10).? == -1);
+}
+
+test "*const ?[*]const T to [*c]const [*c]const T" {
+    var array = [_]u8{ 'o', 'k' };
+    const opt_array_ptr: ?[*]const u8 = &array;
+    const a: *const ?[*]const u8 = &opt_array_ptr;
+    const b: [*c]const [*c]const u8 = a;
+    try expect(b.*[0] == 'o');
+    try expect(b[0][1] == 'k');
+}
test/behavior/int128.zig
@@ -0,0 +1,43 @@
+const std = @import("std");
+const expect = std.testing.expect;
+const maxInt = std.math.maxInt;
+const minInt = std.math.minInt;
+
+test "uint128" {
+    var buff: u128 = maxInt(u128);
+    try expect(buff == maxInt(u128));
+
+    const magic_const = 0x12341234123412341234123412341234;
+    buff = magic_const;
+
+    try expect(buff == magic_const);
+    try expect(magic_const == 0x12341234123412341234123412341234);
+
+    buff = 0;
+    try expect(buff == @as(u128, 0));
+}
+
+test "undefined 128 bit int" {
+    @setRuntimeSafety(true);
+
+    var undef: u128 = undefined;
+    var undef_signed: i128 = undefined;
+    try expect(undef == 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa and @bitCast(u128, undef_signed) == undef);
+}
+
+test "int128" {
+    var buff: i128 = -1;
+    try expect(buff < 0 and (buff + 1) == 0);
+    try expect(@intCast(i8, buff) == @as(i8, -1));
+
+    buff = minInt(i128);
+    try expect(buff < 0);
+
+    // This should be uncommented once wrapping arithmetic is implemented for 128 bit ints:
+    // try expect(buff < 0 and (buff -% 1) > 0)
+}
+
+test "truncate int128" {
+    var buff: u128 = maxInt(u128);
+    try expect(@truncate(u64, buff) == maxInt(u64));
+}
\ No newline at end of file
test/behavior/pointers.zig
@@ -61,12 +61,16 @@ test "initialize const optional C pointer to null" {
 
 test "assigning integer to C pointer" {
     var x: i32 = 0;
+    var y: i32 = 1;
     var ptr: [*c]u8 = 0;
     var ptr2: [*c]u8 = x;
-    if (false) {
-        ptr;
-        ptr2;
-    }
+    var ptr3: [*c]u8 = 1;
+    var ptr4: [*c]u8 = y;
+
+    try expect(ptr == ptr2);
+    try expect(ptr3 == ptr4);
+    try expect(ptr3 > ptr and ptr4 > ptr2 and y > x);
+    try expect(1 > ptr and y > ptr2 and 0 < ptr3 and x < ptr4);
 }
 
 test "C pointer comparison and arithmetic" {
test/behavior.zig
@@ -2,6 +2,7 @@ const builtin = @import("builtin");
 
 test {
     // Tests that pass for stage1, stage2, and the C backend.
+    _ = @import("behavior/align.zig");
     _ = @import("behavior/basic.zig");
     _ = @import("behavior/bitcast.zig");
     _ = @import("behavior/bool.zig");
@@ -19,16 +20,18 @@ test {
     _ = @import("behavior/bugs/4769_b.zig");
     _ = @import("behavior/bugs/6850.zig");
     _ = @import("behavior/call.zig");
+    _ = @import("behavior/cast_c.zig");
     _ = @import("behavior/defer.zig");
     _ = @import("behavior/enum.zig");
     _ = @import("behavior/hasdecl.zig");
     _ = @import("behavior/hasfield.zig");
     _ = @import("behavior/if.zig");
-    _ = @import("behavior/struct.zig");
-    _ = @import("behavior/truncate.zig");
+    _ = @import("behavior/int128.zig");
     _ = @import("behavior/null.zig");
+    _ = @import("behavior/pointers.zig");
     _ = @import("behavior/ptrcast.zig");
     _ = @import("behavior/pub_enum.zig");
+    _ = @import("behavior/struct.zig");
     _ = @import("behavior/truncate.zig");
     _ = @import("behavior/underscore.zig");
     _ = @import("behavior/usingnamespace.zig");
@@ -39,7 +42,6 @@ test {
 
     if (builtin.object_format != .c) {
         // Tests that pass for stage1 and stage2 but not the C backend.
-        _ = @import("behavior/align.zig");
         _ = @import("behavior/array.zig");
         _ = @import("behavior/atomics.zig");
         _ = @import("behavior/basic_llvm.zig");
@@ -60,7 +62,6 @@ test {
         _ = @import("behavior/maximum_minimum.zig");
         _ = @import("behavior/null_llvm.zig");
         _ = @import("behavior/optional.zig");
-        _ = @import("behavior/pointers.zig");
         _ = @import("behavior/popcount.zig");
         _ = @import("behavior/saturating_arithmetic.zig");
         _ = @import("behavior/sizeof_and_typeof.zig");