Sema: const inferred alloc infers comptime-ness

const locals now detect if the value ends up being comptime known. In
such case, it replaces the runtime AIR instructions with a decl_ref
const.

In the backends, some more sophisticated logic for marking decls as
alive was needed to prevent Decls incorrectly being garbage collected
that were indirectly referenced in such manner.
This commit is contained in:
Andrew Kelley
2022-01-06 00:52:10 -07:00
parent 713d2a9b38
commit 8c6175c134
7 changed files with 135 additions and 28 deletions

View File

@@ -2427,7 +2427,57 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com
if (var_is_mut) {
try sema.validateVarType(block, ty_src, final_elem_ty, false);
} else ct: {
// Detect if the value is comptime known. In such case, the
// last 3 AIR instructions of the block will look like this:
//
// %a = constant
// %b = bitcast(%a)
// %c = store(%b, %d)
//
// If `%d` is comptime-known, then we want to store the value
// inside an anonymous Decl and then erase these three AIR
// instructions from the block, replacing the inst_map entry
// corresponding to the ZIR alloc instruction with a constant
// decl_ref pointing at our new Decl.
if (block.instructions.items.len < 3) break :ct;
// zig fmt: off
const const_inst = block.instructions.items[block.instructions.items.len - 3];
const bitcast_inst = block.instructions.items[block.instructions.items.len - 2];
const store_inst = block.instructions.items[block.instructions.items.len - 1];
const air_tags = sema.air_instructions.items(.tag);
const air_datas = sema.air_instructions.items(.data);
if (air_tags[const_inst] != .constant) break :ct;
if (air_tags[bitcast_inst] != .bitcast ) break :ct;
if (air_tags[store_inst] != .store ) break :ct;
// zig fmt: on
const store_op = air_datas[store_inst].bin_op;
const store_val = (try sema.resolveMaybeUndefVal(block, src, store_op.rhs)) orelse break :ct;
if (store_op.lhs != Air.indexToRef(bitcast_inst)) break :ct;
if (air_datas[bitcast_inst].ty_op.operand != Air.indexToRef(const_inst)) break :ct;
const bitcast_ty_ref = air_datas[bitcast_inst].ty_op.ty;
const new_decl = d: {
var anon_decl = try block.startAnonDecl();
defer anon_decl.deinit();
const new_decl = try anon_decl.finish(
try final_elem_ty.copy(anon_decl.arena()),
try store_val.copy(anon_decl.arena()),
);
break :d new_decl;
};
try sema.mod.declareDeclDependency(sema.owner_decl, new_decl);
// Even though we reuse the constant instruction, we still remove it from the
// block so that codegen does not see it.
block.instructions.shrinkRetainingCapacity(block.instructions.items.len - 3);
sema.air_values.items[value_index] = try Value.Tag.decl_ref.create(sema.arena, new_decl);
air_datas[ptr_inst].ty_pl.ty = bitcast_ty_ref;
return;
}
// Change it to a normal alloc.
const final_ptr_ty = try Type.ptr(sema.arena, .{
.pointee_type = final_elem_ty,

View File

@@ -1047,7 +1047,7 @@ fn lowerDeclRef(self: *Self, ty: Type, val: Value, decl: *Module.Decl) InnerErro
const offset = @intCast(u32, self.code.items.len);
const atom = &self.decl.link.wasm;
const target_sym_index = decl.link.wasm.sym_index;
decl.alive = true;
markDeclAlive(decl);
if (decl.ty.zigTypeTag() == .Fn) {
// We found a function pointer, so add it to our table,
// as function pointers are not allowed to be stored inside the data section,
@@ -1876,7 +1876,7 @@ fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void {
try self.emitConstant(slice.data.len, Type.usize);
} else if (val.castTag(.decl_ref)) |payload| {
const decl = payload.data;
decl.alive = true;
markDeclAlive(decl);
// Function pointers use a table index, rather than a memory address
if (decl.ty.zigTypeTag() == .Fn) {
const target_sym_index = decl.link.wasm.sym_index;
@@ -1985,6 +1985,19 @@ fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void {
}
}
fn markDeclAlive(decl: *Decl) void {
if (decl.alive) return;
decl.alive = true;
// This is the first time we are marking this Decl alive. We must
// therefore recurse into its value and mark any Decl it references
// as also alive, so that any Decl referenced does not get garbage collected.
if (decl.val.pointerDecl()) |pointee| {
return markDeclAlive(pointee);
}
}
fn emitUndefined(self: *Self, ty: Type) InnerError!void {
switch (ty.zigTypeTag()) {
.Int => switch (ty.intInfo(self.target).bits) {

View File

@@ -464,7 +464,7 @@ fn lowerDeclRef(
}
if (decl.analysis != .complete) return error.AnalysisFail;
decl.alive = true;
markDeclAlive(decl);
// TODO handle the dependency of this symbol on the decl's vaddr.
// If the decl changes vaddr, then this symbol needs to get regenerated.
const vaddr = bin_file.getDeclVAddr(decl);
@@ -478,3 +478,16 @@ fn lowerDeclRef(
return Result{ .appended = {} };
}
fn markDeclAlive(decl: *Module.Decl) void {
if (decl.alive) return;
decl.alive = true;
// This is the first time we are marking this Decl alive. We must
// therefore recurse into its value and mark any Decl it references
// as also alive, so that any Decl referenced does not get garbage collected.
if (decl.val.pointerDecl()) |pointee| {
return markDeclAlive(pointee);
}
}

View File

@@ -195,7 +195,7 @@ pub const DeclGen = struct {
val: Value,
decl: *Decl,
) error{ OutOfMemory, AnalysisFail }!void {
decl.alive = true;
markDeclAlive(decl);
if (ty.isSlice()) {
try writer.writeByte('(');
@@ -227,6 +227,19 @@ pub const DeclGen = struct {
try dg.renderDeclName(decl, writer);
}
fn markDeclAlive(decl: *Decl) void {
if (decl.alive) return;
decl.alive = true;
// This is the first time we are marking this Decl alive. We must
// therefore recurse into its value and mark any Decl it references
// as also alive, so that any Decl referenced does not get garbage collected.
if (decl.val.pointerDecl()) |pointee| {
return markDeclAlive(pointee);
}
}
fn renderInt128(
writer: anytype,
int_val: anytype,

View File

@@ -749,7 +749,6 @@ pub const DeclGen = struct {
fn llvmType(dg: *DeclGen, t: Type) Error!*const llvm.Type {
const gpa = dg.gpa;
log.debug("llvmType for {}", .{t});
switch (t.zigTypeTag()) {
.Void, .NoReturn => return dg.context.voidType(),
.Int => {
@@ -1168,7 +1167,7 @@ pub const DeclGen = struct {
.decl_ref => return lowerDeclRefValue(dg, tv, tv.val.castTag(.decl_ref).?.data),
.variable => {
const decl = tv.val.castTag(.variable).?.data.owner_decl;
decl.alive = true;
dg.markDeclAlive(decl);
const val = try dg.resolveGlobalDecl(decl);
const llvm_var_type = try dg.llvmType(tv.ty);
const llvm_addrspace = dg.llvmAddressSpace(decl.@"addrspace");
@@ -1317,7 +1316,7 @@ pub const DeclGen = struct {
.function => tv.val.castTag(.function).?.data.owner_decl,
else => unreachable,
};
fn_decl.alive = true;
dg.markDeclAlive(fn_decl);
return dg.resolveLlvmFunction(fn_decl);
},
.ErrorSet => {
@@ -1625,7 +1624,7 @@ pub const DeclGen = struct {
ptr_val: Value,
decl: *Module.Decl,
) Error!ParentPtr {
decl.alive = true;
dg.markDeclAlive(decl);
var ptr_ty_payload: Type.Payload.ElemType = .{
.base = .{ .tag = .single_mut_pointer },
.data = decl.ty,
@@ -1707,7 +1706,7 @@ pub const DeclGen = struct {
return self.lowerPtrToVoid(tv.ty);
}
decl.alive = true;
self.markDeclAlive(decl);
const llvm_val = if (decl.ty.zigTypeTag() == .Fn)
try self.resolveLlvmFunction(decl)
@@ -1718,6 +1717,24 @@ pub const DeclGen = struct {
return llvm_val.constBitCast(llvm_type);
}
fn markDeclAlive(dg: *DeclGen, decl: *Module.Decl) void {
if (decl.alive) return;
decl.alive = true;
log.debug("{*} ({s}) marked alive by {*} ({s})", .{
decl, decl.name,
dg.decl, dg.decl.name,
});
// This is the first time we are marking this Decl alive. We must
// therefore recurse into its value and mark any Decl it references
// as also alive, so that any Decl referenced does not get garbage collected.
if (decl.val.pointerDecl()) |pointee| {
return dg.markDeclAlive(pointee);
}
}
fn lowerPtrToVoid(dg: *DeclGen, ptr_ty: Type) !*const llvm.Value {
const target = dg.module.getTarget();
const alignment = ptr_ty.ptrAlignment(target);

View File

@@ -285,3 +285,23 @@ fn getB(data: *const BitField1) u3 {
fn getC(data: *const BitField1) u2 {
return data.c;
}
test "default struct initialization fields" {
const S = struct {
a: i32 = 1234,
b: i32,
};
const x = S{
.b = 5,
};
var five: i32 = 5;
const y = S{
.b = five,
};
if (x.a + x.b != 1239) {
@compileError("it should be comptime known");
}
try expect(y.a == x.a);
try expect(y.b == x.b);
try expect(1239 == x.a + x.b);
}

View File

@@ -166,25 +166,6 @@ test "packed struct with fp fields" {
try expectEqual(@as(f32, 20.0), s.data[2]);
}
test "default struct initialization fields" {
const S = struct {
a: i32 = 1234,
b: i32,
};
const x = S{
.b = 5,
};
var five: i32 = 5;
const y = S{
.b = five,
};
if (x.a + x.b != 1239) {
@compileError("it should be comptime known");
}
try expectEqual(y, x);
try expectEqual(1239, x.a + x.b);
}
test "fn with C calling convention returns struct by value" {
const S = struct {
fn entry() !void {