aboutsummaryrefslogtreecommitdiff
path: root/src/Module.zig
diff options
context:
space:
mode:
authormlugg <mlugg@mlugg.co.uk>2023-05-29 05:07:17 +0100
committermlugg <mlugg@mlugg.co.uk>2023-05-29 23:06:08 +0100
commit4976b58ab16069f8d3267b69ed030f29685c1abe (patch)
tree400f632d11eec4f3c330bee15d59f8fd6219c20f /src/Module.zig
parentb5fad3a40a86eb379903d6a803bdbe66dcaa5487 (diff)
downloadzig-4976b58ab16069f8d3267b69ed030f29685c1abe.tar.gz
zig-4976b58ab16069f8d3267b69ed030f29685c1abe.zip
Prevent analysis of functions only referenced at comptime
The idea here is that there are two ways we can reference a function at runtime: * Through a direct call, i.e. where the function is comptime-known * Through a function pointer This means we can easily perform a form of rudimentary escape analysis on functions. If we ever see a `decl_ref` or `ref` of a function, we have a function pointer, which could "leak" into runtime code, so we emit the function; but for a plain `decl_val`, there's no need to. This change means that `comptime { _ = f; }` no longer forces a function to be emitted, which was used for some things (mainly tests). These use sites have been replaced with `_ = &f;`, which still triggers analysis of the function body, since you're taking a pointer to the function. Resolves: #6256 Resolves: #15353
Diffstat (limited to 'src/Module.zig')
-rw-r--r--src/Module.zig74
1 files changed, 59 insertions, 15 deletions
diff --git a/src/Module.zig b/src/Module.zig
index 61843f5a8f..59ee21d8cf 100644
--- a/src/Module.zig
+++ b/src/Module.zig
@@ -1638,6 +1638,10 @@ pub const Fn = struct {
inferred_error_sets: InferredErrorSetList = .{},
pub const Analysis = enum {
+ /// This function has not yet undergone analysis, because we have not
+ /// seen a potential runtime call. It may be analyzed in future.
+ none,
+ /// Analysis for this function has been queued, but not yet completed.
queued,
/// This function intentionally only has ZIR generated because it is marked
/// inline, which means no runtime version of the function will be generated.
@@ -4323,7 +4327,7 @@ pub fn ensureFuncBodyAnalyzed(mod: *Module, func: *Fn) SemaError!void {
.complete, .codegen_failure_retryable => {
switch (func.state) {
.sema_failure, .dependency_failure => return error.AnalysisFail,
- .queued => {},
+ .none, .queued => {},
.in_progress => unreachable,
.inline_only => unreachable, // don't queue work for this
.success => return,
@@ -4426,6 +4430,60 @@ pub fn ensureFuncBodyAnalyzed(mod: *Module, func: *Fn) SemaError!void {
}
}
+/// Ensure this function's body is or will be analyzed and emitted. This should
+/// be called whenever a potential runtime call of a function is seen.
+///
+/// The caller is responsible for ensuring the function decl itself is already
+/// analyzed, and for ensuring it can exist at runtime (see
+/// `sema.fnHasRuntimeBits`). This function does *not* guarantee that the body
+/// will be analyzed when it returns: for that, see `ensureFuncBodyAnalyzed`.
+pub fn ensureFuncBodyAnalysisQueued(mod: *Module, func: *Fn) !void {
+ const decl_index = func.owner_decl;
+ const decl = mod.declPtr(decl_index);
+
+ switch (decl.analysis) {
+ .unreferenced => unreachable,
+ .in_progress => unreachable,
+ .outdated => unreachable,
+
+ .file_failure,
+ .sema_failure,
+ .liveness_failure,
+ .codegen_failure,
+ .dependency_failure,
+ .sema_failure_retryable,
+ .codegen_failure_retryable,
+ // The function analysis failed, but we've already emitted an error for
+ // that. The callee doesn't need the function to be analyzed right now,
+ // so its analysis can safely continue.
+ => return,
+
+ .complete => {},
+ }
+
+ assert(decl.has_tv);
+
+ switch (func.state) {
+ .none => {},
+ .queued => return,
+ // As above, we don't need to forward errors here.
+ .sema_failure, .dependency_failure => return,
+ .in_progress => return,
+ .inline_only => unreachable, // don't queue work for this
+ .success => return,
+ }
+
+ // Decl itself is safely analyzed, and body analysis is not yet queued
+
+ try mod.comp.work_queue.writeItem(.{ .codegen_func = func });
+ if (mod.emit_h != null) {
+ // TODO: we ideally only want to do this if the function's type changed
+ // since the last update
+ try mod.comp.work_queue.writeItem(.{ .emit_h_decl = decl_index });
+ }
+ func.state = .queued;
+}
+
pub fn updateEmbedFile(mod: *Module, embed_file: *EmbedFile) SemaError!void {
const tracy = trace(@src());
defer tracy.end();
@@ -4733,20 +4791,6 @@ fn semaDecl(mod: *Module, decl_index: Decl.Index) !bool {
decl.analysis = .complete;
decl.generation = mod.generation;
- const has_runtime_bits = try sema.fnHasRuntimeBits(decl.ty);
-
- if (has_runtime_bits) {
- // 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, increasing how many computations can be done in parallel.
- try mod.comp.work_queue.writeItem(.{ .codegen_func = func });
- if (type_changed and mod.emit_h != null) {
- try mod.comp.work_queue.writeItem(.{ .emit_h_decl = decl_index });
- }
- } else if (!prev_is_inline and prev_type_has_bits) {
- mod.comp.bin_file.freeDecl(decl_index);
- }
-
const is_inline = decl.ty.fnCallingConvention() == .Inline;
if (decl.is_exported) {
const export_src: LazySrcLoc = .{ .token_offset = @boolToInt(decl.is_pub) };