aboutsummaryrefslogtreecommitdiff
path: root/src/Package/Module.zig
diff options
context:
space:
mode:
authorAndrew Kelley <andrew@ziglang.org>2023-12-10 15:25:06 -0700
committerAndrew Kelley <andrew@ziglang.org>2024-01-01 17:51:18 -0700
commit12de7e3472cb2292e75578d33a8b8cc91f1ef0b0 (patch)
tree77d38282ed0b8cc4911df38b702c09762b52c681 /src/Package/Module.zig
parentb92e30ff0bd2b77a486451b21d17666a311407f3 (diff)
downloadzig-12de7e3472cb2292e75578d33a8b8cc91f1ef0b0.tar.gz
zig-12de7e3472cb2292e75578d33a8b8cc91f1ef0b0.zip
WIP: move many global settings to become per-Module
Much of the logic from Compilation.create() is extracted into Compilation.Config.resolve() which accepts many optional settings and produces concrete settings. This separate step is needed by API users of Compilation so that they can pass the resolved global settings to the Module creation function, which itself needs to resolve per-Module settings. Since the target and other things are no longer global settings, I did not want them stored in link.File (in the `options` field). That options field was already a kludge; those options should be resolved into concrete settings. This commit also starts to work on that, deleting link.Options, moving the fields into Compilation and ObjectFormat-specific structs instead. Some fields were ephemeral and should not have been stored at all, such as symbol_size_hint. The link.File object of Compilation is now a `?*link.File` and `null` when -fno-emit-bin is passed. It is now arena-allocated along with Compilation itself, avoiding some messy cleanup code that was there before. On the command line, it is now possible to configure the standard library itself by using `--mod std` just like any other module. This meant that the CLI needed to create the standard library module rather than having Compilation create it. There are a lot of changes in this commit and it's still not done. I didn't realize how quickly this changeset was going to balloon out of control, and there are still many lines that need to be changed before it even compiles successfully. * introduce std.Build.Cache.HashHelper.oneShot * add error_tracing to std.Build.Module * extract build.zig file generation into src/Builtin.zig * each CSourceFile and RcSourceFile now has a Module owner, which determines some of the C compiler flags.
Diffstat (limited to 'src/Package/Module.zig')
-rw-r--r--src/Package/Module.zig409
1 files changed, 403 insertions, 6 deletions
diff --git a/src/Package/Module.zig b/src/Package/Module.zig
index 0c8d40bffa..94431856ac 100644
--- a/src/Package/Module.zig
+++ b/src/Package/Module.zig
@@ -1,6 +1,6 @@
//! Corresponds to something that Zig source code can `@import`.
-//! Not to be confused with src/Module.zig which should be renamed
-//! to something else. https://github.com/ziglang/zig/issues/14307
+//! Not to be confused with src/Module.zig which will be renamed
+//! to Zcu. https://github.com/ziglang/zig/issues/14307
/// Only files inside this directory can be imported.
root: Package.Path,
@@ -14,6 +14,26 @@ fully_qualified_name: []const u8,
/// responsible for detecting these names and using the correct package.
deps: Deps = .{},
+resolved_target: ResolvedTarget,
+optimize_mode: std.builtin.OptimizeMode,
+code_model: std.builtin.CodeModel,
+single_threaded: bool,
+error_tracing: bool,
+valgrind: bool,
+pic: bool,
+strip: bool,
+omit_frame_pointer: bool,
+stack_check: bool,
+stack_protector: u32,
+red_zone: bool,
+sanitize_c: bool,
+sanitize_thread: bool,
+unwind_tables: bool,
+cc_argv: []const []const u8,
+
+/// The contents of `@import("builtin")` for this module.
+generated_builtin_source: []const u8,
+
pub const Deps = std.StringArrayHashMapUnmanaged(*Module);
pub const Tree = struct {
@@ -21,10 +41,382 @@ pub const Tree = struct {
build_module_table: std.AutoArrayHashMapUnmanaged(MultiHashHexDigest, *Module),
};
-pub fn create(allocator: Allocator, m: Module) Allocator.Error!*Module {
- const new = try allocator.create(Module);
- new.* = m;
- return new;
+pub const CreateOptions = struct {
+ /// Where to store builtin.zig. The global cache directory is used because
+ /// it is a pure function based on CLI flags.
+ global_cache_directory: Cache.Directory,
+ paths: Paths,
+ fully_qualified_name: []const u8,
+
+ cc_argv: []const []const u8,
+ inherited: Inherited,
+ global: Compilation.Config,
+ /// If this is null then `resolved_target` must be non-null.
+ parent: ?*Package.Module,
+
+ builtin_mod: ?*Package.Module,
+
+ pub const Paths = struct {
+ root: Package.Path,
+ /// Relative to `root`. May contain path separators.
+ root_src_path: []const u8,
+ };
+
+ pub const Inherited = struct {
+ /// If this is null then `parent` must be non-null.
+ resolved_target: ?ResolvedTarget = null,
+ optimize_mode: ?std.builtin.OptimizeMode = null,
+ code_model: ?std.builtin.CodeModel = null,
+ single_threaded: ?bool = null,
+ error_tracing: ?bool = null,
+ valgrind: ?bool = null,
+ pic: ?bool = null,
+ strip: ?bool = null,
+ omit_frame_pointer: ?bool = null,
+ stack_check: ?bool = null,
+ /// null means default.
+ /// 0 means no stack protector.
+ /// other number means stack protection with that buffer size.
+ stack_protector: ?u32 = null,
+ red_zone: ?bool = null,
+ unwind_tables: ?bool = null,
+ sanitize_c: ?bool = null,
+ sanitize_thread: ?bool = null,
+ };
+};
+
+pub const ResolvedTarget = struct {
+ result: std.Target,
+ is_native_os: bool,
+ is_native_abi: bool,
+ llvm_cpu_features: ?[*:0]const u8 = null,
+};
+
+/// At least one of `parent` and `resolved_target` must be non-null.
+pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module {
+ const resolved_target = options.inherited.resolved_target orelse options.parent.?.resolved_target;
+ const target = resolved_target.result;
+
+ const optimize_mode = options.inherited.optimize_mode orelse
+ if (options.parent) |p| p.optimize_mode else .Debug;
+
+ const unwind_tables = options.inherited.unwind_tables orelse
+ if (options.parent) |p| p.unwind_tables else options.global.any_unwind_tables;
+
+ const strip = b: {
+ if (options.inherited.strip) |x| break :b x;
+ if (options.parent) |p| break :b p.strip;
+ if (optimize_mode == .ReleaseSmall) break :b true;
+ if (!target_util.hasDebugInfo(target)) break :b true;
+ break :b false;
+ };
+
+ const valgrind = b: {
+ if (!target_util.hasValgrindSupport(target)) {
+ if (options.inherited.valgrind == true)
+ return error.ValgrindUnsupportedOnTarget;
+ break :b false;
+ }
+ if (options.inherited.valgrind) |x| break :b x;
+ if (options.parent) |p| break :b p.valgrind;
+ if (strip) break :b false;
+ break :b optimize_mode == .Debug;
+ };
+
+ const zig_backend = target_util.zigBackend(target, options.global.use_llvm);
+
+ const single_threaded = b: {
+ if (target_util.alwaysSingleThreaded(target)) {
+ if (options.inherited.single_threaded == false)
+ return error.TargetRequiresSingleThreaded;
+ break :b true;
+ }
+
+ if (options.global.have_zcu) {
+ if (!target_util.supportsThreads(target, zig_backend)) {
+ if (options.inherited.single_threaded == false)
+ return error.BackendRequiresSingleThreaded;
+ break :b true;
+ }
+ }
+
+ if (options.inherited.single_threaded) |x| break :b x;
+ if (options.parent) |p| break :b p.single_threaded;
+ break :b target_util.defaultSingleThreaded(target);
+ };
+
+ const error_tracing = b: {
+ if (options.inherited.error_tracing) |x| break :b x;
+ if (options.parent) |p| break :b p.error_tracing;
+ if (strip) break :b false;
+ break :b switch (optimize_mode) {
+ .Debug => true,
+ .ReleaseSafe, .ReleaseFast, .ReleaseSmall => false,
+ };
+ };
+
+ const pic = b: {
+ if (target_util.requiresPIC(target, options.global.link_libc)) {
+ if (options.inherited.pic == false)
+ return error.TargetRequiresPic;
+ break :b true;
+ }
+ if (options.global.pie) {
+ if (options.inherited.pic == false)
+ return error.PieRequiresPic;
+ break :b true;
+ }
+ if (options.global.link_mode == .Dynamic) {
+ if (options.inherited.pic == false)
+ return error.DynamicLinkingRequiresPic;
+ break :b true;
+ }
+ if (options.inherited.pic) |x| break :b x;
+ if (options.parent) |p| break :b p.pic;
+ break :b false;
+ };
+
+ const red_zone = b: {
+ if (!target_util.hasRedZone(target)) {
+ if (options.inherited.red_zone == true)
+ return error.TargetHasNoRedZone;
+ break :b true;
+ }
+ if (options.inherited.red_zone) |x| break :b x;
+ if (options.parent) |p| break :b p.red_zone;
+ break :b true;
+ };
+
+ const omit_frame_pointer = b: {
+ if (options.inherited.omit_frame_pointer) |x| break :b x;
+ if (options.parent) |p| break :b p.omit_frame_pointer;
+ if (optimize_mode == .Debug) break :b false;
+ break :b true;
+ };
+
+ const sanitize_thread = b: {
+ if (options.inherited.sanitize_thread) |x| break :b x;
+ if (options.parent) |p| break :b p.sanitize_thread;
+ break :b false;
+ };
+
+ const code_model = b: {
+ if (options.inherited.code_model) |x| break :b x;
+ if (options.parent) |p| break :b p.code_model;
+ break :b .default;
+ };
+
+ const is_safe_mode = switch (optimize_mode) {
+ .Debug, .ReleaseSafe => true,
+ .ReleaseFast, .ReleaseSmall => false,
+ };
+
+ const sanitize_c = b: {
+ if (options.inherited.sanitize_c) |x| break :b x;
+ if (options.parent) |p| break :b p.sanitize_c;
+ break :b is_safe_mode;
+ };
+
+ const stack_check = b: {
+ if (!target_util.supportsStackProbing(target)) {
+ if (options.inherited.stack_check == true)
+ return error.StackCheckUnsupportedByTarget;
+ break :b false;
+ }
+ if (options.inherited.stack_check) |x| break :b x;
+ if (options.parent) |p| break :b p.stack_check;
+ break :b is_safe_mode;
+ };
+
+ const stack_protector: u32 = sp: {
+ if (!target_util.supportsStackProtector(target, zig_backend)) {
+ if (options.inherited.stack_protector) |x| {
+ if (x > 0) return error.StackProtectorUnsupportedByTarget;
+ }
+ break :sp 0;
+ }
+
+ // This logic is checking for linking libc because otherwise our start code
+ // which is trying to set up TLS (i.e. the fs/gs registers) but the stack
+ // protection code depends on fs/gs registers being already set up.
+ // If we were able to annotate start code, or perhaps the entire std lib,
+ // as being exempt from stack protection checks, we could change this logic
+ // to supporting stack protection even when not linking libc.
+ // TODO file issue about this
+ if (!options.global.link_libc) {
+ if (options.inherited.stack_protector) |x| {
+ if (x > 0) return error.StackProtectorUnavailableWithoutLibC;
+ }
+ break :sp 0;
+ }
+
+ if (options.inherited.stack_protector) |x| break :sp x;
+ if (options.parent) |p| break :sp p.stack_protector;
+ if (!is_safe_mode) break :sp 0;
+
+ break :sp target_util.default_stack_protector_buffer_size;
+ };
+
+ const llvm_cpu_features: ?[*:0]const u8 = b: {
+ if (resolved_target.llvm_cpu_features) |x| break :b x;
+ if (!options.global.use_llvm) break :b null;
+
+ var buf = std.ArrayList(u8).init(arena);
+ for (target.cpu.arch.allFeaturesList(), 0..) |feature, index_usize| {
+ const index = @as(std.Target.Cpu.Feature.Set.Index, @intCast(index_usize));
+ const is_enabled = target.cpu.features.isEnabled(index);
+
+ if (feature.llvm_name) |llvm_name| {
+ const plus_or_minus = "-+"[@intFromBool(is_enabled)];
+ try buf.ensureUnusedCapacity(2 + llvm_name.len);
+ buf.appendAssumeCapacity(plus_or_minus);
+ buf.appendSliceAssumeCapacity(llvm_name);
+ buf.appendSliceAssumeCapacity(",");
+ }
+ }
+ if (buf.items.len == 0) break :b "";
+ assert(std.mem.endsWith(u8, buf.items, ","));
+ buf.items[buf.items.len - 1] = 0;
+ buf.shrinkAndFree(buf.items.len);
+ break :b buf.items[0 .. buf.items.len - 1 :0].ptr;
+ };
+
+ const builtin_mod = options.builtin_mod orelse b: {
+ const generated_builtin_source = try Builtin.generate(.{
+ .target = target,
+ .zig_backend = zig_backend,
+ .output_mode = options.global.output_mode,
+ .link_mode = options.global.link_mode,
+ .is_test = options.global.is_test,
+ .test_evented_io = options.global.test_evented_io,
+ .single_threaded = single_threaded,
+ .link_libc = options.global.link_libc,
+ .link_libcpp = options.global.link_libcpp,
+ .optimize_mode = optimize_mode,
+ .error_tracing = error_tracing,
+ .valgrind = valgrind,
+ .sanitize_thread = sanitize_thread,
+ .pic = pic,
+ .pie = options.global.pie,
+ .strip = strip,
+ .code_model = code_model,
+ .omit_frame_pointer = omit_frame_pointer,
+ .wasi_exec_model = options.global.wasi_exec_model,
+ }, arena);
+
+ const digest = Cache.HashHelper.oneShot(generated_builtin_source);
+ const builtin_sub_path = try arena.dupe(u8, "b" ++ std.fs.path.sep_str ++ digest);
+ const new = try arena.create(Module);
+ new.* = .{
+ .root = .{
+ .root_dir = options.global_cache_directory,
+ .sub_path = builtin_sub_path,
+ },
+ .root_src_path = "builtin.zig",
+ .fully_qualified_name = if (options.parent == null)
+ "builtin"
+ else
+ try std.fmt.allocPrint(arena, "{s}.builtin", .{options.fully_qualified_name}),
+ .resolved_target = .{
+ .result = target,
+ .is_native_os = resolved_target.is_native_os,
+ .is_native_abi = resolved_target.is_native_abi,
+ .llvm_cpu_features = llvm_cpu_features,
+ },
+ .optimize_mode = optimize_mode,
+ .single_threaded = single_threaded,
+ .error_tracing = error_tracing,
+ .valgrind = valgrind,
+ .pic = pic,
+ .strip = strip,
+ .omit_frame_pointer = omit_frame_pointer,
+ .stack_check = stack_check,
+ .stack_protector = stack_protector,
+ .code_model = code_model,
+ .red_zone = red_zone,
+ .generated_builtin_source = generated_builtin_source,
+ .sanitize_c = sanitize_c,
+ .sanitize_thread = sanitize_thread,
+ .unwind_tables = unwind_tables,
+ .cc_argv = &.{},
+ };
+ break :b new;
+ };
+
+ const mod = try arena.create(Module);
+ mod.* = .{
+ .root = options.paths.root,
+ .root_src_path = options.paths.root_src_path,
+ .fully_qualified_name = options.fully_qualified_name,
+ .resolved_target = .{
+ .result = target,
+ .is_native_os = resolved_target.is_native_os,
+ .is_native_abi = resolved_target.is_native_abi,
+ .llvm_cpu_features = llvm_cpu_features,
+ },
+ .optimize_mode = optimize_mode,
+ .single_threaded = single_threaded,
+ .error_tracing = error_tracing,
+ .valgrind = valgrind,
+ .pic = pic,
+ .strip = strip,
+ .omit_frame_pointer = omit_frame_pointer,
+ .stack_check = stack_check,
+ .stack_protector = stack_protector,
+ .code_model = code_model,
+ .red_zone = red_zone,
+ .generated_builtin_source = builtin_mod.generated_builtin_source,
+ .sanitize_c = sanitize_c,
+ .sanitize_thread = sanitize_thread,
+ .unwind_tables = unwind_tables,
+ .cc_argv = options.cc_argv,
+ };
+
+ try mod.deps.ensureUnusedCapacity(arena, 1);
+ mod.deps.putAssumeCapacityNoClobber("builtin", builtin_mod);
+
+ return mod;
+}
+
+/// All fields correspond to `CreateOptions`.
+pub const LimitedOptions = struct {
+ root: Package.Path,
+ root_src_path: []const u8,
+ fully_qualified_name: []const u8,
+};
+
+/// This one can only be used if the Module will only be used for AstGen and earlier in
+/// the pipeline. Illegal behavior occurs if a limited module touches Sema.
+pub fn createLimited(gpa: Allocator, options: LimitedOptions) Allocator.Error!*Package.Module {
+ const mod = try gpa.create(Module);
+ mod.* = .{
+ .root = options.root,
+ .root_src_path = options.root_src_path,
+ .fully_qualified_name = options.fully_qualified_name,
+
+ .resolved_target = undefined,
+ .optimize_mode = undefined,
+ .code_model = undefined,
+ .single_threaded = undefined,
+ .error_tracing = undefined,
+ .valgrind = undefined,
+ .pic = undefined,
+ .strip = undefined,
+ .omit_frame_pointer = undefined,
+ .stack_check = undefined,
+ .stack_protector = undefined,
+ .red_zone = undefined,
+ .sanitize_c = undefined,
+ .sanitize_thread = undefined,
+ .unwind_tables = undefined,
+ .cc_argv = undefined,
+ .generated_builtin_source = undefined,
+ };
+ return mod;
+}
+
+pub fn getBuiltinDependency(m: *Module) *Module {
+ return m.deps.values()[0];
}
const Module = @This();
@@ -32,3 +424,8 @@ const Package = @import("../Package.zig");
const std = @import("std");
const Allocator = std.mem.Allocator;
const MultiHashHexDigest = Package.Manifest.MultiHashHexDigest;
+const target_util = @import("../target.zig");
+const Cache = std.Build.Cache;
+const Builtin = @import("../Builtin.zig");
+const assert = std.debug.assert;
+const Compilation = @import("../Compilation.zig");