From 9bf1681990fe87a6b2e5fc644a89f1aece304579 Mon Sep 17 00:00:00 2001 From: drew Date: Sun, 14 Nov 2021 18:28:44 -0800 Subject: [PATCH 01/14] 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 --- src/codegen/c.zig | 166 ++++++++++++++++++++----- test/behavior.zig | 9 +- test/behavior/cast.zig | 234 ---------------------------------- test/behavior/cast_c.zig | 249 +++++++++++++++++++++++++++++++++++++ test/behavior/int128.zig | 43 +++++++ test/behavior/pointers.zig | 12 +- 6 files changed, 443 insertions(+), 270 deletions(-) create mode 100644 test/behavior/cast_c.zig create mode 100644 test/behavior/int128.zig diff --git a/src/codegen/c.zig b/src/codegen/c.zig index e95a5a77ec..44271d0657 100644 --- a/src/codegen/c.zig +++ b/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; diff --git a/test/behavior.zig b/test/behavior.zig index 7cdb8386bc..0abc15035b 100644 --- a/test/behavior.zig +++ b/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"); diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index 83230c64d0..6c41152fe9 100644 --- a/test/behavior/cast.zig +++ b/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'); -} diff --git a/test/behavior/cast_c.zig b/test/behavior/cast_c.zig new file mode 100644 index 0000000000..e634103d42 --- /dev/null +++ b/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'); +} diff --git a/test/behavior/int128.zig b/test/behavior/int128.zig new file mode 100644 index 0000000000..444096ff18 --- /dev/null +++ b/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 diff --git a/test/behavior/pointers.zig b/test/behavior/pointers.zig index 69f9e2af2a..32b88a2522 100644 --- a/test/behavior/pointers.zig +++ b/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" { From 34684725aa728e392368a5743e1a2405328d5788 Mon Sep 17 00:00:00 2001 From: drew Date: Sun, 14 Nov 2021 18:31:31 -0800 Subject: [PATCH 02/14] fmt --- src/codegen/c.zig | 6 +++--- test/behavior/int128.zig | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 44271d0657..7dabf3bf9e 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -248,10 +248,10 @@ pub const DeclGen = struct { if (is_neg) try writer.writeByte('-'); assert(high > 0); - try writer.print("(((uint128_t)0x{x}u<<64)", .{ high }); + try writer.print("(((uint128_t)0x{x}u<<64)", .{high}); if (low > 0) - try writer.print("+(uint128_t)0x{x}u", .{ low }); + try writer.print("+(uint128_t)0x{x}u", .{low}); return writer.writeByte(')'); } @@ -306,7 +306,7 @@ pub const DeclGen = struct { if (ty.isSignedInt()) return writer.print("{d}", .{val.toSignedInt()}); return writer.print("{d}u", .{val.toUnsignedInt()}); - } + }, }, .Float => { if (ty.floatBits(dg.module.getTarget()) <= 64) { diff --git a/test/behavior/int128.zig b/test/behavior/int128.zig index 444096ff18..76b12ef1d9 100644 --- a/test/behavior/int128.zig +++ b/test/behavior/int128.zig @@ -40,4 +40,4 @@ test "int128" { test "truncate int128" { var buff: u128 = maxInt(u128); try expect(@truncate(u64, buff) == maxInt(u64)); -} \ No newline at end of file +} From ad4627ea3b645e4dd51397fe4a2d92878c2bd9bf Mon Sep 17 00:00:00 2001 From: drew Date: Sun, 14 Nov 2021 19:08:14 -0800 Subject: [PATCH 03/14] small changes + align tests obviously shouldn't have passed --- src/codegen/c.zig | 2 +- test/behavior.zig | 2 +- test/behavior/int128.zig | 3 --- 3 files changed, 2 insertions(+), 5 deletions(-) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 7dabf3bf9e..554a60ad65 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -842,7 +842,7 @@ pub const DeclGen = struct { .Array => { // We are referencing the array so it will decay to a C pointer. // 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. + // or are already pointed to; our only job is to render the element type. return dg.renderType(w, t.elemType()); }, .Optional => { diff --git a/test/behavior.zig b/test/behavior.zig index 0abc15035b..739aeabb94 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -2,7 +2,6 @@ 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"); @@ -42,6 +41,7 @@ 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"); diff --git a/test/behavior/int128.zig b/test/behavior/int128.zig index 76b12ef1d9..4b0232feb8 100644 --- a/test/behavior/int128.zig +++ b/test/behavior/int128.zig @@ -32,9 +32,6 @@ test "int128" { 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" { From dffa6dcaf93621ab8bbfdb8effad8bde6d73c20a Mon Sep 17 00:00:00 2001 From: drew Date: Sun, 14 Nov 2021 19:26:12 -0800 Subject: [PATCH 04/14] make it more clear we should do UB wrapping optimizations for ptr arithmetic --- src/codegen/c.zig | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 554a60ad65..5fdc2b1c2a 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1123,15 +1123,14 @@ 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 => try airBinOp (f, inst, " + "), + .ptr_add => try airPtrAddSub (f, inst, " + "), // TODO use a different strategy for sub that communicates to the optimizer // that wrapping is UB. .sub => try airBinOp (f, inst, " - "), + .ptr_sub => try airPtrAddSub (f, inst, " - "), // TODO use a different strategy for mul that communicates to the optimizer // that wrapping is UB. .mul => try airBinOp (f, inst, " * "), From 89793bdfa78badd6e29252e5c10a21233ea9b822 Mon Sep 17 00:00:00 2001 From: drew Date: Sun, 14 Nov 2021 19:28:55 -0800 Subject: [PATCH 05/14] add additional negative big int constant test case --- test/behavior/int128.zig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test/behavior/int128.zig b/test/behavior/int128.zig index 4b0232feb8..18375a3e17 100644 --- a/test/behavior/int128.zig +++ b/test/behavior/int128.zig @@ -32,6 +32,9 @@ test "int128" { buff = minInt(i128); try expect(buff < 0); + + buff = -0x12341234123412341234123412341234; + try expect(-buff == 0x12341234123412341234123412341234); } test "truncate int128" { From 0300ec4ef70f524718d01f0f351d41349465387c Mon Sep 17 00:00:00 2001 From: drew Date: Sun, 14 Nov 2021 19:51:36 -0800 Subject: [PATCH 06/14] fix assumption where all positive big ints are unsigned --- src/codegen/c.zig | 31 ++++++++++++++++++++++--------- 1 file changed, 22 insertions(+), 9 deletions(-) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 5fdc2b1c2a..a82fb42625 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -19,6 +19,7 @@ const Zir = @import("../Zir.zig"); const Liveness = @import("../Liveness.zig"); const Mutability = enum { Const, Mut }; +const BigIntConst = std.math.big.int.Const; pub const CValue = union(enum) { none: void, @@ -226,8 +227,7 @@ 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( + fn renderInt128( writer: anytype, int_val: anytype, ) error{ OutOfMemory, AnalysisFail }!void { @@ -256,6 +256,23 @@ pub const DeclGen = struct { return writer.writeByte(')'); } + fn renderBigIntConst( + dg: *DeclGen, + writer: anytype, + val: BigIntConst, + signed: bool, + ) error{ OutOfMemory, AnalysisFail }!void { + if (signed) { + try renderInt128(writer, val.to(i128) catch { + return dg.fail("TODO implement integer constants larger than 128 bits", .{}); + }); + } else { + try renderInt128(writer, val.to(u128) catch { + return dg.fail("TODO implement integer constants larger than 128 bits", .{}); + }); + } + } + fn renderValue( dg: *DeclGen, writer: anytype, @@ -274,7 +291,7 @@ pub const DeclGen = struct { 16 => return writer.writeAll("0xaaaau"), 32 => return writer.writeAll("0xaaaaaaaau"), 64 => return writer.writeAll("0xaaaaaaaaaaaaaaaau"), - 128 => return renderBigInt(writer, @as(u128, 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)), + 128 => return renderInt128(writer, @as(u128, 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa)), else => unreachable, } }, @@ -296,12 +313,8 @@ pub const DeclGen = struct { } switch (ty.zigTypeTag()) { .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", .{}); - }), + .int_big_positive => try dg.renderBigIntConst(writer, val.castTag(.int_big_positive).?.asBigInt(), ty.isSignedInt()), + .int_big_negative => try dg.renderBigIntConst(writer, val.castTag(.int_big_negative).?.asBigInt(), true), else => { if (ty.isSignedInt()) return writer.print("{d}", .{val.toSignedInt()}); From 3896de307888d598cca1baba8e559debc81d2080 Mon Sep 17 00:00:00 2001 From: drew Date: Sun, 14 Nov 2021 22:06:12 -0800 Subject: [PATCH 07/14] simplify things --- src/codegen/c.zig | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index a82fb42625..49cf48cfb0 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -342,12 +342,6 @@ pub const DeclGen = struct { // 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); @@ -376,7 +370,7 @@ pub const DeclGen = struct { const decl = val.castTag(.extern_fn).?.data; try dg.renderDeclName(decl, writer); }, - .int_u64 => { + .int_u64, .one => { try writer.writeAll("(("); try dg.renderType(writer, ty); try writer.print(")0x{x}u)", .{val.toUnsignedInt()}); From cf99afc52568ab121db2db8aa3ba94b97109f396 Mon Sep 17 00:00:00 2001 From: drew Date: Sun, 14 Nov 2021 23:27:13 -0800 Subject: [PATCH 08/14] add generics behavior test -airLoad and airStore now properly report an error if they are used with an array, instead of having the C compiler emit a vague error -airStoreUndefined now works with array types -structFieldPtr now works with array types, allowing generics' tests to pass --- src/codegen/c.zig | 21 ++++++++++++++++----- test/behavior.zig | 2 +- 2 files changed, 17 insertions(+), 6 deletions(-) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 49cf48cfb0..1dfcab5d61 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1431,6 +1431,8 @@ fn airLoad(f: *Function, inst: Air.Inst.Index) !CValue { if (!is_volatile and f.liveness.isUnused(inst)) return CValue.none; const inst_ty = f.air.typeOfIndex(inst); + if (inst_ty.zigTypeTag() == .Array) + return f.fail("TODO: C backend: implement airLoad for arrays", .{}); const operand = try f.resolveInst(ty_op.operand); const writer = f.object.writer(); const local = try f.allocLocal(inst_ty, .Const); @@ -1557,7 +1559,7 @@ fn airBoolToInt(f: *Function, inst: Air.Inst.Index) !CValue { return local; } -fn airStoreUndefined(f: *Function, dest_ptr: CValue) !CValue { +fn airStoreUndefined(f: *Function, dest_ptr: CValue, dest_type: Type) !CValue { const is_debug_build = f.object.dg.module.optimizeMode() == .Debug; if (!is_debug_build) return CValue.none; @@ -1581,9 +1583,11 @@ fn airStoreUndefined(f: *Function, dest_ptr: CValue) !CValue { try writer.writeAll("));\n"); }, else => { + const indirection = if (dest_type.zigTypeTag() == .Array) "" else "*"; + try writer.writeAll("memset("); try f.writeCValue(writer, dest_ptr); - try writer.writeAll(", 0xaa, sizeof(*"); + try writer.print(", 0xaa, sizeof({s}", .{indirection}); try f.writeCValue(writer, dest_ptr); try writer.writeAll("));\n"); }, @@ -1596,11 +1600,16 @@ fn airStore(f: *Function, inst: Air.Inst.Index) !CValue { const bin_op = f.air.instructions.items(.data)[inst].bin_op; const dest_ptr = try f.resolveInst(bin_op.lhs); const src_val = try f.resolveInst(bin_op.rhs); + const lhs_type = f.air.typeOf(bin_op.lhs); const src_val_is_undefined = if (f.air.value(bin_op.rhs)) |v| v.isUndef() else false; if (src_val_is_undefined) - return try airStoreUndefined(f, dest_ptr); + return try airStoreUndefined(f, dest_ptr, lhs_type); + + // Don't check this for airStoreUndefined as that will work for arrays already + if (lhs_type.zigTypeTag() == .Array) + return f.fail("TODO: C backend: implement airStore for arrays", .{}); const writer = f.object.writer(); switch (dest_ptr) { @@ -2420,15 +2429,17 @@ fn structFieldPtr(f: *Function, inst: Air.Inst.Index, struct_ptr_ty: Type, struc const writer = f.object.writer(); const struct_obj = struct_ptr_ty.elemType().castTag(.@"struct").?.data; const field_name = struct_obj.fields.keys()[index]; + const field_val = struct_obj.fields.values()[index]; + const addrof = if (field_val.ty.zigTypeTag() == .Array) "" else "&"; const inst_ty = f.air.typeOfIndex(inst); const local = try f.allocLocal(inst_ty, .Const); switch (struct_ptr) { .local_ref => |i| { - try writer.print(" = &t{d}.{};\n", .{ i, fmtIdent(field_name) }); + try writer.print(" = {s}t{d}.{};\n", .{ addrof, i, fmtIdent(field_name) }); }, else => { - try writer.writeAll(" = &"); + try writer.print(" = {s}", .{addrof}); try f.writeCValue(writer, struct_ptr); try writer.print("->{};\n", .{fmtIdent(field_name)}); }, diff --git a/test/behavior.zig b/test/behavior.zig index 739aeabb94..db6002e3f2 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -38,6 +38,7 @@ test { _ = @import("behavior/this.zig"); _ = @import("behavior/member_func.zig"); _ = @import("behavior/translate_c_macros.zig"); + _ = @import("behavior/generics.zig"); if (builtin.object_format != .c) { // Tests that pass for stage1 and stage2 but not the C backend. @@ -57,7 +58,6 @@ test { _ = @import("behavior/floatop.zig"); _ = @import("behavior/fn.zig"); _ = @import("behavior/for.zig"); - _ = @import("behavior/generics.zig"); _ = @import("behavior/math.zig"); _ = @import("behavior/maximum_minimum.zig"); _ = @import("behavior/null_llvm.zig"); From f33af8f071a1782d222067e48b68eaed5d1a05a8 Mon Sep 17 00:00:00 2001 From: drew Date: Mon, 15 Nov 2021 00:08:57 -0800 Subject: [PATCH 09/14] fix array airStoreUndefined for arrays --- src/codegen/c.zig | 4 ++-- test/behavior/cast_c.zig | 11 +++++++++++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 1dfcab5d61..92d973fb4f 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1583,7 +1583,7 @@ fn airStoreUndefined(f: *Function, dest_ptr: CValue, dest_type: Type) !CValue { try writer.writeAll("));\n"); }, else => { - const indirection = if (dest_type.zigTypeTag() == .Array) "" else "*"; + const indirection = if (dest_type.childType().zigTypeTag() == .Array) "" else "*"; try writer.writeAll("memset("); try f.writeCValue(writer, dest_ptr); @@ -1608,7 +1608,7 @@ fn airStore(f: *Function, inst: Air.Inst.Index) !CValue { return try airStoreUndefined(f, dest_ptr, lhs_type); // Don't check this for airStoreUndefined as that will work for arrays already - if (lhs_type.zigTypeTag() == .Array) + if (lhs_type.childType().zigTypeTag() == .Array) return f.fail("TODO: C backend: implement airStore for arrays", .{}); const writer = f.object.writer(); diff --git a/test/behavior/cast_c.zig b/test/behavior/cast_c.zig index e634103d42..0e74cd0b58 100644 --- a/test/behavior/cast_c.zig +++ b/test/behavior/cast_c.zig @@ -247,3 +247,14 @@ test "*const ?[*]const T to [*c]const [*c]const T" { try expect(b.*[0] == 'o'); try expect(b[0][1] == 'k'); } + +test "array coersion to undefined at runtime" { + @setRuntimeSafety(true); + + var array = [4]u8{ 3, 4, 5, 6 }; + var undefined_val = [4]u8{ 0xAA, 0xAA, 0xAA, 0xAA }; + + try expect(std.mem.eql(u8, &array, &array)); + array = undefined; + try expect(std.mem.eql(u8, &array, &undefined_val)); +} From 0249344a479a8eb51637c04fdecd947445ece0d5 Mon Sep 17 00:00:00 2001 From: drew Date: Mon, 15 Nov 2021 17:57:48 -0800 Subject: [PATCH 10/14] cast -> cast_llvm, cast_c -> cast (doesn't work on LLVM backend) --- test/behavior.zig | 4 +- test/behavior/cast.zig | 267 +++++++++++++++++++++++++++++++----- test/behavior/cast_c.zig | 260 ----------------------------------- test/behavior/cast_llvm.zig | 67 +++++++++ 4 files changed, 299 insertions(+), 299 deletions(-) delete mode 100644 test/behavior/cast_c.zig create mode 100644 test/behavior/cast_llvm.zig diff --git a/test/behavior.zig b/test/behavior.zig index db6002e3f2..bb155a7de4 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -19,7 +19,7 @@ test { _ = @import("behavior/bugs/4769_b.zig"); _ = @import("behavior/bugs/6850.zig"); _ = @import("behavior/call.zig"); - _ = @import("behavior/cast_c.zig"); + _ = @import("behavior/cast.zig"); _ = @import("behavior/defer.zig"); _ = @import("behavior/enum.zig"); _ = @import("behavior/hasdecl.zig"); @@ -52,7 +52,7 @@ test { _ = @import("behavior/bugs/1741.zig"); _ = @import("behavior/bugs/2006.zig"); _ = @import("behavior/bugs/3112.zig"); - _ = @import("behavior/cast.zig"); + _ = @import("behavior/cast_llvm.zig"); _ = @import("behavior/error.zig"); _ = @import("behavior/eval.zig"); _ = @import("behavior/floatop.zig"); diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index 6c41152fe9..0e74cd0b58 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -2,18 +2,95 @@ const std = @import("std"); const expect = std.testing.expect; const mem = std.mem; const maxInt = std.math.maxInt; -const native_endian = @import("builtin").target.cpu.arch.endian(); -test "pointer reinterpret const float to int" { - // The hex representation is 0x3fe3333333333303. - const float: f64 = 5.99999999999994648725e-01; - const float_ptr = &float; - const int_ptr = @ptrCast(*const i32, float_ptr); - const int_val = int_ptr.*; - if (native_endian == .Little) - try expect(int_val == 0x33333303) - else - try expect(int_val == 0x3fe33333); +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" { @@ -22,46 +99,162 @@ test "@floatToInt" { } fn testFloatToInts() !void { - try expectFloatToInt(f16, 255.1, u8, 255); - try expectFloatToInt(f16, 127.2, i8, 127); - try expectFloatToInt(f16, -128.2, i8, -128); + 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 "implicit cast from [*]T to ?*c_void" { - var a = [_]u8{ 3, 2, 1 }; - var runtime_zero: usize = 0; - incrementVoidPtrArray(a[runtime_zero..].ptr, 3); - try expect(std.mem.eql(u8, &a, &[_]u8{ 4, 3, 2 })); +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)); } -fn incrementVoidPtrArray(array: ?*c_void, len: usize) void { - var n: usize = 0; - while (n < len) : (n += 1) { - @ptrCast([*]u8, array.?)[n] += 1; +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 "compile time int to ptr of function" { - try foobar(FUNCTION_CONSTANT); +test "coerce undefined to optional" { + try expect(MakeType(void).getNull() == null); + try expect(MakeType(void).getNonNull() != null); } -pub const FUNCTION_CONSTANT = @intToPtr(PFN_void, maxInt(usize)); -pub const PFN_void = fn (*c_void) callconv(.C) void; +fn MakeType(comptime T: type) type { + return struct { + fn getNull() ?T { + return null; + } -fn foobar(func: PFN_void) !void { - try std.testing.expect(@ptrToInt(func) == maxInt(usize)); + fn getNonNull() ?T { + return @as(T, undefined); + } + }; } -test "implicit ptr to *c_void" { - var a: u32 = 1; - var ptr: *align(@alignOf(u32)) c_void = &a; - var b: *u32 = @ptrCast(*u32, ptr); - try expect(b.* == 1); - var ptr2: ?*align(@alignOf(u32)) c_void = &a; - var c: *u32 = @ptrCast(*u32, ptr2.?); - try expect(c.* == 1); +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 "array coersion to undefined at runtime" { + @setRuntimeSafety(true); + + var array = [4]u8{ 3, 4, 5, 6 }; + var undefined_val = [4]u8{ 0xAA, 0xAA, 0xAA, 0xAA }; + + try expect(std.mem.eql(u8, &array, &array)); + array = undefined; + try expect(std.mem.eql(u8, &array, &undefined_val)); } diff --git a/test/behavior/cast_c.zig b/test/behavior/cast_c.zig deleted file mode 100644 index 0e74cd0b58..0000000000 --- a/test/behavior/cast_c.zig +++ /dev/null @@ -1,260 +0,0 @@ -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 "array coersion to undefined at runtime" { - @setRuntimeSafety(true); - - var array = [4]u8{ 3, 4, 5, 6 }; - var undefined_val = [4]u8{ 0xAA, 0xAA, 0xAA, 0xAA }; - - try expect(std.mem.eql(u8, &array, &array)); - array = undefined; - try expect(std.mem.eql(u8, &array, &undefined_val)); -} diff --git a/test/behavior/cast_llvm.zig b/test/behavior/cast_llvm.zig new file mode 100644 index 0000000000..6c41152fe9 --- /dev/null +++ b/test/behavior/cast_llvm.zig @@ -0,0 +1,67 @@ +const std = @import("std"); +const expect = std.testing.expect; +const mem = std.mem; +const maxInt = std.math.maxInt; +const native_endian = @import("builtin").target.cpu.arch.endian(); + +test "pointer reinterpret const float to int" { + // The hex representation is 0x3fe3333333333303. + const float: f64 = 5.99999999999994648725e-01; + const float_ptr = &float; + const int_ptr = @ptrCast(*const i32, float_ptr); + const int_val = int_ptr.*; + if (native_endian == .Little) + try expect(int_val == 0x33333303) + else + try expect(int_val == 0x3fe33333); +} + +test "@floatToInt" { + try testFloatToInts(); + comptime try testFloatToInts(); +} + +fn testFloatToInts() !void { + try expectFloatToInt(f16, 255.1, u8, 255); + try expectFloatToInt(f16, 127.2, i8, 127); + try expectFloatToInt(f16, -128.2, i8, -128); +} + +fn expectFloatToInt(comptime F: type, f: F, comptime I: type, i: I) !void { + try expect(@floatToInt(I, f) == i); +} + +test "implicit cast from [*]T to ?*c_void" { + var a = [_]u8{ 3, 2, 1 }; + var runtime_zero: usize = 0; + incrementVoidPtrArray(a[runtime_zero..].ptr, 3); + try expect(std.mem.eql(u8, &a, &[_]u8{ 4, 3, 2 })); +} + +fn incrementVoidPtrArray(array: ?*c_void, len: usize) void { + var n: usize = 0; + while (n < len) : (n += 1) { + @ptrCast([*]u8, array.?)[n] += 1; + } +} + +test "compile time int to ptr of function" { + try foobar(FUNCTION_CONSTANT); +} + +pub const FUNCTION_CONSTANT = @intToPtr(PFN_void, maxInt(usize)); +pub const PFN_void = fn (*c_void) callconv(.C) void; + +fn foobar(func: PFN_void) !void { + try std.testing.expect(@ptrToInt(func) == maxInt(usize)); +} + +test "implicit ptr to *c_void" { + var a: u32 = 1; + var ptr: *align(@alignOf(u32)) c_void = &a; + var b: *u32 = @ptrCast(*u32, ptr); + try expect(b.* == 1); + var ptr2: ?*align(@alignOf(u32)) c_void = &a; + var c: *u32 = @ptrCast(*u32, ptr2.?); + try expect(c.* == 1); +} From a1d760416293b20b6a6a72f39a5957fbf22187cc Mon Sep 17 00:00:00 2001 From: drew Date: Mon, 15 Nov 2021 18:07:48 -0800 Subject: [PATCH 11/14] correct misnamed variables caused by copy-paste --- src/codegen/c.zig | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 92d973fb4f..908b85e701 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -1306,15 +1306,15 @@ fn airPtrSliceFieldPtr(f: *Function, inst: Air.Inst.Index, suffix: []const u8) ! 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; + const ptr_ty = f.air.typeOf(bin_op.lhs); + if (!ptr_ty.isVolatilePtr() and f.liveness.isUnused(inst)) return CValue.none; - const arr = try f.resolveInst(bin_op.lhs); + const ptr = 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 f.writeCValue(writer, ptr); try writer.writeByte('['); try f.writeCValue(writer, index); try writer.writeAll("];\n"); @@ -1327,12 +1327,12 @@ fn airPtrElemPtr(f: *Function, inst: Air.Inst.Index) !CValue { 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 ptr = 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 f.writeCValue(writer, ptr); try writer.writeByte('['); try f.writeCValue(writer, index); try writer.writeAll("];\n"); From 30ed91b7b9f70d3f64e0c3a1a8d0eb0f3b499d50 Mon Sep 17 00:00:00 2001 From: Drew P Date: Mon, 15 Nov 2021 22:29:24 -0800 Subject: [PATCH 12/14] fix tests failing on stage2 release modes due to setRuntimeSafety not being recognized --- test/behavior/cast.zig | 3 +++ test/behavior/int128.zig | 3 +++ 2 files changed, 6 insertions(+) diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index 0e74cd0b58..8c9f2b285d 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -251,6 +251,9 @@ test "*const ?[*]const T to [*c]const [*c]const T" { test "array coersion to undefined at runtime" { @setRuntimeSafety(true); + // setRuntimeSafety isn't recognized on stage2 + if (@import("builtin").zig_is_stage2 and (@import("builtin").mode != .Debug or @import("builtin").mode != .ReleaseSafe)) return error.SkipZigTest; + var array = [4]u8{ 3, 4, 5, 6 }; var undefined_val = [4]u8{ 0xAA, 0xAA, 0xAA, 0xAA }; diff --git a/test/behavior/int128.zig b/test/behavior/int128.zig index 18375a3e17..5113ca63eb 100644 --- a/test/behavior/int128.zig +++ b/test/behavior/int128.zig @@ -20,6 +20,9 @@ test "uint128" { test "undefined 128 bit int" { @setRuntimeSafety(true); + // setRuntimeSafety isn't recognized on stage2 + if (@import("builtin").zig_is_stage2 and (@import("builtin").mode != .Debug or @import("builtin").mode != .ReleaseSafe)) return error.SkipZigTest; + var undef: u128 = undefined; var undef_signed: i128 = undefined; try expect(undef == 0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa and @bitCast(u128, undef_signed) == undef); From 68fe391de02fd4c99f39bd6e0af643e1e327e52a Mon Sep 17 00:00:00 2001 From: Drew P Date: Mon, 15 Nov 2021 22:33:56 -0800 Subject: [PATCH 13/14] typo --- test/behavior/cast.zig | 2 +- test/behavior/int128.zig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index 8c9f2b285d..5350b09534 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -252,7 +252,7 @@ test "array coersion to undefined at runtime" { @setRuntimeSafety(true); // setRuntimeSafety isn't recognized on stage2 - if (@import("builtin").zig_is_stage2 and (@import("builtin").mode != .Debug or @import("builtin").mode != .ReleaseSafe)) return error.SkipZigTest; + if (@import("builtin").zig_is_stage2 and @import("builtin").mode != .Debug and @import("builtin").mode != .ReleaseSafe) return error.SkipZigTest; var array = [4]u8{ 3, 4, 5, 6 }; var undefined_val = [4]u8{ 0xAA, 0xAA, 0xAA, 0xAA }; diff --git a/test/behavior/int128.zig b/test/behavior/int128.zig index 5113ca63eb..6b4e2de895 100644 --- a/test/behavior/int128.zig +++ b/test/behavior/int128.zig @@ -21,7 +21,7 @@ test "undefined 128 bit int" { @setRuntimeSafety(true); // setRuntimeSafety isn't recognized on stage2 - if (@import("builtin").zig_is_stage2 and (@import("builtin").mode != .Debug or @import("builtin").mode != .ReleaseSafe)) return error.SkipZigTest; + if (@import("builtin").zig_is_stage2 and @import("builtin").mode != .Debug and @import("builtin").mode != .ReleaseSafe) return error.SkipZigTest; var undef: u128 = undefined; var undef_signed: i128 = undefined; From 09588c795c08064971f61ee147d06972f0add94e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 16 Nov 2021 17:46:39 -0700 Subject: [PATCH 14/14] stage2: LLVM backend: memset to 0xaa for undefined stores Also support `one` and `int_big_positive` tags for const pointers. --- src/codegen/c.zig | 8 +++++--- src/codegen/llvm.zig | 30 ++++++++++++++++++++++++++---- src/value.zig | 7 +++++++ test/behavior/cast.zig | 9 +++++++-- test/behavior/int128.zig | 9 +++++++-- 5 files changed, 52 insertions(+), 11 deletions(-) diff --git a/src/codegen/c.zig b/src/codegen/c.zig index 908b85e701..3aef5a8f92 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -279,7 +279,7 @@ pub const DeclGen = struct { ty: Type, val: Value, ) error{ OutOfMemory, AnalysisFail }!void { - if (val.isUndef()) { + if (val.isUndefDeep()) { switch (ty.zigTypeTag()) { // Using '{}' for integer and floats seemed to error C compilers (both GCC and Clang) // with 'error: expected expression' (including when built with 'zig cc') @@ -1049,7 +1049,7 @@ pub fn genDecl(o: *Object) !void { } try fwd_decl_writer.writeAll(";\n"); - if (variable.init.isUndef()) { + if (variable.init.isUndefDeep()) { return; } @@ -1602,8 +1602,10 @@ fn airStore(f: *Function, inst: Air.Inst.Index) !CValue { const src_val = try f.resolveInst(bin_op.rhs); const lhs_type = f.air.typeOf(bin_op.lhs); + // TODO Sema should emit a different instruction when the store should + // possibly do the safety 0xaa bytes for undefined. const src_val_is_undefined = - if (f.air.value(bin_op.rhs)) |v| v.isUndef() else false; + if (f.air.value(bin_op.rhs)) |v| v.isUndefDeep() else false; if (src_val_is_undefined) return try airStoreUndefined(f, dest_ptr, lhs_type); diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index d0f6d62ad7..306a3df83c 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -1078,7 +1078,7 @@ pub const DeclGen = struct { }; return self.context.constStruct(&fields, fields.len, .False); }, - .int_u64 => { + .int_u64, .one, .int_big_positive => { const llvm_usize = try self.llvmType(Type.usize); const llvm_int = llvm_usize.constInt(tv.val.toUnsignedInt(), .False); return llvm_int.constIntToPtr(try self.llvmType(tv.ty)); @@ -3464,8 +3464,30 @@ pub const FuncGen = struct { const bin_op = self.air.instructions.items(.data)[inst].bin_op; const dest_ptr = try self.resolveInst(bin_op.lhs); const ptr_ty = self.air.typeOf(bin_op.lhs); - const src_operand = try self.resolveInst(bin_op.rhs); - self.store(dest_ptr, ptr_ty, src_operand, .NotAtomic); + + // TODO Sema should emit a different instruction when the store should + // possibly do the safety 0xaa bytes for undefined. + const val_is_undef = if (self.air.value(bin_op.rhs)) |val| val.isUndefDeep() else false; + if (val_is_undef) { + const elem_ty = ptr_ty.childType(); + const target = self.dg.module.getTarget(); + const elem_size = elem_ty.abiSize(target); + const u8_llvm_ty = self.context.intType(8); + const ptr_u8_llvm_ty = u8_llvm_ty.pointerType(0); + const dest_ptr_u8 = self.builder.buildBitCast(dest_ptr, ptr_u8_llvm_ty, ""); + const fill_char = u8_llvm_ty.constInt(0xaa, .False); + const dest_ptr_align = ptr_ty.ptrAlignment(target); + const usize_llvm_ty = try self.dg.llvmType(Type.usize); + const len = usize_llvm_ty.constInt(elem_size, .False); + _ = self.builder.buildMemSet(dest_ptr_u8, fill_char, len, dest_ptr_align, ptr_ty.isVolatilePtr()); + if (self.dg.module.comp.bin_file.options.valgrind) { + // TODO generate valgrind client request to mark byte range as undefined + // see gen_valgrind_undef() in codegen.cpp + } + } else { + const src_operand = try self.resolveInst(bin_op.rhs); + self.store(dest_ptr, ptr_ty, src_operand, .NotAtomic); + } return null; } @@ -3651,7 +3673,7 @@ pub const FuncGen = struct { const dest_ptr = try self.resolveInst(pl_op.operand); const ptr_ty = self.air.typeOf(pl_op.operand); const value = try self.resolveInst(extra.lhs); - const val_is_undef = if (self.air.value(extra.lhs)) |val| val.isUndef() else false; + const val_is_undef = if (self.air.value(extra.lhs)) |val| val.isUndefDeep() else false; const len = try self.resolveInst(extra.rhs); const u8_llvm_ty = self.context.intType(8); const ptr_u8_llvm_ty = u8_llvm_ty.pointerType(0); diff --git a/src/value.zig b/src/value.zig index 4b571891f4..4eaa865149 100644 --- a/src/value.zig +++ b/src/value.zig @@ -1802,6 +1802,13 @@ pub const Value = extern union { return self.tag() == .undef; } + /// TODO: check for cases such as array that is not marked undef but all the element + /// values are marked undef, or struct that is not marked undef but all fields are marked + /// undef, etc. + pub fn isUndefDeep(self: Value) bool { + return self.isUndef(); + } + /// Asserts the value is not undefined and not unreachable. /// Integer value 0 is considered null because of C pointers. pub fn isNull(self: Value) bool { diff --git a/test/behavior/cast.zig b/test/behavior/cast.zig index 5350b09534..607df6a8e8 100644 --- a/test/behavior/cast.zig +++ b/test/behavior/cast.zig @@ -251,8 +251,13 @@ test "*const ?[*]const T to [*c]const [*c]const T" { test "array coersion to undefined at runtime" { @setRuntimeSafety(true); - // setRuntimeSafety isn't recognized on stage2 - if (@import("builtin").zig_is_stage2 and @import("builtin").mode != .Debug and @import("builtin").mode != .ReleaseSafe) return error.SkipZigTest; + // TODO implement @setRuntimeSafety in stage2 + if (@import("builtin").zig_is_stage2 and + @import("builtin").mode != .Debug and + @import("builtin").mode != .ReleaseSafe) + { + return error.SkipZigTest; + } var array = [4]u8{ 3, 4, 5, 6 }; var undefined_val = [4]u8{ 0xAA, 0xAA, 0xAA, 0xAA }; diff --git a/test/behavior/int128.zig b/test/behavior/int128.zig index 6b4e2de895..12367b2e9c 100644 --- a/test/behavior/int128.zig +++ b/test/behavior/int128.zig @@ -20,8 +20,13 @@ test "uint128" { test "undefined 128 bit int" { @setRuntimeSafety(true); - // setRuntimeSafety isn't recognized on stage2 - if (@import("builtin").zig_is_stage2 and @import("builtin").mode != .Debug and @import("builtin").mode != .ReleaseSafe) return error.SkipZigTest; + // TODO implement @setRuntimeSafety in stage2 + if (@import("builtin").zig_is_stage2 and + @import("builtin").mode != .Debug and + @import("builtin").mode != .ReleaseSafe) + { + return error.SkipZigTest; + } var undef: u128 = undefined; var undef_signed: i128 = undefined;