Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Module.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -6607,6 +6607,7 @@ pub fn unionFieldNormalAlignment(mod: *Module, u: InternPool.UnionType, field_in
return field_ty.abiAlignment(mod);
}

/// Returns the index of the active field, given the current tag value
pub fn unionTagFieldIndex(mod: *Module, u: InternPool.UnionType, enum_tag: Value) ?u32 {
const ip = &mod.intern_pool;
if (enum_tag.toIntern() == .none) return null;
Expand Down
132 changes: 110 additions & 22 deletions src/Sema.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 {
Expand All@@ -27260,7 +27260,7 @@ fn unionFieldVal(
else
union_ty.unionFieldType(un.tag.toValue(), mod).?;

if (try sema.bitCastVal(block, src, un.val.toValue(), old_ty, field_ty, 0)) |new_val| {
if (try sema.bitCastUnionFieldVal(block, src, un.val.toValue(), old_ty, field_ty, layout)) |new_val| {
return Air.internedToRef(new_val.toIntern());
}
}
Expand DownExpand Up@@ -29781,13 +29781,19 @@ fn storePtrVal(
error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{mut_kit.ty.fmt(mod)}),
};
operand_val.writeToMemory(operand_ty, mod, buffer[reinterpret.byte_offset..]) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ReinterpretDeclRef => unreachable,
error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{operand_ty.fmt(mod)}),
};

if (reinterpret.write_packed) {
operand_val.writeToPackedMemory(operand_ty, mod, buffer[reinterpret.byte_offset..], 0) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ReinterpretDeclRef => unreachable,
};
} else {
operand_val.writeToMemory(operand_ty, mod, buffer[reinterpret.byte_offset..]) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ReinterpretDeclRef => unreachable,
error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{operand_ty.fmt(mod)}),
};
}
const val = Value.readFromMemory(mut_kit.ty, mod, buffer, sema.arena) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.IllDefinedMemoryLayout => unreachable,
Expand DownExpand Up@@ -29819,6 +29825,8 @@ const ComptimePtrMutationKit = struct {
reinterpret: struct {
val_ptr: *Value,
byte_offset: usize,
/// If set, write the operand to packed memory
write_packed: bool = false,
},
/// If the root decl could not be used as parent, this means `ty` is the type that
/// caused that by not having a well-defined layout.
Expand DownExpand Up@@ -30182,21 +30190,43 @@ fn beginComptimePtrMutation(
);
},
.@"union" => {
// We need to set the active field of the union.
const union_tag_ty = base_child_ty.unionTagTypeHypothetical(mod);

const payload = &val_ptr.castTag(.@"union").?.data;
payload.tag = try mod.enumValueFieldIndex(union_tag_ty, field_index);
const layout = base_child_ty.containerLayout(mod);

return beginComptimePtrMutationInner(
sema,
block,
src,
parent.ty.structFieldType(field_index, mod),
&payload.val,
ptr_elem_ty,
parent.mut_decl,
);
const tag_type = base_child_ty.unionTagTypeHypothetical(mod);
const hypothetical_tag = try mod.enumValueFieldIndex(tag_type, field_index);
if (layout == .Auto or (payload.tag != null and hypothetical_tag.eql(payload.tag.?, tag_type, mod))) {
// We need to set the active field of the union.
payload.tag = hypothetical_tag;

const field_ty = parent.ty.structFieldType(field_index, mod);
return beginComptimePtrMutationInner(
sema,
block,
src,
field_ty,
&payload.val,
ptr_elem_ty,
parent.mut_decl,
);
} else {
// Writing to a different field (a different or unknown tag is active) requires reinterpreting
// memory of the entire union, which requires knowing its abiSize.
try sema.resolveTypeLayout(parent.ty);

// This union value no longer has a well-defined tag type.
// The reinterpretation will read it back out as .none.
payload.val = try payload.val.unintern(sema.arena, mod);
return ComptimePtrMutationKit{
.mut_decl = parent.mut_decl,
.pointee = .{ .reinterpret = .{
.val_ptr = val_ptr,
.byte_offset = 0,
.write_packed = layout == .Packed,
} },
.ty = parent.ty,
};
}
},
.slice => switch (field_index) {
Value.slice_ptr_index => return beginComptimePtrMutationInner(
Expand DownExpand Up@@ -30697,6 +30727,7 @@ fn bitCastVal(
// For types with well-defined memory layouts, we serialize them a byte buffer,
// then deserialize to the new type.
const abi_size = try sema.usizeCast(block, src, old_ty.abiSize(mod));

const buffer = try sema.gpa.alloc(u8, abi_size);
defer sema.gpa.free(buffer);
val.writeToMemory(old_ty, mod, buffer) catch |err| switch (err) {
Expand All@@ -30713,6 +30744,63 @@ fn bitCastVal(
};
}

fn bitCastUnionFieldVal(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
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);

// 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[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)}),
};
}

fn coerceArrayPtrToSlice(
sema: *Sema,
block: *Block,
Expand Down
24 changes: 17 additions & 7 deletions src/TypedValue.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -84,22 +84,27 @@ pub fn print(
if (level == 0) {
return writer.writeAll(".{ ... }");
}
const union_val = val.castTag(.@"union").?.data;
const payload = val.castTag(.@"union").?.data;
try writer.writeAll(".{ ");

if (union_val.tag.toIntern() != .none) {
if (payload.tag) |tag| {
try print(.{
.ty = ip.indexToKey(ty.toIntern()).union_type.enum_tag_ty.toType(),
.val = union_val.tag,
.val = tag,
}, writer, level - 1, mod);
try writer.writeAll(" = ");
const field_ty = ty.unionFieldType(union_val.tag, mod).?;
const field_ty = ty.unionFieldType(tag, mod).?;
try print(.{
.ty = field_ty,
.val = union_val.val,
.val = payload.val,
}, writer, level - 1, mod);
} else {
return writer.writeAll("(unknown tag)");
try writer.writeAll("(unknown tag) = ");
const backing_ty = try ty.unionBackingType(mod);
try print(.{
.ty = backing_ty,
.val = payload.val,
}, writer, level - 1, mod);
}

return writer.writeAll(" }");
Expand DownExpand Up@@ -421,7 +426,12 @@ pub fn print(
.val = un.val.toValue(),
}, writer, level - 1, mod);
} else {
try writer.writeAll("(unknown tag)");
try writer.writeAll("(unknown tag) = ");
const backing_ty = try ty.unionBackingType(mod);
try print(.{
.ty = backing_ty,
.val = un.val.toValue(),
}, writer, level - 1, mod);
}
} else try writer.writeAll("...");
return writer.writeAll(" }");
Expand Down
10 changes: 10 additions & 0 deletions src/type.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -1954,6 +1954,16 @@ pub const Type = struct {
return true;
}

/// Returns the type used for backing storage of this union during comptime operations.
/// Asserts the type is either an extern or packed union.
pub fn unionBackingType(ty: Type, mod: *Module) !Type {
return switch (ty.containerLayout(mod)) {
.Extern => try mod.arrayType(.{ .len = ty.abiSize(mod), .child = .u8_type }),
.Packed => try mod.intType(.unsigned, @intCast(ty.bitSize(mod))),
.Auto => unreachable,
};
}

pub fn unionGetLayout(ty: Type, mod: *Module) Module.UnionLayout {
const ip = &mod.intern_pool;
const union_type = ip.indexToKey(ty.toIntern()).union_type;
Expand Down
47 changes: 25 additions & 22 deletions src/value.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -327,11 +327,19 @@ pub const Value = struct {
},
.@"union" => {
const pl = val.castTag(.@"union").?.data;
return mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = try pl.tag.intern(ty.unionTagTypeHypothetical(mod), mod),
.val = try pl.val.intern(ty.unionFieldType(pl.tag, mod).?, mod),
} });
if (pl.tag) |pl_tag| {
return mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = try pl_tag.intern(ty.unionTagTypeHypothetical(mod), mod),
.val = try pl.val.intern(ty.unionFieldType(pl_tag, mod).?, mod),
} });
} else {
return mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = .none,
.val = try pl.val.intern(try ty.unionBackingType(mod), mod),
} });
}
},
}
}
Expand DownExpand Up@@ -399,10 +407,7 @@ pub const Value = struct {

.un => |un| Tag.@"union".create(arena, .{
// toValue asserts that the value cannot be .none which is valid on unions.
.tag = .{
.ip_index = un.tag,
.legacy = undefined,
},
.tag = if (un.tag == .none) null else un.tag.toValue(),
.val = un.val.toValue(),
}),

Expand DownExpand Up@@ -709,21 +714,22 @@ pub const Value = struct {
.Union => switch (ty.containerLayout(mod)) {
.Auto => return error.IllDefinedMemoryLayout, // Sema is supposed to have emitted a compile error already
.Extern => {
const union_obj = mod.typeToUnion(ty).?;
if (val.unionTag(mod)) |union_tag| {
const union_obj = mod.typeToUnion(ty).?;
const field_index = mod.unionTagFieldIndex(union_obj, union_tag).?;
const field_type = union_obj.field_types.get(&mod.intern_pool)[field_index].toType();
const field_val = try val.fieldValue(mod, field_index);
const byte_count = @as(usize, @intCast(field_type.abiSize(mod)));
return writeToMemory(field_val, field_type, mod, buffer[0..byte_count]);
} else {
const union_size = ty.abiSize(mod);
const array_type = try mod.arrayType(.{ .len = union_size, .child = .u8_type });
return writeToMemory(val.unionValue(mod), array_type, mod, buffer[0..@as(usize, @intCast(union_size))]);
const backing_ty = try ty.unionBackingType(mod);
const byte_count: usize = @intCast(backing_ty.abiSize(mod));
return writeToMemory(val.unionValue(mod), backing_ty, mod, buffer[0..byte_count]);
}
},
.Packed => {
const byte_count = (@as(usize, @intCast(ty.bitSize(mod))) + 7) / 8;
const backing_ty = try ty.unionBackingType(mod);
const byte_count: usize = @intCast(backing_ty.abiSize(mod));
return writeToPackedMemory(val, ty, mod, buffer[0..byte_count], 0);
},
},
Expand DownExpand Up@@ -842,9 +848,8 @@ pub const Value = struct {
const field_val = try val.fieldValue(mod, field_index);
return field_val.writeToPackedMemory(field_type, mod, buffer, bit_offset);
} else {
const union_bits: u16 = @intCast(ty.bitSize(mod));
const int_ty = try mod.intType(.unsigned, union_bits);
return val.unionValue(mod).writeToPackedMemory(int_ty, mod, buffer, bit_offset);
const backing_ty = try ty.unionBackingType(mod);
return val.unionValue(mod).writeToPackedMemory(backing_ty, mod, buffer, bit_offset);
}
},
}
Expand DownExpand Up@@ -1146,10 +1151,8 @@ pub const Value = struct {
.Union => switch (ty.containerLayout(mod)) {
.Auto, .Extern => unreachable, // Handled by non-packed readFromMemory
.Packed => {
const union_bits: u16 = @intCast(ty.bitSize(mod));
assert(union_bits != 0);
const int_ty = try mod.intType(.unsigned, union_bits);
const val = (try readFromPackedMemory(int_ty, mod, buffer, bit_offset, arena)).toIntern();
const backing_ty = try ty.unionBackingType(mod);
const val = (try readFromPackedMemory(backing_ty, mod, buffer, bit_offset, arena)).toIntern();
return (try mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = .none,
Expand DownExpand Up@@ -4017,7 +4020,7 @@ pub const Value = struct {
data: Data,

pub const Data = struct {
tag: Value,
tag: ?Value,
val: Value,
};
};
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
sema: Support reinterpreting extern/packed unions at comptime via field access by kcbanner · Pull Request #17352 · ziglang/zig · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Module.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -6607,6 +6607,7 @@ pub fn unionFieldNormalAlignment(mod: *Module, u: InternPool.UnionType, field_in
return field_ty.abiAlignment(mod);
}

/// Returns the index of the active field, given the current tag value
pub fn unionTagFieldIndex(mod: *Module, u: InternPool.UnionType, enum_tag: Value) ?u32 {
const ip = &mod.intern_pool;
if (enum_tag.toIntern() == .none) return null;
Expand Down
132 changes: 110 additions & 22 deletions src/Sema.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 {
Expand All@@ -27260,7 +27260,7 @@ fn unionFieldVal(
else
union_ty.unionFieldType(un.tag.toValue(), mod).?;

if (try sema.bitCastVal(block, src, un.val.toValue(), old_ty, field_ty, 0)) |new_val| {
if (try sema.bitCastUnionFieldVal(block, src, un.val.toValue(), old_ty, field_ty, layout)) |new_val| {
return Air.internedToRef(new_val.toIntern());
}
}
Expand DownExpand Up@@ -29781,13 +29781,19 @@ fn storePtrVal(
error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{mut_kit.ty.fmt(mod)}),
};
operand_val.writeToMemory(operand_ty, mod, buffer[reinterpret.byte_offset..]) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ReinterpretDeclRef => unreachable,
error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{operand_ty.fmt(mod)}),
};

if (reinterpret.write_packed) {
operand_val.writeToPackedMemory(operand_ty, mod, buffer[reinterpret.byte_offset..], 0) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ReinterpretDeclRef => unreachable,
};
} else {
operand_val.writeToMemory(operand_ty, mod, buffer[reinterpret.byte_offset..]) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ReinterpretDeclRef => unreachable,
error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{operand_ty.fmt(mod)}),
};
}
const val = Value.readFromMemory(mut_kit.ty, mod, buffer, sema.arena) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.IllDefinedMemoryLayout => unreachable,
Expand DownExpand Up@@ -29819,6 +29825,8 @@ const ComptimePtrMutationKit = struct {
reinterpret: struct {
val_ptr: *Value,
byte_offset: usize,
/// If set, write the operand to packed memory
write_packed: bool = false,
},
/// If the root decl could not be used as parent, this means `ty` is the type that
/// caused that by not having a well-defined layout.
Expand DownExpand Up@@ -30182,21 +30190,43 @@ fn beginComptimePtrMutation(
);
},
.@"union" => {
// We need to set the active field of the union.
const union_tag_ty = base_child_ty.unionTagTypeHypothetical(mod);

const payload = &val_ptr.castTag(.@"union").?.data;
payload.tag = try mod.enumValueFieldIndex(union_tag_ty, field_index);
const layout = base_child_ty.containerLayout(mod);

return beginComptimePtrMutationInner(
sema,
block,
src,
parent.ty.structFieldType(field_index, mod),
&payload.val,
ptr_elem_ty,
parent.mut_decl,
);
const tag_type = base_child_ty.unionTagTypeHypothetical(mod);
const hypothetical_tag = try mod.enumValueFieldIndex(tag_type, field_index);
if (layout == .Auto or (payload.tag != null and hypothetical_tag.eql(payload.tag.?, tag_type, mod))) {
// We need to set the active field of the union.
payload.tag = hypothetical_tag;

const field_ty = parent.ty.structFieldType(field_index, mod);
return beginComptimePtrMutationInner(
sema,
block,
src,
field_ty,
&payload.val,
ptr_elem_ty,
parent.mut_decl,
);
} else {
// Writing to a different field (a different or unknown tag is active) requires reinterpreting
// memory of the entire union, which requires knowing its abiSize.
try sema.resolveTypeLayout(parent.ty);

// This union value no longer has a well-defined tag type.
// The reinterpretation will read it back out as .none.
payload.val = try payload.val.unintern(sema.arena, mod);
return ComptimePtrMutationKit{
.mut_decl = parent.mut_decl,
.pointee = .{ .reinterpret = .{
.val_ptr = val_ptr,
.byte_offset = 0,
.write_packed = layout == .Packed,
} },
.ty = parent.ty,
};
}
},
.slice => switch (field_index) {
Value.slice_ptr_index => return beginComptimePtrMutationInner(
Expand DownExpand Up@@ -30697,6 +30727,7 @@ fn bitCastVal(
// For types with well-defined memory layouts, we serialize them a byte buffer,
// then deserialize to the new type.
const abi_size = try sema.usizeCast(block, src, old_ty.abiSize(mod));

const buffer = try sema.gpa.alloc(u8, abi_size);
defer sema.gpa.free(buffer);
val.writeToMemory(old_ty, mod, buffer) catch |err| switch (err) {
Expand All@@ -30713,6 +30744,63 @@ fn bitCastVal(
};
}

fn bitCastUnionFieldVal(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
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);

// 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[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)}),
};
}

fn coerceArrayPtrToSlice(
sema: *Sema,
block: *Block,
Expand Down
24 changes: 17 additions & 7 deletions src/TypedValue.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -84,22 +84,27 @@ pub fn print(
if (level == 0) {
return writer.writeAll(".{ ... }");
}
const union_val = val.castTag(.@"union").?.data;
const payload = val.castTag(.@"union").?.data;
try writer.writeAll(".{ ");

if (union_val.tag.toIntern() != .none) {
if (payload.tag) |tag| {
try print(.{
.ty = ip.indexToKey(ty.toIntern()).union_type.enum_tag_ty.toType(),
.val = union_val.tag,
.val = tag,
}, writer, level - 1, mod);
try writer.writeAll(" = ");
const field_ty = ty.unionFieldType(union_val.tag, mod).?;
const field_ty = ty.unionFieldType(tag, mod).?;
try print(.{
.ty = field_ty,
.val = union_val.val,
.val = payload.val,
}, writer, level - 1, mod);
} else {
return writer.writeAll("(unknown tag)");
try writer.writeAll("(unknown tag) = ");
const backing_ty = try ty.unionBackingType(mod);
try print(.{
.ty = backing_ty,
.val = payload.val,
}, writer, level - 1, mod);
}

return writer.writeAll(" }");
Expand DownExpand Up@@ -421,7 +426,12 @@ pub fn print(
.val = un.val.toValue(),
}, writer, level - 1, mod);
} else {
try writer.writeAll("(unknown tag)");
try writer.writeAll("(unknown tag) = ");
const backing_ty = try ty.unionBackingType(mod);
try print(.{
.ty = backing_ty,
.val = un.val.toValue(),
}, writer, level - 1, mod);
}
} else try writer.writeAll("...");
return writer.writeAll(" }");
Expand Down
10 changes: 10 additions & 0 deletions src/type.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -1954,6 +1954,16 @@ pub const Type = struct {
return true;
}

/// Returns the type used for backing storage of this union during comptime operations.
/// Asserts the type is either an extern or packed union.
pub fn unionBackingType(ty: Type, mod: *Module) !Type {
return switch (ty.containerLayout(mod)) {
.Extern => try mod.arrayType(.{ .len = ty.abiSize(mod), .child = .u8_type }),
.Packed => try mod.intType(.unsigned, @intCast(ty.bitSize(mod))),
.Auto => unreachable,
};
}

pub fn unionGetLayout(ty: Type, mod: *Module) Module.UnionLayout {
const ip = &mod.intern_pool;
const union_type = ip.indexToKey(ty.toIntern()).union_type;
Expand Down
47 changes: 25 additions & 22 deletions src/value.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -327,11 +327,19 @@ pub const Value = struct {
},
.@"union" => {
const pl = val.castTag(.@"union").?.data;
return mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = try pl.tag.intern(ty.unionTagTypeHypothetical(mod), mod),
.val = try pl.val.intern(ty.unionFieldType(pl.tag, mod).?, mod),
} });
if (pl.tag) |pl_tag| {
return mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = try pl_tag.intern(ty.unionTagTypeHypothetical(mod), mod),
.val = try pl.val.intern(ty.unionFieldType(pl_tag, mod).?, mod),
} });
} else {
return mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = .none,
.val = try pl.val.intern(try ty.unionBackingType(mod), mod),
} });
}
},
}
}
Expand DownExpand Up@@ -399,10 +407,7 @@ pub const Value = struct {

.un => |un| Tag.@"union".create(arena, .{
// toValue asserts that the value cannot be .none which is valid on unions.
.tag = .{
.ip_index = un.tag,
.legacy = undefined,
},
.tag = if (un.tag == .none) null else un.tag.toValue(),
.val = un.val.toValue(),
}),

Expand DownExpand Up@@ -709,21 +714,22 @@ pub const Value = struct {
.Union => switch (ty.containerLayout(mod)) {
.Auto => return error.IllDefinedMemoryLayout, // Sema is supposed to have emitted a compile error already
.Extern => {
const union_obj = mod.typeToUnion(ty).?;
if (val.unionTag(mod)) |union_tag| {
const union_obj = mod.typeToUnion(ty).?;
const field_index = mod.unionTagFieldIndex(union_obj, union_tag).?;
const field_type = union_obj.field_types.get(&mod.intern_pool)[field_index].toType();
const field_val = try val.fieldValue(mod, field_index);
const byte_count = @as(usize, @intCast(field_type.abiSize(mod)));
return writeToMemory(field_val, field_type, mod, buffer[0..byte_count]);
} else {
const union_size = ty.abiSize(mod);
const array_type = try mod.arrayType(.{ .len = union_size, .child = .u8_type });
return writeToMemory(val.unionValue(mod), array_type, mod, buffer[0..@as(usize, @intCast(union_size))]);
const backing_ty = try ty.unionBackingType(mod);
const byte_count: usize = @intCast(backing_ty.abiSize(mod));
return writeToMemory(val.unionValue(mod), backing_ty, mod, buffer[0..byte_count]);
}
},
.Packed => {
const byte_count = (@as(usize, @intCast(ty.bitSize(mod))) + 7) / 8;
const backing_ty = try ty.unionBackingType(mod);
const byte_count: usize = @intCast(backing_ty.abiSize(mod));
return writeToPackedMemory(val, ty, mod, buffer[0..byte_count], 0);
},
},
Expand DownExpand Up@@ -842,9 +848,8 @@ pub const Value = struct {
const field_val = try val.fieldValue(mod, field_index);
return field_val.writeToPackedMemory(field_type, mod, buffer, bit_offset);
} else {
const union_bits: u16 = @intCast(ty.bitSize(mod));
const int_ty = try mod.intType(.unsigned, union_bits);
return val.unionValue(mod).writeToPackedMemory(int_ty, mod, buffer, bit_offset);
const backing_ty = try ty.unionBackingType(mod);
return val.unionValue(mod).writeToPackedMemory(backing_ty, mod, buffer, bit_offset);
}
},
}
Expand DownExpand Up@@ -1146,10 +1151,8 @@ pub const Value = struct {
.Union => switch (ty.containerLayout(mod)) {
.Auto, .Extern => unreachable, // Handled by non-packed readFromMemory
.Packed => {
const union_bits: u16 = @intCast(ty.bitSize(mod));
assert(union_bits != 0);
const int_ty = try mod.intType(.unsigned, union_bits);
const val = (try readFromPackedMemory(int_ty, mod, buffer, bit_offset, arena)).toIntern();
const backing_ty = try ty.unionBackingType(mod);
const val = (try readFromPackedMemory(backing_ty, mod, buffer, bit_offset, arena)).toIntern();
return (try mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = .none,
Expand DownExpand Up@@ -4017,7 +4020,7 @@ pub const Value = struct {
data: Data,

pub const Data = struct {
tag: Value,
tag: ?Value,
val: Value,
};
};
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' sema: Support reinterpreting extern/packed unions at comptime via field access by kcbanner · Pull Request #17352 · ziglang/zig · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Module.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -6607,6 +6607,7 @@ pub fn unionFieldNormalAlignment(mod: *Module, u: InternPool.UnionType, field_in
return field_ty.abiAlignment(mod);
}

/// Returns the index of the active field, given the current tag value
pub fn unionTagFieldIndex(mod: *Module, u: InternPool.UnionType, enum_tag: Value) ?u32 {
const ip = &mod.intern_pool;
if (enum_tag.toIntern() == .none) return null;
Expand Down
132 changes: 110 additions & 22 deletions src/Sema.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 {
Expand All@@ -27260,7 +27260,7 @@ fn unionFieldVal(
else
union_ty.unionFieldType(un.tag.toValue(), mod).?;

if (try sema.bitCastVal(block, src, un.val.toValue(), old_ty, field_ty, 0)) |new_val| {
if (try sema.bitCastUnionFieldVal(block, src, un.val.toValue(), old_ty, field_ty, layout)) |new_val| {
return Air.internedToRef(new_val.toIntern());
}
}
Expand DownExpand Up@@ -29781,13 +29781,19 @@ fn storePtrVal(
error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{mut_kit.ty.fmt(mod)}),
};
operand_val.writeToMemory(operand_ty, mod, buffer[reinterpret.byte_offset..]) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ReinterpretDeclRef => unreachable,
error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{operand_ty.fmt(mod)}),
};

if (reinterpret.write_packed) {
operand_val.writeToPackedMemory(operand_ty, mod, buffer[reinterpret.byte_offset..], 0) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ReinterpretDeclRef => unreachable,
};
} else {
operand_val.writeToMemory(operand_ty, mod, buffer[reinterpret.byte_offset..]) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ReinterpretDeclRef => unreachable,
error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{operand_ty.fmt(mod)}),
};
}
const val = Value.readFromMemory(mut_kit.ty, mod, buffer, sema.arena) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.IllDefinedMemoryLayout => unreachable,
Expand DownExpand Up@@ -29819,6 +29825,8 @@ const ComptimePtrMutationKit = struct {
reinterpret: struct {
val_ptr: *Value,
byte_offset: usize,
/// If set, write the operand to packed memory
write_packed: bool = false,
},
/// If the root decl could not be used as parent, this means `ty` is the type that
/// caused that by not having a well-defined layout.
Expand DownExpand Up@@ -30182,21 +30190,43 @@ fn beginComptimePtrMutation(
);
},
.@"union" => {
// We need to set the active field of the union.
const union_tag_ty = base_child_ty.unionTagTypeHypothetical(mod);

const payload = &val_ptr.castTag(.@"union").?.data;
payload.tag = try mod.enumValueFieldIndex(union_tag_ty, field_index);
const layout = base_child_ty.containerLayout(mod);

return beginComptimePtrMutationInner(
sema,
block,
src,
parent.ty.structFieldType(field_index, mod),
&payload.val,
ptr_elem_ty,
parent.mut_decl,
);
const tag_type = base_child_ty.unionTagTypeHypothetical(mod);
const hypothetical_tag = try mod.enumValueFieldIndex(tag_type, field_index);
if (layout == .Auto or (payload.tag != null and hypothetical_tag.eql(payload.tag.?, tag_type, mod))) {
// We need to set the active field of the union.
payload.tag = hypothetical_tag;

const field_ty = parent.ty.structFieldType(field_index, mod);
return beginComptimePtrMutationInner(
sema,
block,
src,
field_ty,
&payload.val,
ptr_elem_ty,
parent.mut_decl,
);
} else {
// Writing to a different field (a different or unknown tag is active) requires reinterpreting
// memory of the entire union, which requires knowing its abiSize.
try sema.resolveTypeLayout(parent.ty);

// This union value no longer has a well-defined tag type.
// The reinterpretation will read it back out as .none.
payload.val = try payload.val.unintern(sema.arena, mod);
return ComptimePtrMutationKit{
.mut_decl = parent.mut_decl,
.pointee = .{ .reinterpret = .{
.val_ptr = val_ptr,
.byte_offset = 0,
.write_packed = layout == .Packed,
} },
.ty = parent.ty,
};
}
},
.slice => switch (field_index) {
Value.slice_ptr_index => return beginComptimePtrMutationInner(
Expand DownExpand Up@@ -30697,6 +30727,7 @@ fn bitCastVal(
// For types with well-defined memory layouts, we serialize them a byte buffer,
// then deserialize to the new type.
const abi_size = try sema.usizeCast(block, src, old_ty.abiSize(mod));

const buffer = try sema.gpa.alloc(u8, abi_size);
defer sema.gpa.free(buffer);
val.writeToMemory(old_ty, mod, buffer) catch |err| switch (err) {
Expand All@@ -30713,6 +30744,63 @@ fn bitCastVal(
};
}

fn bitCastUnionFieldVal(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
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);

// 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[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)}),
};
}

fn coerceArrayPtrToSlice(
sema: *Sema,
block: *Block,
Expand Down
24 changes: 17 additions & 7 deletions src/TypedValue.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -84,22 +84,27 @@ pub fn print(
if (level == 0) {
return writer.writeAll(".{ ... }");
}
const union_val = val.castTag(.@"union").?.data;
const payload = val.castTag(.@"union").?.data;
try writer.writeAll(".{ ");

if (union_val.tag.toIntern() != .none) {
if (payload.tag) |tag| {
try print(.{
.ty = ip.indexToKey(ty.toIntern()).union_type.enum_tag_ty.toType(),
.val = union_val.tag,
.val = tag,
}, writer, level - 1, mod);
try writer.writeAll(" = ");
const field_ty = ty.unionFieldType(union_val.tag, mod).?;
const field_ty = ty.unionFieldType(tag, mod).?;
try print(.{
.ty = field_ty,
.val = union_val.val,
.val = payload.val,
}, writer, level - 1, mod);
} else {
return writer.writeAll("(unknown tag)");
try writer.writeAll("(unknown tag) = ");
const backing_ty = try ty.unionBackingType(mod);
try print(.{
.ty = backing_ty,
.val = payload.val,
}, writer, level - 1, mod);
}

return writer.writeAll(" }");
Expand DownExpand Up@@ -421,7 +426,12 @@ pub fn print(
.val = un.val.toValue(),
}, writer, level - 1, mod);
} else {
try writer.writeAll("(unknown tag)");
try writer.writeAll("(unknown tag) = ");
const backing_ty = try ty.unionBackingType(mod);
try print(.{
.ty = backing_ty,
.val = un.val.toValue(),
}, writer, level - 1, mod);
}
} else try writer.writeAll("...");
return writer.writeAll(" }");
Expand Down
10 changes: 10 additions & 0 deletions src/type.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -1954,6 +1954,16 @@ pub const Type = struct {
return true;
}

/// Returns the type used for backing storage of this union during comptime operations.
/// Asserts the type is either an extern or packed union.
pub fn unionBackingType(ty: Type, mod: *Module) !Type {
return switch (ty.containerLayout(mod)) {
.Extern => try mod.arrayType(.{ .len = ty.abiSize(mod), .child = .u8_type }),
.Packed => try mod.intType(.unsigned, @intCast(ty.bitSize(mod))),
.Auto => unreachable,
};
}

pub fn unionGetLayout(ty: Type, mod: *Module) Module.UnionLayout {
const ip = &mod.intern_pool;
const union_type = ip.indexToKey(ty.toIntern()).union_type;
Expand Down
47 changes: 25 additions & 22 deletions src/value.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -327,11 +327,19 @@ pub const Value = struct {
},
.@"union" => {
const pl = val.castTag(.@"union").?.data;
return mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = try pl.tag.intern(ty.unionTagTypeHypothetical(mod), mod),
.val = try pl.val.intern(ty.unionFieldType(pl.tag, mod).?, mod),
} });
if (pl.tag) |pl_tag| {
return mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = try pl_tag.intern(ty.unionTagTypeHypothetical(mod), mod),
.val = try pl.val.intern(ty.unionFieldType(pl_tag, mod).?, mod),
} });
} else {
return mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = .none,
.val = try pl.val.intern(try ty.unionBackingType(mod), mod),
} });
}
},
}
}
Expand DownExpand Up@@ -399,10 +407,7 @@ pub const Value = struct {

.un => |un| Tag.@"union".create(arena, .{
// toValue asserts that the value cannot be .none which is valid on unions.
.tag = .{
.ip_index = un.tag,
.legacy = undefined,
},
.tag = if (un.tag == .none) null else un.tag.toValue(),
.val = un.val.toValue(),
}),

Expand DownExpand Up@@ -709,21 +714,22 @@ pub const Value = struct {
.Union => switch (ty.containerLayout(mod)) {
.Auto => return error.IllDefinedMemoryLayout, // Sema is supposed to have emitted a compile error already
.Extern => {
const union_obj = mod.typeToUnion(ty).?;
if (val.unionTag(mod)) |union_tag| {
const union_obj = mod.typeToUnion(ty).?;
const field_index = mod.unionTagFieldIndex(union_obj, union_tag).?;
const field_type = union_obj.field_types.get(&mod.intern_pool)[field_index].toType();
const field_val = try val.fieldValue(mod, field_index);
const byte_count = @as(usize, @intCast(field_type.abiSize(mod)));
return writeToMemory(field_val, field_type, mod, buffer[0..byte_count]);
} else {
const union_size = ty.abiSize(mod);
const array_type = try mod.arrayType(.{ .len = union_size, .child = .u8_type });
return writeToMemory(val.unionValue(mod), array_type, mod, buffer[0..@as(usize, @intCast(union_size))]);
const backing_ty = try ty.unionBackingType(mod);
const byte_count: usize = @intCast(backing_ty.abiSize(mod));
return writeToMemory(val.unionValue(mod), backing_ty, mod, buffer[0..byte_count]);
}
},
.Packed => {
const byte_count = (@as(usize, @intCast(ty.bitSize(mod))) + 7) / 8;
const backing_ty = try ty.unionBackingType(mod);
const byte_count: usize = @intCast(backing_ty.abiSize(mod));
return writeToPackedMemory(val, ty, mod, buffer[0..byte_count], 0);
},
},
Expand DownExpand Up@@ -842,9 +848,8 @@ pub const Value = struct {
const field_val = try val.fieldValue(mod, field_index);
return field_val.writeToPackedMemory(field_type, mod, buffer, bit_offset);
} else {
const union_bits: u16 = @intCast(ty.bitSize(mod));
const int_ty = try mod.intType(.unsigned, union_bits);
return val.unionValue(mod).writeToPackedMemory(int_ty, mod, buffer, bit_offset);
const backing_ty = try ty.unionBackingType(mod);
return val.unionValue(mod).writeToPackedMemory(backing_ty, mod, buffer, bit_offset);
}
},
}
Expand DownExpand Up@@ -1146,10 +1151,8 @@ pub const Value = struct {
.Union => switch (ty.containerLayout(mod)) {
.Auto, .Extern => unreachable, // Handled by non-packed readFromMemory
.Packed => {
const union_bits: u16 = @intCast(ty.bitSize(mod));
assert(union_bits != 0);
const int_ty = try mod.intType(.unsigned, union_bits);
const val = (try readFromPackedMemory(int_ty, mod, buffer, bit_offset, arena)).toIntern();
const backing_ty = try ty.unionBackingType(mod);
const val = (try readFromPackedMemory(backing_ty, mod, buffer, bit_offset, arena)).toIntern();
return (try mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = .none,
Expand DownExpand Up@@ -4017,7 +4020,7 @@ pub const Value = struct {
data: Data,

pub const Data = struct {
tag: Value,
tag: ?Value,
val: Value,
};
};
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' sema: Support reinterpreting extern/packed unions at comptime via field access by kcbanner · Pull Request #17352 · ziglang/zig · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Module.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -6607,6 +6607,7 @@ pub fn unionFieldNormalAlignment(mod: *Module, u: InternPool.UnionType, field_in
return field_ty.abiAlignment(mod);
}

/// Returns the index of the active field, given the current tag value
pub fn unionTagFieldIndex(mod: *Module, u: InternPool.UnionType, enum_tag: Value) ?u32 {
const ip = &mod.intern_pool;
if (enum_tag.toIntern() == .none) return null;
Expand Down
132 changes: 110 additions & 22 deletions src/Sema.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 {
Expand All@@ -27260,7 +27260,7 @@ fn unionFieldVal(
else
union_ty.unionFieldType(un.tag.toValue(), mod).?;

if (try sema.bitCastVal(block, src, un.val.toValue(), old_ty, field_ty, 0)) |new_val| {
if (try sema.bitCastUnionFieldVal(block, src, un.val.toValue(), old_ty, field_ty, layout)) |new_val| {
return Air.internedToRef(new_val.toIntern());
}
}
Expand DownExpand Up@@ -29781,13 +29781,19 @@ fn storePtrVal(
error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{mut_kit.ty.fmt(mod)}),
};
operand_val.writeToMemory(operand_ty, mod, buffer[reinterpret.byte_offset..]) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ReinterpretDeclRef => unreachable,
error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{operand_ty.fmt(mod)}),
};

if (reinterpret.write_packed) {
operand_val.writeToPackedMemory(operand_ty, mod, buffer[reinterpret.byte_offset..], 0) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ReinterpretDeclRef => unreachable,
};
} else {
operand_val.writeToMemory(operand_ty, mod, buffer[reinterpret.byte_offset..]) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ReinterpretDeclRef => unreachable,
error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{operand_ty.fmt(mod)}),
};
}
const val = Value.readFromMemory(mut_kit.ty, mod, buffer, sema.arena) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.IllDefinedMemoryLayout => unreachable,
Expand DownExpand Up@@ -29819,6 +29825,8 @@ const ComptimePtrMutationKit = struct {
reinterpret: struct {
val_ptr: *Value,
byte_offset: usize,
/// If set, write the operand to packed memory
write_packed: bool = false,
},
/// If the root decl could not be used as parent, this means `ty` is the type that
/// caused that by not having a well-defined layout.
Expand DownExpand Up@@ -30182,21 +30190,43 @@ fn beginComptimePtrMutation(
);
},
.@"union" => {
// We need to set the active field of the union.
const union_tag_ty = base_child_ty.unionTagTypeHypothetical(mod);

const payload = &val_ptr.castTag(.@"union").?.data;
payload.tag = try mod.enumValueFieldIndex(union_tag_ty, field_index);
const layout = base_child_ty.containerLayout(mod);

return beginComptimePtrMutationInner(
sema,
block,
src,
parent.ty.structFieldType(field_index, mod),
&payload.val,
ptr_elem_ty,
parent.mut_decl,
);
const tag_type = base_child_ty.unionTagTypeHypothetical(mod);
const hypothetical_tag = try mod.enumValueFieldIndex(tag_type, field_index);
if (layout == .Auto or (payload.tag != null and hypothetical_tag.eql(payload.tag.?, tag_type, mod))) {
// We need to set the active field of the union.
payload.tag = hypothetical_tag;

const field_ty = parent.ty.structFieldType(field_index, mod);
return beginComptimePtrMutationInner(
sema,
block,
src,
field_ty,
&payload.val,
ptr_elem_ty,
parent.mut_decl,
);
} else {
// Writing to a different field (a different or unknown tag is active) requires reinterpreting
// memory of the entire union, which requires knowing its abiSize.
try sema.resolveTypeLayout(parent.ty);

// This union value no longer has a well-defined tag type.
// The reinterpretation will read it back out as .none.
payload.val = try payload.val.unintern(sema.arena, mod);
return ComptimePtrMutationKit{
.mut_decl = parent.mut_decl,
.pointee = .{ .reinterpret = .{
.val_ptr = val_ptr,
.byte_offset = 0,
.write_packed = layout == .Packed,
} },
.ty = parent.ty,
};
}
},
.slice => switch (field_index) {
Value.slice_ptr_index => return beginComptimePtrMutationInner(
Expand DownExpand Up@@ -30697,6 +30727,7 @@ fn bitCastVal(
// For types with well-defined memory layouts, we serialize them a byte buffer,
// then deserialize to the new type.
const abi_size = try sema.usizeCast(block, src, old_ty.abiSize(mod));

const buffer = try sema.gpa.alloc(u8, abi_size);
defer sema.gpa.free(buffer);
val.writeToMemory(old_ty, mod, buffer) catch |err| switch (err) {
Expand All@@ -30713,6 +30744,63 @@ fn bitCastVal(
};
}

fn bitCastUnionFieldVal(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
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);

// 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[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)}),
};
}

fn coerceArrayPtrToSlice(
sema: *Sema,
block: *Block,
Expand Down
24 changes: 17 additions & 7 deletions src/TypedValue.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -84,22 +84,27 @@ pub fn print(
if (level == 0) {
return writer.writeAll(".{ ... }");
}
const union_val = val.castTag(.@"union").?.data;
const payload = val.castTag(.@"union").?.data;
try writer.writeAll(".{ ");

if (union_val.tag.toIntern() != .none) {
if (payload.tag) |tag| {
try print(.{
.ty = ip.indexToKey(ty.toIntern()).union_type.enum_tag_ty.toType(),
.val = union_val.tag,
.val = tag,
}, writer, level - 1, mod);
try writer.writeAll(" = ");
const field_ty = ty.unionFieldType(union_val.tag, mod).?;
const field_ty = ty.unionFieldType(tag, mod).?;
try print(.{
.ty = field_ty,
.val = union_val.val,
.val = payload.val,
}, writer, level - 1, mod);
} else {
return writer.writeAll("(unknown tag)");
try writer.writeAll("(unknown tag) = ");
const backing_ty = try ty.unionBackingType(mod);
try print(.{
.ty = backing_ty,
.val = payload.val,
}, writer, level - 1, mod);
}

return writer.writeAll(" }");
Expand DownExpand Up@@ -421,7 +426,12 @@ pub fn print(
.val = un.val.toValue(),
}, writer, level - 1, mod);
} else {
try writer.writeAll("(unknown tag)");
try writer.writeAll("(unknown tag) = ");
const backing_ty = try ty.unionBackingType(mod);
try print(.{
.ty = backing_ty,
.val = un.val.toValue(),
}, writer, level - 1, mod);
}
} else try writer.writeAll("...");
return writer.writeAll(" }");
Expand Down
10 changes: 10 additions & 0 deletions src/type.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -1954,6 +1954,16 @@ pub const Type = struct {
return true;
}

/// Returns the type used for backing storage of this union during comptime operations.
/// Asserts the type is either an extern or packed union.
pub fn unionBackingType(ty: Type, mod: *Module) !Type {
return switch (ty.containerLayout(mod)) {
.Extern => try mod.arrayType(.{ .len = ty.abiSize(mod), .child = .u8_type }),
.Packed => try mod.intType(.unsigned, @intCast(ty.bitSize(mod))),
.Auto => unreachable,
};
}

pub fn unionGetLayout(ty: Type, mod: *Module) Module.UnionLayout {
const ip = &mod.intern_pool;
const union_type = ip.indexToKey(ty.toIntern()).union_type;
Expand Down
47 changes: 25 additions & 22 deletions src/value.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -327,11 +327,19 @@ pub const Value = struct {
},
.@"union" => {
const pl = val.castTag(.@"union").?.data;
return mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = try pl.tag.intern(ty.unionTagTypeHypothetical(mod), mod),
.val = try pl.val.intern(ty.unionFieldType(pl.tag, mod).?, mod),
} });
if (pl.tag) |pl_tag| {
return mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = try pl_tag.intern(ty.unionTagTypeHypothetical(mod), mod),
.val = try pl.val.intern(ty.unionFieldType(pl_tag, mod).?, mod),
} });
} else {
return mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = .none,
.val = try pl.val.intern(try ty.unionBackingType(mod), mod),
} });
}
},
}
}
Expand DownExpand Up@@ -399,10 +407,7 @@ pub const Value = struct {

.un => |un| Tag.@"union".create(arena, .{
// toValue asserts that the value cannot be .none which is valid on unions.
.tag = .{
.ip_index = un.tag,
.legacy = undefined,
},
.tag = if (un.tag == .none) null else un.tag.toValue(),
.val = un.val.toValue(),
}),

Expand DownExpand Up@@ -709,21 +714,22 @@ pub const Value = struct {
.Union => switch (ty.containerLayout(mod)) {
.Auto => return error.IllDefinedMemoryLayout, // Sema is supposed to have emitted a compile error already
.Extern => {
const union_obj = mod.typeToUnion(ty).?;
if (val.unionTag(mod)) |union_tag| {
const union_obj = mod.typeToUnion(ty).?;
const field_index = mod.unionTagFieldIndex(union_obj, union_tag).?;
const field_type = union_obj.field_types.get(&mod.intern_pool)[field_index].toType();
const field_val = try val.fieldValue(mod, field_index);
const byte_count = @as(usize, @intCast(field_type.abiSize(mod)));
return writeToMemory(field_val, field_type, mod, buffer[0..byte_count]);
} else {
const union_size = ty.abiSize(mod);
const array_type = try mod.arrayType(.{ .len = union_size, .child = .u8_type });
return writeToMemory(val.unionValue(mod), array_type, mod, buffer[0..@as(usize, @intCast(union_size))]);
const backing_ty = try ty.unionBackingType(mod);
const byte_count: usize = @intCast(backing_ty.abiSize(mod));
return writeToMemory(val.unionValue(mod), backing_ty, mod, buffer[0..byte_count]);
}
},
.Packed => {
const byte_count = (@as(usize, @intCast(ty.bitSize(mod))) + 7) / 8;
const backing_ty = try ty.unionBackingType(mod);
const byte_count: usize = @intCast(backing_ty.abiSize(mod));
return writeToPackedMemory(val, ty, mod, buffer[0..byte_count], 0);
},
},
Expand DownExpand Up@@ -842,9 +848,8 @@ pub const Value = struct {
const field_val = try val.fieldValue(mod, field_index);
return field_val.writeToPackedMemory(field_type, mod, buffer, bit_offset);
} else {
const union_bits: u16 = @intCast(ty.bitSize(mod));
const int_ty = try mod.intType(.unsigned, union_bits);
return val.unionValue(mod).writeToPackedMemory(int_ty, mod, buffer, bit_offset);
const backing_ty = try ty.unionBackingType(mod);
return val.unionValue(mod).writeToPackedMemory(backing_ty, mod, buffer, bit_offset);
}
},
}
Expand DownExpand Up@@ -1146,10 +1151,8 @@ pub const Value = struct {
.Union => switch (ty.containerLayout(mod)) {
.Auto, .Extern => unreachable, // Handled by non-packed readFromMemory
.Packed => {
const union_bits: u16 = @intCast(ty.bitSize(mod));
assert(union_bits != 0);
const int_ty = try mod.intType(.unsigned, union_bits);
const val = (try readFromPackedMemory(int_ty, mod, buffer, bit_offset, arena)).toIntern();
const backing_ty = try ty.unionBackingType(mod);
const val = (try readFromPackedMemory(backing_ty, mod, buffer, bit_offset, arena)).toIntern();
return (try mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = .none,
Expand DownExpand Up@@ -4017,7 +4020,7 @@ pub const Value = struct {
data: Data,

pub const Data = struct {
tag: Value,
tag: ?Value,
val: Value,
};
};
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' sema: Support reinterpreting extern/packed unions at comptime via field access by kcbanner · Pull Request #17352 · ziglang/zig · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Module.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -6607,6 +6607,7 @@ pub fn unionFieldNormalAlignment(mod: *Module, u: InternPool.UnionType, field_in
return field_ty.abiAlignment(mod);
}

/// Returns the index of the active field, given the current tag value
pub fn unionTagFieldIndex(mod: *Module, u: InternPool.UnionType, enum_tag: Value) ?u32 {
const ip = &mod.intern_pool;
if (enum_tag.toIntern() == .none) return null;
Expand Down
132 changes: 110 additions & 22 deletions src/Sema.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 {
Expand All@@ -27260,7 +27260,7 @@ fn unionFieldVal(
else
union_ty.unionFieldType(un.tag.toValue(), mod).?;

if (try sema.bitCastVal(block, src, un.val.toValue(), old_ty, field_ty, 0)) |new_val| {
if (try sema.bitCastUnionFieldVal(block, src, un.val.toValue(), old_ty, field_ty, layout)) |new_val| {
return Air.internedToRef(new_val.toIntern());
}
}
Expand DownExpand Up@@ -29781,13 +29781,19 @@ fn storePtrVal(
error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{mut_kit.ty.fmt(mod)}),
};
operand_val.writeToMemory(operand_ty, mod, buffer[reinterpret.byte_offset..]) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ReinterpretDeclRef => unreachable,
error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{operand_ty.fmt(mod)}),
};

if (reinterpret.write_packed) {
operand_val.writeToPackedMemory(operand_ty, mod, buffer[reinterpret.byte_offset..], 0) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ReinterpretDeclRef => unreachable,
};
} else {
operand_val.writeToMemory(operand_ty, mod, buffer[reinterpret.byte_offset..]) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ReinterpretDeclRef => unreachable,
error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{operand_ty.fmt(mod)}),
};
}
const val = Value.readFromMemory(mut_kit.ty, mod, buffer, sema.arena) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.IllDefinedMemoryLayout => unreachable,
Expand DownExpand Up@@ -29819,6 +29825,8 @@ const ComptimePtrMutationKit = struct {
reinterpret: struct {
val_ptr: *Value,
byte_offset: usize,
/// If set, write the operand to packed memory
write_packed: bool = false,
},
/// If the root decl could not be used as parent, this means `ty` is the type that
/// caused that by not having a well-defined layout.
Expand DownExpand Up@@ -30182,21 +30190,43 @@ fn beginComptimePtrMutation(
);
},
.@"union" => {
// We need to set the active field of the union.
const union_tag_ty = base_child_ty.unionTagTypeHypothetical(mod);

const payload = &val_ptr.castTag(.@"union").?.data;
payload.tag = try mod.enumValueFieldIndex(union_tag_ty, field_index);
const layout = base_child_ty.containerLayout(mod);

return beginComptimePtrMutationInner(
sema,
block,
src,
parent.ty.structFieldType(field_index, mod),
&payload.val,
ptr_elem_ty,
parent.mut_decl,
);
const tag_type = base_child_ty.unionTagTypeHypothetical(mod);
const hypothetical_tag = try mod.enumValueFieldIndex(tag_type, field_index);
if (layout == .Auto or (payload.tag != null and hypothetical_tag.eql(payload.tag.?, tag_type, mod))) {
// We need to set the active field of the union.
payload.tag = hypothetical_tag;

const field_ty = parent.ty.structFieldType(field_index, mod);
return beginComptimePtrMutationInner(
sema,
block,
src,
field_ty,
&payload.val,
ptr_elem_ty,
parent.mut_decl,
);
} else {
// Writing to a different field (a different or unknown tag is active) requires reinterpreting
// memory of the entire union, which requires knowing its abiSize.
try sema.resolveTypeLayout(parent.ty);

// This union value no longer has a well-defined tag type.
// The reinterpretation will read it back out as .none.
payload.val = try payload.val.unintern(sema.arena, mod);
return ComptimePtrMutationKit{
.mut_decl = parent.mut_decl,
.pointee = .{ .reinterpret = .{
.val_ptr = val_ptr,
.byte_offset = 0,
.write_packed = layout == .Packed,
} },
.ty = parent.ty,
};
}
},
.slice => switch (field_index) {
Value.slice_ptr_index => return beginComptimePtrMutationInner(
Expand DownExpand Up@@ -30697,6 +30727,7 @@ fn bitCastVal(
// For types with well-defined memory layouts, we serialize them a byte buffer,
// then deserialize to the new type.
const abi_size = try sema.usizeCast(block, src, old_ty.abiSize(mod));

const buffer = try sema.gpa.alloc(u8, abi_size);
defer sema.gpa.free(buffer);
val.writeToMemory(old_ty, mod, buffer) catch |err| switch (err) {
Expand All@@ -30713,6 +30744,63 @@ fn bitCastVal(
};
}

fn bitCastUnionFieldVal(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
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);

// 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[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)}),
};
}

fn coerceArrayPtrToSlice(
sema: *Sema,
block: *Block,
Expand Down
24 changes: 17 additions & 7 deletions src/TypedValue.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -84,22 +84,27 @@ pub fn print(
if (level == 0) {
return writer.writeAll(".{ ... }");
}
const union_val = val.castTag(.@"union").?.data;
const payload = val.castTag(.@"union").?.data;
try writer.writeAll(".{ ");

if (union_val.tag.toIntern() != .none) {
if (payload.tag) |tag| {
try print(.{
.ty = ip.indexToKey(ty.toIntern()).union_type.enum_tag_ty.toType(),
.val = union_val.tag,
.val = tag,
}, writer, level - 1, mod);
try writer.writeAll(" = ");
const field_ty = ty.unionFieldType(union_val.tag, mod).?;
const field_ty = ty.unionFieldType(tag, mod).?;
try print(.{
.ty = field_ty,
.val = union_val.val,
.val = payload.val,
}, writer, level - 1, mod);
} else {
return writer.writeAll("(unknown tag)");
try writer.writeAll("(unknown tag) = ");
const backing_ty = try ty.unionBackingType(mod);
try print(.{
.ty = backing_ty,
.val = payload.val,
}, writer, level - 1, mod);
}

return writer.writeAll(" }");
Expand DownExpand Up@@ -421,7 +426,12 @@ pub fn print(
.val = un.val.toValue(),
}, writer, level - 1, mod);
} else {
try writer.writeAll("(unknown tag)");
try writer.writeAll("(unknown tag) = ");
const backing_ty = try ty.unionBackingType(mod);
try print(.{
.ty = backing_ty,
.val = un.val.toValue(),
}, writer, level - 1, mod);
}
} else try writer.writeAll("...");
return writer.writeAll(" }");
Expand Down
10 changes: 10 additions & 0 deletions src/type.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -1954,6 +1954,16 @@ pub const Type = struct {
return true;
}

/// Returns the type used for backing storage of this union during comptime operations.
/// Asserts the type is either an extern or packed union.
pub fn unionBackingType(ty: Type, mod: *Module) !Type {
return switch (ty.containerLayout(mod)) {
.Extern => try mod.arrayType(.{ .len = ty.abiSize(mod), .child = .u8_type }),
.Packed => try mod.intType(.unsigned, @intCast(ty.bitSize(mod))),
.Auto => unreachable,
};
}

pub fn unionGetLayout(ty: Type, mod: *Module) Module.UnionLayout {
const ip = &mod.intern_pool;
const union_type = ip.indexToKey(ty.toIntern()).union_type;
Expand Down
47 changes: 25 additions & 22 deletions src/value.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -327,11 +327,19 @@ pub const Value = struct {
},
.@"union" => {
const pl = val.castTag(.@"union").?.data;
return mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = try pl.tag.intern(ty.unionTagTypeHypothetical(mod), mod),
.val = try pl.val.intern(ty.unionFieldType(pl.tag, mod).?, mod),
} });
if (pl.tag) |pl_tag| {
return mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = try pl_tag.intern(ty.unionTagTypeHypothetical(mod), mod),
.val = try pl.val.intern(ty.unionFieldType(pl_tag, mod).?, mod),
} });
} else {
return mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = .none,
.val = try pl.val.intern(try ty.unionBackingType(mod), mod),
} });
}
},
}
}
Expand DownExpand Up@@ -399,10 +407,7 @@ pub const Value = struct {

.un => |un| Tag.@"union".create(arena, .{
// toValue asserts that the value cannot be .none which is valid on unions.
.tag = .{
.ip_index = un.tag,
.legacy = undefined,
},
.tag = if (un.tag == .none) null else un.tag.toValue(),
.val = un.val.toValue(),
}),

Expand DownExpand Up@@ -709,21 +714,22 @@ pub const Value = struct {
.Union => switch (ty.containerLayout(mod)) {
.Auto => return error.IllDefinedMemoryLayout, // Sema is supposed to have emitted a compile error already
.Extern => {
const union_obj = mod.typeToUnion(ty).?;
if (val.unionTag(mod)) |union_tag| {
const union_obj = mod.typeToUnion(ty).?;
const field_index = mod.unionTagFieldIndex(union_obj, union_tag).?;
const field_type = union_obj.field_types.get(&mod.intern_pool)[field_index].toType();
const field_val = try val.fieldValue(mod, field_index);
const byte_count = @as(usize, @intCast(field_type.abiSize(mod)));
return writeToMemory(field_val, field_type, mod, buffer[0..byte_count]);
} else {
const union_size = ty.abiSize(mod);
const array_type = try mod.arrayType(.{ .len = union_size, .child = .u8_type });
return writeToMemory(val.unionValue(mod), array_type, mod, buffer[0..@as(usize, @intCast(union_size))]);
const backing_ty = try ty.unionBackingType(mod);
const byte_count: usize = @intCast(backing_ty.abiSize(mod));
return writeToMemory(val.unionValue(mod), backing_ty, mod, buffer[0..byte_count]);
}
},
.Packed => {
const byte_count = (@as(usize, @intCast(ty.bitSize(mod))) + 7) / 8;
const backing_ty = try ty.unionBackingType(mod);
const byte_count: usize = @intCast(backing_ty.abiSize(mod));
return writeToPackedMemory(val, ty, mod, buffer[0..byte_count], 0);
},
},
Expand DownExpand Up@@ -842,9 +848,8 @@ pub const Value = struct {
const field_val = try val.fieldValue(mod, field_index);
return field_val.writeToPackedMemory(field_type, mod, buffer, bit_offset);
} else {
const union_bits: u16 = @intCast(ty.bitSize(mod));
const int_ty = try mod.intType(.unsigned, union_bits);
return val.unionValue(mod).writeToPackedMemory(int_ty, mod, buffer, bit_offset);
const backing_ty = try ty.unionBackingType(mod);
return val.unionValue(mod).writeToPackedMemory(backing_ty, mod, buffer, bit_offset);
}
},
}
Expand DownExpand Up@@ -1146,10 +1151,8 @@ pub const Value = struct {
.Union => switch (ty.containerLayout(mod)) {
.Auto, .Extern => unreachable, // Handled by non-packed readFromMemory
.Packed => {
const union_bits: u16 = @intCast(ty.bitSize(mod));
assert(union_bits != 0);
const int_ty = try mod.intType(.unsigned, union_bits);
const val = (try readFromPackedMemory(int_ty, mod, buffer, bit_offset, arena)).toIntern();
const backing_ty = try ty.unionBackingType(mod);
const val = (try readFromPackedMemory(backing_ty, mod, buffer, bit_offset, arena)).toIntern();
return (try mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = .none,
Expand DownExpand Up@@ -4017,7 +4020,7 @@ pub const Value = struct {
data: Data,

pub const Data = struct {
tag: Value,
tag: ?Value,
val: Value,
};
};
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' sema: Support reinterpreting extern/packed unions at comptime via field access by kcbanner · Pull Request #17352 · ziglang/zig · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Module.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -6607,6 +6607,7 @@ pub fn unionFieldNormalAlignment(mod: *Module, u: InternPool.UnionType, field_in
return field_ty.abiAlignment(mod);
}

/// Returns the index of the active field, given the current tag value
pub fn unionTagFieldIndex(mod: *Module, u: InternPool.UnionType, enum_tag: Value) ?u32 {
const ip = &mod.intern_pool;
if (enum_tag.toIntern() == .none) return null;
Expand Down
132 changes: 110 additions & 22 deletions src/Sema.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 {
Expand All@@ -27260,7 +27260,7 @@ fn unionFieldVal(
else
union_ty.unionFieldType(un.tag.toValue(), mod).?;

if (try sema.bitCastVal(block, src, un.val.toValue(), old_ty, field_ty, 0)) |new_val| {
if (try sema.bitCastUnionFieldVal(block, src, un.val.toValue(), old_ty, field_ty, layout)) |new_val| {
return Air.internedToRef(new_val.toIntern());
}
}
Expand DownExpand Up@@ -29781,13 +29781,19 @@ fn storePtrVal(
error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{mut_kit.ty.fmt(mod)}),
};
operand_val.writeToMemory(operand_ty, mod, buffer[reinterpret.byte_offset..]) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ReinterpretDeclRef => unreachable,
error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{operand_ty.fmt(mod)}),
};

if (reinterpret.write_packed) {
operand_val.writeToPackedMemory(operand_ty, mod, buffer[reinterpret.byte_offset..], 0) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ReinterpretDeclRef => unreachable,
};
} else {
operand_val.writeToMemory(operand_ty, mod, buffer[reinterpret.byte_offset..]) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ReinterpretDeclRef => unreachable,
error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{operand_ty.fmt(mod)}),
};
}
const val = Value.readFromMemory(mut_kit.ty, mod, buffer, sema.arena) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.IllDefinedMemoryLayout => unreachable,
Expand DownExpand Up@@ -29819,6 +29825,8 @@ const ComptimePtrMutationKit = struct {
reinterpret: struct {
val_ptr: *Value,
byte_offset: usize,
/// If set, write the operand to packed memory
write_packed: bool = false,
},
/// If the root decl could not be used as parent, this means `ty` is the type that
/// caused that by not having a well-defined layout.
Expand DownExpand Up@@ -30182,21 +30190,43 @@ fn beginComptimePtrMutation(
);
},
.@"union" => {
// We need to set the active field of the union.
const union_tag_ty = base_child_ty.unionTagTypeHypothetical(mod);

const payload = &val_ptr.castTag(.@"union").?.data;
payload.tag = try mod.enumValueFieldIndex(union_tag_ty, field_index);
const layout = base_child_ty.containerLayout(mod);

return beginComptimePtrMutationInner(
sema,
block,
src,
parent.ty.structFieldType(field_index, mod),
&payload.val,
ptr_elem_ty,
parent.mut_decl,
);
const tag_type = base_child_ty.unionTagTypeHypothetical(mod);
const hypothetical_tag = try mod.enumValueFieldIndex(tag_type, field_index);
if (layout == .Auto or (payload.tag != null and hypothetical_tag.eql(payload.tag.?, tag_type, mod))) {
// We need to set the active field of the union.
payload.tag = hypothetical_tag;

const field_ty = parent.ty.structFieldType(field_index, mod);
return beginComptimePtrMutationInner(
sema,
block,
src,
field_ty,
&payload.val,
ptr_elem_ty,
parent.mut_decl,
);
} else {
// Writing to a different field (a different or unknown tag is active) requires reinterpreting
// memory of the entire union, which requires knowing its abiSize.
try sema.resolveTypeLayout(parent.ty);

// This union value no longer has a well-defined tag type.
// The reinterpretation will read it back out as .none.
payload.val = try payload.val.unintern(sema.arena, mod);
return ComptimePtrMutationKit{
.mut_decl = parent.mut_decl,
.pointee = .{ .reinterpret = .{
.val_ptr = val_ptr,
.byte_offset = 0,
.write_packed = layout == .Packed,
} },
.ty = parent.ty,
};
}
},
.slice => switch (field_index) {
Value.slice_ptr_index => return beginComptimePtrMutationInner(
Expand DownExpand Up@@ -30697,6 +30727,7 @@ fn bitCastVal(
// For types with well-defined memory layouts, we serialize them a byte buffer,
// then deserialize to the new type.
const abi_size = try sema.usizeCast(block, src, old_ty.abiSize(mod));

const buffer = try sema.gpa.alloc(u8, abi_size);
defer sema.gpa.free(buffer);
val.writeToMemory(old_ty, mod, buffer) catch |err| switch (err) {
Expand All@@ -30713,6 +30744,63 @@ fn bitCastVal(
};
}

fn bitCastUnionFieldVal(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
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);

// 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[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)}),
};
}

fn coerceArrayPtrToSlice(
sema: *Sema,
block: *Block,
Expand Down
24 changes: 17 additions & 7 deletions src/TypedValue.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -84,22 +84,27 @@ pub fn print(
if (level == 0) {
return writer.writeAll(".{ ... }");
}
const union_val = val.castTag(.@"union").?.data;
const payload = val.castTag(.@"union").?.data;
try writer.writeAll(".{ ");

if (union_val.tag.toIntern() != .none) {
if (payload.tag) |tag| {
try print(.{
.ty = ip.indexToKey(ty.toIntern()).union_type.enum_tag_ty.toType(),
.val = union_val.tag,
.val = tag,
}, writer, level - 1, mod);
try writer.writeAll(" = ");
const field_ty = ty.unionFieldType(union_val.tag, mod).?;
const field_ty = ty.unionFieldType(tag, mod).?;
try print(.{
.ty = field_ty,
.val = union_val.val,
.val = payload.val,
}, writer, level - 1, mod);
} else {
return writer.writeAll("(unknown tag)");
try writer.writeAll("(unknown tag) = ");
const backing_ty = try ty.unionBackingType(mod);
try print(.{
.ty = backing_ty,
.val = payload.val,
}, writer, level - 1, mod);
}

return writer.writeAll(" }");
Expand DownExpand Up@@ -421,7 +426,12 @@ pub fn print(
.val = un.val.toValue(),
}, writer, level - 1, mod);
} else {
try writer.writeAll("(unknown tag)");
try writer.writeAll("(unknown tag) = ");
const backing_ty = try ty.unionBackingType(mod);
try print(.{
.ty = backing_ty,
.val = un.val.toValue(),
}, writer, level - 1, mod);
}
} else try writer.writeAll("...");
return writer.writeAll(" }");
Expand Down
10 changes: 10 additions & 0 deletions src/type.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -1954,6 +1954,16 @@ pub const Type = struct {
return true;
}

/// Returns the type used for backing storage of this union during comptime operations.
/// Asserts the type is either an extern or packed union.
pub fn unionBackingType(ty: Type, mod: *Module) !Type {
return switch (ty.containerLayout(mod)) {
.Extern => try mod.arrayType(.{ .len = ty.abiSize(mod), .child = .u8_type }),
.Packed => try mod.intType(.unsigned, @intCast(ty.bitSize(mod))),
.Auto => unreachable,
};
}

pub fn unionGetLayout(ty: Type, mod: *Module) Module.UnionLayout {
const ip = &mod.intern_pool;
const union_type = ip.indexToKey(ty.toIntern()).union_type;
Expand Down
47 changes: 25 additions & 22 deletions src/value.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -327,11 +327,19 @@ pub const Value = struct {
},
.@"union" => {
const pl = val.castTag(.@"union").?.data;
return mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = try pl.tag.intern(ty.unionTagTypeHypothetical(mod), mod),
.val = try pl.val.intern(ty.unionFieldType(pl.tag, mod).?, mod),
} });
if (pl.tag) |pl_tag| {
return mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = try pl_tag.intern(ty.unionTagTypeHypothetical(mod), mod),
.val = try pl.val.intern(ty.unionFieldType(pl_tag, mod).?, mod),
} });
} else {
return mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = .none,
.val = try pl.val.intern(try ty.unionBackingType(mod), mod),
} });
}
},
}
}
Expand DownExpand Up@@ -399,10 +407,7 @@ pub const Value = struct {

.un => |un| Tag.@"union".create(arena, .{
// toValue asserts that the value cannot be .none which is valid on unions.
.tag = .{
.ip_index = un.tag,
.legacy = undefined,
},
.tag = if (un.tag == .none) null else un.tag.toValue(),
.val = un.val.toValue(),
}),

Expand DownExpand Up@@ -709,21 +714,22 @@ pub const Value = struct {
.Union => switch (ty.containerLayout(mod)) {
.Auto => return error.IllDefinedMemoryLayout, // Sema is supposed to have emitted a compile error already
.Extern => {
const union_obj = mod.typeToUnion(ty).?;
if (val.unionTag(mod)) |union_tag| {
const union_obj = mod.typeToUnion(ty).?;
const field_index = mod.unionTagFieldIndex(union_obj, union_tag).?;
const field_type = union_obj.field_types.get(&mod.intern_pool)[field_index].toType();
const field_val = try val.fieldValue(mod, field_index);
const byte_count = @as(usize, @intCast(field_type.abiSize(mod)));
return writeToMemory(field_val, field_type, mod, buffer[0..byte_count]);
} else {
const union_size = ty.abiSize(mod);
const array_type = try mod.arrayType(.{ .len = union_size, .child = .u8_type });
return writeToMemory(val.unionValue(mod), array_type, mod, buffer[0..@as(usize, @intCast(union_size))]);
const backing_ty = try ty.unionBackingType(mod);
const byte_count: usize = @intCast(backing_ty.abiSize(mod));
return writeToMemory(val.unionValue(mod), backing_ty, mod, buffer[0..byte_count]);
}
},
.Packed => {
const byte_count = (@as(usize, @intCast(ty.bitSize(mod))) + 7) / 8;
const backing_ty = try ty.unionBackingType(mod);
const byte_count: usize = @intCast(backing_ty.abiSize(mod));
return writeToPackedMemory(val, ty, mod, buffer[0..byte_count], 0);
},
},
Expand DownExpand Up@@ -842,9 +848,8 @@ pub const Value = struct {
const field_val = try val.fieldValue(mod, field_index);
return field_val.writeToPackedMemory(field_type, mod, buffer, bit_offset);
} else {
const union_bits: u16 = @intCast(ty.bitSize(mod));
const int_ty = try mod.intType(.unsigned, union_bits);
return val.unionValue(mod).writeToPackedMemory(int_ty, mod, buffer, bit_offset);
const backing_ty = try ty.unionBackingType(mod);
return val.unionValue(mod).writeToPackedMemory(backing_ty, mod, buffer, bit_offset);
}
},
}
Expand DownExpand Up@@ -1146,10 +1151,8 @@ pub const Value = struct {
.Union => switch (ty.containerLayout(mod)) {
.Auto, .Extern => unreachable, // Handled by non-packed readFromMemory
.Packed => {
const union_bits: u16 = @intCast(ty.bitSize(mod));
assert(union_bits != 0);
const int_ty = try mod.intType(.unsigned, union_bits);
const val = (try readFromPackedMemory(int_ty, mod, buffer, bit_offset, arena)).toIntern();
const backing_ty = try ty.unionBackingType(mod);
const val = (try readFromPackedMemory(backing_ty, mod, buffer, bit_offset, arena)).toIntern();
return (try mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = .none,
Expand DownExpand Up@@ -4017,7 +4020,7 @@ pub const Value = struct {
data: Data,

pub const Data = struct {
tag: Value,
tag: ?Value,
val: Value,
};
};
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' sema: Support reinterpreting extern/packed unions at comptime via field access by kcbanner · Pull Request #17352 · ziglang/zig · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Module.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -6607,6 +6607,7 @@ pub fn unionFieldNormalAlignment(mod: *Module, u: InternPool.UnionType, field_in
return field_ty.abiAlignment(mod);
}

/// Returns the index of the active field, given the current tag value
pub fn unionTagFieldIndex(mod: *Module, u: InternPool.UnionType, enum_tag: Value) ?u32 {
const ip = &mod.intern_pool;
if (enum_tag.toIntern() == .none) return null;
Expand Down
132 changes: 110 additions & 22 deletions src/Sema.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 {
Expand All@@ -27260,7 +27260,7 @@ fn unionFieldVal(
else
union_ty.unionFieldType(un.tag.toValue(), mod).?;

if (try sema.bitCastVal(block, src, un.val.toValue(), old_ty, field_ty, 0)) |new_val| {
if (try sema.bitCastUnionFieldVal(block, src, un.val.toValue(), old_ty, field_ty, layout)) |new_val| {
return Air.internedToRef(new_val.toIntern());
}
}
Expand DownExpand Up@@ -29781,13 +29781,19 @@ fn storePtrVal(
error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{mut_kit.ty.fmt(mod)}),
};
operand_val.writeToMemory(operand_ty, mod, buffer[reinterpret.byte_offset..]) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ReinterpretDeclRef => unreachable,
error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{operand_ty.fmt(mod)}),
};

if (reinterpret.write_packed) {
operand_val.writeToPackedMemory(operand_ty, mod, buffer[reinterpret.byte_offset..], 0) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ReinterpretDeclRef => unreachable,
};
} else {
operand_val.writeToMemory(operand_ty, mod, buffer[reinterpret.byte_offset..]) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ReinterpretDeclRef => unreachable,
error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{operand_ty.fmt(mod)}),
};
}
const val = Value.readFromMemory(mut_kit.ty, mod, buffer, sema.arena) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.IllDefinedMemoryLayout => unreachable,
Expand DownExpand Up@@ -29819,6 +29825,8 @@ const ComptimePtrMutationKit = struct {
reinterpret: struct {
val_ptr: *Value,
byte_offset: usize,
/// If set, write the operand to packed memory
write_packed: bool = false,
},
/// If the root decl could not be used as parent, this means `ty` is the type that
/// caused that by not having a well-defined layout.
Expand DownExpand Up@@ -30182,21 +30190,43 @@ fn beginComptimePtrMutation(
);
},
.@"union" => {
// We need to set the active field of the union.
const union_tag_ty = base_child_ty.unionTagTypeHypothetical(mod);

const payload = &val_ptr.castTag(.@"union").?.data;
payload.tag = try mod.enumValueFieldIndex(union_tag_ty, field_index);
const layout = base_child_ty.containerLayout(mod);

return beginComptimePtrMutationInner(
sema,
block,
src,
parent.ty.structFieldType(field_index, mod),
&payload.val,
ptr_elem_ty,
parent.mut_decl,
);
const tag_type = base_child_ty.unionTagTypeHypothetical(mod);
const hypothetical_tag = try mod.enumValueFieldIndex(tag_type, field_index);
if (layout == .Auto or (payload.tag != null and hypothetical_tag.eql(payload.tag.?, tag_type, mod))) {
// We need to set the active field of the union.
payload.tag = hypothetical_tag;

const field_ty = parent.ty.structFieldType(field_index, mod);
return beginComptimePtrMutationInner(
sema,
block,
src,
field_ty,
&payload.val,
ptr_elem_ty,
parent.mut_decl,
);
} else {
// Writing to a different field (a different or unknown tag is active) requires reinterpreting
// memory of the entire union, which requires knowing its abiSize.
try sema.resolveTypeLayout(parent.ty);

// This union value no longer has a well-defined tag type.
// The reinterpretation will read it back out as .none.
payload.val = try payload.val.unintern(sema.arena, mod);
return ComptimePtrMutationKit{
.mut_decl = parent.mut_decl,
.pointee = .{ .reinterpret = .{
.val_ptr = val_ptr,
.byte_offset = 0,
.write_packed = layout == .Packed,
} },
.ty = parent.ty,
};
}
},
.slice => switch (field_index) {
Value.slice_ptr_index => return beginComptimePtrMutationInner(
Expand DownExpand Up@@ -30697,6 +30727,7 @@ fn bitCastVal(
// For types with well-defined memory layouts, we serialize them a byte buffer,
// then deserialize to the new type.
const abi_size = try sema.usizeCast(block, src, old_ty.abiSize(mod));

const buffer = try sema.gpa.alloc(u8, abi_size);
defer sema.gpa.free(buffer);
val.writeToMemory(old_ty, mod, buffer) catch |err| switch (err) {
Expand All@@ -30713,6 +30744,63 @@ fn bitCastVal(
};
}

fn bitCastUnionFieldVal(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
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);

// 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[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)}),
};
}

fn coerceArrayPtrToSlice(
sema: *Sema,
block: *Block,
Expand Down
24 changes: 17 additions & 7 deletions src/TypedValue.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -84,22 +84,27 @@ pub fn print(
if (level == 0) {
return writer.writeAll(".{ ... }");
}
const union_val = val.castTag(.@"union").?.data;
const payload = val.castTag(.@"union").?.data;
try writer.writeAll(".{ ");

if (union_val.tag.toIntern() != .none) {
if (payload.tag) |tag| {
try print(.{
.ty = ip.indexToKey(ty.toIntern()).union_type.enum_tag_ty.toType(),
.val = union_val.tag,
.val = tag,
}, writer, level - 1, mod);
try writer.writeAll(" = ");
const field_ty = ty.unionFieldType(union_val.tag, mod).?;
const field_ty = ty.unionFieldType(tag, mod).?;
try print(.{
.ty = field_ty,
.val = union_val.val,
.val = payload.val,
}, writer, level - 1, mod);
} else {
return writer.writeAll("(unknown tag)");
try writer.writeAll("(unknown tag) = ");
const backing_ty = try ty.unionBackingType(mod);
try print(.{
.ty = backing_ty,
.val = payload.val,
}, writer, level - 1, mod);
}

return writer.writeAll(" }");
Expand DownExpand Up@@ -421,7 +426,12 @@ pub fn print(
.val = un.val.toValue(),
}, writer, level - 1, mod);
} else {
try writer.writeAll("(unknown tag)");
try writer.writeAll("(unknown tag) = ");
const backing_ty = try ty.unionBackingType(mod);
try print(.{
.ty = backing_ty,
.val = un.val.toValue(),
}, writer, level - 1, mod);
}
} else try writer.writeAll("...");
return writer.writeAll(" }");
Expand Down
10 changes: 10 additions & 0 deletions src/type.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -1954,6 +1954,16 @@ pub const Type = struct {
return true;
}

/// Returns the type used for backing storage of this union during comptime operations.
/// Asserts the type is either an extern or packed union.
pub fn unionBackingType(ty: Type, mod: *Module) !Type {
return switch (ty.containerLayout(mod)) {
.Extern => try mod.arrayType(.{ .len = ty.abiSize(mod), .child = .u8_type }),
.Packed => try mod.intType(.unsigned, @intCast(ty.bitSize(mod))),
.Auto => unreachable,
};
}

pub fn unionGetLayout(ty: Type, mod: *Module) Module.UnionLayout {
const ip = &mod.intern_pool;
const union_type = ip.indexToKey(ty.toIntern()).union_type;
Expand Down
47 changes: 25 additions & 22 deletions src/value.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -327,11 +327,19 @@ pub const Value = struct {
},
.@"union" => {
const pl = val.castTag(.@"union").?.data;
return mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = try pl.tag.intern(ty.unionTagTypeHypothetical(mod), mod),
.val = try pl.val.intern(ty.unionFieldType(pl.tag, mod).?, mod),
} });
if (pl.tag) |pl_tag| {
return mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = try pl_tag.intern(ty.unionTagTypeHypothetical(mod), mod),
.val = try pl.val.intern(ty.unionFieldType(pl_tag, mod).?, mod),
} });
} else {
return mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = .none,
.val = try pl.val.intern(try ty.unionBackingType(mod), mod),
} });
}
},
}
}
Expand DownExpand Up@@ -399,10 +407,7 @@ pub const Value = struct {

.un => |un| Tag.@"union".create(arena, .{
// toValue asserts that the value cannot be .none which is valid on unions.
.tag = .{
.ip_index = un.tag,
.legacy = undefined,
},
.tag = if (un.tag == .none) null else un.tag.toValue(),
.val = un.val.toValue(),
}),

Expand DownExpand Up@@ -709,21 +714,22 @@ pub const Value = struct {
.Union => switch (ty.containerLayout(mod)) {
.Auto => return error.IllDefinedMemoryLayout, // Sema is supposed to have emitted a compile error already
.Extern => {
const union_obj = mod.typeToUnion(ty).?;
if (val.unionTag(mod)) |union_tag| {
const union_obj = mod.typeToUnion(ty).?;
const field_index = mod.unionTagFieldIndex(union_obj, union_tag).?;
const field_type = union_obj.field_types.get(&mod.intern_pool)[field_index].toType();
const field_val = try val.fieldValue(mod, field_index);
const byte_count = @as(usize, @intCast(field_type.abiSize(mod)));
return writeToMemory(field_val, field_type, mod, buffer[0..byte_count]);
} else {
const union_size = ty.abiSize(mod);
const array_type = try mod.arrayType(.{ .len = union_size, .child = .u8_type });
return writeToMemory(val.unionValue(mod), array_type, mod, buffer[0..@as(usize, @intCast(union_size))]);
const backing_ty = try ty.unionBackingType(mod);
const byte_count: usize = @intCast(backing_ty.abiSize(mod));
return writeToMemory(val.unionValue(mod), backing_ty, mod, buffer[0..byte_count]);
}
},
.Packed => {
const byte_count = (@as(usize, @intCast(ty.bitSize(mod))) + 7) / 8;
const backing_ty = try ty.unionBackingType(mod);
const byte_count: usize = @intCast(backing_ty.abiSize(mod));
return writeToPackedMemory(val, ty, mod, buffer[0..byte_count], 0);
},
},
Expand DownExpand Up@@ -842,9 +848,8 @@ pub const Value = struct {
const field_val = try val.fieldValue(mod, field_index);
return field_val.writeToPackedMemory(field_type, mod, buffer, bit_offset);
} else {
const union_bits: u16 = @intCast(ty.bitSize(mod));
const int_ty = try mod.intType(.unsigned, union_bits);
return val.unionValue(mod).writeToPackedMemory(int_ty, mod, buffer, bit_offset);
const backing_ty = try ty.unionBackingType(mod);
return val.unionValue(mod).writeToPackedMemory(backing_ty, mod, buffer, bit_offset);
}
},
}
Expand DownExpand Up@@ -1146,10 +1151,8 @@ pub const Value = struct {
.Union => switch (ty.containerLayout(mod)) {
.Auto, .Extern => unreachable, // Handled by non-packed readFromMemory
.Packed => {
const union_bits: u16 = @intCast(ty.bitSize(mod));
assert(union_bits != 0);
const int_ty = try mod.intType(.unsigned, union_bits);
const val = (try readFromPackedMemory(int_ty, mod, buffer, bit_offset, arena)).toIntern();
const backing_ty = try ty.unionBackingType(mod);
const val = (try readFromPackedMemory(backing_ty, mod, buffer, bit_offset, arena)).toIntern();
return (try mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = .none,
Expand DownExpand Up@@ -4017,7 +4020,7 @@ pub const Value = struct {
data: Data,

pub const Data = struct {
tag: Value,
tag: ?Value,
val: Value,
};
};
Expand Down
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); sema: Support reinterpreting extern/packed unions at comptime via field access by kcbanner · Pull Request #17352 · ziglang/zig · GitHub
Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Module.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -6607,6 +6607,7 @@ pub fn unionFieldNormalAlignment(mod: *Module, u: InternPool.UnionType, field_in
return field_ty.abiAlignment(mod);
}

/// Returns the index of the active field, given the current tag value
pub fn unionTagFieldIndex(mod: *Module, u: InternPool.UnionType, enum_tag: Value) ?u32 {
const ip = &mod.intern_pool;
if (enum_tag.toIntern() == .none) return null;
Expand Down
132 changes: 110 additions & 22 deletions src/Sema.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -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 {
Expand All@@ -27260,7 +27260,7 @@ fn unionFieldVal(
else
union_ty.unionFieldType(un.tag.toValue(), mod).?;

if (try sema.bitCastVal(block, src, un.val.toValue(), old_ty, field_ty, 0)) |new_val| {
if (try sema.bitCastUnionFieldVal(block, src, un.val.toValue(), old_ty, field_ty, layout)) |new_val| {
return Air.internedToRef(new_val.toIntern());
}
}
Expand DownExpand Up@@ -29781,13 +29781,19 @@ fn storePtrVal(
error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{mut_kit.ty.fmt(mod)}),
};
operand_val.writeToMemory(operand_ty, mod, buffer[reinterpret.byte_offset..]) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ReinterpretDeclRef => unreachable,
error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{operand_ty.fmt(mod)}),
};

if (reinterpret.write_packed) {
operand_val.writeToPackedMemory(operand_ty, mod, buffer[reinterpret.byte_offset..], 0) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ReinterpretDeclRef => unreachable,
};
} else {
operand_val.writeToMemory(operand_ty, mod, buffer[reinterpret.byte_offset..]) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.ReinterpretDeclRef => unreachable,
error.IllDefinedMemoryLayout => unreachable, // Sema was supposed to emit a compile error already
error.Unimplemented => return sema.fail(block, src, "TODO: implement writeToMemory for type '{}'", .{operand_ty.fmt(mod)}),
};
}
const val = Value.readFromMemory(mut_kit.ty, mod, buffer, sema.arena) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.IllDefinedMemoryLayout => unreachable,
Expand DownExpand Up@@ -29819,6 +29825,8 @@ const ComptimePtrMutationKit = struct {
reinterpret: struct {
val_ptr: *Value,
byte_offset: usize,
/// If set, write the operand to packed memory
write_packed: bool = false,
},
/// If the root decl could not be used as parent, this means `ty` is the type that
/// caused that by not having a well-defined layout.
Expand DownExpand Up@@ -30182,21 +30190,43 @@ fn beginComptimePtrMutation(
);
},
.@"union" => {
// We need to set the active field of the union.
const union_tag_ty = base_child_ty.unionTagTypeHypothetical(mod);

const payload = &val_ptr.castTag(.@"union").?.data;
payload.tag = try mod.enumValueFieldIndex(union_tag_ty, field_index);
const layout = base_child_ty.containerLayout(mod);

return beginComptimePtrMutationInner(
sema,
block,
src,
parent.ty.structFieldType(field_index, mod),
&payload.val,
ptr_elem_ty,
parent.mut_decl,
);
const tag_type = base_child_ty.unionTagTypeHypothetical(mod);
const hypothetical_tag = try mod.enumValueFieldIndex(tag_type, field_index);
if (layout == .Auto or (payload.tag != null and hypothetical_tag.eql(payload.tag.?, tag_type, mod))) {
// We need to set the active field of the union.
payload.tag = hypothetical_tag;

const field_ty = parent.ty.structFieldType(field_index, mod);
return beginComptimePtrMutationInner(
sema,
block,
src,
field_ty,
&payload.val,
ptr_elem_ty,
parent.mut_decl,
);
} else {
// Writing to a different field (a different or unknown tag is active) requires reinterpreting
// memory of the entire union, which requires knowing its abiSize.
try sema.resolveTypeLayout(parent.ty);

// This union value no longer has a well-defined tag type.
// The reinterpretation will read it back out as .none.
payload.val = try payload.val.unintern(sema.arena, mod);
return ComptimePtrMutationKit{
.mut_decl = parent.mut_decl,
.pointee = .{ .reinterpret = .{
.val_ptr = val_ptr,
.byte_offset = 0,
.write_packed = layout == .Packed,
} },
.ty = parent.ty,
};
}
},
.slice => switch (field_index) {
Value.slice_ptr_index => return beginComptimePtrMutationInner(
Expand DownExpand Up@@ -30697,6 +30727,7 @@ fn bitCastVal(
// For types with well-defined memory layouts, we serialize them a byte buffer,
// then deserialize to the new type.
const abi_size = try sema.usizeCast(block, src, old_ty.abiSize(mod));

const buffer = try sema.gpa.alloc(u8, abi_size);
defer sema.gpa.free(buffer);
val.writeToMemory(old_ty, mod, buffer) catch |err| switch (err) {
Expand All@@ -30713,6 +30744,63 @@ fn bitCastVal(
};
}

fn bitCastUnionFieldVal(
sema: *Sema,
block: *Block,
src: LazySrcLoc,
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);

// 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[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)}),
};
}

fn coerceArrayPtrToSlice(
sema: *Sema,
block: *Block,
Expand Down
24 changes: 17 additions & 7 deletions src/TypedValue.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -84,22 +84,27 @@ pub fn print(
if (level == 0) {
return writer.writeAll(".{ ... }");
}
const union_val = val.castTag(.@"union").?.data;
const payload = val.castTag(.@"union").?.data;
try writer.writeAll(".{ ");

if (union_val.tag.toIntern() != .none) {
if (payload.tag) |tag| {
try print(.{
.ty = ip.indexToKey(ty.toIntern()).union_type.enum_tag_ty.toType(),
.val = union_val.tag,
.val = tag,
}, writer, level - 1, mod);
try writer.writeAll(" = ");
const field_ty = ty.unionFieldType(union_val.tag, mod).?;
const field_ty = ty.unionFieldType(tag, mod).?;
try print(.{
.ty = field_ty,
.val = union_val.val,
.val = payload.val,
}, writer, level - 1, mod);
} else {
return writer.writeAll("(unknown tag)");
try writer.writeAll("(unknown tag) = ");
const backing_ty = try ty.unionBackingType(mod);
try print(.{
.ty = backing_ty,
.val = payload.val,
}, writer, level - 1, mod);
}

return writer.writeAll(" }");
Expand DownExpand Up@@ -421,7 +426,12 @@ pub fn print(
.val = un.val.toValue(),
}, writer, level - 1, mod);
} else {
try writer.writeAll("(unknown tag)");
try writer.writeAll("(unknown tag) = ");
const backing_ty = try ty.unionBackingType(mod);
try print(.{
.ty = backing_ty,
.val = un.val.toValue(),
}, writer, level - 1, mod);
}
} else try writer.writeAll("...");
return writer.writeAll(" }");
Expand Down
10 changes: 10 additions & 0 deletions src/type.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -1954,6 +1954,16 @@ pub const Type = struct {
return true;
}

/// Returns the type used for backing storage of this union during comptime operations.
/// Asserts the type is either an extern or packed union.
pub fn unionBackingType(ty: Type, mod: *Module) !Type {
return switch (ty.containerLayout(mod)) {
.Extern => try mod.arrayType(.{ .len = ty.abiSize(mod), .child = .u8_type }),
.Packed => try mod.intType(.unsigned, @intCast(ty.bitSize(mod))),
.Auto => unreachable,
};
}

pub fn unionGetLayout(ty: Type, mod: *Module) Module.UnionLayout {
const ip = &mod.intern_pool;
const union_type = ip.indexToKey(ty.toIntern()).union_type;
Expand Down
47 changes: 25 additions & 22 deletions src/value.zig
Original file line numberDiff line numberDiff line change
Expand Up@@ -327,11 +327,19 @@ pub const Value = struct {
},
.@"union" => {
const pl = val.castTag(.@"union").?.data;
return mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = try pl.tag.intern(ty.unionTagTypeHypothetical(mod), mod),
.val = try pl.val.intern(ty.unionFieldType(pl.tag, mod).?, mod),
} });
if (pl.tag) |pl_tag| {
return mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = try pl_tag.intern(ty.unionTagTypeHypothetical(mod), mod),
.val = try pl.val.intern(ty.unionFieldType(pl_tag, mod).?, mod),
} });
} else {
return mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = .none,
.val = try pl.val.intern(try ty.unionBackingType(mod), mod),
} });
}
},
}
}
Expand DownExpand Up@@ -399,10 +407,7 @@ pub const Value = struct {

.un => |un| Tag.@"union".create(arena, .{
// toValue asserts that the value cannot be .none which is valid on unions.
.tag = .{
.ip_index = un.tag,
.legacy = undefined,
},
.tag = if (un.tag == .none) null else un.tag.toValue(),
.val = un.val.toValue(),
}),

Expand DownExpand Up@@ -709,21 +714,22 @@ pub const Value = struct {
.Union => switch (ty.containerLayout(mod)) {
.Auto => return error.IllDefinedMemoryLayout, // Sema is supposed to have emitted a compile error already
.Extern => {
const union_obj = mod.typeToUnion(ty).?;
if (val.unionTag(mod)) |union_tag| {
const union_obj = mod.typeToUnion(ty).?;
const field_index = mod.unionTagFieldIndex(union_obj, union_tag).?;
const field_type = union_obj.field_types.get(&mod.intern_pool)[field_index].toType();
const field_val = try val.fieldValue(mod, field_index);
const byte_count = @as(usize, @intCast(field_type.abiSize(mod)));
return writeToMemory(field_val, field_type, mod, buffer[0..byte_count]);
} else {
const union_size = ty.abiSize(mod);
const array_type = try mod.arrayType(.{ .len = union_size, .child = .u8_type });
return writeToMemory(val.unionValue(mod), array_type, mod, buffer[0..@as(usize, @intCast(union_size))]);
const backing_ty = try ty.unionBackingType(mod);
const byte_count: usize = @intCast(backing_ty.abiSize(mod));
return writeToMemory(val.unionValue(mod), backing_ty, mod, buffer[0..byte_count]);
}
},
.Packed => {
const byte_count = (@as(usize, @intCast(ty.bitSize(mod))) + 7) / 8;
const backing_ty = try ty.unionBackingType(mod);
const byte_count: usize = @intCast(backing_ty.abiSize(mod));
return writeToPackedMemory(val, ty, mod, buffer[0..byte_count], 0);
},
},
Expand DownExpand Up@@ -842,9 +848,8 @@ pub const Value = struct {
const field_val = try val.fieldValue(mod, field_index);
return field_val.writeToPackedMemory(field_type, mod, buffer, bit_offset);
} else {
const union_bits: u16 = @intCast(ty.bitSize(mod));
const int_ty = try mod.intType(.unsigned, union_bits);
return val.unionValue(mod).writeToPackedMemory(int_ty, mod, buffer, bit_offset);
const backing_ty = try ty.unionBackingType(mod);
return val.unionValue(mod).writeToPackedMemory(backing_ty, mod, buffer, bit_offset);
}
},
}
Expand DownExpand Up@@ -1146,10 +1151,8 @@ pub const Value = struct {
.Union => switch (ty.containerLayout(mod)) {
.Auto, .Extern => unreachable, // Handled by non-packed readFromMemory
.Packed => {
const union_bits: u16 = @intCast(ty.bitSize(mod));
assert(union_bits != 0);
const int_ty = try mod.intType(.unsigned, union_bits);
const val = (try readFromPackedMemory(int_ty, mod, buffer, bit_offset, arena)).toIntern();
const backing_ty = try ty.unionBackingType(mod);
const val = (try readFromPackedMemory(backing_ty, mod, buffer, bit_offset, arena)).toIntern();
return (try mod.intern(.{ .un = .{
.ty = ty.toIntern(),
.tag = .none,
Expand DownExpand Up@@ -4017,7 +4020,7 @@ pub const Value = struct {
data: Data,

pub const Data = struct {
tag: Value,
tag: ?Value,
val: Value,
};
};
Expand Down
Loading