diff options
| -rw-r--r-- | src/Sema.zig | 46 | ||||
| -rw-r--r-- | test/behavior/comptime_memory.zig | 161 | ||||
| -rw-r--r-- | test/behavior/union.zig | 219 |
3 files changed, 247 insertions, 179 deletions
diff --git a/src/Sema.zig b/src/Sema.zig index 26247fbb2c..90f4925e9e 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -27251,7 +27251,7 @@ fn unionFieldVal( return sema.failWithOwnedErrorMsg(block, msg); } }, - .Packed, .Extern => { + .Packed, .Extern => |layout| { if (tag_matches) { return Air.internedToRef(un.val); } else { @@ -27260,7 +27260,7 @@ fn unionFieldVal( else union_ty.unionFieldType(un.tag.toValue(), mod).?; - if (try sema.bitCastUnionFieldVal(block, src, un.val.toValue(), old_ty, field_ty)) |new_val| { + if (try sema.bitCastUnionFieldVal(block, src, un.val.toValue(), old_ty, field_ty, layout)) |new_val| { return Air.internedToRef(new_val.toIntern()); } } @@ -30751,26 +30751,50 @@ fn bitCastUnionFieldVal( val: Value, old_ty: Type, field_ty: Type, + layout: std.builtin.Type.ContainerLayout, ) !?Value { const mod = sema.mod; if (old_ty.eql(field_ty, mod)) return val; const old_size = try sema.usizeCast(block, src, old_ty.abiSize(mod)); const field_size = try sema.usizeCast(block, src, field_ty.abiSize(mod)); + const endian = mod.getTarget().cpu.arch.endian(); const buffer = try sema.gpa.alloc(u8, @max(old_size, field_size)); defer sema.gpa.free(buffer); - val.writeToMemory(old_ty, mod, buffer) catch |err| switch (err) { - error.OutOfMemory => return error.OutOfMemory, - error.ReinterpretDeclRef => return null, - error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already - error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{old_ty.fmt(mod)}), - }; - // Reading a larger value means we need to reinterpret from undefined bytes - if (field_size > old_size) @memset(buffer[old_size..], 0xaa); + // Reading a larger value means we need to reinterpret from undefined bytes. + const offset = switch (layout) { + .Extern => offset: { + if (field_size > old_size) @memset(buffer[old_size..], 0xaa); + val.writeToMemory(old_ty, mod, buffer) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.ReinterpretDeclRef => return null, + error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already + error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{old_ty.fmt(mod)}), + }; + break :offset 0; + }, + .Packed => offset: { + if (field_size > old_size) { + const min_size = @max(old_size, 1); + switch (endian) { + .Little => @memset(buffer[min_size - 1 ..], 0xaa), + .Big => @memset(buffer[0 .. buffer.len - min_size + 1], 0xaa), + } + } + + val.writeToPackedMemory(old_ty, mod, buffer, 0) catch |err| switch (err) { + error.OutOfMemory => return error.OutOfMemory, + error.ReinterpretDeclRef => return null, + }; + + break :offset if (endian == .Big) buffer.len - field_size else 0; + }, + .Auto => unreachable, + }; - return Value.readFromMemory(field_ty, mod, buffer[0..], sema.arena) catch |err| switch (err) { + return Value.readFromMemory(field_ty, mod, buffer[offset..], sema.arena) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.IllDefinedMemoryLayout => unreachable, error.Unimplemented => return sema.fail(block, src, "TODO: implement readFromMemory for type '{}'", .{field_ty.fmt(mod)}), diff --git a/test/behavior/comptime_memory.zig b/test/behavior/comptime_memory.zig index c3d80fe4e8..d6b3ae3993 100644 --- a/test/behavior/comptime_memory.zig +++ b/test/behavior/comptime_memory.zig @@ -455,164 +455,3 @@ test "type pun null pointer-like optional" { // note that expectEqual hides the bug try testing.expect(@as(*const ?*i8, @ptrCast(&p)).* == null); } - -test "reinterpret extern union" { - const U = extern union { - foo: u8, - baz: u32 align(8), - bar: u32, - }; - - comptime { - { - // Undefined initialization - const u = blk: { - var u: U = undefined; - @memset(std.mem.asBytes(&u), 0); - u.bar = 0xbbbbbbbb; - u.foo = 0x2a; - break :blk u; - }; - try testing.expectEqual(@as(u8, 0x2a), u.foo); - try testing.expectEqual(@as(u32, 0xbbbbbb2a), u.bar); - try testing.expectEqual(@as(u64, 0x00000000_bbbbbb2a), u.baz); - } - - { - // Union initialization - var u: U = .{ - .foo = 0x2a, - }; - try testing.expectEqual(@as(u8, 0x2a), u.foo); - try testing.expectEqual(@as(u32, 0x2a), u.bar & 0xff); - try testing.expectEqual(@as(u64, 0x2a), u.baz & 0xff); - - // Writing to a larger field - u = .{ - .baz = 0xbbbbbbbb, - }; - try testing.expectEqual(@as(u8, 0xbb), u.foo); - try testing.expectEqual(@as(u32, 0xbbbbbbbb), u.bar); - try testing.expectEqual(@as(u64, 0xbbbbbbbb), u.baz); - - // Writing to the same field - u = .{ - .baz = 0xcccccccc, - }; - try testing.expectEqual(@as(u8, 0xcc), u.foo); - try testing.expectEqual(@as(u32, 0xcccccccc), u.bar); - try testing.expectEqual(@as(u64, 0xcccccccc), u.baz); - - // Writing to a smaller field - u = .{ - .foo = 0xdd, - }; - try testing.expectEqual(@as(u8, 0xdd), u.foo); - try testing.expectEqual(@as(u32, 0xccccccdd), u.bar); - try testing.expectEqual(@as(u64, 0xccccccdd), u.baz); - } - } -} - -test "reinterpret packed union" { - { - const U = packed union { - a: u32, - b: u8 align(8), - }; - - comptime { - var u: U = undefined; - @memset(std.mem.asBytes(&u), 42); - try testing.expect(0x2a2a2a2a == u.a); - try testing.expect(0x2a == u.b); - try testing.expectEqual(@as(u32, 0x2a2a2a2a), u.a); - try testing.expectEqual(0x2a, u.b); - } - } - - { - const U = packed union { - a: u7, - b: u1, - }; - - const S = packed struct { - lsb: U, - msb: U, - }; - - comptime { - var s: S = undefined; - @memset(std.mem.asBytes(&s), 0x55); - try testing.expectEqual(@as(u7, 0x55), s.lsb.a); - try testing.expectEqual(@as(u1, 1), s.lsb.b); - try testing.expectEqual(@as(u7, 0x2a), s.msb.a); - try testing.expectEqual(@as(u1, 0), s.msb.b); - - s.lsb.b = 0; - try testing.expectEqual(@as(u7, 0x54), s.lsb.a); - try testing.expectEqual(@as(u1, 0), s.lsb.b); - s.msb.b = 1; - try testing.expectEqual(@as(u7, 0x2b), s.msb.a); - try testing.expectEqual(@as(u1, 1), s.msb.b); - } - } - - { - const U = packed union { - foo: u8, - bar: u29, - baz: u64, - }; - - comptime { - { - const u = blk: { - var u: U = undefined; - @memset(std.mem.asBytes(&u), 0); - u.baz = 0xbbbbbbbb; - u.foo = 0x2a; - break :blk u; - }; - try testing.expectEqual(@as(u8, 0x2a), u.foo); - try testing.expectEqual(@as(u29, 0x1bbbbb2a), u.bar); - try testing.expectEqual(@as(u64, 0x00000000_bbbbbb2a), u.baz); - } - - { - // Union initialization - var u: U = .{ - .foo = 0x2a, - }; - try testing.expectEqual(@as(u8, 0x2a), u.foo); - try testing.expectEqual(@as(u29, 0x2a), u.bar & 0xff); - try testing.expectEqual(@as(u64, 0x2a), u.baz & 0xff); - - // Writing to a larger field - u = .{ - .baz = 0xbbbbbbbb, - }; - try testing.expectEqual(@as(u8, 0xbb), u.foo); - try testing.expectEqual(@as(u29, 0x1bbbbbbb), u.bar); - try testing.expectEqual(@as(u64, 0xbbbbbbbb), u.baz); - - // Writing to the same field - u = .{ - .baz = 0xcccccccc, - }; - try testing.expectEqual(@as(u8, 0xcc), u.foo); - try testing.expectEqual(@as(u29, 0x0ccccccc), u.bar); - try testing.expectEqual(@as(u64, 0xcccccccc), u.baz); - - // Writing to a smaller field - u = .{ - .foo = 0xdd, - }; - try testing.expectEqual(@as(u8, 0xdd), u.foo); - try testing.expectEqual(@as(u29, 0x0cccccdd), u.bar); - try testing.expectEqual(@as(u64, 0xccccccdd), u.baz); - } - } - } -} diff --git a/test/behavior/union.zig b/test/behavior/union.zig index 48d4ac9a68..321d42e216 100644 --- a/test/behavior/union.zig +++ b/test/behavior/union.zig @@ -1,5 +1,6 @@ const builtin = @import("builtin"); const std = @import("std"); +const endian = builtin.cpu.arch.endian(); const expect = std.testing.expect; const assert = std.debug.assert; const expectEqual = std.testing.expectEqual; @@ -1660,15 +1661,219 @@ test "union with 128 bit integer" { } } -test "memset extern union at comptime" { +test "memset extern union" { const U = extern union { foo: u8, + bar: u32, + }; + + const S = struct { + fn doTheTest() !void { + var u: U = undefined; + @memset(std.mem.asBytes(&u), 0); + try expectEqual(@as(u8, 0), u.foo); + try expectEqual(@as(u32, 0), u.bar); + } + }; + + try comptime S.doTheTest(); + try S.doTheTest(); +} + +test "memset packed union" { + const U = packed union { + a: u32, + b: u8, + }; + + const S = struct { + fn doTheTest() !void { + var u: U = undefined; + @memset(std.mem.asBytes(&u), 42); + try expectEqual(@as(u32, 0x2a2a2a2a), u.a); + try expectEqual(@as(u8, 0x2a), u.b); + } + }; + + try comptime S.doTheTest(); + + if (builtin.cpu.arch.isWasm()) return error.SkipZigTest; // TODO + try S.doTheTest(); +} + +fn littleToNativeEndian(comptime T: type, v: T) T { + return if (endian == .Little) v else @byteSwap(v); +} + +test "reinterpret extern union" { + const U = extern union { + foo: u8, + baz: u32 align(8), + bar: u32, + }; + + const S = struct { + fn doTheTest() !void { + { + // Undefined initialization + const u = blk: { + var u: U = undefined; + @memset(std.mem.asBytes(&u), 0); + u.bar = 0xbbbbbbbb; + u.foo = 0x2a; + break :blk u; + }; + + try expectEqual(@as(u8, 0x2a), u.foo); + try expectEqual(littleToNativeEndian(u32, 0xbbbbbb2a), u.bar); + try expectEqual(littleToNativeEndian(u32, 0xbbbbbb2a), u.baz); + } + + { + // Union initialization + var u: U = .{ + .foo = 0x2a, + }; + + { + const expected, const mask = switch (endian) { + .Little => .{ 0x2a, 0xff }, + .Big => .{ 0x2a000000, 0xff000000 }, + }; + + try expectEqual(@as(u8, 0x2a), u.foo); + try expectEqual(@as(u32, expected), u.bar & mask); + try expectEqual(@as(u32, expected), u.baz & mask); + } + + // Writing to a larger field + u.baz = 0xbbbbbbbb; + try expectEqual(@as(u8, 0xbb), u.foo); + try expectEqual(@as(u32, 0xbbbbbbbb), u.bar); + try expectEqual(@as(u32, 0xbbbbbbbb), u.baz); + + // Writing to the same field + u.baz = 0xcccccccc; + try expectEqual(@as(u8, 0xcc), u.foo); + try expectEqual(@as(u32, 0xcccccccc), u.bar); + try expectEqual(@as(u32, 0xcccccccc), u.baz); + + // Writing to a smaller field + u.foo = 0xdd; + try expectEqual(@as(u8, 0xdd), u.foo); + try expectEqual(littleToNativeEndian(u32, 0xccccccdd), u.bar); + try expectEqual(littleToNativeEndian(u32, 0xccccccdd), u.baz); + } + } + }; + + try comptime S.doTheTest(); + + if (builtin.zig_backend == .stage2_llvm) return error.SkipZigTest; // TODO + try S.doTheTest(); +} + +test "reinterpret packed union" { + const U = packed union { + foo: u8, + bar: u29, + baz: u64, + qux: u12, + }; + + const S = struct { + fn doTheTest() !void { + { + const u = blk: { + var u: U = undefined; + @memset(std.mem.asBytes(&u), 0); + u.baz = 0xbbbbbbbb; + u.qux = 0xe2a; + break :blk u; + }; + + try expectEqual(@as(u8, 0x2a), u.foo); + try expectEqual(@as(u12, 0xe2a), u.qux); + + // https://github.com/ziglang/zig/issues/17360 + if (@inComptime()) { + try expectEqual(@as(u29, 0x1bbbbe2a), u.bar); + try expectEqual(@as(u64, 0xbbbbbe2a), u.baz); + } + } + + { + // Union initialization + var u: U = .{ + .qux = 0xe2a, + }; + try expectEqual(@as(u8, 0x2a), u.foo); + try expectEqual(@as(u12, 0xe2a), u.qux); + try expectEqual(@as(u29, 0xe2a), u.bar & 0xfff); + try expectEqual(@as(u64, 0xe2a), u.baz & 0xfff); + + // Writing to a larger field + u.baz = 0xbbbbbbbb; + try expectEqual(@as(u8, 0xbb), u.foo); + try expectEqual(@as(u12, 0xbbb), u.qux); + try expectEqual(@as(u29, 0x1bbbbbbb), u.bar); + try expectEqual(@as(u64, 0xbbbbbbbb), u.baz); + + // Writing to the same field + u.baz = 0xcccccccc; + try expectEqual(@as(u8, 0xcc), u.foo); + try expectEqual(@as(u12, 0xccc), u.qux); + try expectEqual(@as(u29, 0x0ccccccc), u.bar); + try expectEqual(@as(u64, 0xcccccccc), u.baz); + + // Writing to a smaller field + u.foo = 0xdd; + try expectEqual(@as(u8, 0xdd), u.foo); + try expectEqual(@as(u12, 0xcdd), u.qux); + try expectEqual(@as(u29, 0x0cccccdd), u.bar); + try expectEqual(@as(u64, 0xccccccdd), u.baz); + } + } }; - const u = comptime blk: { - var u: U = undefined; - @memset(std.mem.asBytes(&u), 0); - u.foo = 0; - break :blk u; + + try comptime S.doTheTest(); + + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + if (builtin.cpu.arch.isWasm()) return error.SkipZigTest; // TODO + try S.doTheTest(); +} + +test "reinterpret packed union inside packed struct" { + const U = packed union { + a: u7, + b: u1, + }; + + const V = packed struct { + lo: U, + hi: U, }; - try expect(u.foo == 0); + + const S = struct { + fn doTheTest() !void { + var v: V = undefined; + @memset(std.mem.asBytes(&v), 0x55); + try expectEqual(@as(u7, 0x55), v.lo.a); + try expectEqual(@as(u1, 1), v.lo.b); + try expectEqual(@as(u7, 0x2a), v.hi.a); + try expectEqual(@as(u1, 0), v.hi.b); + + v.lo.b = 0; + try expectEqual(@as(u7, 0x54), v.lo.a); + try expectEqual(@as(u1, 0), v.lo.b); + v.hi.b = 1; + try expectEqual(@as(u7, 0x2b), v.hi.a); + try expectEqual(@as(u1, 1), v.hi.b); + } + }; + + try comptime S.doTheTest(); + + if (builtin.zig_backend == .stage2_c) return error.SkipZigTest; // TODO + try S.doTheTest(); } |
