| author | |
| committer | |
| log | 8c6175c1343a00278efc029a0be4091ff505dc3d |
| tree | 63f9c87d0f37757f5c0c0b543fc94ea4fe68ae22 |
| parent | 713d2a9b3883942491b40738245232680877cc66 |
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.7 files changed, 135 insertions(+), 28 deletions(-)
src/Sema.zig+50| ... | @@ -2427,7 +2427,57 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com | ... | @@ -2427,7 +2427,57 @@ fn zirResolveInferredAlloc(sema: *Sema, block: *Block, inst: Zir.Inst.Index) Com |
| 2427 | 2427 | ||
| 2428 | if (var_is_mut) { | 2428 | if (var_is_mut) { |
| 2429 | try sema.validateVarType(block, ty_src, final_elem_ty, false); | 2429 | try sema.validateVarType(block, ty_src, final_elem_ty, false); |
| 2430 | } else ct: { | ||
| 2431 | // Detect if the value is comptime known. In such case, the | ||
| 2432 | // last 3 AIR instructions of the block will look like this: | ||
| 2433 | // | ||
| 2434 | // %a = constant | ||
| 2435 | // %b = bitcast(%a) | ||
| 2436 | // %c = store(%b, %d) | ||
| 2437 | // | ||
| 2438 | // If `%d` is comptime-known, then we want to store the value | ||
| 2439 | // inside an anonymous Decl and then erase these three AIR | ||
| 2440 | // instructions from the block, replacing the inst_map entry | ||
| 2441 | // corresponding to the ZIR alloc instruction with a constant | ||
| 2442 | // decl_ref pointing at our new Decl. | ||
| 2443 | if (block.instructions.items.len < 3) break :ct; | ||
| 2444 | // zig fmt: off | ||
| 2445 | const const_inst = block.instructions.items[block.instructions.items.len - 3]; | ||
| 2446 | const bitcast_inst = block.instructions.items[block.instructions.items.len - 2]; | ||
| 2447 | const store_inst = block.instructions.items[block.instructions.items.len - 1]; | ||
| 2448 | const air_tags = sema.air_instructions.items(.tag); | ||
| 2449 | const air_datas = sema.air_instructions.items(.data); | ||
| 2450 | if (air_tags[const_inst] != .constant) break :ct; | ||
| 2451 | if (air_tags[bitcast_inst] != .bitcast ) break :ct; | ||
| 2452 | if (air_tags[store_inst] != .store ) break :ct; | ||
| 2453 | // zig fmt: on | ||
| 2454 | const store_op = air_datas[store_inst].bin_op; | ||
| 2455 | const store_val = (try sema.resolveMaybeUndefVal(block, src, store_op.rhs)) orelse break :ct; | ||
| 2456 | if (store_op.lhs != Air.indexToRef(bitcast_inst)) break :ct; | ||
| 2457 | if (air_datas[bitcast_inst].ty_op.operand != Air.indexToRef(const_inst)) break :ct; | ||
| 2458 | |||
| 2459 | const bitcast_ty_ref = air_datas[bitcast_inst].ty_op.ty; | ||
| 2460 | |||
| 2461 | const new_decl = d: { | ||
| 2462 | var anon_decl = try block.startAnonDecl(); | ||
| 2463 | defer anon_decl.deinit(); | ||
| 2464 | const new_decl = try anon_decl.finish( | ||
| 2465 | try final_elem_ty.copy(anon_decl.arena()), | ||
| 2466 | try store_val.copy(anon_decl.arena()), | ||
| 2467 | ); | ||
| 2468 | break :d new_decl; | ||
| 2469 | }; | ||
| 2470 | try sema.mod.declareDeclDependency(sema.owner_decl, new_decl); | ||
| 2471 | |||
| 2472 | // Even though we reuse the constant instruction, we still remove it from the | ||
| 2473 | // block so that codegen does not see it. | ||
| 2474 | block.instructions.shrinkRetainingCapacity(block.instructions.items.len - 3); | ||
| 2475 | sema.air_values.items[value_index] = try Value.Tag.decl_ref.create(sema.arena, new_decl); | ||
| 2476 | air_datas[ptr_inst].ty_pl.ty = bitcast_ty_ref; | ||
| 2477 | |||
| 2478 | return; | ||
| 2430 | } | 2479 | } |
| 2480 | |||
| 2431 | // Change it to a normal alloc. | 2481 | // Change it to a normal alloc. |
| 2432 | const final_ptr_ty = try Type.ptr(sema.arena, .{ | 2482 | const final_ptr_ty = try Type.ptr(sema.arena, .{ |
| 2433 | .pointee_type = final_elem_ty, | 2483 | .pointee_type = final_elem_ty, |
src/arch/wasm/CodeGen.zig+15-2| ... | @@ -1047,7 +1047,7 @@ fn lowerDeclRef(self: *Self, ty: Type, val: Value, decl: *Module.Decl) InnerErro | ... | @@ -1047,7 +1047,7 @@ fn lowerDeclRef(self: *Self, ty: Type, val: Value, decl: *Module.Decl) InnerErro |
| 1047 | const offset = @intCast(u32, self.code.items.len); | 1047 | const offset = @intCast(u32, self.code.items.len); |
| 1048 | const atom = &self.decl.link.wasm; | 1048 | const atom = &self.decl.link.wasm; |
| 1049 | const target_sym_index = decl.link.wasm.sym_index; | 1049 | const target_sym_index = decl.link.wasm.sym_index; |
| 1050 | decl.alive = true; | 1050 | markDeclAlive(decl); |
| 1051 | if (decl.ty.zigTypeTag() == .Fn) { | 1051 | if (decl.ty.zigTypeTag() == .Fn) { |
| 1052 | // We found a function pointer, so add it to our table, | 1052 | // We found a function pointer, so add it to our table, |
| 1053 | // as function pointers are not allowed to be stored inside the data section, | 1053 | // 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 { | ... | @@ -1876,7 +1876,7 @@ fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void { |
| 1876 | try self.emitConstant(slice.data.len, Type.usize); | 1876 | try self.emitConstant(slice.data.len, Type.usize); |
| 1877 | } else if (val.castTag(.decl_ref)) |payload| { | 1877 | } else if (val.castTag(.decl_ref)) |payload| { |
| 1878 | const decl = payload.data; | 1878 | const decl = payload.data; |
| 1879 | decl.alive = true; | 1879 | markDeclAlive(decl); |
| 1880 | // Function pointers use a table index, rather than a memory address | 1880 | // Function pointers use a table index, rather than a memory address |
| 1881 | if (decl.ty.zigTypeTag() == .Fn) { | 1881 | if (decl.ty.zigTypeTag() == .Fn) { |
| 1882 | const target_sym_index = decl.link.wasm.sym_index; | 1882 | const target_sym_index = decl.link.wasm.sym_index; |
| ... | @@ -1985,6 +1985,19 @@ fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void { | ... | @@ -1985,6 +1985,19 @@ fn emitConstant(self: *Self, val: Value, ty: Type) InnerError!void { |
| 1985 | } | 1985 | } |
| 1986 | } | 1986 | } |
| 1987 | 1987 | ||
| 1988 | fn markDeclAlive(decl: *Decl) void { | ||
| 1989 | if (decl.alive) return; | ||
| 1990 | decl.alive = true; | ||
| 1991 | |||
| 1992 | // This is the first time we are marking this Decl alive. We must | ||
| 1993 | // therefore recurse into its value and mark any Decl it references | ||
| 1994 | // as also alive, so that any Decl referenced does not get garbage collected. | ||
| 1995 | |||
| 1996 | if (decl.val.pointerDecl()) |pointee| { | ||
| 1997 | return markDeclAlive(pointee); | ||
| 1998 | } | ||
| 1999 | } | ||
| 2000 | |||
| 1988 | fn emitUndefined(self: *Self, ty: Type) InnerError!void { | 2001 | fn emitUndefined(self: *Self, ty: Type) InnerError!void { |
| 1989 | switch (ty.zigTypeTag()) { | 2002 | switch (ty.zigTypeTag()) { |
| 1990 | .Int => switch (ty.intInfo(self.target).bits) { | 2003 | .Int => switch (ty.intInfo(self.target).bits) { |
src/codegen.zig+14-1| ... | @@ -464,7 +464,7 @@ fn lowerDeclRef( | ... | @@ -464,7 +464,7 @@ fn lowerDeclRef( |
| 464 | } | 464 | } |
| 465 | 465 | ||
| 466 | if (decl.analysis != .complete) return error.AnalysisFail; | 466 | if (decl.analysis != .complete) return error.AnalysisFail; |
| 467 | decl.alive = true; | 467 | markDeclAlive(decl); |
| 468 | // TODO handle the dependency of this symbol on the decl's vaddr. | 468 | // TODO handle the dependency of this symbol on the decl's vaddr. |
| 469 | // If the decl changes vaddr, then this symbol needs to get regenerated. | 469 | // If the decl changes vaddr, then this symbol needs to get regenerated. |
| 470 | const vaddr = bin_file.getDeclVAddr(decl); | 470 | const vaddr = bin_file.getDeclVAddr(decl); |
| ... | @@ -478,3 +478,16 @@ fn lowerDeclRef( | ... | @@ -478,3 +478,16 @@ fn lowerDeclRef( |
| 478 | 478 | ||
| 479 | return Result{ .appended = {} }; | 479 | return Result{ .appended = {} }; |
| 480 | } | 480 | } |
| 481 | |||
| 482 | fn markDeclAlive(decl: *Module.Decl) void { | ||
| 483 | if (decl.alive) return; | ||
| 484 | decl.alive = true; | ||
| 485 | |||
| 486 | // This is the first time we are marking this Decl alive. We must | ||
| 487 | // therefore recurse into its value and mark any Decl it references | ||
| 488 | // as also alive, so that any Decl referenced does not get garbage collected. | ||
| 489 | |||
| 490 | if (decl.val.pointerDecl()) |pointee| { | ||
| 491 | return markDeclAlive(pointee); | ||
| 492 | } | ||
| 493 | } |
src/codegen/c.zig+14-1| ... | @@ -195,7 +195,7 @@ pub const DeclGen = struct { | ... | @@ -195,7 +195,7 @@ pub const DeclGen = struct { |
| 195 | val: Value, | 195 | val: Value, |
| 196 | decl: *Decl, | 196 | decl: *Decl, |
| 197 | ) error{ OutOfMemory, AnalysisFail }!void { | 197 | ) error{ OutOfMemory, AnalysisFail }!void { |
| 198 | decl.alive = true; | 198 | markDeclAlive(decl); |
| 199 | 199 | ||
| 200 | if (ty.isSlice()) { | 200 | if (ty.isSlice()) { |
| 201 | try writer.writeByte('('); | 201 | try writer.writeByte('('); |
| ... | @@ -227,6 +227,19 @@ pub const DeclGen = struct { | ... | @@ -227,6 +227,19 @@ pub const DeclGen = struct { |
| 227 | try dg.renderDeclName(decl, writer); | 227 | try dg.renderDeclName(decl, writer); |
| 228 | } | 228 | } |
| 229 | 229 | ||
| 230 | fn markDeclAlive(decl: *Decl) void { | ||
| 231 | if (decl.alive) return; | ||
| 232 | decl.alive = true; | ||
| 233 | |||
| 234 | // This is the first time we are marking this Decl alive. We must | ||
| 235 | // therefore recurse into its value and mark any Decl it references | ||
| 236 | // as also alive, so that any Decl referenced does not get garbage collected. | ||
| 237 | |||
| 238 | if (decl.val.pointerDecl()) |pointee| { | ||
| 239 | return markDeclAlive(pointee); | ||
| 240 | } | ||
| 241 | } | ||
| 242 | |||
| 230 | fn renderInt128( | 243 | fn renderInt128( |
| 231 | writer: anytype, | 244 | writer: anytype, |
| 232 | int_val: anytype, | 245 | int_val: anytype, |
src/codegen/llvm.zig+22-5| ... | @@ -749,7 +749,6 @@ pub const DeclGen = struct { | ... | @@ -749,7 +749,6 @@ pub const DeclGen = struct { |
| 749 | 749 | ||
| 750 | fn llvmType(dg: *DeclGen, t: Type) Error!*const llvm.Type { | 750 | fn llvmType(dg: *DeclGen, t: Type) Error!*const llvm.Type { |
| 751 | const gpa = dg.gpa; | 751 | const gpa = dg.gpa; |
| 752 | log.debug("llvmType for {}", .{t}); | ||
| 753 | switch (t.zigTypeTag()) { | 752 | switch (t.zigTypeTag()) { |
| 754 | .Void, .NoReturn => return dg.context.voidType(), | 753 | .Void, .NoReturn => return dg.context.voidType(), |
| 755 | .Int => { | 754 | .Int => { |
| ... | @@ -1168,7 +1167,7 @@ pub const DeclGen = struct { | ... | @@ -1168,7 +1167,7 @@ pub const DeclGen = struct { |
| 1168 | .decl_ref => return lowerDeclRefValue(dg, tv, tv.val.castTag(.decl_ref).?.data), | 1167 | .decl_ref => return lowerDeclRefValue(dg, tv, tv.val.castTag(.decl_ref).?.data), |
| 1169 | .variable => { | 1168 | .variable => { |
| 1170 | const decl = tv.val.castTag(.variable).?.data.owner_decl; | 1169 | const decl = tv.val.castTag(.variable).?.data.owner_decl; |
| 1171 | decl.alive = true; | 1170 | dg.markDeclAlive(decl); |
| 1172 | const val = try dg.resolveGlobalDecl(decl); | 1171 | const val = try dg.resolveGlobalDecl(decl); |
| 1173 | const llvm_var_type = try dg.llvmType(tv.ty); | 1172 | const llvm_var_type = try dg.llvmType(tv.ty); |
| 1174 | const llvm_addrspace = dg.llvmAddressSpace(decl.@"addrspace"); | 1173 | const llvm_addrspace = dg.llvmAddressSpace(decl.@"addrspace"); |
| ... | @@ -1317,7 +1316,7 @@ pub const DeclGen = struct { | ... | @@ -1317,7 +1316,7 @@ pub const DeclGen = struct { |
| 1317 | .function => tv.val.castTag(.function).?.data.owner_decl, | 1316 | .function => tv.val.castTag(.function).?.data.owner_decl, |
| 1318 | else => unreachable, | 1317 | else => unreachable, |
| 1319 | }; | 1318 | }; |
| 1320 | fn_decl.alive = true; | 1319 | dg.markDeclAlive(fn_decl); |
| 1321 | return dg.resolveLlvmFunction(fn_decl); | 1320 | return dg.resolveLlvmFunction(fn_decl); |
| 1322 | }, | 1321 | }, |
| 1323 | .ErrorSet => { | 1322 | .ErrorSet => { |
| ... | @@ -1625,7 +1624,7 @@ pub const DeclGen = struct { | ... | @@ -1625,7 +1624,7 @@ pub const DeclGen = struct { |
| 1625 | ptr_val: Value, | 1624 | ptr_val: Value, |
| 1626 | decl: *Module.Decl, | 1625 | decl: *Module.Decl, |
| 1627 | ) Error!ParentPtr { | 1626 | ) Error!ParentPtr { |
| 1628 | decl.alive = true; | 1627 | dg.markDeclAlive(decl); |
| 1629 | var ptr_ty_payload: Type.Payload.ElemType = .{ | 1628 | var ptr_ty_payload: Type.Payload.ElemType = .{ |
| 1630 | .base = .{ .tag = .single_mut_pointer }, | 1629 | .base = .{ .tag = .single_mut_pointer }, |
| 1631 | .data = decl.ty, | 1630 | .data = decl.ty, |
| ... | @@ -1707,7 +1706,7 @@ pub const DeclGen = struct { | ... | @@ -1707,7 +1706,7 @@ pub const DeclGen = struct { |
| 1707 | return self.lowerPtrToVoid(tv.ty); | 1706 | return self.lowerPtrToVoid(tv.ty); |
| 1708 | } | 1707 | } |
| 1709 | 1708 | ||
| 1710 | decl.alive = true; | 1709 | self.markDeclAlive(decl); |
| 1711 | 1710 | ||
| 1712 | const llvm_val = if (decl.ty.zigTypeTag() == .Fn) | 1711 | const llvm_val = if (decl.ty.zigTypeTag() == .Fn) |
| 1713 | try self.resolveLlvmFunction(decl) | 1712 | try self.resolveLlvmFunction(decl) |
| ... | @@ -1718,6 +1717,24 @@ pub const DeclGen = struct { | ... | @@ -1718,6 +1717,24 @@ pub const DeclGen = struct { |
| 1718 | return llvm_val.constBitCast(llvm_type); | 1717 | return llvm_val.constBitCast(llvm_type); |
| 1719 | } | 1718 | } |
| 1720 | 1719 | ||
| 1720 | fn markDeclAlive(dg: *DeclGen, decl: *Module.Decl) void { | ||
| 1721 | if (decl.alive) return; | ||
| 1722 | decl.alive = true; | ||
| 1723 | |||
| 1724 | log.debug("{*} ({s}) marked alive by {*} ({s})", .{ | ||
| 1725 | decl, decl.name, | ||
| 1726 | dg.decl, dg.decl.name, | ||
| 1727 | }); | ||
| 1728 | |||
| 1729 | // This is the first time we are marking this Decl alive. We must | ||
| 1730 | // therefore recurse into its value and mark any Decl it references | ||
| 1731 | // as also alive, so that any Decl referenced does not get garbage collected. | ||
| 1732 | |||
| 1733 | if (decl.val.pointerDecl()) |pointee| { | ||
| 1734 | return dg.markDeclAlive(pointee); | ||
| 1735 | } | ||
| 1736 | } | ||
| 1737 | |||
| 1721 | fn lowerPtrToVoid(dg: *DeclGen, ptr_ty: Type) !*const llvm.Value { | 1738 | fn lowerPtrToVoid(dg: *DeclGen, ptr_ty: Type) !*const llvm.Value { |
| 1722 | const target = dg.module.getTarget(); | 1739 | const target = dg.module.getTarget(); |
| 1723 | const alignment = ptr_ty.ptrAlignment(target); | 1740 | const alignment = ptr_ty.ptrAlignment(target); |
test/behavior/struct_llvm.zig+20| ... | @@ -285,3 +285,23 @@ fn getB(data: *const BitField1) u3 { | ... | @@ -285,3 +285,23 @@ fn getB(data: *const BitField1) u3 { |
| 285 | fn getC(data: *const BitField1) u2 { | 285 | fn getC(data: *const BitField1) u2 { |
| 286 | return data.c; | 286 | return data.c; |
| 287 | } | 287 | } |
| 288 | |||
| 289 | test "default struct initialization fields" { | ||
| 290 | const S = struct { | ||
| 291 | a: i32 = 1234, | ||
| 292 | b: i32, | ||
| 293 | }; | ||
| 294 | const x = S{ | ||
| 295 | .b = 5, | ||
| 296 | }; | ||
| 297 | var five: i32 = 5; | ||
| 298 | const y = S{ | ||
| 299 | .b = five, | ||
| 300 | }; | ||
| 301 | if (x.a + x.b != 1239) { | ||
| 302 | @compileError("it should be comptime known"); | ||
| 303 | } | ||
| 304 | try expect(y.a == x.a); | ||
| 305 | try expect(y.b == x.b); | ||
| 306 | try expect(1239 == x.a + x.b); | ||
| 307 | } |
test/behavior/struct_stage1.zig-19| ... | @@ -166,25 +166,6 @@ test "packed struct with fp fields" { | ... | @@ -166,25 +166,6 @@ test "packed struct with fp fields" { |
| 166 | try expectEqual(@as(f32, 20.0), s.data[2]); | 166 | try expectEqual(@as(f32, 20.0), s.data[2]); |
| 167 | } | 167 | } |
| 168 | 168 | ||
| 169 | test "default struct initialization fields" { | ||
| 170 | const S = struct { | ||
| 171 | a: i32 = 1234, | ||
| 172 | b: i32, | ||
| 173 | }; | ||
| 174 | const x = S{ | ||
| 175 | .b = 5, | ||
| 176 | }; | ||
| 177 | var five: i32 = 5; | ||
| 178 | const y = S{ | ||
| 179 | .b = five, | ||
| 180 | }; | ||
| 181 | if (x.a + x.b != 1239) { | ||
| 182 | @compileError("it should be comptime known"); | ||
| 183 | } | ||
| 184 | try expectEqual(y, x); | ||
| 185 | try expectEqual(1239, x.a + x.b); | ||
| 186 | } | ||
| 187 | |||
| 188 | test "fn with C calling convention returns struct by value" { | 169 | test "fn with C calling convention returns struct by value" { |
| 189 | const S = struct { | 170 | const S = struct { |
| 190 | fn entry() !void { | 171 | fn entry() !void { |