mirror of
https://github.com/ziglang/zig.git
synced 2026-01-20 22:35:24 +00:00
sema: handle big-endian when bitcasting between different-sized union fields
Updated the tests to also run at runtime, and moved them to union.zig
This commit is contained in:
parent
d657b6c0e2
commit
fb33bc99e1
46
src/Sema.zig
46
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.
|
||||
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,
|
||||
};
|
||||
|
||||
// Reading a larger value means we need to reinterpret from undefined bytes
|
||||
if (field_size > old_size) @memset(buffer[old_size..], 0xaa);
|
||||
|
||||
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)}),
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 u = comptime blk: {
|
||||
var u: U = undefined;
|
||||
@memset(std.mem.asBytes(&u), 0);
|
||||
u.foo = 0;
|
||||
break :blk u;
|
||||
|
||||
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 expect(u.foo == 0);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user