stage2: garbage collect unused anon decls

After this change, the frontend and backend cooperate to keep track of
which Decls are actually emitted into the machine code. When any backend
sees a `decl_ref` Value, it must mark the corresponding Decl `alive`
field to true.

This prevents unused comptime data from spilling into the output object
files. For example, if you do an `inline for` loop, previously, any
intermediate value calculations would have gone into the object file.
Now they are garbage collected immediately after the owner Decl has its
machine code generated.

In the frontend, when it is time to send a Decl to the linker, if it has
not been marked "alive" then it is deleted instead.

Additional improvements:
 * Resolve type ABI layouts after successful semantic analysis of a
   Decl. This is needed so that the backend has access to struct fields.
 * Sema: fix incorrect logic in resolveMaybeUndefVal. It should return
   "not comptime known" instead of a compile error for global variables.
 * `Value.pointerDeref` now returns `null` in the case that the pointer
   deref cannot happen at compile-time. This is true for global
   variables, for example. Another example is if a comptime known
   pointer has a hard coded address value.
 * Binary arithmetic sets the requireRuntimeBlock source location to the
   lhs_src or rhs_src as appropriate instead of on the operator node.
 * Fix LLVM codegen for slice_elem_val which had the wrong logic for
   when the operand was not a pointer.

As noted in the comment in the implementation of deleteUnusedDecl, a
future improvement will be to rework the frontend/linker interface to
remove the frontend's responsibility of calling allocateDeclIndexes.

I discovered some issues with the plan9 linker backend that are related
to this, and worked around them for now.
This commit is contained in:
Andrew Kelley 2021-07-29 19:30:37 -07:00
parent a5c6e51f03
commit 040c6eaaa0
10 changed files with 234 additions and 139 deletions

View File

@ -2061,11 +2061,19 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
.complete, .codegen_failure_retryable => { .complete, .codegen_failure_retryable => {
if (build_options.omit_stage2) if (build_options.omit_stage2)
@panic("sadly stage2 is omitted from this build to save memory on the CI server"); @panic("sadly stage2 is omitted from this build to save memory on the CI server");
const module = self.bin_file.options.module.?; const module = self.bin_file.options.module.?;
assert(decl.has_tv); assert(decl.has_tv);
assert(decl.ty.hasCodeGenBits()); assert(decl.ty.hasCodeGenBits());
try module.linkerUpdateDecl(decl); if (decl.alive) {
try module.linkerUpdateDecl(decl);
continue;
}
// Instead of sending this decl to the linker, we actually will delete it
// because we found out that it in fact was never referenced.
module.deleteUnusedDecl(decl);
}, },
}, },
.codegen_func => |func| switch (func.owner_decl.analysis) { .codegen_func => |func| switch (func.owner_decl.analysis) {

View File

@ -255,6 +255,15 @@ pub const Decl = struct {
has_align: bool, has_align: bool,
/// Whether the ZIR code provides a linksection instruction. /// Whether the ZIR code provides a linksection instruction.
has_linksection: bool, has_linksection: bool,
/// Flag used by garbage collection to mark and sweep.
/// Decls which correspond to an AST node always have this field set to `true`.
/// Anonymous Decls are initialized with this field set to `false` and then it
/// is the responsibility of machine code backends to mark it `true` whenever
/// a `decl_ref` Value is encountered that points to this Decl.
/// When the `codegen_decl` job is encountered in the main work queue, if the
/// Decl is marked alive, then it sends the Decl to the linker. Otherwise it
/// deletes the Decl on the spot.
alive: bool,
/// Represents the position of the code in the output file. /// Represents the position of the code in the output file.
/// This is populated regardless of semantic analysis and code generation. /// This is populated regardless of semantic analysis and code generation.
@ -2869,6 +2878,7 @@ pub fn semaFile(mod: *Module, file: *Scope.File) SemaError!void {
new_decl.val = struct_val; new_decl.val = struct_val;
new_decl.has_tv = true; new_decl.has_tv = true;
new_decl.owns_tv = true; new_decl.owns_tv = true;
new_decl.alive = true; // This Decl corresponds to a File and is therefore always alive.
new_decl.analysis = .in_progress; new_decl.analysis = .in_progress;
new_decl.generation = mod.generation; new_decl.generation = mod.generation;
@ -2990,6 +3000,7 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
if (linksection_ref == .none) break :blk Value.initTag(.null_value); if (linksection_ref == .none) break :blk Value.initTag(.null_value);
break :blk (try sema.resolveInstConst(&block_scope, src, linksection_ref)).val; break :blk (try sema.resolveInstConst(&block_scope, src, linksection_ref)).val;
}; };
try sema.resolveTypeLayout(&block_scope, src, decl_tv.ty);
// We need the memory for the Type to go into the arena for the Decl // We need the memory for the Type to go into the arena for the Decl
var decl_arena = std.heap.ArenaAllocator.init(gpa); var decl_arena = std.heap.ArenaAllocator.init(gpa);
@ -3027,8 +3038,8 @@ fn semaDecl(mod: *Module, decl: *Decl) !bool {
const is_inline = decl_tv.ty.fnCallingConvention() == .Inline; const is_inline = decl_tv.ty.fnCallingConvention() == .Inline;
if (!is_inline and decl_tv.ty.hasCodeGenBits()) { if (!is_inline and decl_tv.ty.hasCodeGenBits()) {
// We don't fully codegen the decl until later, but we do need to reserve a global // We don't fully codegen the decl until later, but we do need to reserve a global
// offset table index for it. This allows us to codegen decls out of dependency order, // offset table index for it. This allows us to codegen decls out of dependency
// increasing how many computations can be done in parallel. // order, increasing how many computations can be done in parallel.
try mod.comp.bin_file.allocateDeclIndexes(decl); try mod.comp.bin_file.allocateDeclIndexes(decl);
try mod.comp.work_queue.writeItem(.{ .codegen_func = func }); try mod.comp.work_queue.writeItem(.{ .codegen_func = func });
if (type_changed and mod.emit_h != null) { if (type_changed and mod.emit_h != null) {
@ -3387,6 +3398,7 @@ fn scanDecl(iter: *ScanDeclIter, decl_sub_index: usize, flags: u4) SemaError!voi
new_decl.has_align = has_align; new_decl.has_align = has_align;
new_decl.has_linksection = has_linksection; new_decl.has_linksection = has_linksection;
new_decl.zir_decl_index = @intCast(u32, decl_sub_index); new_decl.zir_decl_index = @intCast(u32, decl_sub_index);
new_decl.alive = true; // This Decl corresponds to an AST node and therefore always alive.
return; return;
} }
gpa.free(decl_name); gpa.free(decl_name);
@ -3526,6 +3538,43 @@ pub fn clearDecl(
decl.analysis = .unreferenced; decl.analysis = .unreferenced;
} }
pub fn deleteUnusedDecl(mod: *Module, decl: *Decl) void {
log.debug("deleteUnusedDecl {*} ({s})", .{ decl, decl.name });
// TODO: remove `allocateDeclIndexes` and make the API that the linker backends
// are required to notice the first time `updateDecl` happens and keep track
// of it themselves. However they can rely on getting a `freeDecl` call if any
// `updateDecl` or `updateFunc` calls happen. This will allow us to avoid any call
// into the linker backend here, since the linker backend will never have been told
// about the Decl in the first place.
// Until then, we did call `allocateDeclIndexes` on this anonymous Decl and so we
// must call `freeDecl` in the linker backend now.
if (decl.has_tv) {
if (decl.ty.hasCodeGenBits()) {
mod.comp.bin_file.freeDecl(decl);
}
}
const dependants = decl.dependants.keys();
assert(dependants[0].namespace.anon_decls.swapRemove(decl));
for (dependants) |dep| {
dep.removeDependency(decl);
}
for (decl.dependencies.keys()) |dep| {
dep.removeDependant(decl);
}
decl.destroy(mod);
}
pub fn deleteAnonDecl(mod: *Module, scope: *Scope, decl: *Decl) void {
log.debug("deleteAnonDecl {*} ({s})", .{ decl, decl.name });
const scope_decl = scope.ownerDecl().?;
assert(scope_decl.namespace.anon_decls.swapRemove(decl));
decl.destroy(mod);
}
/// Delete all the Export objects that are caused by this Decl. Re-analysis of /// Delete all the Export objects that are caused by this Decl. Re-analysis of
/// this Decl will cause them to be re-created (or not). /// this Decl will cause them to be re-created (or not).
fn deleteDeclExports(mod: *Module, decl: *Decl) void { fn deleteDeclExports(mod: *Module, decl: *Decl) void {
@ -3713,6 +3762,7 @@ fn allocateNewDecl(mod: *Module, namespace: *Scope.Namespace, src_node: ast.Node
.is_exported = false, .is_exported = false,
.has_linksection = false, .has_linksection = false,
.has_align = false, .has_align = false,
.alive = false,
}; };
return new_decl; return new_decl;
} }
@ -3802,12 +3852,6 @@ pub fn analyzeExport(
errdefer de_gop.value_ptr.* = mod.gpa.shrink(de_gop.value_ptr.*, de_gop.value_ptr.len - 1); errdefer de_gop.value_ptr.* = mod.gpa.shrink(de_gop.value_ptr.*, de_gop.value_ptr.len - 1);
} }
pub fn deleteAnonDecl(mod: *Module, scope: *Scope, decl: *Decl) void {
const scope_decl = scope.ownerDecl().?;
assert(scope_decl.namespace.anon_decls.swapRemove(decl));
decl.destroy(mod);
}
/// Takes ownership of `name` even if it returns an error. /// Takes ownership of `name` even if it returns an error.
pub fn createAnonymousDeclNamed( pub fn createAnonymousDeclNamed(
mod: *Module, mod: *Module,

View File

@ -696,7 +696,7 @@ fn resolveMaybeUndefVal(
) CompileError!?Value { ) CompileError!?Value {
const val = (try sema.resolveMaybeUndefValAllowVariables(block, src, inst)) orelse return null; const val = (try sema.resolveMaybeUndefValAllowVariables(block, src, inst)) orelse return null;
if (val.tag() == .variable) { if (val.tag() == .variable) {
return sema.failWithNeededComptime(block, src); return null;
} }
return val; return val;
} }
@ -2917,12 +2917,13 @@ fn zirOptionalPayloadPtr(
const child_pointer = try Module.simplePtrType(sema.arena, child_type, !optional_ptr_ty.isConstPtr(), .One); const child_pointer = try Module.simplePtrType(sema.arena, child_type, !optional_ptr_ty.isConstPtr(), .One);
if (try sema.resolveDefinedValue(block, src, optional_ptr)) |pointer_val| { if (try sema.resolveDefinedValue(block, src, optional_ptr)) |pointer_val| {
const val = try pointer_val.pointerDeref(sema.arena); if (try pointer_val.pointerDeref(sema.arena)) |val| {
if (val.isNull()) { if (val.isNull()) {
return sema.mod.fail(&block.base, src, "unable to unwrap null", .{}); return sema.mod.fail(&block.base, src, "unable to unwrap null", .{});
}
// The same Value represents the pointer to the optional and the payload.
return sema.addConstant(child_pointer, pointer_val);
} }
// The same Value represents the pointer to the optional and the payload.
return sema.addConstant(child_pointer, pointer_val);
} }
try sema.requireRuntimeBlock(block, src); try sema.requireRuntimeBlock(block, src);
@ -3027,14 +3028,15 @@ fn zirErrUnionPayloadPtr(
const operand_pointer_ty = try Module.simplePtrType(sema.arena, payload_ty, !operand_ty.isConstPtr(), .One); const operand_pointer_ty = try Module.simplePtrType(sema.arena, payload_ty, !operand_ty.isConstPtr(), .One);
if (try sema.resolveDefinedValue(block, src, operand)) |pointer_val| { if (try sema.resolveDefinedValue(block, src, operand)) |pointer_val| {
const val = try pointer_val.pointerDeref(sema.arena); if (try pointer_val.pointerDeref(sema.arena)) |val| {
if (val.getError()) |name| { if (val.getError()) |name| {
return sema.mod.fail(&block.base, src, "caught unexpected error '{s}'", .{name}); return sema.mod.fail(&block.base, src, "caught unexpected error '{s}'", .{name});
}
return sema.addConstant(
operand_pointer_ty,
try Value.Tag.eu_payload_ptr.create(sema.arena, pointer_val),
);
} }
return sema.addConstant(
operand_pointer_ty,
try Value.Tag.eu_payload_ptr.create(sema.arena, pointer_val),
);
} }
try sema.requireRuntimeBlock(block, src); try sema.requireRuntimeBlock(block, src);
@ -3086,10 +3088,11 @@ fn zirErrUnionCodePtr(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) Co
const result_ty = operand_ty.elemType().errorUnionSet(); const result_ty = operand_ty.elemType().errorUnionSet();
if (try sema.resolveDefinedValue(block, src, operand)) |pointer_val| { if (try sema.resolveDefinedValue(block, src, operand)) |pointer_val| {
const val = try pointer_val.pointerDeref(sema.arena); if (try pointer_val.pointerDeref(sema.arena)) |val| {
assert(val.getError() != null); assert(val.getError() != null);
const data = val.castTag(.error_union).?.data; const data = val.castTag(.error_union).?.data;
return sema.addConstant(result_ty, data); return sema.addConstant(result_ty, data);
}
} }
try sema.requireRuntimeBlock(block, src); try sema.requireRuntimeBlock(block, src);
@ -4920,10 +4923,13 @@ fn analyzeArithmetic(
log.debug("{s}({}, {}) result: {}", .{ @tagName(zir_tag), lhs_val, rhs_val, value }); log.debug("{s}({}, {}) result: {}", .{ @tagName(zir_tag), lhs_val, rhs_val, value });
return sema.addConstant(scalar_type, value); return sema.addConstant(scalar_type, value);
} else {
try sema.requireRuntimeBlock(block, rhs_src);
} }
} else {
try sema.requireRuntimeBlock(block, lhs_src);
} }
try sema.requireRuntimeBlock(block, src);
const air_tag: Air.Inst.Tag = switch (zir_tag) { const air_tag: Air.Inst.Tag = switch (zir_tag) {
.add => .add, .add => .add,
.addwrap => .addwrap, .addwrap => .addwrap,
@ -6811,16 +6817,10 @@ fn fieldPtr(
if (mem.eql(u8, field_name, "len")) { if (mem.eql(u8, field_name, "len")) {
var anon_decl = try block.startAnonDecl(); var anon_decl = try block.startAnonDecl();
defer anon_decl.deinit(); defer anon_decl.deinit();
return sema.addConstant( return sema.analyzeDeclRef(try anon_decl.finish(
Type.initTag(.single_const_pointer_to_comptime_int), Type.initTag(.comptime_int),
try Value.Tag.decl_ref.create( try Value.Tag.int_u64.create(anon_decl.arena(), object_ty.arrayLen()),
arena, ));
try anon_decl.finish(
Type.initTag(.comptime_int),
try Value.Tag.int_u64.create(anon_decl.arena(), object_ty.arrayLen()),
),
),
);
} else { } else {
return mod.fail( return mod.fail(
&block.base, &block.base,
@ -6867,16 +6867,10 @@ fn fieldPtr(
if (mem.eql(u8, field_name, "len")) { if (mem.eql(u8, field_name, "len")) {
var anon_decl = try block.startAnonDecl(); var anon_decl = try block.startAnonDecl();
defer anon_decl.deinit(); defer anon_decl.deinit();
return sema.addConstant( return sema.analyzeDeclRef(try anon_decl.finish(
Type.initTag(.single_const_pointer_to_comptime_int), Type.initTag(.comptime_int),
try Value.Tag.decl_ref.create( try Value.Tag.int_u64.create(anon_decl.arena(), ptr_child.arrayLen()),
arena, ));
try anon_decl.finish(
Type.initTag(.comptime_int),
try Value.Tag.int_u64.create(anon_decl.arena(), ptr_child.arrayLen()),
),
),
);
} else { } else {
return mod.fail( return mod.fail(
&block.base, &block.base,
@ -6915,16 +6909,10 @@ fn fieldPtr(
var anon_decl = try block.startAnonDecl(); var anon_decl = try block.startAnonDecl();
defer anon_decl.deinit(); defer anon_decl.deinit();
return sema.addConstant( return sema.analyzeDeclRef(try anon_decl.finish(
try Module.simplePtrType(arena, child_type, false, .One), child_type,
try Value.Tag.decl_ref.create( try Value.Tag.@"error".create(anon_decl.arena(), .{ .name = name }),
arena, ));
try anon_decl.finish(
child_type,
try Value.Tag.@"error".create(anon_decl.arena(), .{ .name = name }),
),
),
);
}, },
.Struct, .Opaque, .Union => { .Struct, .Opaque, .Union => {
if (child_type.getNamespace()) |namespace| { if (child_type.getNamespace()) |namespace| {
@ -6971,16 +6959,10 @@ fn fieldPtr(
const field_index_u32 = @intCast(u32, field_index); const field_index_u32 = @intCast(u32, field_index);
var anon_decl = try block.startAnonDecl(); var anon_decl = try block.startAnonDecl();
defer anon_decl.deinit(); defer anon_decl.deinit();
return sema.addConstant( return sema.analyzeDeclRef(try anon_decl.finish(
try Module.simplePtrType(arena, child_type, false, .One), child_type,
try Value.Tag.decl_ref.create( try Value.Tag.enum_field_index.create(anon_decl.arena(), field_index_u32),
arena, ));
try anon_decl.finish(
child_type,
try Value.Tag.enum_field_index.create(anon_decl.arena(), field_index_u32),
),
),
);
}, },
else => return mod.fail(&block.base, src, "type '{}' has no members", .{child_type}), else => return mod.fail(&block.base, src, "type '{}' has no members", .{child_type}),
} }
@ -7671,21 +7653,18 @@ fn analyzeRef(
operand: Air.Inst.Ref, operand: Air.Inst.Ref,
) CompileError!Air.Inst.Ref { ) CompileError!Air.Inst.Ref {
const operand_ty = sema.typeOf(operand); const operand_ty = sema.typeOf(operand);
const ptr_type = try Module.simplePtrType(sema.arena, operand_ty, false, .One);
if (try sema.resolveMaybeUndefVal(block, src, operand)) |val| { if (try sema.resolveMaybeUndefVal(block, src, operand)) |val| {
var anon_decl = try block.startAnonDecl(); var anon_decl = try block.startAnonDecl();
defer anon_decl.deinit(); defer anon_decl.deinit();
return sema.addConstant( return sema.analyzeDeclRef(try anon_decl.finish(
ptr_type, operand_ty,
try Value.Tag.decl_ref.create( try val.copy(anon_decl.arena()),
sema.arena, ));
try anon_decl.finish(operand_ty, try val.copy(anon_decl.arena())),
),
);
} }
try sema.requireRuntimeBlock(block, src); try sema.requireRuntimeBlock(block, src);
const ptr_type = try Module.simplePtrType(sema.arena, operand_ty, false, .One);
const alloc = try block.addTy(.alloc, ptr_type); const alloc = try block.addTy(.alloc, ptr_type);
try sema.storePtr(block, src, alloc, operand); try sema.storePtr(block, src, alloc, operand);
return alloc; return alloc;
@ -7703,11 +7682,10 @@ fn analyzeLoad(
.Pointer => ptr_ty.elemType(), .Pointer => ptr_ty.elemType(),
else => return sema.mod.fail(&block.base, ptr_src, "expected pointer, found '{}'", .{ptr_ty}), else => return sema.mod.fail(&block.base, ptr_src, "expected pointer, found '{}'", .{ptr_ty}),
}; };
if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| blk: { if (try sema.resolveDefinedValue(block, ptr_src, ptr)) |ptr_val| {
if (ptr_val.tag() == .int_u64) if (try ptr_val.pointerDeref(sema.arena)) |elem_val| {
break :blk; // do it at runtime return sema.addConstant(elem_ty, elem_val);
}
return sema.addConstant(elem_ty, try ptr_val.pointerDeref(sema.arena));
} }
try sema.requireRuntimeBlock(block, src); try sema.requireRuntimeBlock(block, src);
@ -8215,6 +8193,36 @@ fn resolvePeerTypes(
return sema.typeOf(chosen); return sema.typeOf(chosen);
} }
pub fn resolveTypeLayout(
sema: *Sema,
block: *Scope.Block,
src: LazySrcLoc,
ty: Type,
) CompileError!void {
switch (ty.zigTypeTag()) {
.Pointer => {
return sema.resolveTypeLayout(block, src, ty.elemType());
},
.Struct => {
const resolved_ty = try sema.resolveTypeFields(block, src, ty);
const struct_obj = resolved_ty.castTag(.@"struct").?.data;
switch (struct_obj.status) {
.none, .have_field_types => {},
.field_types_wip, .layout_wip => {
return sema.mod.fail(&block.base, src, "struct {} depends on itself", .{ty});
},
.have_layout => return,
}
struct_obj.status = .layout_wip;
for (struct_obj.fields.values()) |field| {
try sema.resolveTypeLayout(block, src, field.ty);
}
struct_obj.status = .have_layout;
},
else => {},
}
}
fn resolveTypeFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) CompileError!Type { fn resolveTypeFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type) CompileError!Type {
switch (ty.tag()) { switch (ty.tag()) {
.@"struct" => { .@"struct" => {
@ -8222,9 +8230,7 @@ fn resolveTypeFields(sema: *Sema, block: *Scope.Block, src: LazySrcLoc, ty: Type
switch (struct_obj.status) { switch (struct_obj.status) {
.none => {}, .none => {},
.field_types_wip => { .field_types_wip => {
return sema.mod.fail(&block.base, src, "struct {} depends on itself", .{ return sema.mod.fail(&block.base, src, "struct {} depends on itself", .{ty});
ty,
});
}, },
.have_field_types, .have_layout, .layout_wip => return ty, .have_field_types, .have_layout, .layout_wip => return ty,
} }

View File

@ -184,6 +184,7 @@ pub fn generateSymbol(
if (typed_value.val.castTag(.decl_ref)) |payload| { if (typed_value.val.castTag(.decl_ref)) |payload| {
const decl = payload.data; const decl = payload.data;
if (decl.analysis != .complete) return error.AnalysisFail; if (decl.analysis != .complete) return error.AnalysisFail;
decl.alive = true;
// TODO handle the dependency of this symbol on the decl's vaddr. // TODO handle the dependency of this symbol on the decl's vaddr.
// If the decl changes vaddr, then this symbol needs to get regenerated. // If the decl changes vaddr, then this symbol needs to get regenerated.
const vaddr = bin_file.getDeclVAddr(decl); const vaddr = bin_file.getDeclVAddr(decl);
@ -4680,13 +4681,13 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}, },
else => { else => {
if (typed_value.val.castTag(.decl_ref)) |payload| { if (typed_value.val.castTag(.decl_ref)) |payload| {
const decl = payload.data;
decl.alive = true;
if (self.bin_file.cast(link.File.Elf)) |elf_file| { if (self.bin_file.cast(link.File.Elf)) |elf_file| {
const decl = payload.data;
const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?]; const got = &elf_file.program_headers.items[elf_file.phdr_got_index.?];
const got_addr = got.p_vaddr + decl.link.elf.offset_table_index * ptr_bytes; const got_addr = got.p_vaddr + decl.link.elf.offset_table_index * ptr_bytes;
return MCValue{ .memory = got_addr }; return MCValue{ .memory = got_addr };
} else if (self.bin_file.cast(link.File.MachO)) |macho_file| { } else if (self.bin_file.cast(link.File.MachO)) |macho_file| {
const decl = payload.data;
const got_addr = blk: { const got_addr = blk: {
const seg = macho_file.load_commands.items[macho_file.data_const_segment_cmd_index.?].Segment; const seg = macho_file.load_commands.items[macho_file.data_const_segment_cmd_index.?].Segment;
const got = seg.sections.items[macho_file.got_section_index.?]; const got = seg.sections.items[macho_file.got_section_index.?];
@ -4698,11 +4699,9 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
}; };
return MCValue{ .memory = got_addr }; return MCValue{ .memory = got_addr };
} else if (self.bin_file.cast(link.File.Coff)) |coff_file| { } else if (self.bin_file.cast(link.File.Coff)) |coff_file| {
const decl = payload.data;
const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes; const got_addr = coff_file.offset_table_virtual_address + decl.link.coff.offset_table_index * ptr_bytes;
return MCValue{ .memory = got_addr }; return MCValue{ .memory = got_addr };
} else if (self.bin_file.cast(link.File.Plan9)) |p9| { } else if (self.bin_file.cast(link.File.Plan9)) |p9| {
const decl = payload.data;
const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes; const got_addr = p9.bases.data + decl.link.plan9.got_index.? * ptr_bytes;
return MCValue{ .memory = got_addr }; return MCValue{ .memory = got_addr };
} else { } else {

View File

@ -262,6 +262,7 @@ pub const DeclGen = struct {
.one => try writer.writeAll("1"), .one => try writer.writeAll("1"),
.decl_ref => { .decl_ref => {
const decl = val.castTag(.decl_ref).?.data; const decl = val.castTag(.decl_ref).?.data;
decl.alive = true;
// Determine if we must pointer cast. // Determine if we must pointer cast.
assert(decl.has_tv); assert(decl.has_tv);
@ -281,21 +282,7 @@ pub const DeclGen = struct {
const decl = val.castTag(.extern_fn).?.data; const decl = val.castTag(.extern_fn).?.data;
try writer.print("{s}", .{decl.name}); try writer.print("{s}", .{decl.name});
}, },
else => switch (t.ptrSize()) { else => unreachable,
.Slice => unreachable,
.Many => unreachable,
.One => {
var arena = std.heap.ArenaAllocator.init(dg.module.gpa);
defer arena.deinit();
const elem_ty = t.elemType();
const elem_val = try val.pointerDeref(&arena.allocator);
try writer.writeAll("&");
try dg.renderValue(writer, elem_ty, elem_val);
},
.C => unreachable,
},
}, },
}, },
.Array => { .Array => {
@ -421,6 +408,7 @@ pub const DeclGen = struct {
.one => try writer.writeAll("1"), .one => try writer.writeAll("1"),
.decl_ref => { .decl_ref => {
const decl = val.castTag(.decl_ref).?.data; const decl = val.castTag(.decl_ref).?.data;
decl.alive = true;
// Determine if we must pointer cast. // Determine if we must pointer cast.
assert(decl.has_tv); assert(decl.has_tv);
@ -433,11 +421,13 @@ pub const DeclGen = struct {
} }
}, },
.function => { .function => {
const func = val.castTag(.function).?.data; const decl = val.castTag(.function).?.data.owner_decl;
try writer.print("{s}", .{func.owner_decl.name}); decl.alive = true;
try writer.print("{s}", .{decl.name});
}, },
.extern_fn => { .extern_fn => {
const decl = val.castTag(.extern_fn).?.data; const decl = val.castTag(.extern_fn).?.data;
decl.alive = true;
try writer.print("{s}", .{decl.name}); try writer.print("{s}", .{decl.name});
}, },
else => unreachable, else => unreachable,

View File

@ -673,17 +673,21 @@ pub const DeclGen = struct {
} }
fn genTypedValue(self: *DeclGen, tv: TypedValue) error{ OutOfMemory, CodegenFail }!*const llvm.Value { fn genTypedValue(self: *DeclGen, tv: TypedValue) error{ OutOfMemory, CodegenFail }!*const llvm.Value {
const llvm_type = try self.llvmType(tv.ty); if (tv.val.isUndef()) {
const llvm_type = try self.llvmType(tv.ty);
if (tv.val.isUndef())
return llvm_type.getUndef(); return llvm_type.getUndef();
}
switch (tv.ty.zigTypeTag()) { switch (tv.ty.zigTypeTag()) {
.Bool => return if (tv.val.toBool()) llvm_type.constAllOnes() else llvm_type.constNull(), .Bool => {
const llvm_type = try self.llvmType(tv.ty);
return if (tv.val.toBool()) llvm_type.constAllOnes() else llvm_type.constNull();
},
.Int => { .Int => {
var bigint_space: Value.BigIntSpace = undefined; var bigint_space: Value.BigIntSpace = undefined;
const bigint = tv.val.toBigInt(&bigint_space); const bigint = tv.val.toBigInt(&bigint_space);
const llvm_type = try self.llvmType(tv.ty);
if (bigint.eqZero()) return llvm_type.constNull(); if (bigint.eqZero()) return llvm_type.constNull();
if (bigint.limbs.len != 1) { if (bigint.limbs.len != 1) {
@ -698,12 +702,17 @@ pub const DeclGen = struct {
.Pointer => switch (tv.val.tag()) { .Pointer => switch (tv.val.tag()) {
.decl_ref => { .decl_ref => {
const decl = tv.val.castTag(.decl_ref).?.data; const decl = tv.val.castTag(.decl_ref).?.data;
decl.alive = true;
const val = try self.resolveGlobalDecl(decl); const val = try self.resolveGlobalDecl(decl);
const llvm_type = try self.llvmType(tv.ty);
return val.constBitCast(llvm_type); return val.constBitCast(llvm_type);
}, },
.variable => { .variable => {
const variable = tv.val.castTag(.variable).?.data; const decl = tv.val.castTag(.variable).?.data.owner_decl;
const val = try self.resolveGlobalDecl(variable.owner_decl); decl.alive = true;
const val = try self.resolveGlobalDecl(decl);
const llvm_var_type = try self.llvmType(tv.ty);
const llvm_type = llvm_var_type.pointerType(0);
return val.constBitCast(llvm_type); return val.constBitCast(llvm_type);
}, },
.slice => { .slice => {
@ -783,6 +792,7 @@ pub const DeclGen = struct {
.decl_ref => tv.val.castTag(.decl_ref).?.data, .decl_ref => tv.val.castTag(.decl_ref).?.data,
else => unreachable, else => unreachable,
}; };
fn_decl.alive = true;
return self.resolveLlvmFunction(fn_decl); return self.resolveLlvmFunction(fn_decl);
}, },
.ErrorSet => { .ErrorSet => {
@ -903,9 +913,7 @@ pub const FuncGen = struct {
return self.dg.genTypedValue(.{ .ty = self.air.typeOf(inst), .val = val }); return self.dg.genTypedValue(.{ .ty = self.air.typeOf(inst), .val = val });
} }
const inst_index = Air.refToIndex(inst).?; const inst_index = Air.refToIndex(inst).?;
if (self.func_inst_table.get(inst_index)) |value| return value; return self.func_inst_table.get(inst_index).?;
return self.todo("implement global llvm values (or the value is not in the func_inst_table table)", .{});
} }
fn genBody(self: *FuncGen, body: []const Air.Inst.Index) error{ OutOfMemory, CodegenFail }!void { fn genBody(self: *FuncGen, body: []const Air.Inst.Index) error{ OutOfMemory, CodegenFail }!void {
@ -966,8 +974,8 @@ pub const FuncGen = struct {
.struct_field_ptr => try self.airStructFieldPtr(inst), .struct_field_ptr => try self.airStructFieldPtr(inst),
.struct_field_val => try self.airStructFieldVal(inst), .struct_field_val => try self.airStructFieldVal(inst),
.slice_elem_val => try self.airSliceElemVal(inst, false), .slice_elem_val => try self.airSliceElemVal(inst),
.ptr_slice_elem_val => try self.airSliceElemVal(inst, true), .ptr_slice_elem_val => try self.airPtrSliceElemVal(inst),
.optional_payload => try self.airOptionalPayload(inst, false), .optional_payload => try self.airOptionalPayload(inst, false),
.optional_payload_ptr => try self.airOptionalPayload(inst, true), .optional_payload_ptr => try self.airOptionalPayload(inst, true),
@ -1170,11 +1178,20 @@ pub const FuncGen = struct {
return self.builder.buildExtractValue(operand, index, ""); return self.builder.buildExtractValue(operand, index, "");
} }
fn airSliceElemVal( fn airSliceElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
self: *FuncGen, if (self.liveness.isUnused(inst))
inst: Air.Inst.Index, return null;
operand_is_ptr: bool,
) !?*const llvm.Value { const bin_op = self.air.instructions.items(.data)[inst].bin_op;
const lhs = try self.resolveInst(bin_op.lhs);
const rhs = try self.resolveInst(bin_op.rhs);
const base_ptr = self.builder.buildExtractValue(lhs, 0, "");
const indices: [1]*const llvm.Value = .{rhs};
const ptr = self.builder.buildInBoundsGEP(base_ptr, &indices, indices.len, "");
return self.builder.buildLoad(ptr, "");
}
fn airPtrSliceElemVal(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
if (self.liveness.isUnused(inst)) if (self.liveness.isUnused(inst))
return null; return null;
@ -1182,7 +1199,7 @@ pub const FuncGen = struct {
const lhs = try self.resolveInst(bin_op.lhs); const lhs = try self.resolveInst(bin_op.lhs);
const rhs = try self.resolveInst(bin_op.rhs); const rhs = try self.resolveInst(bin_op.rhs);
const base_ptr = if (!operand_is_ptr) lhs else ptr: { const base_ptr = ptr: {
const index_type = self.context.intType(32); const index_type = self.context.intType(32);
const indices: [2]*const llvm.Value = .{ const indices: [2]*const llvm.Value = .{
index_type.constNull(), index_type.constNull(),

View File

@ -1016,6 +1016,7 @@ pub const Context = struct {
.Pointer => { .Pointer => {
if (val.castTag(.decl_ref)) |payload| { if (val.castTag(.decl_ref)) |payload| {
const decl = payload.data; const decl = payload.data;
decl.alive = true;
// offset into the offset table within the 'data' section // offset into the offset table within the 'data' section
const ptr_width = self.target.cpu.arch.ptrBitWidth() / 8; const ptr_width = self.target.cpu.arch.ptrBitWidth() / 8;

View File

@ -224,7 +224,9 @@ pub fn flushModule(self: *Plan9, comp: *Compilation) !void {
const mod = self.base.options.module orelse return error.LinkingWithoutZigSourceUnimplemented; const mod = self.base.options.module orelse return error.LinkingWithoutZigSourceUnimplemented;
assert(self.got_len == self.fn_decl_table.count() + self.data_decl_table.count()); // TODO I changed this assert from == to >= but this code all needs to be audited; see
// the comment in `freeDecl`.
assert(self.got_len >= self.fn_decl_table.count() + self.data_decl_table.count());
const got_size = self.got_len * if (!self.sixtyfour_bit) @as(u32, 4) else 8; const got_size = self.got_len * if (!self.sixtyfour_bit) @as(u32, 4) else 8;
var got_table = try self.base.allocator.alloc(u8, got_size); var got_table = try self.base.allocator.alloc(u8, got_size);
defer self.base.allocator.free(got_table); defer self.base.allocator.free(got_table);
@ -358,11 +360,18 @@ fn addDeclExports(
} }
pub fn freeDecl(self: *Plan9, decl: *Module.Decl) void { pub fn freeDecl(self: *Plan9, decl: *Module.Decl) void {
// TODO this is not the correct check for being function body,
// it could just be a function pointer.
// TODO audit the lifetimes of decls table entries. It's possible to get
// allocateDeclIndexes and then freeDecl without any updateDecl in between.
// However that is planned to change, see the TODO comment in Module.zig
// in the deleteUnusedDecl function.
const is_fn = (decl.ty.zigTypeTag() == .Fn); const is_fn = (decl.ty.zigTypeTag() == .Fn);
if (is_fn) if (is_fn) {
assert(self.fn_decl_table.swapRemove(decl)) _ = self.fn_decl_table.swapRemove(decl);
else } else {
assert(self.data_decl_table.swapRemove(decl)); _ = self.data_decl_table.swapRemove(decl);
}
} }
pub fn updateDeclExports( pub fn updateDeclExports(

View File

@ -103,6 +103,7 @@ pub const Value = extern union {
/// Represents a comptime variables storage. /// Represents a comptime variables storage.
comptime_alloc, comptime_alloc,
/// Represents a pointer to a decl, not the value of the decl. /// Represents a pointer to a decl, not the value of the decl.
/// When machine codegen backend sees this, it must set the Decl's `alive` field to true.
decl_ref, decl_ref,
elem_ptr, elem_ptr,
field_ptr, field_ptr,
@ -1346,28 +1347,48 @@ pub const Value = extern union {
/// Asserts the value is a pointer and dereferences it. /// Asserts the value is a pointer and dereferences it.
/// Returns error.AnalysisFail if the pointer points to a Decl that failed semantic analysis. /// Returns error.AnalysisFail if the pointer points to a Decl that failed semantic analysis.
pub fn pointerDeref(self: Value, allocator: *Allocator) error{ AnalysisFail, OutOfMemory }!Value { pub fn pointerDeref(
return switch (self.tag()) { self: Value,
allocator: *Allocator,
) error{ AnalysisFail, OutOfMemory }!?Value {
const sub_val: Value = switch (self.tag()) {
.comptime_alloc => self.castTag(.comptime_alloc).?.data.val, .comptime_alloc => self.castTag(.comptime_alloc).?.data.val,
.decl_ref => self.castTag(.decl_ref).?.data.value(), .decl_ref => try self.castTag(.decl_ref).?.data.value(),
.elem_ptr => { .elem_ptr => blk: {
const elem_ptr = self.castTag(.elem_ptr).?.data; const elem_ptr = self.castTag(.elem_ptr).?.data;
const array_val = try elem_ptr.array_ptr.pointerDeref(allocator); const array_val = (try elem_ptr.array_ptr.pointerDeref(allocator)) orelse return null;
return array_val.elemValue(allocator, elem_ptr.index); break :blk try array_val.elemValue(allocator, elem_ptr.index);
}, },
.field_ptr => { .field_ptr => blk: {
const field_ptr = self.castTag(.field_ptr).?.data; const field_ptr = self.castTag(.field_ptr).?.data;
const container_val = try field_ptr.container_ptr.pointerDeref(allocator); const container_val = (try field_ptr.container_ptr.pointerDeref(allocator)) orelse return null;
return container_val.fieldValue(allocator, field_ptr.field_index); break :blk try container_val.fieldValue(allocator, field_ptr.field_index);
}, },
.eu_payload_ptr => { .eu_payload_ptr => blk: {
const err_union_ptr = self.castTag(.eu_payload_ptr).?.data; const err_union_ptr = self.castTag(.eu_payload_ptr).?.data;
const err_union_val = try err_union_ptr.pointerDeref(allocator); const err_union_val = (try err_union_ptr.pointerDeref(allocator)) orelse return null;
return err_union_val.castTag(.error_union).?.data; break :blk err_union_val.castTag(.error_union).?.data;
}, },
.zero,
.one,
.int_u64,
.int_i64,
.int_big_positive,
.int_big_negative,
.variable,
.extern_fn,
.function,
=> return null,
else => unreachable, else => unreachable,
}; };
if (sub_val.tag() == .variable) {
// This would be loading a runtime value at compile-time so we return
// the indicator that this pointer dereference requires being done at runtime.
return null;
}
return sub_val;
} }
pub fn sliceLen(val: Value) u64 { pub fn sliceLen(val: Value) u64 {

View File

@ -49,7 +49,7 @@ pub fn addCases(ctx: *TestContext) !void {
\\export fn foo() callconv(y) c_int { \\export fn foo() callconv(y) c_int {
\\ return 0; \\ return 0;
\\} \\}
\\var y: i32 = 1234; \\var y: @import("std").builtin.CallingConvention = .C;
, &.{ , &.{
":2:22: error: unable to resolve comptime value", ":2:22: error: unable to resolve comptime value",
":5:26: error: unable to resolve comptime value", ":5:26: error: unable to resolve comptime value",