diff --git a/CMakeLists.txt b/CMakeLists.txt
index e606855555..867f2684db 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -426,6 +426,7 @@ set(ZIG_SOURCES
)
set(ZIG_CPP_SOURCES
"${CMAKE_SOURCE_DIR}/src/zig_llvm.cpp"
+ "${CMAKE_SOURCE_DIR}/src/windows_sdk.cpp"
)
set(ZIG_STD_FILES
@@ -479,6 +480,7 @@ set(ZIG_STD_FILES
"index.zig"
"io.zig"
"json.zig"
+ "lazy_init.zig"
"linked_list.zig"
"macho.zig"
"math/acos.zig"
@@ -488,6 +490,7 @@ set(ZIG_STD_FILES
"math/atan.zig"
"math/atan2.zig"
"math/atanh.zig"
+ "math/big/index.zig"
"math/big/int.zig"
"math/cbrt.zig"
"math/ceil.zig"
@@ -553,9 +556,10 @@ set(ZIG_STD_FILES
"net.zig"
"os/child_process.zig"
"os/darwin.zig"
- "os/darwin_errno.zig"
+ "os/darwin/errno.zig"
"os/epoch.zig"
"os/file.zig"
+ "os/get_app_data_dir.zig"
"os/get_user_id.zig"
"os/index.zig"
"os/linux/errno.zig"
@@ -564,8 +568,14 @@ set(ZIG_STD_FILES
"os/linux/x86_64.zig"
"os/path.zig"
"os/time.zig"
+ "os/windows/advapi32.zig"
"os/windows/error.zig"
"os/windows/index.zig"
+ "os/windows/kernel32.zig"
+ "os/windows/ole32.zig"
+ "os/windows/shell32.zig"
+ "os/windows/shlwapi.zig"
+ "os/windows/user32.zig"
"os/windows/util.zig"
"os/zen.zig"
"rand/index.zig"
@@ -614,6 +624,7 @@ set(ZIG_STD_FILES
"zig/ast.zig"
"zig/index.zig"
"zig/parse.zig"
+ "zig/parse_string_literal.zig"
"zig/render.zig"
"zig/tokenizer.zig"
)
diff --git a/README.md b/README.md
index 23b8238cd2..3ada62f107 100644
--- a/README.md
+++ b/README.md
@@ -21,19 +21,19 @@ clarity.
* Compatible with C libraries with no wrapper necessary. Directly include
C .h files and get access to the functions and symbols therein.
* Provides standard library which competes with the C standard library and is
- always compiled against statically in source form. Compile units do not
+ always compiled against statically in source form. Zig binaries do not
depend on libc unless explicitly linked.
- * Nullable type instead of null pointers.
+ * Optional type instead of null pointers.
* Safe unions, tagged unions, and C ABI compatible unions.
* Generics so that one can write efficient data structures that work for any
data type.
* No header files required. Top level declarations are entirely
order-independent.
* Compile-time code execution. Compile-time reflection.
- * Partial compile-time function evaluation with eliminates the need for
+ * Partial compile-time function evaluation which eliminates the need for
a preprocessor or macros.
* The binaries produced by Zig have complete debugging information so you can,
- for example, use GDB or MSVC to debug your software.
+ for example, use GDB, MSVC, or LLDB to debug your software.
* Built-in unit tests with `zig test`.
* Friendly toward package maintainers. Reproducible build, bootstrapping
process carefully documented. Issues filed by package maintainers are
@@ -70,7 +70,7 @@ that counts as "freestanding" for the purposes of this table.
## Community
- * IRC: `#zig` on Freenode.
+ * IRC: `#zig` on Freenode ([Channel Logs](https://irclog.whitequark.org/zig/)).
* Reddit: [/r/zig](https://www.reddit.com/r/zig)
* Email list: [ziglang@googlegroups.com](https://groups.google.com/forum/#!forum/ziglang)
diff --git a/build.zig b/build.zig
index c9e70887e3..e7a5c5cba1 100644
--- a/build.zig
+++ b/build.zig
@@ -92,7 +92,7 @@ pub fn build(b: *Builder) !void {
test_step.dependOn(tests.addPkgTests(b, test_filter, "std/special/compiler_rt/index.zig", "compiler-rt", "Run the compiler_rt tests", modes));
test_step.dependOn(tests.addCompareOutputTests(b, test_filter, modes));
- test_step.dependOn(tests.addBuildExampleTests(b, test_filter));
+ test_step.dependOn(tests.addBuildExampleTests(b, test_filter, modes));
test_step.dependOn(tests.addCompileErrorTests(b, test_filter, modes));
test_step.dependOn(tests.addAssembleAndLinkTests(b, test_filter, modes));
test_step.dependOn(tests.addRuntimeSafetyTests(b, test_filter, modes));
diff --git a/doc/langref.html.in b/doc/langref.html.in
index 46b325832b..60ba09d391 100644
--- a/doc/langref.html.in
+++ b/doc/langref.html.in
@@ -1087,7 +1087,7 @@ unwrapped == 1234
If a is false, returns false
- without evaluating b. Otherwise, retuns b.
+ without evaluating b. Otherwise, returns b.
|
false and true == false
@@ -1102,7 +1102,7 @@ unwrapped == 1234
|
If a is true, returns true
- without evaluating b. Otherwise, retuns b.
+ without evaluating b. Otherwise, returns b.
|
false or true == true
@@ -1483,7 +1483,7 @@ test "pointer array access" {
}
test "pointer slicing" {
- // In Zig, we prefer using slices over null-terminated pointers.
+ // In Zig, we prefer slices over pointers to null-terminated arrays.
// You can turn an array into a slice using slice syntax:
var array = []u8{ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 };
const slice = array[2..4];
diff --git a/src-self-hosted/c.zig b/src-self-hosted/c.zig
index 3912462985..778d851240 100644
--- a/src-self-hosted/c.zig
+++ b/src-self-hosted/c.zig
@@ -4,4 +4,5 @@ pub use @cImport({
@cInclude("inttypes.h");
@cInclude("config.h");
@cInclude("zig_llvm.h");
+ @cInclude("windows_sdk.h");
});
diff --git a/src-self-hosted/c_int.zig b/src-self-hosted/c_int.zig
new file mode 100644
index 0000000000..10ce54da05
--- /dev/null
+++ b/src-self-hosted/c_int.zig
@@ -0,0 +1,68 @@
+pub const CInt = struct {
+ id: Id,
+ zig_name: []const u8,
+ c_name: []const u8,
+ is_signed: bool,
+
+ pub const Id = enum {
+ Short,
+ UShort,
+ Int,
+ UInt,
+ Long,
+ ULong,
+ LongLong,
+ ULongLong,
+ };
+
+ pub const list = []CInt{
+ CInt{
+ .id = Id.Short,
+ .zig_name = "c_short",
+ .c_name = "short",
+ .is_signed = true,
+ },
+ CInt{
+ .id = Id.UShort,
+ .zig_name = "c_ushort",
+ .c_name = "unsigned short",
+ .is_signed = false,
+ },
+ CInt{
+ .id = Id.Int,
+ .zig_name = "c_int",
+ .c_name = "int",
+ .is_signed = true,
+ },
+ CInt{
+ .id = Id.UInt,
+ .zig_name = "c_uint",
+ .c_name = "unsigned int",
+ .is_signed = false,
+ },
+ CInt{
+ .id = Id.Long,
+ .zig_name = "c_long",
+ .c_name = "long",
+ .is_signed = true,
+ },
+ CInt{
+ .id = Id.ULong,
+ .zig_name = "c_ulong",
+ .c_name = "unsigned long",
+ .is_signed = false,
+ },
+ CInt{
+ .id = Id.LongLong,
+ .zig_name = "c_longlong",
+ .c_name = "long long",
+ .is_signed = true,
+ },
+ CInt{
+ .id = Id.ULongLong,
+ .zig_name = "c_ulonglong",
+ .c_name = "unsigned long long",
+ .is_signed = false,
+ },
+ };
+};
diff --git a/src-self-hosted/codegen.zig b/src-self-hosted/codegen.zig
index 698f1e5b45..ad3dce061e 100644
--- a/src-self-hosted/codegen.zig
+++ b/src-self-hosted/codegen.zig
@@ -1,19 +1,22 @@
const std = @import("std");
+const builtin = @import("builtin");
const Compilation = @import("compilation.zig").Compilation;
-// we go through llvm instead of c for 2 reasons:
-// 1. to avoid accidentally calling the non-thread-safe functions
-// 2. patch up some of the types to remove nullability
const llvm = @import("llvm.zig");
+const c = @import("c.zig");
const ir = @import("ir.zig");
const Value = @import("value.zig").Value;
const Type = @import("type.zig").Type;
const event = std.event;
const assert = std.debug.assert;
+const DW = std.dwarf;
pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code) !void {
fn_val.base.ref();
defer fn_val.base.deref(comp);
- defer code.destroy(comp.a());
+ defer code.destroy(comp.gpa());
+
+ var output_path = try await (async comp.createRandomOutputPath(comp.target.objFileExt()) catch unreachable);
+ errdefer output_path.deinit();
const llvm_handle = try comp.event_loop_local.getAnyLlvmContext();
defer llvm_handle.release(comp.event_loop_local);
@@ -23,15 +26,59 @@ pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code)
const module = llvm.ModuleCreateWithNameInContext(comp.name.ptr(), context) orelse return error.OutOfMemory;
defer llvm.DisposeModule(module);
+ llvm.SetTarget(module, comp.llvm_triple.ptr());
+ llvm.SetDataLayout(module, comp.target_layout_str);
+
+ if (comp.target.getObjectFormat() == builtin.ObjectFormat.coff) {
+ llvm.AddModuleCodeViewFlag(module);
+ } else {
+ llvm.AddModuleDebugInfoFlag(module);
+ }
+
const builder = llvm.CreateBuilderInContext(context) orelse return error.OutOfMemory;
defer llvm.DisposeBuilder(builder);
+ const dibuilder = llvm.CreateDIBuilder(module, true) orelse return error.OutOfMemory;
+ defer llvm.DisposeDIBuilder(dibuilder);
+
+ // Don't use ZIG_VERSION_STRING here. LLVM misparses it when it includes
+ // the git revision.
+ const producer = try std.Buffer.allocPrint(
+ &code.arena.allocator,
+ "zig {}.{}.{}",
+ u32(c.ZIG_VERSION_MAJOR),
+ u32(c.ZIG_VERSION_MINOR),
+ u32(c.ZIG_VERSION_PATCH),
+ );
+ const flags = c"";
+ const runtime_version = 0;
+ const compile_unit_file = llvm.CreateFile(
+ dibuilder,
+ comp.name.ptr(),
+ comp.root_package.root_src_dir.ptr(),
+ ) orelse return error.OutOfMemory;
+ const is_optimized = comp.build_mode != builtin.Mode.Debug;
+ const compile_unit = llvm.CreateCompileUnit(
+ dibuilder,
+ DW.LANG_C99,
+ compile_unit_file,
+ producer.ptr(),
+ is_optimized,
+ flags,
+ runtime_version,
+ c"",
+ 0,
+ !comp.strip,
+ ) orelse return error.OutOfMemory;
+
var ofile = ObjectFile{
.comp = comp,
.module = module,
.builder = builder,
+ .dibuilder = dibuilder,
.context = context,
.lock = event.Lock.init(comp.loop),
+ .arena = &code.arena.allocator,
};
try renderToLlvmModule(&ofile, fn_val, code);
@@ -41,10 +88,10 @@ pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code)
// LLVMSetModuleInlineAsm(g->module, buf_ptr(&g->global_asm));
//}
- // TODO
- //ZigLLVMDIBuilderFinalize(g->dbuilder);
+ llvm.DIBuilderFinalize(dibuilder);
if (comp.verbose_llvm_ir) {
+ std.debug.warn("raw module:\n");
llvm.DumpModule(ofile.module);
}
@@ -53,23 +100,56 @@ pub async fn renderToLlvm(comp: *Compilation, fn_val: *Value.Fn, code: *ir.Code)
var error_ptr: ?[*]u8 = null;
_ = llvm.VerifyModule(ofile.module, llvm.AbortProcessAction, &error_ptr);
}
+
+ assert(comp.emit_file_type == Compilation.Emit.Binary); // TODO support other types
+
+ const is_small = comp.build_mode == builtin.Mode.ReleaseSmall;
+ const is_debug = comp.build_mode == builtin.Mode.Debug;
+
+ var err_msg: [*]u8 = undefined;
+ // TODO integrate this with evented I/O
+ if (llvm.TargetMachineEmitToFile(
+ comp.target_machine,
+ module,
+ output_path.ptr(),
+ llvm.EmitBinary,
+ &err_msg,
+ is_debug,
+ is_small,
+ )) {
+ if (std.debug.runtime_safety) {
+ std.debug.panic("unable to write object file {}: {s}\n", output_path.toSliceConst(), err_msg);
+ }
+ return error.WritingObjectFileFailed;
+ }
+ //validate_inline_fns(g); TODO
+ fn_val.containing_object = output_path;
+ if (comp.verbose_llvm_ir) {
+ std.debug.warn("optimized module:\n");
+ llvm.DumpModule(ofile.module);
+ }
+ if (comp.verbose_link) {
+ std.debug.warn("created {}\n", output_path.toSliceConst());
+ }
}
pub const ObjectFile = struct {
comp: *Compilation,
module: llvm.ModuleRef,
builder: llvm.BuilderRef,
+ dibuilder: *llvm.DIBuilder,
context: llvm.ContextRef,
lock: event.Lock,
+ arena: *std.mem.Allocator,
- fn a(self: *ObjectFile) *std.mem.Allocator {
- return self.comp.a();
+ fn gpa(self: *ObjectFile) *std.mem.Allocator {
+ return self.comp.gpa();
}
};
pub fn renderToLlvmModule(ofile: *ObjectFile, fn_val: *Value.Fn, code: *ir.Code) !void {
// TODO audit more of codegen.cpp:fn_llvm_value and port more logic
- const llvm_fn_type = try fn_val.base.typeof.getLlvmType(ofile);
+ const llvm_fn_type = try fn_val.base.typ.getLlvmType(ofile.arena, ofile.context);
const llvm_fn = llvm.AddFunction(
ofile.module,
fn_val.symbol_name.ptr(),
@@ -87,7 +167,7 @@ pub fn renderToLlvmModule(ofile: *ObjectFile, fn_val: *Value.Fn, code: *ir.Code)
// try addLLVMFnAttrInt(ofile, llvm_fn, "alignstack", align_stack);
//}
- const fn_type = fn_val.base.typeof.cast(Type.Fn).?;
+ const fn_type = fn_val.base.typ.cast(Type.Fn).?;
try addLLVMFnAttr(ofile, llvm_fn, "nounwind");
//add_uwtable_attr(g, fn_table_entry->llvm_value);
diff --git a/src-self-hosted/compilation.zig b/src-self-hosted/compilation.zig
index 1dbbf21206..093aab21da 100644
--- a/src-self-hosted/compilation.zig
+++ b/src-self-hosted/compilation.zig
@@ -21,24 +21,47 @@ const Scope = @import("scope.zig").Scope;
const Decl = @import("decl.zig").Decl;
const ir = @import("ir.zig");
const Visib = @import("visib.zig").Visib;
-const ParsedFile = @import("parsed_file.zig").ParsedFile;
const Value = @import("value.zig").Value;
const Type = Value.Type;
const Span = errmsg.Span;
+const Msg = errmsg.Msg;
const codegen = @import("codegen.zig");
+const Package = @import("package.zig").Package;
+const link = @import("link.zig").link;
+const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
+const CInt = @import("c_int.zig").CInt;
/// Data that is local to the event loop.
pub const EventLoopLocal = struct {
loop: *event.Loop,
llvm_handle_pool: std.atomic.Stack(llvm.ContextRef),
- fn init(loop: *event.Loop) EventLoopLocal {
+ /// TODO pool these so that it doesn't have to lock
+ prng: event.Locked(std.rand.DefaultPrng),
+
+ native_libc: event.Future(LibCInstallation),
+
+ var lazy_init_targets = std.lazyInit(void);
+
+ fn init(loop: *event.Loop) !EventLoopLocal {
+ lazy_init_targets.get() orelse {
+ Target.initializeAll();
+ lazy_init_targets.resolve();
+ };
+
+ var seed_bytes: [@sizeOf(u64)]u8 = undefined;
+ try std.os.getRandomBytes(seed_bytes[0..]);
+ const seed = std.mem.readInt(seed_bytes, u64, builtin.Endian.Big);
+
return EventLoopLocal{
.loop = loop,
.llvm_handle_pool = std.atomic.Stack(llvm.ContextRef).init(),
+ .prng = event.Locked(std.rand.DefaultPrng).init(loop, std.rand.DefaultPrng.init(seed)),
+ .native_libc = event.Future(LibCInstallation).init(loop),
};
}
+ /// Must be called only after EventLoop.run completes.
fn deinit(self: *EventLoopLocal) void {
while (self.llvm_handle_pool.pop()) |node| {
c.LLVMContextDispose(node.data);
@@ -62,6 +85,13 @@ pub const EventLoopLocal = struct {
return LlvmHandle{ .node = node };
}
+
+ pub async fn getNativeLibC(self: *EventLoopLocal) !*LibCInstallation {
+ if (await (async self.native_libc.start() catch unreachable)) |ptr| return ptr;
+ try await (async self.native_libc.data.findNative(self.loop) catch unreachable);
+ self.native_libc.resolve();
+ return &self.native_libc.data;
+ }
};
pub const LlvmHandle = struct {
@@ -76,23 +106,22 @@ pub const Compilation = struct {
event_loop_local: *EventLoopLocal,
loop: *event.Loop,
name: Buffer,
+ llvm_triple: Buffer,
root_src_path: ?[]const u8,
target: Target,
+ llvm_target: llvm.TargetRef,
build_mode: builtin.Mode,
zig_lib_dir: []const u8,
+ zig_std_dir: []const u8,
+
+ /// lazily created when we need it
+ tmp_dir: event.Future(BuildError![]u8),
version_major: u32,
version_minor: u32,
version_patch: u32,
linker_script: ?[]const u8,
- cache_dir: []const u8,
- libc_lib_dir: ?[]const u8,
- libc_static_lib_dir: ?[]const u8,
- libc_include_dir: ?[]const u8,
- msvc_lib_dir: ?[]const u8,
- kernel32_lib_dir: ?[]const u8,
- dynamic_linker: ?[]const u8,
out_h_path: ?[]const u8,
is_test: bool,
@@ -106,8 +135,16 @@ pub const Compilation = struct {
lib_dirs: []const []const u8,
rpath_list: []const []const u8,
assembly_files: []const []const u8,
+
+ /// paths that are explicitly provided by the user to link against
link_objects: []const []const u8,
+ /// functions that have their own objects that we need to link
+ /// it uses an optional pointer so that tombstone removals are possible
+ fn_link_set: event.Locked(FnLinkSet),
+
+ pub const FnLinkSet = std.LinkedList(?*Value.Fn);
+
windows_subsystem_windows: bool,
windows_subsystem_console: bool,
@@ -141,7 +178,7 @@ pub const Compilation = struct {
/// Before code generation starts, must wait on this group to make sure
/// the build is complete.
- build_group: event.Group(BuildError!void),
+ prelink_group: event.Group(BuildError!void),
compile_errors: event.Locked(CompileErrList),
@@ -149,13 +186,49 @@ pub const Compilation = struct {
void_type: *Type.Void,
bool_type: *Type.Bool,
noreturn_type: *Type.NoReturn,
+ comptime_int_type: *Type.ComptimeInt,
+ u8_type: *Type.Int,
void_value: *Value.Void,
true_value: *Value.Bool,
false_value: *Value.Bool,
noreturn_value: *Value.NoReturn,
- const CompileErrList = std.ArrayList(*errmsg.Msg);
+ target_machine: llvm.TargetMachineRef,
+ target_data_ref: llvm.TargetDataRef,
+ target_layout_str: [*]u8,
+ target_ptr_bits: u32,
+
+ /// for allocating things which have the same lifetime as this Compilation
+ arena_allocator: std.heap.ArenaAllocator,
+
+ root_package: *Package,
+ std_package: *Package,
+
+ override_libc: ?*LibCInstallation,
+
+ /// need to wait on this group before deinitializing
+ deinit_group: event.Group(void),
+
+ destroy_handle: promise,
+
+ have_err_ret_tracing: bool,
+
+ /// not locked because it is read-only
+ primitive_type_table: TypeTable,
+
+ int_type_table: event.Locked(IntTypeTable),
+ array_type_table: event.Locked(ArrayTypeTable),
+ ptr_type_table: event.Locked(PtrTypeTable),
+
+ c_int_types: [CInt.list.len]*Type.Int,
+
+ const IntTypeTable = std.HashMap(*const Type.Int.Key, *Type.Int, Type.Int.Key.hash, Type.Int.Key.eql);
+ const ArrayTypeTable = std.HashMap(*const Type.Array.Key, *Type.Array, Type.Array.Key.hash, Type.Array.Key.eql);
+ const PtrTypeTable = std.HashMap(*const Type.Pointer.Key, *Type.Pointer, Type.Pointer.Key.hash, Type.Pointer.Key.eql);
+ const TypeTable = std.HashMap([]const u8, *Type, mem.hash_slice_u8, mem.eql_slice_u8);
+
+ const CompileErrList = std.ArrayList(*Msg);
// TODO handle some of these earlier and report them in a way other than error codes
pub const BuildError = error{
@@ -195,12 +268,21 @@ pub const Compilation = struct {
BufferTooSmall,
Unimplemented, // TODO remove this one
SemanticAnalysisFailed, // TODO remove this one
+ ReadOnlyFileSystem,
+ LinkQuotaExceeded,
+ EnvironmentVariableNotFound,
+ AppDataDirUnavailable,
+ LinkFailed,
+ LibCRequiredButNotProvidedOrFound,
+ LibCMissingDynamicLinker,
+ InvalidDarwinVersionString,
+ UnsupportedLinkArchitecture,
};
pub const Event = union(enum) {
Ok,
Error: BuildError,
- Fail: []*errmsg.Msg,
+ Fail: []*Msg,
};
pub const DarwinVersionMin = union(enum) {
@@ -234,31 +316,29 @@ pub const Compilation = struct {
event_loop_local: *EventLoopLocal,
name: []const u8,
root_src_path: ?[]const u8,
- target: *const Target,
+ target: Target,
kind: Kind,
build_mode: builtin.Mode,
+ is_static: bool,
zig_lib_dir: []const u8,
- cache_dir: []const u8,
) !*Compilation {
const loop = event_loop_local.loop;
-
- var name_buffer = try Buffer.init(loop.allocator, name);
- errdefer name_buffer.deinit();
-
- const events = try event.Channel(Event).create(loop, 0);
- errdefer events.destroy();
-
- const comp = try loop.allocator.create(Compilation{
+ const comp = try event_loop_local.loop.allocator.create(Compilation{
.loop = loop,
+ .arena_allocator = std.heap.ArenaAllocator.init(loop.allocator),
.event_loop_local = event_loop_local,
- .events = events,
- .name = name_buffer,
+ .events = undefined,
.root_src_path = root_src_path,
- .target = target.*,
+ .target = target,
+ .llvm_target = undefined,
.kind = kind,
.build_mode = build_mode,
.zig_lib_dir = zig_lib_dir,
- .cache_dir = cache_dir,
+ .zig_std_dir = undefined,
+ .tmp_dir = event.Future(BuildError![]u8).init(loop),
+
+ .name = undefined,
+ .llvm_triple = undefined,
.version_major = 0,
.version_minor = 0,
@@ -273,17 +353,11 @@ pub const Compilation = struct {
.verbose_link = false,
.linker_script = null,
- .libc_lib_dir = null,
- .libc_static_lib_dir = null,
- .libc_include_dir = null,
- .msvc_lib_dir = null,
- .kernel32_lib_dir = null,
- .dynamic_linker = null,
.out_h_path = null,
.is_test = false,
.each_lib_rpath = false,
.strip = false,
- .is_static = false,
+ .is_static = is_static,
.linker_rdynamic = false,
.clang_argv = [][]const u8{},
.llvm_argv = [][]const u8{},
@@ -291,9 +365,10 @@ pub const Compilation = struct {
.rpath_list = [][]const u8{},
.assembly_files = [][]const u8{},
.link_objects = [][]const u8{},
+ .fn_link_set = event.Locked(FnLinkSet).init(loop, FnLinkSet.init()),
.windows_subsystem_windows = false,
.windows_subsystem_console = false,
- .link_libs_list = ArrayList(*LinkLib).init(loop.allocator),
+ .link_libs_list = undefined,
.libc_link_lib = null,
.err_color = errmsg.Color.Auto,
.darwin_frameworks = [][]const u8{},
@@ -303,8 +378,13 @@ pub const Compilation = struct {
.emit_file_type = Emit.Binary,
.link_out_file = null,
.exported_symbol_names = event.Locked(Decl.Table).init(loop, Decl.Table.init(loop.allocator)),
- .build_group = event.Group(BuildError!void).init(loop),
+ .prelink_group = event.Group(BuildError!void).init(loop),
+ .deinit_group = event.Group(void).init(loop),
.compile_errors = event.Locked(CompileErrList).init(loop, CompileErrList.init(loop.allocator)),
+ .int_type_table = event.Locked(IntTypeTable).init(loop, IntTypeTable.init(loop.allocator)),
+ .array_type_table = event.Locked(ArrayTypeTable).init(loop, ArrayTypeTable.init(loop.allocator)),
+ .ptr_type_table = event.Locked(PtrTypeTable).init(loop, PtrTypeTable.init(loop.allocator)),
+ .c_int_types = undefined,
.meta_type = undefined,
.void_type = undefined,
@@ -314,120 +394,307 @@ pub const Compilation = struct {
.false_value = undefined,
.noreturn_type = undefined,
.noreturn_value = undefined,
+ .comptime_int_type = undefined,
+ .u8_type = undefined,
+
+ .target_machine = undefined,
+ .target_data_ref = undefined,
+ .target_layout_str = undefined,
+ .target_ptr_bits = target.getArchPtrBitWidth(),
+
+ .root_package = undefined,
+ .std_package = undefined,
+
+ .override_libc = null,
+ .destroy_handle = undefined,
+ .have_err_ret_tracing = false,
+ .primitive_type_table = undefined,
});
+ errdefer {
+ comp.int_type_table.private_data.deinit();
+ comp.array_type_table.private_data.deinit();
+ comp.ptr_type_table.private_data.deinit();
+ comp.arena_allocator.deinit();
+ comp.loop.allocator.destroy(comp);
+ }
+
+ comp.name = try Buffer.init(comp.arena(), name);
+ comp.llvm_triple = try target.getTriple(comp.arena());
+ comp.llvm_target = try Target.llvmTargetFromTriple(comp.llvm_triple);
+ comp.link_libs_list = ArrayList(*LinkLib).init(comp.arena());
+ comp.zig_std_dir = try std.os.path.join(comp.arena(), zig_lib_dir, "std");
+ comp.primitive_type_table = TypeTable.init(comp.arena());
+
+ const opt_level = switch (build_mode) {
+ builtin.Mode.Debug => llvm.CodeGenLevelNone,
+ else => llvm.CodeGenLevelAggressive,
+ };
+
+ const reloc_mode = if (is_static) llvm.RelocStatic else llvm.RelocPIC;
+
+ // LLVM creates invalid binaries on Windows sometimes.
+ // See https://github.com/ziglang/zig/issues/508
+ // As a workaround we do not use target native features on Windows.
+ var target_specific_cpu_args: ?[*]u8 = null;
+ var target_specific_cpu_features: ?[*]u8 = null;
+ errdefer llvm.DisposeMessage(target_specific_cpu_args);
+ errdefer llvm.DisposeMessage(target_specific_cpu_features);
+ if (target == Target.Native and !target.isWindows()) {
+ target_specific_cpu_args = llvm.GetHostCPUName() orelse return error.OutOfMemory;
+ target_specific_cpu_features = llvm.GetNativeFeatures() orelse return error.OutOfMemory;
+ }
+
+ comp.target_machine = llvm.CreateTargetMachine(
+ comp.llvm_target,
+ comp.llvm_triple.ptr(),
+ target_specific_cpu_args orelse c"",
+ target_specific_cpu_features orelse c"",
+ opt_level,
+ reloc_mode,
+ llvm.CodeModelDefault,
+ ) orelse return error.OutOfMemory;
+ errdefer llvm.DisposeTargetMachine(comp.target_machine);
+
+ comp.target_data_ref = llvm.CreateTargetDataLayout(comp.target_machine) orelse return error.OutOfMemory;
+ errdefer llvm.DisposeTargetData(comp.target_data_ref);
+
+ comp.target_layout_str = llvm.CopyStringRepOfTargetData(comp.target_data_ref) orelse return error.OutOfMemory;
+ errdefer llvm.DisposeMessage(comp.target_layout_str);
+
+ comp.events = try event.Channel(Event).create(comp.loop, 0);
+ errdefer comp.events.destroy();
+
+ if (root_src_path) |root_src| {
+ const dirname = std.os.path.dirname(root_src) orelse ".";
+ const basename = std.os.path.basename(root_src);
+
+ comp.root_package = try Package.create(comp.arena(), dirname, basename);
+ comp.std_package = try Package.create(comp.arena(), comp.zig_std_dir, "index.zig");
+ try comp.root_package.add("std", comp.std_package);
+ } else {
+ comp.root_package = try Package.create(comp.arena(), ".", "");
+ }
+
try comp.initTypes();
+
+ comp.destroy_handle = try async comp.internalDeinit();
+
return comp;
}
+ /// it does ref the result because it could be an arbitrary integer size
+ pub async fn getPrimitiveType(comp: *Compilation, name: []const u8) !?*Type {
+ if (name.len >= 2) {
+ switch (name[0]) {
+ 'i', 'u' => blk: {
+ for (name[1..]) |byte|
+ switch (byte) {
+ '0'...'9' => {},
+ else => break :blk,
+ };
+ const is_signed = name[0] == 'i';
+ const bit_count = std.fmt.parseUnsigned(u32, name[1..], 10) catch |err| switch (err) {
+ error.Overflow => return error.Overflow,
+ error.InvalidCharacter => unreachable, // we just checked the characters above
+ };
+ const int_type = try await (async Type.Int.get(comp, Type.Int.Key{
+ .bit_count = bit_count,
+ .is_signed = is_signed,
+ }) catch unreachable);
+ errdefer int_type.base.base.deref();
+ return &int_type.base;
+ },
+ else => {},
+ }
+ }
+
+ if (comp.primitive_type_table.get(name)) |entry| {
+ entry.value.base.ref();
+ return entry.value;
+ }
+
+ return null;
+ }
+
fn initTypes(comp: *Compilation) !void {
- comp.meta_type = try comp.a().create(Type.MetaType{
+ comp.meta_type = try comp.arena().create(Type.MetaType{
.base = Type{
+ .name = "type",
.base = Value{
.id = Value.Id.Type,
- .typeof = undefined,
+ .typ = undefined,
.ref_count = std.atomic.Int(usize).init(3), // 3 because it references itself twice
},
.id = builtin.TypeId.Type,
+ .abi_alignment = Type.AbiAlignment.init(comp.loop),
},
.value = undefined,
});
comp.meta_type.value = &comp.meta_type.base;
- comp.meta_type.base.base.typeof = &comp.meta_type.base;
- errdefer comp.a().destroy(comp.meta_type);
+ comp.meta_type.base.base.typ = &comp.meta_type.base;
+ assert((try comp.primitive_type_table.put(comp.meta_type.base.name, &comp.meta_type.base)) == null);
- comp.void_type = try comp.a().create(Type.Void{
+ comp.void_type = try comp.arena().create(Type.Void{
.base = Type{
+ .name = "void",
.base = Value{
.id = Value.Id.Type,
- .typeof = &Type.MetaType.get(comp).base,
+ .typ = &Type.MetaType.get(comp).base,
.ref_count = std.atomic.Int(usize).init(1),
},
.id = builtin.TypeId.Void,
+ .abi_alignment = Type.AbiAlignment.init(comp.loop),
},
});
- errdefer comp.a().destroy(comp.void_type);
+ assert((try comp.primitive_type_table.put(comp.void_type.base.name, &comp.void_type.base)) == null);
- comp.noreturn_type = try comp.a().create(Type.NoReturn{
+ comp.noreturn_type = try comp.arena().create(Type.NoReturn{
.base = Type{
+ .name = "noreturn",
.base = Value{
.id = Value.Id.Type,
- .typeof = &Type.MetaType.get(comp).base,
+ .typ = &Type.MetaType.get(comp).base,
.ref_count = std.atomic.Int(usize).init(1),
},
.id = builtin.TypeId.NoReturn,
+ .abi_alignment = Type.AbiAlignment.init(comp.loop),
},
});
- errdefer comp.a().destroy(comp.noreturn_type);
+ assert((try comp.primitive_type_table.put(comp.noreturn_type.base.name, &comp.noreturn_type.base)) == null);
- comp.bool_type = try comp.a().create(Type.Bool{
+ comp.comptime_int_type = try comp.arena().create(Type.ComptimeInt{
.base = Type{
+ .name = "comptime_int",
.base = Value{
.id = Value.Id.Type,
- .typeof = &Type.MetaType.get(comp).base,
+ .typ = &Type.MetaType.get(comp).base,
+ .ref_count = std.atomic.Int(usize).init(1),
+ },
+ .id = builtin.TypeId.ComptimeInt,
+ .abi_alignment = Type.AbiAlignment.init(comp.loop),
+ },
+ });
+ assert((try comp.primitive_type_table.put(comp.comptime_int_type.base.name, &comp.comptime_int_type.base)) == null);
+
+ comp.bool_type = try comp.arena().create(Type.Bool{
+ .base = Type{
+ .name = "bool",
+ .base = Value{
+ .id = Value.Id.Type,
+ .typ = &Type.MetaType.get(comp).base,
.ref_count = std.atomic.Int(usize).init(1),
},
.id = builtin.TypeId.Bool,
+ .abi_alignment = Type.AbiAlignment.init(comp.loop),
},
});
- errdefer comp.a().destroy(comp.bool_type);
+ assert((try comp.primitive_type_table.put(comp.bool_type.base.name, &comp.bool_type.base)) == null);
- comp.void_value = try comp.a().create(Value.Void{
+ comp.void_value = try comp.arena().create(Value.Void{
.base = Value{
.id = Value.Id.Void,
- .typeof = &Type.Void.get(comp).base,
+ .typ = &Type.Void.get(comp).base,
.ref_count = std.atomic.Int(usize).init(1),
},
});
- errdefer comp.a().destroy(comp.void_value);
- comp.true_value = try comp.a().create(Value.Bool{
+ comp.true_value = try comp.arena().create(Value.Bool{
.base = Value{
.id = Value.Id.Bool,
- .typeof = &Type.Bool.get(comp).base,
+ .typ = &Type.Bool.get(comp).base,
.ref_count = std.atomic.Int(usize).init(1),
},
.x = true,
});
- errdefer comp.a().destroy(comp.true_value);
- comp.false_value = try comp.a().create(Value.Bool{
+ comp.false_value = try comp.arena().create(Value.Bool{
.base = Value{
.id = Value.Id.Bool,
- .typeof = &Type.Bool.get(comp).base,
+ .typ = &Type.Bool.get(comp).base,
.ref_count = std.atomic.Int(usize).init(1),
},
.x = false,
});
- errdefer comp.a().destroy(comp.false_value);
- comp.noreturn_value = try comp.a().create(Value.NoReturn{
+ comp.noreturn_value = try comp.arena().create(Value.NoReturn{
.base = Value{
.id = Value.Id.NoReturn,
- .typeof = &Type.NoReturn.get(comp).base,
+ .typ = &Type.NoReturn.get(comp).base,
.ref_count = std.atomic.Int(usize).init(1),
},
});
- errdefer comp.a().destroy(comp.noreturn_value);
+
+ for (CInt.list) |cint, i| {
+ const c_int_type = try comp.arena().create(Type.Int{
+ .base = Type{
+ .name = cint.zig_name,
+ .base = Value{
+ .id = Value.Id.Type,
+ .typ = &Type.MetaType.get(comp).base,
+ .ref_count = std.atomic.Int(usize).init(1),
+ },
+ .id = builtin.TypeId.Int,
+ .abi_alignment = Type.AbiAlignment.init(comp.loop),
+ },
+ .key = Type.Int.Key{
+ .is_signed = cint.is_signed,
+ .bit_count = comp.target.cIntTypeSizeInBits(cint.id),
+ },
+ .garbage_node = undefined,
+ });
+ comp.c_int_types[i] = c_int_type;
+ assert((try comp.primitive_type_table.put(cint.zig_name, &c_int_type.base)) == null);
+ }
+ comp.u8_type = try comp.arena().create(Type.Int{
+ .base = Type{
+ .name = "u8",
+ .base = Value{
+ .id = Value.Id.Type,
+ .typ = &Type.MetaType.get(comp).base,
+ .ref_count = std.atomic.Int(usize).init(1),
+ },
+ .id = builtin.TypeId.Int,
+ .abi_alignment = Type.AbiAlignment.init(comp.loop),
+ },
+ .key = Type.Int.Key{
+ .is_signed = false,
+ .bit_count = 8,
+ },
+ .garbage_node = undefined,
+ });
+ assert((try comp.primitive_type_table.put(comp.u8_type.base.name, &comp.u8_type.base)) == null);
+ }
+
+ /// This function can safely use async/await, because it manages Compilation's lifetime,
+ /// and EventLoopLocal.deinit will not be called until the event.Loop.run() completes.
+ async fn internalDeinit(self: *Compilation) void {
+ suspend;
+
+ await (async self.deinit_group.wait() catch unreachable);
+ if (self.tmp_dir.getOrNull()) |tmp_dir_result| if (tmp_dir_result.*) |tmp_dir| {
+ // TODO evented I/O?
+ os.deleteTree(self.arena(), tmp_dir) catch {};
+ } else |_| {};
+
+ self.events.destroy();
+
+ llvm.DisposeMessage(self.target_layout_str);
+ llvm.DisposeTargetData(self.target_data_ref);
+ llvm.DisposeTargetMachine(self.target_machine);
+
+ self.primitive_type_table.deinit();
+
+ self.arena_allocator.deinit();
+ self.gpa().destroy(self);
}
pub fn destroy(self: *Compilation) void {
- self.noreturn_value.base.deref(self);
- self.void_value.base.deref(self);
- self.false_value.base.deref(self);
- self.true_value.base.deref(self);
- self.noreturn_type.base.base.deref(self);
- self.void_type.base.base.deref(self);
- self.meta_type.base.base.deref(self);
-
- self.events.destroy();
- self.name.deinit();
-
- self.a().destroy(self);
+ resume self.destroy_handle;
}
pub fn build(self: *Compilation) !void {
if (self.llvm_argv.len != 0) {
- var c_compatible_args = try std.cstr.NullTerminated2DArray.fromSlices(self.a(), [][]const []const u8{
+ var c_compatible_args = try std.cstr.NullTerminated2DArray.fromSlices(self.arena(), [][]const []const u8{
[][]const u8{"zig (LLVM option parsing)"},
self.llvm_argv,
});
@@ -436,14 +703,13 @@ pub const Compilation = struct {
c.ZigLLVMParseCommandLineOptions(self.llvm_argv.len + 1, c_compatible_args.ptr);
}
- _ = try async self.buildAsync();
+ _ = try async self.buildAsync();
}
async fn buildAsync(self: *Compilation) void {
while (true) {
// TODO directly awaiting async should guarantee memory allocation elision
- // TODO also async before suspending should guarantee memory allocation elision
- const build_result = await (async self.addRootSrc() catch unreachable);
+ const build_result = await (async self.compileAndLink() catch unreachable);
// this makes a handy error return trace and stack trace in debug mode
if (std.debug.runtime_safety) {
@@ -464,7 +730,7 @@ pub const Compilation = struct {
}
} else |err| {
// if there's an error then the compile errors have dangling references
- self.a().free(compile_errors);
+ self.gpa().free(compile_errors);
await (async self.events.put(Event{ .Error = err }) catch unreachable);
}
@@ -474,111 +740,215 @@ pub const Compilation = struct {
}
}
- async fn addRootSrc(self: *Compilation) !void {
- const root_src_path = self.root_src_path orelse @panic("TODO handle null root src path");
- // TODO async/await os.path.real
- const root_src_real_path = os.path.real(self.a(), root_src_path) catch |err| {
- try printError("unable to get real path '{}': {}", root_src_path, err);
- return err;
- };
- errdefer self.a().free(root_src_real_path);
+ async fn compileAndLink(self: *Compilation) !void {
+ if (self.root_src_path) |root_src_path| {
+ // TODO async/await os.path.real
+ const root_src_real_path = os.path.real(self.gpa(), root_src_path) catch |err| {
+ try printError("unable to get real path '{}': {}", root_src_path, err);
+ return err;
+ };
+ const root_scope = blk: {
+ errdefer self.gpa().free(root_src_real_path);
- // TODO async/await readFileAlloc()
- const source_code = io.readFileAlloc(self.a(), root_src_real_path) catch |err| {
- try printError("unable to open '{}': {}", root_src_real_path, err);
- return err;
- };
- errdefer self.a().free(source_code);
+ // TODO async/await readFileAlloc()
+ const source_code = io.readFileAlloc(self.gpa(), root_src_real_path) catch |err| {
+ try printError("unable to open '{}': {}", root_src_real_path, err);
+ return err;
+ };
+ errdefer self.gpa().free(source_code);
- const parsed_file = try self.a().create(ParsedFile{
- .tree = undefined,
- .realpath = root_src_real_path,
- });
- errdefer self.a().destroy(parsed_file);
+ const tree = try self.gpa().createOne(ast.Tree);
+ tree.* = try std.zig.parse(self.gpa(), source_code);
+ errdefer {
+ tree.deinit();
+ self.gpa().destroy(tree);
+ }
- parsed_file.tree = try std.zig.parse(self.a(), source_code);
- errdefer parsed_file.tree.deinit();
+ break :blk try Scope.Root.create(self, tree, root_src_real_path);
+ };
+ defer root_scope.base.deref(self);
+ const tree = root_scope.tree;
- const tree = &parsed_file.tree;
+ var error_it = tree.errors.iterator(0);
+ while (error_it.next()) |parse_error| {
+ const msg = try Msg.createFromParseErrorAndScope(self, root_scope, parse_error);
+ errdefer msg.destroy();
- // create empty struct for it
- const decls = try Scope.Decls.create(self, null);
- defer decls.base.deref(self);
-
- var decl_group = event.Group(BuildError!void).init(self.loop);
- errdefer decl_group.cancelAll();
-
- var it = tree.root_node.decls.iterator(0);
- while (it.next()) |decl_ptr| {
- const decl = decl_ptr.*;
- switch (decl.id) {
- ast.Node.Id.Comptime => @panic("TODO"),
- ast.Node.Id.VarDecl => @panic("TODO"),
- ast.Node.Id.FnProto => {
- const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl);
-
- const name = if (fn_proto.name_token) |name_token| tree.tokenSlice(name_token) else {
- try self.addCompileError(parsed_file, Span{
- .first = fn_proto.fn_token,
- .last = fn_proto.fn_token + 1,
- }, "missing function name");
- continue;
- };
-
- const fn_decl = try self.a().create(Decl.Fn{
- .base = Decl{
- .id = Decl.Id.Fn,
- .name = name,
- .visib = parseVisibToken(tree, fn_proto.visib_token),
- .resolution = event.Future(BuildError!void).init(self.loop),
- .resolution_in_progress = 0,
- .parsed_file = parsed_file,
- .parent_scope = &decls.base,
- },
- .value = Decl.Fn.Val{ .Unresolved = {} },
- .fn_proto = fn_proto,
- });
- errdefer self.a().destroy(fn_decl);
-
- try decl_group.call(addTopLevelDecl, self, &fn_decl.base);
- },
- ast.Node.Id.TestDecl => @panic("TODO"),
- else => unreachable,
+ try await (async self.addCompileErrorAsync(msg) catch unreachable);
}
+ if (tree.errors.len != 0) {
+ return;
+ }
+
+ const decls = try Scope.Decls.create(self, &root_scope.base);
+ defer decls.base.deref(self);
+
+ var decl_group = event.Group(BuildError!void).init(self.loop);
+ var decl_group_consumed = false;
+ errdefer if (!decl_group_consumed) decl_group.cancelAll();
+
+ var it = tree.root_node.decls.iterator(0);
+ while (it.next()) |decl_ptr| {
+ const decl = decl_ptr.*;
+ switch (decl.id) {
+ ast.Node.Id.Comptime => {
+ const comptime_node = @fieldParentPtr(ast.Node.Comptime, "base", decl);
+
+ try self.prelink_group.call(addCompTimeBlock, self, &decls.base, comptime_node);
+ },
+ ast.Node.Id.VarDecl => @panic("TODO"),
+ ast.Node.Id.FnProto => {
+ const fn_proto = @fieldParentPtr(ast.Node.FnProto, "base", decl);
+
+ const name = if (fn_proto.name_token) |name_token| tree.tokenSlice(name_token) else {
+ try self.addCompileError(root_scope, Span{
+ .first = fn_proto.fn_token,
+ .last = fn_proto.fn_token + 1,
+ }, "missing function name");
+ continue;
+ };
+
+ const fn_decl = try self.gpa().create(Decl.Fn{
+ .base = Decl{
+ .id = Decl.Id.Fn,
+ .name = name,
+ .visib = parseVisibToken(tree, fn_proto.visib_token),
+ .resolution = event.Future(BuildError!void).init(self.loop),
+ .parent_scope = &decls.base,
+ },
+ .value = Decl.Fn.Val{ .Unresolved = {} },
+ .fn_proto = fn_proto,
+ });
+ errdefer self.gpa().destroy(fn_decl);
+
+ try decl_group.call(addTopLevelDecl, self, decls, &fn_decl.base);
+ },
+ ast.Node.Id.TestDecl => @panic("TODO"),
+ else => unreachable,
+ }
+ }
+ decl_group_consumed = true;
+ try await (async decl_group.wait() catch unreachable);
+
+ // Now other code can rely on the decls scope having a complete list of names.
+ decls.name_future.resolve();
+ }
+
+ (await (async self.prelink_group.wait() catch unreachable)) catch |err| switch (err) {
+ error.SemanticAnalysisFailed => {},
+ else => return err,
+ };
+
+ const any_prelink_errors = blk: {
+ const compile_errors = await (async self.compile_errors.acquire() catch unreachable);
+ defer compile_errors.release();
+
+ break :blk compile_errors.value.len != 0;
+ };
+
+ if (!any_prelink_errors) {
+ try await (async link(self) catch unreachable);
}
- try await (async decl_group.wait() catch unreachable);
- try await (async self.build_group.wait() catch unreachable);
}
- async fn addTopLevelDecl(self: *Compilation, decl: *Decl) !void {
- const is_export = decl.isExported(&decl.parsed_file.tree);
+ /// caller takes ownership of resulting Code
+ async fn genAndAnalyzeCode(
+ comp: *Compilation,
+ scope: *Scope,
+ node: *ast.Node,
+ expected_type: ?*Type,
+ ) !*ir.Code {
+ const unanalyzed_code = try await (async ir.gen(
+ comp,
+ node,
+ scope,
+ ) catch unreachable);
+ defer unanalyzed_code.destroy(comp.gpa());
+
+ if (comp.verbose_ir) {
+ std.debug.warn("unanalyzed:\n");
+ unanalyzed_code.dump();
+ }
+
+ const analyzed_code = try await (async ir.analyze(
+ comp,
+ unanalyzed_code,
+ expected_type,
+ ) catch unreachable);
+ errdefer analyzed_code.destroy(comp.gpa());
+
+ if (comp.verbose_ir) {
+ std.debug.warn("analyzed:\n");
+ analyzed_code.dump();
+ }
+
+ return analyzed_code;
+ }
+
+ async fn addCompTimeBlock(
+ comp: *Compilation,
+ scope: *Scope,
+ comptime_node: *ast.Node.Comptime,
+ ) !void {
+ const void_type = Type.Void.get(comp);
+ defer void_type.base.base.deref(comp);
+
+ const analyzed_code = (await (async genAndAnalyzeCode(
+ comp,
+ scope,
+ comptime_node.expr,
+ &void_type.base,
+ ) catch unreachable)) catch |err| switch (err) {
+ // This poison value should not cause the errdefers to run. It simply means
+ // that comp.compile_errors is populated.
+ error.SemanticAnalysisFailed => return {},
+ else => return err,
+ };
+ analyzed_code.destroy(comp.gpa());
+ }
+
+ async fn addTopLevelDecl(self: *Compilation, decls: *Scope.Decls, decl: *Decl) !void {
+ const tree = decl.findRootScope().tree;
+ const is_export = decl.isExported(tree);
+
+ var add_to_table_resolved = false;
+ const add_to_table = async self.addDeclToTable(decls, decl) catch unreachable;
+ errdefer if (!add_to_table_resolved) cancel add_to_table; // TODO https://github.com/ziglang/zig/issues/1261
if (is_export) {
- try self.build_group.call(verifyUniqueSymbol, self, decl);
- try self.build_group.call(resolveDecl, self, decl);
+ try self.prelink_group.call(verifyUniqueSymbol, self, decl);
+ try self.prelink_group.call(resolveDecl, self, decl);
+ }
+
+ add_to_table_resolved = true;
+ try await add_to_table;
+ }
+
+ async fn addDeclToTable(self: *Compilation, decls: *Scope.Decls, decl: *Decl) !void {
+ const held = await (async decls.table.acquire() catch unreachable);
+ defer held.release();
+
+ if (try held.value.put(decl.name, decl)) |other_decl| {
+ try self.addCompileError(decls.base.findRoot(), decl.getSpan(), "redefinition of '{}'", decl.name);
+ // TODO note: other definition here
}
}
- fn addCompileError(self: *Compilation, parsed_file: *ParsedFile, span: Span, comptime fmt: []const u8, args: ...) !void {
- const text = try std.fmt.allocPrint(self.loop.allocator, fmt, args);
- errdefer self.loop.allocator.free(text);
+ fn addCompileError(self: *Compilation, root: *Scope.Root, span: Span, comptime fmt: []const u8, args: ...) !void {
+ const text = try std.fmt.allocPrint(self.gpa(), fmt, args);
+ errdefer self.gpa().free(text);
- try self.build_group.call(addCompileErrorAsync, self, parsed_file, span, text);
+ const msg = try Msg.createFromScope(self, root, span, text);
+ errdefer msg.destroy();
+
+ try self.prelink_group.call(addCompileErrorAsync, self, msg);
}
async fn addCompileErrorAsync(
self: *Compilation,
- parsed_file: *ParsedFile,
- span: Span,
- text: []u8,
+ msg: *Msg,
) !void {
- const msg = try self.loop.allocator.create(errmsg.Msg{
- .path = parsed_file.realpath,
- .text = text,
- .span = span,
- .tree = &parsed_file.tree,
- });
- errdefer self.loop.allocator.destroy(msg);
+ errdefer msg.destroy();
const compile_errors = await (async self.compile_errors.acquire() catch unreachable);
defer compile_errors.release();
@@ -592,7 +962,7 @@ pub const Compilation = struct {
if (try exported_symbol_names.value.put(decl.name, decl)) |other_decl| {
try self.addCompileError(
- decl.parsed_file,
+ decl.findRootScope(),
decl.getSpan(),
"exported symbol collision: '{}'",
decl.name,
@@ -601,11 +971,6 @@ pub const Compilation = struct {
}
}
- pub fn link(self: *Compilation, out_file: ?[]const u8) !void {
- warn("TODO link");
- return error.Todo;
- }
-
pub fn haveLibC(self: *Compilation) bool {
return self.libc_link_lib != null;
}
@@ -625,22 +990,127 @@ pub const Compilation = struct {
}
}
- const link_lib = try self.a().create(LinkLib{
+ const link_lib = try self.gpa().create(LinkLib{
.name = name,
.path = null,
.provided_explicitly = provided_explicitly,
- .symbols = ArrayList([]u8).init(self.a()),
+ .symbols = ArrayList([]u8).init(self.gpa()),
});
try self.link_libs_list.append(link_lib);
if (is_libc) {
self.libc_link_lib = link_lib;
+
+ // get a head start on looking for the native libc
+ if (self.target == Target.Native and self.override_libc == null) {
+ try self.deinit_group.call(startFindingNativeLibC, self);
+ }
}
return link_lib;
}
- fn a(self: Compilation) *mem.Allocator {
+ /// cancels itself so no need to await or cancel the promise.
+ async fn startFindingNativeLibC(self: *Compilation) void {
+ await (async self.loop.yield() catch unreachable);
+ // we don't care if it fails, we're just trying to kick off the future resolution
+ _ = (await (async self.event_loop_local.getNativeLibC() catch unreachable)) catch return;
+ }
+
+ /// General Purpose Allocator. Must free when done.
+ fn gpa(self: Compilation) *mem.Allocator {
return self.loop.allocator;
}
+
+ /// Arena Allocator. Automatically freed when the Compilation is destroyed.
+ fn arena(self: *Compilation) *mem.Allocator {
+ return &self.arena_allocator.allocator;
+ }
+
+ /// If the temporary directory for this compilation has not been created, it creates it.
+ /// Then it creates a random file name in that dir and returns it.
+ pub async fn createRandomOutputPath(self: *Compilation, suffix: []const u8) !Buffer {
+ const tmp_dir = try await (async self.getTmpDir() catch unreachable);
+ const file_prefix = await (async self.getRandomFileName() catch unreachable);
+
+ const file_name = try std.fmt.allocPrint(self.gpa(), "{}{}", file_prefix[0..], suffix);
+ defer self.gpa().free(file_name);
+
+ const full_path = try os.path.join(self.gpa(), tmp_dir, file_name[0..]);
+ errdefer self.gpa().free(full_path);
+
+ return Buffer.fromOwnedSlice(self.gpa(), full_path);
+ }
+
+ /// If the temporary directory for this Compilation has not been created, creates it.
+ /// Then returns it. The directory is unique to this Compilation and cleaned up when
+ /// the Compilation deinitializes.
+ async fn getTmpDir(self: *Compilation) ![]const u8 {
+ if (await (async self.tmp_dir.start() catch unreachable)) |ptr| return ptr.*;
+ self.tmp_dir.data = await (async self.getTmpDirImpl() catch unreachable);
+ self.tmp_dir.resolve();
+ return self.tmp_dir.data;
+ }
+
+ async fn getTmpDirImpl(self: *Compilation) ![]u8 {
+ const comp_dir_name = await (async self.getRandomFileName() catch unreachable);
+ const zig_dir_path = try getZigDir(self.gpa());
+ defer self.gpa().free(zig_dir_path);
+
+ const tmp_dir = try os.path.join(self.arena(), zig_dir_path, comp_dir_name[0..]);
+ try os.makePath(self.gpa(), tmp_dir);
+ return tmp_dir;
+ }
+
+ async fn getRandomFileName(self: *Compilation) [12]u8 {
+ // here we replace the standard +/ with -_ so that it can be used in a file name
+ const b64_fs_encoder = std.base64.Base64Encoder.init(
+ "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_",
+ std.base64.standard_pad_char,
+ );
+
+ var rand_bytes: [9]u8 = undefined;
+
+ {
+ const held = await (async self.event_loop_local.prng.acquire() catch unreachable);
+ defer held.release();
+
+ held.value.random.bytes(rand_bytes[0..]);
+ }
+
+ var result: [12]u8 = undefined;
+ b64_fs_encoder.encode(result[0..], rand_bytes);
+ return result;
+ }
+
+ fn registerGarbage(comp: *Compilation, comptime T: type, node: *std.atomic.Stack(*T).Node) void {
+ // TODO put the garbage somewhere
+ }
+
+ /// Returns a value which has been ref()'d once
+ async fn analyzeConstValue(comp: *Compilation, scope: *Scope, node: *ast.Node, expected_type: *Type) !*Value {
+ const analyzed_code = try await (async comp.genAndAnalyzeCode(scope, node, expected_type) catch unreachable);
+ defer analyzed_code.destroy(comp.gpa());
+
+ return analyzed_code.getCompTimeResult(comp);
+ }
+
+ async fn analyzeTypeExpr(comp: *Compilation, scope: *Scope, node: *ast.Node) !*Type {
+ const meta_type = &Type.MetaType.get(comp).base;
+ defer meta_type.base.deref(comp);
+
+ const result_val = try await (async comp.analyzeConstValue(scope, node, meta_type) catch unreachable);
+ errdefer result_val.base.deref(comp);
+
+ return result_val.cast(Type).?;
+ }
+
+ /// This declaration has been blessed as going into the final code generation.
+ pub async fn resolveDecl(comp: *Compilation, decl: *Decl) !void {
+ if (await (async decl.resolution.start() catch unreachable)) |ptr| return ptr.*;
+
+ decl.resolution.data = try await (async generateDecl(comp, decl) catch unreachable);
+ decl.resolution.resolve();
+ return decl.resolution.data;
+ }
};
fn printError(comptime format: []const u8, args: ...) !void {
@@ -660,17 +1130,6 @@ fn parseVisibToken(tree: *ast.Tree, optional_token_index: ?ast.TokenIndex) Visib
}
}
-/// This declaration has been blessed as going into the final code generation.
-pub async fn resolveDecl(comp: *Compilation, decl: *Decl) !void {
- if (@atomicRmw(u8, &decl.resolution_in_progress, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) == 0) {
- decl.resolution.data = await (async generateDecl(comp, decl) catch unreachable);
- decl.resolution.resolve();
- return decl.resolution.data;
- } else {
- return (await (async decl.resolution.get() catch unreachable)).*;
- }
-}
-
/// The function that actually does the generation.
async fn generateDecl(comp: *Compilation, decl: *Decl) !void {
switch (decl.id) {
@@ -684,68 +1143,99 @@ async fn generateDecl(comp: *Compilation, decl: *Decl) !void {
}
async fn generateDeclFn(comp: *Compilation, fn_decl: *Decl.Fn) !void {
- const body_node = fn_decl.fn_proto.body_node orelse @panic("TODO extern fn proto decl");
+ const body_node = fn_decl.fn_proto.body_node orelse return await (async generateDeclFnProto(comp, fn_decl) catch unreachable);
const fndef_scope = try Scope.FnDef.create(comp, fn_decl.base.parent_scope);
defer fndef_scope.base.deref(comp);
- // TODO actually look at the return type of the AST
- const return_type = &Type.Void.get(comp).base;
- defer return_type.base.deref(comp);
-
- const is_var_args = false;
- const params = ([*]Type.Fn.Param)(undefined)[0..0];
- const fn_type = try Type.Fn.create(comp, return_type, params, is_var_args);
+ const fn_type = try await (async analyzeFnType(comp, fn_decl.base.parent_scope, fn_decl.fn_proto) catch unreachable);
defer fn_type.base.base.deref(comp);
- var symbol_name = try std.Buffer.init(comp.a(), fn_decl.base.name);
- errdefer symbol_name.deinit();
+ var symbol_name = try std.Buffer.init(comp.gpa(), fn_decl.base.name);
+ var symbol_name_consumed = false;
+ errdefer if (!symbol_name_consumed) symbol_name.deinit();
+ // The Decl.Fn owns the initial 1 reference count
const fn_val = try Value.Fn.create(comp, fn_type, fndef_scope, symbol_name);
- defer fn_val.base.deref(comp);
+ fn_decl.value = Decl.Fn.Val{ .Fn = fn_val };
+ symbol_name_consumed = true;
- fn_decl.value = Decl.Fn.Val{ .Ok = fn_val };
-
- const unanalyzed_code = (await (async ir.gen(
- comp,
- body_node,
+ const analyzed_code = try await (async comp.genAndAnalyzeCode(
&fndef_scope.base,
- Span.token(body_node.lastToken()),
- fn_decl.base.parsed_file,
- ) catch unreachable)) catch |err| switch (err) {
- // This poison value should not cause the errdefers to run. It simply means
- // that self.compile_errors is populated.
- // TODO https://github.com/ziglang/zig/issues/769
- error.SemanticAnalysisFailed => return {},
- else => return err,
- };
- defer unanalyzed_code.destroy(comp.a());
-
- if (comp.verbose_ir) {
- std.debug.warn("unanalyzed:\n");
- unanalyzed_code.dump();
- }
-
- const analyzed_code = (await (async ir.analyze(
- comp,
- fn_decl.base.parsed_file,
- unanalyzed_code,
- null,
- ) catch unreachable)) catch |err| switch (err) {
- // This poison value should not cause the errdefers to run. It simply means
- // that self.compile_errors is populated.
- // TODO https://github.com/ziglang/zig/issues/769
- error.SemanticAnalysisFailed => return {},
- else => return err,
- };
- errdefer analyzed_code.destroy(comp.a());
-
- if (comp.verbose_ir) {
- std.debug.warn("analyzed:\n");
- analyzed_code.dump();
- }
+ body_node,
+ fn_type.return_type,
+ ) catch unreachable);
+ errdefer analyzed_code.destroy(comp.gpa());
// Kick off rendering to LLVM module, but it doesn't block the fn decl
// analysis from being complete.
- try comp.build_group.call(codegen.renderToLlvm, comp, fn_val, analyzed_code);
+ try comp.prelink_group.call(codegen.renderToLlvm, comp, fn_val, analyzed_code);
+ try comp.prelink_group.call(addFnToLinkSet, comp, fn_val);
+}
+
+async fn addFnToLinkSet(comp: *Compilation, fn_val: *Value.Fn) void {
+ fn_val.base.ref();
+ defer fn_val.base.deref(comp);
+
+ fn_val.link_set_node.data = fn_val;
+
+ const held = await (async comp.fn_link_set.acquire() catch unreachable);
+ defer held.release();
+
+ held.value.append(fn_val.link_set_node);
+}
+
+fn getZigDir(allocator: *mem.Allocator) ![]u8 {
+ return os.getAppDataDir(allocator, "zig");
+}
+
+async fn analyzeFnType(comp: *Compilation, scope: *Scope, fn_proto: *ast.Node.FnProto) !*Type.Fn {
+ const return_type_node = switch (fn_proto.return_type) {
+ ast.Node.FnProto.ReturnType.Explicit => |n| n,
+ ast.Node.FnProto.ReturnType.InferErrorSet => |n| n,
+ };
+ const return_type = try await (async comp.analyzeTypeExpr(scope, return_type_node) catch unreachable);
+ return_type.base.deref(comp);
+
+ var params = ArrayList(Type.Fn.Param).init(comp.gpa());
+ var params_consumed = false;
+ defer if (params_consumed) {
+ for (params.toSliceConst()) |param| {
+ param.typ.base.deref(comp);
+ }
+ params.deinit();
+ };
+
+ const is_var_args = false;
+ {
+ var it = fn_proto.params.iterator(0);
+ while (it.next()) |param_node_ptr| {
+ const param_node = param_node_ptr.*.cast(ast.Node.ParamDecl).?;
+ const param_type = try await (async comp.analyzeTypeExpr(scope, param_node.type_node) catch unreachable);
+ errdefer param_type.base.deref(comp);
+ try params.append(Type.Fn.Param{
+ .typ = param_type,
+ .is_noalias = param_node.noalias_token != null,
+ });
+ }
+ }
+ const fn_type = try Type.Fn.create(comp, return_type, params.toOwnedSlice(), is_var_args);
+ params_consumed = true;
+ errdefer fn_type.base.base.deref(comp);
+
+ return fn_type;
+}
+
+async fn generateDeclFnProto(comp: *Compilation, fn_decl: *Decl.Fn) !void {
+ const fn_type = try await (async analyzeFnType(comp, fn_decl.base.parent_scope, fn_decl.fn_proto) catch unreachable);
+ defer fn_type.base.base.deref(comp);
+
+ var symbol_name = try std.Buffer.init(comp.gpa(), fn_decl.base.name);
+ var symbol_name_consumed = false;
+ defer if (!symbol_name_consumed) symbol_name.deinit();
+
+ // The Decl.Fn owns the initial 1 reference count
+ const fn_proto_val = try Value.FnProto.create(comp, fn_type, symbol_name);
+ fn_decl.value = Decl.Fn.Val{ .FnProto = fn_proto_val };
+ symbol_name_consumed = true;
}
diff --git a/src-self-hosted/decl.zig b/src-self-hosted/decl.zig
index c0173266ee..6e80243038 100644
--- a/src-self-hosted/decl.zig
+++ b/src-self-hosted/decl.zig
@@ -3,7 +3,6 @@ const Allocator = mem.Allocator;
const mem = std.mem;
const ast = std.zig.ast;
const Visib = @import("visib.zig").Visib;
-const ParsedFile = @import("parsed_file.zig").ParsedFile;
const event = std.event;
const Value = @import("value.zig").Value;
const Token = std.zig.Token;
@@ -16,8 +15,6 @@ pub const Decl = struct {
name: []const u8,
visib: Visib,
resolution: event.Future(Compilation.BuildError!void),
- resolution_in_progress: u8,
- parsed_file: *ParsedFile,
parent_scope: *Scope,
pub const Table = std.HashMap([]const u8, *Decl, mem.hash_slice_u8, mem.eql_slice_u8);
@@ -48,6 +45,10 @@ pub const Decl = struct {
}
}
+ pub fn findRootScope(base: *const Decl) *Scope.Root {
+ return base.parent_scope.findRoot();
+ }
+
pub const Id = enum {
Var,
Fn,
@@ -61,12 +62,13 @@ pub const Decl = struct {
pub const Fn = struct {
base: Decl,
value: Val,
- fn_proto: *const ast.Node.FnProto,
+ fn_proto: *ast.Node.FnProto,
// TODO https://github.com/ziglang/zig/issues/683 and then make this anonymous
- pub const Val = union {
+ pub const Val = union(enum) {
Unresolved: void,
- Ok: *Value.Fn,
+ Fn: *Value.Fn,
+ FnProto: *Value.FnProto,
};
pub fn externLibName(self: Fn, tree: *ast.Tree) ?[]const u8 {
diff --git a/src-self-hosted/errmsg.zig b/src-self-hosted/errmsg.zig
index 4e353bfb14..51e135686a 100644
--- a/src-self-hosted/errmsg.zig
+++ b/src-self-hosted/errmsg.zig
@@ -4,6 +4,8 @@ const os = std.os;
const Token = std.zig.Token;
const ast = std.zig.ast;
const TokenIndex = std.zig.ast.TokenIndex;
+const Compilation = @import("compilation.zig").Compilation;
+const Scope = @import("scope.zig").Scope;
pub const Color = enum {
Auto,
@@ -16,85 +18,220 @@ pub const Span = struct {
last: ast.TokenIndex,
pub fn token(i: TokenIndex) Span {
- return Span {
+ return Span{
.first = i,
.last = i,
};
}
+
+ pub fn node(n: *ast.Node) Span {
+ return Span{
+ .first = n.firstToken(),
+ .last = n.lastToken(),
+ };
+ }
};
pub const Msg = struct {
- path: []const u8,
- text: []u8,
span: Span,
- tree: *ast.Tree,
-};
+ text: []u8,
+ data: Data,
-/// `path` must outlive the returned Msg
-/// `tree` must outlive the returned Msg
-/// Caller owns returned Msg and must free with `allocator`
-pub fn createFromParseError(
- allocator: *mem.Allocator,
- parse_error: *const ast.Error,
- tree: *ast.Tree,
- path: []const u8,
-) !*Msg {
- const loc_token = parse_error.loc();
- var text_buf = try std.Buffer.initSize(allocator, 0);
- defer text_buf.deinit();
+ const Data = union(enum) {
+ PathAndTree: PathAndTree,
+ ScopeAndComp: ScopeAndComp,
+ };
- var out_stream = &std.io.BufferOutStream.init(&text_buf).stream;
- try parse_error.render(&tree.tokens, out_stream);
+ const PathAndTree = struct {
+ realpath: []const u8,
+ tree: *ast.Tree,
+ allocator: *mem.Allocator,
+ };
- const msg = try allocator.create(Msg{
- .tree = tree,
- .path = path,
- .text = text_buf.toOwnedSlice(),
- .span = Span{
- .first = loc_token,
- .last = loc_token,
- },
- });
- errdefer allocator.destroy(msg);
+ const ScopeAndComp = struct {
+ root_scope: *Scope.Root,
+ compilation: *Compilation,
+ };
- return msg;
-}
+ pub fn destroy(self: *Msg) void {
+ switch (self.data) {
+ Data.PathAndTree => |path_and_tree| {
+ path_and_tree.allocator.free(self.text);
+ path_and_tree.allocator.destroy(self);
+ },
+ Data.ScopeAndComp => |scope_and_comp| {
+ scope_and_comp.root_scope.base.deref(scope_and_comp.compilation);
+ scope_and_comp.compilation.gpa().free(self.text);
+ scope_and_comp.compilation.gpa().destroy(self);
+ },
+ }
+ }
+
+ fn getAllocator(self: *const Msg) *mem.Allocator {
+ switch (self.data) {
+ Data.PathAndTree => |path_and_tree| {
+ return path_and_tree.allocator;
+ },
+ Data.ScopeAndComp => |scope_and_comp| {
+ return scope_and_comp.compilation.gpa();
+ },
+ }
+ }
+
+ pub fn getRealPath(self: *const Msg) []const u8 {
+ switch (self.data) {
+ Data.PathAndTree => |path_and_tree| {
+ return path_and_tree.realpath;
+ },
+ Data.ScopeAndComp => |scope_and_comp| {
+ return scope_and_comp.root_scope.realpath;
+ },
+ }
+ }
+
+ pub fn getTree(self: *const Msg) *ast.Tree {
+ switch (self.data) {
+ Data.PathAndTree => |path_and_tree| {
+ return path_and_tree.tree;
+ },
+ Data.ScopeAndComp => |scope_and_comp| {
+ return scope_and_comp.root_scope.tree;
+ },
+ }
+ }
+
+ /// Takes ownership of text
+ /// References root_scope, and derefs when the msg is freed
+ pub fn createFromScope(comp: *Compilation, root_scope: *Scope.Root, span: Span, text: []u8) !*Msg {
+ const msg = try comp.gpa().create(Msg{
+ .text = text,
+ .span = span,
+ .data = Data{
+ .ScopeAndComp = ScopeAndComp{
+ .root_scope = root_scope,
+ .compilation = comp,
+ },
+ },
+ });
+ root_scope.base.ref();
+ return msg;
+ }
+
+ pub fn createFromParseErrorAndScope(
+ comp: *Compilation,
+ root_scope: *Scope.Root,
+ parse_error: *const ast.Error,
+ ) !*Msg {
+ const loc_token = parse_error.loc();
+ var text_buf = try std.Buffer.initSize(comp.gpa(), 0);
+ defer text_buf.deinit();
+
+ var out_stream = &std.io.BufferOutStream.init(&text_buf).stream;
+ try parse_error.render(&root_scope.tree.tokens, out_stream);
+
+ const msg = try comp.gpa().create(Msg{
+ .text = undefined,
+ .span = Span{
+ .first = loc_token,
+ .last = loc_token,
+ },
+ .data = Data{
+ .ScopeAndComp = ScopeAndComp{
+ .root_scope = root_scope,
+ .compilation = comp,
+ },
+ },
+ });
+ root_scope.base.ref();
+ msg.text = text_buf.toOwnedSlice();
+ return msg;
+ }
+
+ /// `realpath` must outlive the returned Msg
+ /// `tree` must outlive the returned Msg
+ /// Caller owns returned Msg and must free with `allocator`
+ /// allocator will additionally be used for printing messages later.
+ pub fn createFromParseError(
+ allocator: *mem.Allocator,
+ parse_error: *const ast.Error,
+ tree: *ast.Tree,
+ realpath: []const u8,
+ ) !*Msg {
+ const loc_token = parse_error.loc();
+ var text_buf = try std.Buffer.initSize(allocator, 0);
+ defer text_buf.deinit();
+
+ var out_stream = &std.io.BufferOutStream.init(&text_buf).stream;
+ try parse_error.render(&tree.tokens, out_stream);
+
+ const msg = try allocator.create(Msg{
+ .text = undefined,
+ .data = Data{
+ .PathAndTree = PathAndTree{
+ .allocator = allocator,
+ .realpath = realpath,
+ .tree = tree,
+ },
+ },
+ .span = Span{
+ .first = loc_token,
+ .last = loc_token,
+ },
+ });
+ msg.text = text_buf.toOwnedSlice();
+ errdefer allocator.destroy(msg);
+
+ return msg;
+ }
+
+ pub fn printToStream(msg: *const Msg, stream: var, color_on: bool) !void {
+ const allocator = msg.getAllocator();
+ const realpath = msg.getRealPath();
+ const tree = msg.getTree();
+
+ const cwd = try os.getCwd(allocator);
+ defer allocator.free(cwd);
+
+ const relpath = try os.path.relative(allocator, cwd, realpath);
+ defer allocator.free(relpath);
+
+ const path = if (relpath.len < realpath.len) relpath else realpath;
+
+ const first_token = tree.tokens.at(msg.span.first);
+ const last_token = tree.tokens.at(msg.span.last);
+ const start_loc = tree.tokenLocationPtr(0, first_token);
+ const end_loc = tree.tokenLocationPtr(first_token.end, last_token);
+ if (!color_on) {
+ try stream.print(
+ "{}:{}:{}: error: {}\n",
+ path,
+ start_loc.line + 1,
+ start_loc.column + 1,
+ msg.text,
+ );
+ return;
+ }
-pub fn printToStream(stream: var, msg: *const Msg, color_on: bool) !void {
- const first_token = msg.tree.tokens.at(msg.span.first);
- const last_token = msg.tree.tokens.at(msg.span.last);
- const start_loc = msg.tree.tokenLocationPtr(0, first_token);
- const end_loc = msg.tree.tokenLocationPtr(first_token.end, last_token);
- if (!color_on) {
try stream.print(
- "{}:{}:{}: error: {}\n",
- msg.path,
+ "{}:{}:{}: error: {}\n{}\n",
+ path,
start_loc.line + 1,
start_loc.column + 1,
msg.text,
+ tree.source[start_loc.line_start..start_loc.line_end],
);
- return;
+ try stream.writeByteNTimes(' ', start_loc.column);
+ try stream.writeByteNTimes('~', last_token.end - first_token.start);
+ try stream.write("\n");
}
- try stream.print(
- "{}:{}:{}: error: {}\n{}\n",
- msg.path,
- start_loc.line + 1,
- start_loc.column + 1,
- msg.text,
- msg.tree.source[start_loc.line_start..start_loc.line_end],
- );
- try stream.writeByteNTimes(' ', start_loc.column);
- try stream.writeByteNTimes('~', last_token.end - first_token.start);
- try stream.write("\n");
-}
-
-pub fn printToFile(file: *os.File, msg: *const Msg, color: Color) !void {
- const color_on = switch (color) {
- Color.Auto => file.isTty(),
- Color.On => true,
- Color.Off => false,
- };
- var stream = &std.io.FileOutStream.init(file).stream;
- return printToStream(stream, msg, color_on);
-}
+ pub fn printToFile(msg: *const Msg, file: *os.File, color: Color) !void {
+ const color_on = switch (color) {
+ Color.Auto => file.isTty(),
+ Color.On => true,
+ Color.Off => false,
+ };
+ var stream = &std.io.FileOutStream.init(file).stream;
+ return msg.printToStream(stream, color_on);
+ }
+};
diff --git a/src-self-hosted/ir.zig b/src-self-hosted/ir.zig
index 0e0a4f9bf3..c34f06753d 100644
--- a/src-self-hosted/ir.zig
+++ b/src-self-hosted/ir.zig
@@ -8,10 +8,10 @@ const Value = @import("value.zig").Value;
const Type = Value.Type;
const assert = std.debug.assert;
const Token = std.zig.Token;
-const ParsedFile = @import("parsed_file.zig").ParsedFile;
const Span = @import("errmsg.zig").Span;
const llvm = @import("llvm.zig");
const ObjectFile = @import("codegen.zig").ObjectFile;
+const Decl = @import("decl.zig").Decl;
pub const LVal = enum {
None,
@@ -31,10 +31,10 @@ pub const IrVal = union(enum) {
pub fn dump(self: IrVal) void {
switch (self) {
- IrVal.Unknown => typeof.dump(),
- IrVal.KnownType => |typeof| {
+ IrVal.Unknown => std.debug.warn("Unknown"),
+ IrVal.KnownType => |typ| {
std.debug.warn("KnownType(");
- typeof.dump();
+ typ.dump();
std.debug.warn(")");
},
IrVal.KnownValue => |value| {
@@ -46,27 +46,28 @@ pub const IrVal = union(enum) {
}
};
-pub const Instruction = struct {
+pub const Inst = struct {
id: Id,
scope: *Scope,
debug_id: usize,
val: IrVal,
ref_count: usize,
span: Span,
+ owner_bb: *BasicBlock,
/// true if this instruction was generated by zig and not from user code
is_generated: bool,
/// the instruction that is derived from this one in analysis
- child: ?*Instruction,
+ child: ?*Inst,
/// the instruction that this one derives from in analysis
- parent: ?*Instruction,
+ parent: ?*Inst,
/// populated durign codegen
llvm_value: ?llvm.ValueRef,
- pub fn cast(base: *Instruction, comptime T: type) ?*T {
+ pub fn cast(base: *Inst, comptime T: type) ?*T {
if (base.id == comptime typeToId(T)) {
return @fieldParentPtr(T, "base", base);
}
@@ -76,18 +77,18 @@ pub const Instruction = struct {
pub fn typeToId(comptime T: type) Id {
comptime var i = 0;
inline while (i < @memberCount(Id)) : (i += 1) {
- if (T == @field(Instruction, @memberName(Id, i))) {
+ if (T == @field(Inst, @memberName(Id, i))) {
return @field(Id, @memberName(Id, i));
}
}
unreachable;
}
- pub fn dump(base: *const Instruction) void {
+ pub fn dump(base: *const Inst) void {
comptime var i = 0;
inline while (i < @memberCount(Id)) : (i += 1) {
if (base.id == @field(Id, @memberName(Id, i))) {
- const T = @field(Instruction, @memberName(Id, i));
+ const T = @field(Inst, @memberName(Id, i));
std.debug.warn("#{} = {}(", base.debug_id, @tagName(base.id));
@fieldParentPtr(T, "base", base).dump();
std.debug.warn(")");
@@ -97,32 +98,40 @@ pub const Instruction = struct {
unreachable;
}
- pub fn hasSideEffects(base: *const Instruction) bool {
+ pub fn hasSideEffects(base: *const Inst) bool {
comptime var i = 0;
inline while (i < @memberCount(Id)) : (i += 1) {
if (base.id == @field(Id, @memberName(Id, i))) {
- const T = @field(Instruction, @memberName(Id, i));
+ const T = @field(Inst, @memberName(Id, i));
return @fieldParentPtr(T, "base", base).hasSideEffects();
}
}
unreachable;
}
- pub fn analyze(base: *Instruction, ira: *Analyze) Analyze.Error!*Instruction {
- comptime var i = 0;
- inline while (i < @memberCount(Id)) : (i += 1) {
- if (base.id == @field(Id, @memberName(Id, i))) {
- const T = @field(Instruction, @memberName(Id, i));
- return @fieldParentPtr(T, "base", base).analyze(ira);
- }
+ pub async fn analyze(base: *Inst, ira: *Analyze) Analyze.Error!*Inst {
+ switch (base.id) {
+ Id.Return => return @fieldParentPtr(Return, "base", base).analyze(ira),
+ Id.Const => return @fieldParentPtr(Const, "base", base).analyze(ira),
+ Id.Call => return @fieldParentPtr(Call, "base", base).analyze(ira),
+ Id.DeclRef => return await (async @fieldParentPtr(DeclRef, "base", base).analyze(ira) catch unreachable),
+ Id.Ref => return await (async @fieldParentPtr(Ref, "base", base).analyze(ira) catch unreachable),
+ Id.DeclVar => return @fieldParentPtr(DeclVar, "base", base).analyze(ira),
+ Id.CheckVoidStmt => return @fieldParentPtr(CheckVoidStmt, "base", base).analyze(ira),
+ Id.Phi => return @fieldParentPtr(Phi, "base", base).analyze(ira),
+ Id.Br => return @fieldParentPtr(Br, "base", base).analyze(ira),
+ Id.AddImplicitReturnType => return @fieldParentPtr(AddImplicitReturnType, "base", base).analyze(ira),
+ Id.PtrType => return await (async @fieldParentPtr(PtrType, "base", base).analyze(ira) catch unreachable),
}
- unreachable;
}
- pub fn render(base: *Instruction, ofile: *ObjectFile, fn_val: *Value.Fn) (error{OutOfMemory}!?llvm.ValueRef) {
+ pub fn render(base: *Inst, ofile: *ObjectFile, fn_val: *Value.Fn) (error{OutOfMemory}!?llvm.ValueRef) {
switch (base.id) {
Id.Return => return @fieldParentPtr(Return, "base", base).render(ofile, fn_val),
Id.Const => return @fieldParentPtr(Const, "base", base).render(ofile, fn_val),
+ Id.Call => return @fieldParentPtr(Call, "base", base).render(ofile, fn_val),
+ Id.DeclRef => unreachable,
+ Id.PtrType => unreachable,
Id.Ref => @panic("TODO"),
Id.DeclVar => @panic("TODO"),
Id.CheckVoidStmt => @panic("TODO"),
@@ -132,7 +141,22 @@ pub const Instruction = struct {
}
}
- fn getAsParam(param: *Instruction) !*Instruction {
+ fn ref(base: *Inst, builder: *Builder) void {
+ base.ref_count += 1;
+ if (base.owner_bb != builder.current_basic_block and !base.isCompTime()) {
+ base.owner_bb.ref(builder);
+ }
+ }
+
+ fn copyVal(base: *Inst, comp: *Compilation) !*Value {
+ if (base.parent.?.ref_count == 0) {
+ return base.val.KnownValue.derefAndCopy(comp);
+ }
+ return base.val.KnownValue.copy(comp);
+ }
+
+ fn getAsParam(param: *Inst) !*Inst {
+ param.ref_count -= 1;
const child = param.child orelse return error.SemanticAnalysisFailed;
switch (child.val) {
IrVal.Unknown => return error.SemanticAnalysisFailed,
@@ -140,28 +164,72 @@ pub const Instruction = struct {
}
}
+ fn getConstVal(self: *Inst, ira: *Analyze) !*Value {
+ if (self.isCompTime()) {
+ return self.val.KnownValue;
+ } else {
+ try ira.addCompileError(self.span, "unable to evaluate constant expression");
+ return error.SemanticAnalysisFailed;
+ }
+ }
+
+ fn getAsConstType(param: *Inst, ira: *Analyze) !*Type {
+ const meta_type = Type.MetaType.get(ira.irb.comp);
+ meta_type.base.base.deref(ira.irb.comp);
+
+ const inst = try param.getAsParam();
+ const casted = try ira.implicitCast(inst, &meta_type.base);
+ const val = try casted.getConstVal(ira);
+ return val.cast(Value.Type).?;
+ }
+
+ fn getAsConstAlign(param: *Inst, ira: *Analyze) !u32 {
+ return error.Unimplemented;
+ //const align_type = Type.Int.get_align(ira.irb.comp);
+ //align_type.base.base.deref(ira.irb.comp);
+
+ //const inst = try param.getAsParam();
+ //const casted = try ira.implicitCast(inst, align_type);
+ //const val = try casted.getConstVal(ira);
+
+ //uint32_t align_bytes = bigint_as_unsigned(&const_val->data.x_bigint);
+ //if (align_bytes == 0) {
+ // ir_add_error(ira, value, buf_sprintf("alignment must be >= 1"));
+ // return false;
+ //}
+
+ //if (!is_power_of_2(align_bytes)) {
+ // ir_add_error(ira, value, buf_sprintf("alignment value %" PRIu32 " is not a power of 2", align_bytes));
+ // return false;
+ //}
+ }
+
/// asserts that the type is known
- fn getKnownType(self: *Instruction) *Type {
+ fn getKnownType(self: *Inst) *Type {
switch (self.val) {
- IrVal.KnownType => |typeof| return typeof,
- IrVal.KnownValue => |value| return value.typeof,
+ IrVal.KnownType => |typ| return typ,
+ IrVal.KnownValue => |value| return value.typ,
IrVal.Unknown => unreachable,
}
}
- pub fn setGenerated(base: *Instruction) void {
+ pub fn setGenerated(base: *Inst) void {
base.is_generated = true;
}
- pub fn isNoReturn(base: *const Instruction) bool {
+ pub fn isNoReturn(base: *const Inst) bool {
switch (base.val) {
IrVal.Unknown => return false,
- IrVal.KnownValue => |x| return x.typeof.id == Type.Id.NoReturn,
- IrVal.KnownType => |typeof| return typeof.id == Type.Id.NoReturn,
+ IrVal.KnownValue => |x| return x.typ.id == Type.Id.NoReturn,
+ IrVal.KnownType => |typ| return typ.id == Type.Id.NoReturn,
}
}
- pub fn linkToParent(self: *Instruction, parent: *Instruction) void {
+ pub fn isCompTime(base: *const Inst) bool {
+ return base.val == IrVal.KnownValue;
+ }
+
+ pub fn linkToParent(self: *Inst, parent: *Inst) void {
assert(self.parent == null);
assert(parent.child == null);
self.parent = parent;
@@ -177,10 +245,89 @@ pub const Instruction = struct {
Phi,
Br,
AddImplicitReturnType,
+ Call,
+ DeclRef,
+ PtrType,
+ };
+
+ pub const Call = struct {
+ base: Inst,
+ params: Params,
+
+ const Params = struct {
+ fn_ref: *Inst,
+ args: []*Inst,
+ };
+
+ const ir_val_init = IrVal.Init.Unknown;
+
+ pub fn dump(self: *const Call) void {
+ std.debug.warn("#{}(", self.params.fn_ref.debug_id);
+ for (self.params.args) |arg| {
+ std.debug.warn("#{},", arg.debug_id);
+ }
+ std.debug.warn(")");
+ }
+
+ pub fn hasSideEffects(self: *const Call) bool {
+ return true;
+ }
+
+ pub fn analyze(self: *const Call, ira: *Analyze) !*Inst {
+ const fn_ref = try self.params.fn_ref.getAsParam();
+ const fn_ref_type = fn_ref.getKnownType();
+ const fn_type = fn_ref_type.cast(Type.Fn) orelse {
+ try ira.addCompileError(fn_ref.span, "type '{}' not a function", fn_ref_type.name);
+ return error.SemanticAnalysisFailed;
+ };
+
+ if (fn_type.params.len != self.params.args.len) {
+ try ira.addCompileError(
+ self.base.span,
+ "expected {} arguments, found {}",
+ fn_type.params.len,
+ self.params.args.len,
+ );
+ return error.SemanticAnalysisFailed;
+ }
+
+ const args = try ira.irb.arena().alloc(*Inst, self.params.args.len);
+ for (self.params.args) |arg, i| {
+ args[i] = try arg.getAsParam();
+ }
+ const new_inst = try ira.irb.build(Call, self.base.scope, self.base.span, Params{
+ .fn_ref = fn_ref,
+ .args = args,
+ });
+ new_inst.val = IrVal{ .KnownType = fn_type.return_type };
+ return new_inst;
+ }
+
+ pub fn render(self: *Call, ofile: *ObjectFile, fn_val: *Value.Fn) !?llvm.ValueRef {
+ const fn_ref = self.params.fn_ref.llvm_value.?;
+
+ const args = try ofile.arena.alloc(llvm.ValueRef, self.params.args.len);
+ for (self.params.args) |arg, i| {
+ args[i] = arg.llvm_value.?;
+ }
+
+ const llvm_cc = llvm.CCallConv;
+ const fn_inline = llvm.FnInline.Auto;
+
+ return llvm.BuildCall(
+ ofile.builder,
+ fn_ref,
+ args.ptr,
+ @intCast(c_uint, args.len),
+ llvm_cc,
+ fn_inline,
+ c"",
+ ) orelse error.OutOfMemory;
+ }
};
pub const Const = struct {
- base: Instruction,
+ base: Inst,
params: Params,
const Params = struct {};
@@ -197,7 +344,7 @@ pub const Instruction = struct {
return false;
}
- pub fn analyze(self: *const Const, ira: *Analyze) !*Instruction {
+ pub fn analyze(self: *const Const, ira: *Analyze) !*Inst {
const new_inst = try ira.irb.build(Const, self.base.scope, self.base.span, Params{});
new_inst.val = IrVal{ .KnownValue = self.base.val.KnownValue.getRef() };
return new_inst;
@@ -209,11 +356,11 @@ pub const Instruction = struct {
};
pub const Return = struct {
- base: Instruction,
+ base: Inst,
params: Params,
const Params = struct {
- return_value: *Instruction,
+ return_value: *Inst,
};
const ir_val_init = IrVal.Init.NoReturn;
@@ -226,7 +373,7 @@ pub const Instruction = struct {
return true;
}
- pub fn analyze(self: *const Return, ira: *Analyze) !*Instruction {
+ pub fn analyze(self: *const Return, ira: *Analyze) !*Inst {
const value = try self.params.return_value.getAsParam();
const casted_value = try ira.implicitCast(value, ira.explicit_return_type);
@@ -235,25 +382,25 @@ pub const Instruction = struct {
return ira.irb.build(Return, self.base.scope, self.base.span, Params{ .return_value = casted_value });
}
- pub fn render(self: *Return, ofile: *ObjectFile, fn_val: *Value.Fn) ?llvm.ValueRef {
+ pub fn render(self: *Return, ofile: *ObjectFile, fn_val: *Value.Fn) !?llvm.ValueRef {
const value = self.params.return_value.llvm_value;
const return_type = self.params.return_value.getKnownType();
if (return_type.handleIsPtr()) {
@panic("TODO");
} else {
- _ = llvm.BuildRet(ofile.builder, value);
+ _ = llvm.BuildRet(ofile.builder, value) orelse return error.OutOfMemory;
}
return null;
}
};
pub const Ref = struct {
- base: Instruction,
+ base: Inst,
params: Params,
const Params = struct {
- target: *Instruction,
+ target: *Inst,
mut: Type.Pointer.Mut,
volatility: Type.Pointer.Vol,
};
@@ -266,7 +413,7 @@ pub const Instruction = struct {
return false;
}
- pub fn analyze(self: *const Ref, ira: *Analyze) !*Instruction {
+ pub async fn analyze(self: *const Ref, ira: *Analyze) !*Inst {
const target = try self.params.target.getAsParam();
if (ira.getCompTimeValOrNullUndefOk(target)) |val| {
@@ -275,7 +422,6 @@ pub const Instruction = struct {
Value.Ptr.Mut.CompTimeConst,
self.params.mut,
self.params.volatility,
- val.typeof.getAbiAlignment(ira.irb.comp),
);
}
@@ -285,14 +431,13 @@ pub const Instruction = struct {
.volatility = self.params.volatility,
});
const elem_type = target.getKnownType();
- const ptr_type = Type.Pointer.get(
- ira.irb.comp,
- elem_type,
- self.params.mut,
- self.params.volatility,
- Type.Pointer.Size.One,
- elem_type.getAbiAlignment(ira.irb.comp),
- );
+ const ptr_type = try await (async Type.Pointer.get(ira.irb.comp, Type.Pointer.Key{
+ .child_type = elem_type,
+ .mut = self.params.mut,
+ .vol = self.params.volatility,
+ .size = Type.Pointer.Size.One,
+ .alignment = Type.Pointer.Align.Abi,
+ }) catch unreachable);
// TODO: potentially set the hint that this is a stack pointer. But it might not be - this
// could be a ref of a global, for example
new_inst.val = IrVal{ .KnownType = &ptr_type.base };
@@ -301,8 +446,99 @@ pub const Instruction = struct {
}
};
+ pub const DeclRef = struct {
+ base: Inst,
+ params: Params,
+
+ const Params = struct {
+ decl: *Decl,
+ lval: LVal,
+ };
+
+ const ir_val_init = IrVal.Init.Unknown;
+
+ pub fn dump(inst: *const DeclRef) void {}
+
+ pub fn hasSideEffects(inst: *const DeclRef) bool {
+ return false;
+ }
+
+ pub async fn analyze(self: *const DeclRef, ira: *Analyze) !*Inst {
+ (await (async ira.irb.comp.resolveDecl(self.params.decl) catch unreachable)) catch |err| switch (err) {
+ error.OutOfMemory => return error.OutOfMemory,
+ else => return error.SemanticAnalysisFailed,
+ };
+ switch (self.params.decl.id) {
+ Decl.Id.CompTime => unreachable,
+ Decl.Id.Var => return error.Unimplemented,
+ Decl.Id.Fn => {
+ const fn_decl = @fieldParentPtr(Decl.Fn, "base", self.params.decl);
+ const decl_val = switch (fn_decl.value) {
+ Decl.Fn.Val.Unresolved => unreachable,
+ Decl.Fn.Val.Fn => |fn_val| &fn_val.base,
+ Decl.Fn.Val.FnProto => |fn_proto| &fn_proto.base,
+ };
+ switch (self.params.lval) {
+ LVal.None => {
+ return ira.irb.buildConstValue(self.base.scope, self.base.span, decl_val);
+ },
+ LVal.Ptr => return error.Unimplemented,
+ }
+ },
+ }
+ }
+ };
+
+ pub const PtrType = struct {
+ base: Inst,
+ params: Params,
+
+ const Params = struct {
+ child_type: *Inst,
+ mut: Type.Pointer.Mut,
+ vol: Type.Pointer.Vol,
+ size: Type.Pointer.Size,
+ alignment: ?*Inst,
+ };
+
+ const ir_val_init = IrVal.Init.Unknown;
+
+ pub fn dump(inst: *const PtrType) void {}
+
+ pub fn hasSideEffects(inst: *const PtrType) bool {
+ return false;
+ }
+
+ pub async fn analyze(self: *const PtrType, ira: *Analyze) !*Inst {
+ const child_type = try self.params.child_type.getAsConstType(ira);
+ // if (child_type->id == TypeTableEntryIdUnreachable) {
+ // ir_add_error(ira, &instruction->base, buf_sprintf("pointer to noreturn not allowed"));
+ // return ira->codegen->builtin_types.entry_invalid;
+ // } else if (child_type->id == TypeTableEntryIdOpaque && instruction->ptr_len == PtrLenUnknown) {
+ // ir_add_error(ira, &instruction->base, buf_sprintf("unknown-length pointer to opaque"));
+ // return ira->codegen->builtin_types.entry_invalid;
+ // }
+ const alignment = if (self.params.alignment) |align_inst| blk: {
+ const amt = try align_inst.getAsConstAlign(ira);
+ break :blk Type.Pointer.Align{ .Override = amt };
+ } else blk: {
+ break :blk Type.Pointer.Align{ .Abi = {} };
+ };
+ const ptr_type = try await (async Type.Pointer.get(ira.irb.comp, Type.Pointer.Key{
+ .child_type = child_type,
+ .mut = self.params.mut,
+ .vol = self.params.vol,
+ .size = self.params.size,
+ .alignment = alignment,
+ }) catch unreachable);
+ ptr_type.base.base.deref(ira.irb.comp);
+
+ return ira.irb.buildConstValue(self.base.scope, self.base.span, &ptr_type.base.base);
+ }
+ };
+
pub const DeclVar = struct {
- base: Instruction,
+ base: Inst,
params: Params,
const Params = struct {
@@ -317,39 +553,46 @@ pub const Instruction = struct {
return true;
}
- pub fn analyze(self: *const DeclVar, ira: *Analyze) !*Instruction {
+ pub fn analyze(self: *const DeclVar, ira: *Analyze) !*Inst {
return error.Unimplemented; // TODO
}
};
pub const CheckVoidStmt = struct {
- base: Instruction,
+ base: Inst,
params: Params,
const Params = struct {
- target: *Instruction,
+ target: *Inst,
};
const ir_val_init = IrVal.Init.Unknown;
- pub fn dump(inst: *const CheckVoidStmt) void {}
+ pub fn dump(self: *const CheckVoidStmt) void {
+ std.debug.warn("#{}", self.params.target.debug_id);
+ }
pub fn hasSideEffects(inst: *const CheckVoidStmt) bool {
return true;
}
- pub fn analyze(self: *const CheckVoidStmt, ira: *Analyze) !*Instruction {
- return error.Unimplemented; // TODO
+ pub fn analyze(self: *const CheckVoidStmt, ira: *Analyze) !*Inst {
+ const target = try self.params.target.getAsParam();
+ if (target.getKnownType().id != Type.Id.Void) {
+ try ira.addCompileError(self.base.span, "expression value is ignored");
+ return error.SemanticAnalysisFailed;
+ }
+ return ira.irb.buildConstVoid(self.base.scope, self.base.span, true);
}
};
pub const Phi = struct {
- base: Instruction,
+ base: Inst,
params: Params,
const Params = struct {
incoming_blocks: []*BasicBlock,
- incoming_values: []*Instruction,
+ incoming_values: []*Inst,
};
const ir_val_init = IrVal.Init.Unknown;
@@ -360,18 +603,18 @@ pub const Instruction = struct {
return false;
}
- pub fn analyze(self: *const Phi, ira: *Analyze) !*Instruction {
+ pub fn analyze(self: *const Phi, ira: *Analyze) !*Inst {
return error.Unimplemented; // TODO
}
};
pub const Br = struct {
- base: Instruction,
+ base: Inst,
params: Params,
const Params = struct {
dest_block: *BasicBlock,
- is_comptime: *Instruction,
+ is_comptime: *Inst,
};
const ir_val_init = IrVal.Init.NoReturn;
@@ -382,17 +625,41 @@ pub const Instruction = struct {
return true;
}
- pub fn analyze(self: *const Br, ira: *Analyze) !*Instruction {
+ pub fn analyze(self: *const Br, ira: *Analyze) !*Inst {
+ return error.Unimplemented; // TODO
+ }
+ };
+
+ pub const CondBr = struct {
+ base: Inst,
+ params: Params,
+
+ const Params = struct {
+ condition: *Inst,
+ then_block: *BasicBlock,
+ else_block: *BasicBlock,
+ is_comptime: *Inst,
+ };
+
+ const ir_val_init = IrVal.Init.NoReturn;
+
+ pub fn dump(inst: *const CondBr) void {}
+
+ pub fn hasSideEffects(inst: *const CondBr) bool {
+ return true;
+ }
+
+ pub fn analyze(self: *const CondBr, ira: *Analyze) !*Inst {
return error.Unimplemented; // TODO
}
};
pub const AddImplicitReturnType = struct {
- base: Instruction,
+ base: Inst,
params: Params,
pub const Params = struct {
- target: *Instruction,
+ target: *Inst,
};
const ir_val_init = IrVal.Init.Unknown;
@@ -405,12 +672,117 @@ pub const Instruction = struct {
return true;
}
- pub fn analyze(self: *const AddImplicitReturnType, ira: *Analyze) !*Instruction {
+ pub fn analyze(self: *const AddImplicitReturnType, ira: *Analyze) !*Inst {
const target = try self.params.target.getAsParam();
try ira.src_implicit_return_type_list.append(target);
return ira.irb.buildConstVoid(self.base.scope, self.base.span, true);
}
};
+
+ pub const TestErr = struct {
+ base: Inst,
+ params: Params,
+
+ pub const Params = struct {
+ target: *Inst,
+ };
+
+ const ir_val_init = IrVal.Init.Unknown;
+
+ pub fn dump(inst: *const TestErr) void {
+ std.debug.warn("#{}", inst.params.target.debug_id);
+ }
+
+ pub fn hasSideEffects(inst: *const TestErr) bool {
+ return false;
+ }
+
+ pub fn analyze(self: *const TestErr, ira: *Analyze) !*Inst {
+ const target = try self.params.target.getAsParam();
+ const target_type = target.getKnownType();
+ switch (target_type.id) {
+ Type.Id.ErrorUnion => {
+ return error.Unimplemented;
+ // if (instr_is_comptime(value)) {
+ // ConstExprValue *err_union_val = ir_resolve_const(ira, value, UndefBad);
+ // if (!err_union_val)
+ // return ira->codegen->builtin_types.entry_invalid;
+
+ // if (err_union_val->special != ConstValSpecialRuntime) {
+ // ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base);
+ // out_val->data.x_bool = (err_union_val->data.x_err_union.err != nullptr);
+ // return ira->codegen->builtin_types.entry_bool;
+ // }
+ // }
+
+ // TypeTableEntry *err_set_type = type_entry->data.error_union.err_set_type;
+ // if (!resolve_inferred_error_set(ira->codegen, err_set_type, instruction->base.source_node)) {
+ // return ira->codegen->builtin_types.entry_invalid;
+ // }
+ // if (!type_is_global_error_set(err_set_type) &&
+ // err_set_type->data.error_set.err_count == 0)
+ // {
+ // assert(err_set_type->data.error_set.infer_fn == nullptr);
+ // ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base);
+ // out_val->data.x_bool = false;
+ // return ira->codegen->builtin_types.entry_bool;
+ // }
+
+ // ir_build_test_err_from(&ira->new_irb, &instruction->base, value);
+ // return ira->codegen->builtin_types.entry_bool;
+ },
+ Type.Id.ErrorSet => {
+ return ira.irb.buildConstBool(self.base.scope, self.base.span, true);
+ },
+ else => {
+ return ira.irb.buildConstBool(self.base.scope, self.base.span, false);
+ },
+ }
+ }
+ };
+
+ pub const TestCompTime = struct {
+ base: Inst,
+ params: Params,
+
+ pub const Params = struct {
+ target: *Inst,
+ };
+
+ const ir_val_init = IrVal.Init.Unknown;
+
+ pub fn dump(inst: *const TestCompTime) void {
+ std.debug.warn("#{}", inst.params.target.debug_id);
+ }
+
+ pub fn hasSideEffects(inst: *const TestCompTime) bool {
+ return false;
+ }
+
+ pub fn analyze(self: *const TestCompTime, ira: *Analyze) !*Inst {
+ const target = try self.params.target.getAsParam();
+ return ira.irb.buildConstBool(self.base.scope, self.base.span, target.isCompTime());
+ }
+ };
+
+ pub const SaveErrRetAddr = struct {
+ base: Inst,
+ params: Params,
+
+ const Params = struct {};
+
+ const ir_val_init = IrVal.Init.Unknown;
+
+ pub fn dump(inst: *const SaveErrRetAddr) void {}
+
+ pub fn hasSideEffects(inst: *const SaveErrRetAddr) bool {
+ return true;
+ }
+
+ pub fn analyze(self: *const SaveErrRetAddr, ira: *Analyze) !*Inst {
+ return ira.irb.build(Inst.SaveErrRetAddr, self.base.scope, self.base.span, Params{});
+ }
+ };
};
pub const Variable = struct {
@@ -422,8 +794,8 @@ pub const BasicBlock = struct {
name_hint: [*]const u8, // must be a C string literal
debug_id: usize,
scope: *Scope,
- instruction_list: std.ArrayList(*Instruction),
- ref_instruction: ?*Instruction,
+ instruction_list: std.ArrayList(*Inst),
+ ref_instruction: ?*Inst,
/// for codegen
llvm_block: llvm.BasicBlockRef,
@@ -435,7 +807,7 @@ pub const BasicBlock = struct {
/// the basic block that this one derives from in analysis
parent: ?*BasicBlock,
- pub fn ref(self: *BasicBlock) void {
+ pub fn ref(self: *BasicBlock, builder: *Builder) void {
self.ref_count += 1;
}
@@ -453,7 +825,7 @@ pub const Code = struct {
arena: std.heap.ArenaAllocator,
return_type: ?*Type,
- /// allocator is comp.a()
+ /// allocator is comp.gpa()
pub fn destroy(self: *Code, allocator: *Allocator) void {
self.arena.deinit();
allocator.destroy(self);
@@ -470,6 +842,33 @@ pub const Code = struct {
}
}
}
+
+ /// returns a ref-incremented value, or adds a compile error
+ pub fn getCompTimeResult(self: *Code, comp: *Compilation) !*Value {
+ const bb = self.basic_block_list.at(0);
+ for (bb.instruction_list.toSliceConst()) |inst| {
+ if (inst.cast(Inst.Return)) |ret_inst| {
+ const ret_value = ret_inst.params.return_value;
+ if (ret_value.isCompTime()) {
+ return ret_value.val.KnownValue.getRef();
+ }
+ try comp.addCompileError(
+ ret_value.scope.findRoot(),
+ ret_value.span,
+ "unable to evaluate constant expression",
+ );
+ return error.SemanticAnalysisFailed;
+ } else if (inst.hasSideEffects()) {
+ try comp.addCompileError(
+ inst.scope.findRoot(),
+ inst.span,
+ "unable to evaluate constant expression",
+ );
+ return error.SemanticAnalysisFailed;
+ }
+ }
+ unreachable;
+ }
};
pub const Builder = struct {
@@ -477,32 +876,36 @@ pub const Builder = struct {
code: *Code,
current_basic_block: *BasicBlock,
next_debug_id: usize,
- parsed_file: *ParsedFile,
+ root_scope: *Scope.Root,
is_comptime: bool,
+ is_async: bool,
+ begin_scope: ?*Scope,
pub const Error = Analyze.Error;
- pub fn init(comp: *Compilation, parsed_file: *ParsedFile) !Builder {
- const code = try comp.a().create(Code{
+ pub fn init(comp: *Compilation, root_scope: *Scope.Root, begin_scope: ?*Scope) !Builder {
+ const code = try comp.gpa().create(Code{
.basic_block_list = undefined,
- .arena = std.heap.ArenaAllocator.init(comp.a()),
+ .arena = std.heap.ArenaAllocator.init(comp.gpa()),
.return_type = null,
});
code.basic_block_list = std.ArrayList(*BasicBlock).init(&code.arena.allocator);
- errdefer code.destroy(comp.a());
+ errdefer code.destroy(comp.gpa());
return Builder{
.comp = comp,
- .parsed_file = parsed_file,
+ .root_scope = root_scope,
.current_basic_block = undefined,
.code = code,
.next_debug_id = 0,
.is_comptime = false,
+ .is_async = false,
+ .begin_scope = begin_scope,
};
}
pub fn abort(self: *Builder) void {
- self.code.destroy(self.comp.a());
+ self.code.destroy(self.comp.gpa());
}
/// Call code.destroy() when done
@@ -517,7 +920,7 @@ pub const Builder = struct {
.name_hint = name_hint,
.debug_id = self.next_debug_id,
.scope = scope,
- .instruction_list = std.ArrayList(*Instruction).init(self.arena()),
+ .instruction_list = std.ArrayList(*Inst).init(self.arena()),
.child = null,
.parent = null,
.ref_instruction = null,
@@ -537,69 +940,210 @@ pub const Builder = struct {
self.current_basic_block = basic_block;
}
- pub fn genNode(irb: *Builder, node: *ast.Node, scope: *Scope, lval: LVal) Error!*Instruction {
+ pub async fn genNode(irb: *Builder, node: *ast.Node, scope: *Scope, lval: LVal) Error!*Inst {
switch (node.id) {
ast.Node.Id.Root => unreachable,
ast.Node.Id.Use => unreachable,
ast.Node.Id.TestDecl => unreachable,
- ast.Node.Id.VarDecl => @panic("TODO"),
- ast.Node.Id.Defer => @panic("TODO"),
- ast.Node.Id.InfixOp => @panic("TODO"),
- ast.Node.Id.PrefixOp => @panic("TODO"),
- ast.Node.Id.SuffixOp => @panic("TODO"),
- ast.Node.Id.Switch => @panic("TODO"),
- ast.Node.Id.While => @panic("TODO"),
- ast.Node.Id.For => @panic("TODO"),
- ast.Node.Id.If => @panic("TODO"),
- ast.Node.Id.ControlFlowExpression => return error.Unimplemented,
- ast.Node.Id.Suspend => @panic("TODO"),
- ast.Node.Id.VarType => @panic("TODO"),
- ast.Node.Id.ErrorType => @panic("TODO"),
- ast.Node.Id.FnProto => @panic("TODO"),
- ast.Node.Id.PromiseType => @panic("TODO"),
- ast.Node.Id.IntegerLiteral => @panic("TODO"),
- ast.Node.Id.FloatLiteral => @panic("TODO"),
- ast.Node.Id.StringLiteral => @panic("TODO"),
- ast.Node.Id.MultilineStringLiteral => @panic("TODO"),
- ast.Node.Id.CharLiteral => @panic("TODO"),
- ast.Node.Id.BoolLiteral => @panic("TODO"),
- ast.Node.Id.NullLiteral => @panic("TODO"),
- ast.Node.Id.UndefinedLiteral => @panic("TODO"),
- ast.Node.Id.ThisLiteral => @panic("TODO"),
- ast.Node.Id.Unreachable => @panic("TODO"),
- ast.Node.Id.Identifier => @panic("TODO"),
+ ast.Node.Id.VarDecl => return error.Unimplemented,
+ ast.Node.Id.Defer => return error.Unimplemented,
+ ast.Node.Id.InfixOp => return error.Unimplemented,
+ ast.Node.Id.PrefixOp => {
+ const prefix_op = @fieldParentPtr(ast.Node.PrefixOp, "base", node);
+ switch (prefix_op.op) {
+ ast.Node.PrefixOp.Op.AddressOf => return error.Unimplemented,
+ ast.Node.PrefixOp.Op.ArrayType => |n| return error.Unimplemented,
+ ast.Node.PrefixOp.Op.Await => return error.Unimplemented,
+ ast.Node.PrefixOp.Op.BitNot => return error.Unimplemented,
+ ast.Node.PrefixOp.Op.BoolNot => return error.Unimplemented,
+ ast.Node.PrefixOp.Op.Cancel => return error.Unimplemented,
+ ast.Node.PrefixOp.Op.OptionalType => return error.Unimplemented,
+ ast.Node.PrefixOp.Op.Negation => return error.Unimplemented,
+ ast.Node.PrefixOp.Op.NegationWrap => return error.Unimplemented,
+ ast.Node.PrefixOp.Op.Resume => return error.Unimplemented,
+ ast.Node.PrefixOp.Op.PtrType => |ptr_info| {
+ const inst = try await (async irb.genPtrType(prefix_op, ptr_info, scope) catch unreachable);
+ return irb.lvalWrap(scope, inst, lval);
+ },
+ ast.Node.PrefixOp.Op.SliceType => |ptr_info| return error.Unimplemented,
+ ast.Node.PrefixOp.Op.Try => return error.Unimplemented,
+ }
+ },
+ ast.Node.Id.SuffixOp => {
+ const suffix_op = @fieldParentPtr(ast.Node.SuffixOp, "base", node);
+ switch (suffix_op.op) {
+ @TagType(ast.Node.SuffixOp.Op).Call => |*call| {
+ const inst = try await (async irb.genCall(suffix_op, call, scope) catch unreachable);
+ return irb.lvalWrap(scope, inst, lval);
+ },
+ @TagType(ast.Node.SuffixOp.Op).ArrayAccess => |n| return error.Unimplemented,
+ @TagType(ast.Node.SuffixOp.Op).Slice => |slice| return error.Unimplemented,
+ @TagType(ast.Node.SuffixOp.Op).ArrayInitializer => |init_list| return error.Unimplemented,
+ @TagType(ast.Node.SuffixOp.Op).StructInitializer => |init_list| return error.Unimplemented,
+ @TagType(ast.Node.SuffixOp.Op).Deref => return error.Unimplemented,
+ @TagType(ast.Node.SuffixOp.Op).UnwrapOptional => return error.Unimplemented,
+ }
+ },
+ ast.Node.Id.Switch => return error.Unimplemented,
+ ast.Node.Id.While => return error.Unimplemented,
+ ast.Node.Id.For => return error.Unimplemented,
+ ast.Node.Id.If => return error.Unimplemented,
+ ast.Node.Id.ControlFlowExpression => {
+ const control_flow_expr = @fieldParentPtr(ast.Node.ControlFlowExpression, "base", node);
+ return await (async irb.genControlFlowExpr(control_flow_expr, scope, lval) catch unreachable);
+ },
+ ast.Node.Id.Suspend => return error.Unimplemented,
+ ast.Node.Id.VarType => return error.Unimplemented,
+ ast.Node.Id.ErrorType => return error.Unimplemented,
+ ast.Node.Id.FnProto => return error.Unimplemented,
+ ast.Node.Id.PromiseType => return error.Unimplemented,
+ ast.Node.Id.IntegerLiteral => {
+ const int_lit = @fieldParentPtr(ast.Node.IntegerLiteral, "base", node);
+ return irb.lvalWrap(scope, try irb.genIntLit(int_lit, scope), lval);
+ },
+ ast.Node.Id.FloatLiteral => return error.Unimplemented,
+ ast.Node.Id.StringLiteral => {
+ const str_lit = @fieldParentPtr(ast.Node.StringLiteral, "base", node);
+ const inst = try await (async irb.genStrLit(str_lit, scope) catch unreachable);
+ return irb.lvalWrap(scope, inst, lval);
+ },
+ ast.Node.Id.MultilineStringLiteral => return error.Unimplemented,
+ ast.Node.Id.CharLiteral => return error.Unimplemented,
+ ast.Node.Id.BoolLiteral => return error.Unimplemented,
+ ast.Node.Id.NullLiteral => return error.Unimplemented,
+ ast.Node.Id.UndefinedLiteral => return error.Unimplemented,
+ ast.Node.Id.ThisLiteral => return error.Unimplemented,
+ ast.Node.Id.Unreachable => return error.Unimplemented,
+ ast.Node.Id.Identifier => {
+ const identifier = @fieldParentPtr(ast.Node.Identifier, "base", node);
+ return await (async irb.genIdentifier(identifier, scope, lval) catch unreachable);
+ },
ast.Node.Id.GroupedExpression => {
const grouped_expr = @fieldParentPtr(ast.Node.GroupedExpression, "base", node);
- return irb.genNode(grouped_expr.expr, scope, lval);
+ return await (async irb.genNode(grouped_expr.expr, scope, lval) catch unreachable);
},
- ast.Node.Id.BuiltinCall => @panic("TODO"),
- ast.Node.Id.ErrorSetDecl => @panic("TODO"),
- ast.Node.Id.ContainerDecl => @panic("TODO"),
- ast.Node.Id.Asm => @panic("TODO"),
- ast.Node.Id.Comptime => @panic("TODO"),
+ ast.Node.Id.BuiltinCall => return error.Unimplemented,
+ ast.Node.Id.ErrorSetDecl => return error.Unimplemented,
+ ast.Node.Id.ContainerDecl => return error.Unimplemented,
+ ast.Node.Id.Asm => return error.Unimplemented,
+ ast.Node.Id.Comptime => return error.Unimplemented,
ast.Node.Id.Block => {
const block = @fieldParentPtr(ast.Node.Block, "base", node);
- return irb.lvalWrap(scope, try irb.genBlock(block, scope), lval);
+ const inst = try await (async irb.genBlock(block, scope) catch unreachable);
+ return irb.lvalWrap(scope, inst, lval);
},
- ast.Node.Id.DocComment => @panic("TODO"),
- ast.Node.Id.SwitchCase => @panic("TODO"),
- ast.Node.Id.SwitchElse => @panic("TODO"),
- ast.Node.Id.Else => @panic("TODO"),
- ast.Node.Id.Payload => @panic("TODO"),
- ast.Node.Id.PointerPayload => @panic("TODO"),
- ast.Node.Id.PointerIndexPayload => @panic("TODO"),
- ast.Node.Id.StructField => @panic("TODO"),
- ast.Node.Id.UnionTag => @panic("TODO"),
- ast.Node.Id.EnumTag => @panic("TODO"),
- ast.Node.Id.ErrorTag => @panic("TODO"),
- ast.Node.Id.AsmInput => @panic("TODO"),
- ast.Node.Id.AsmOutput => @panic("TODO"),
- ast.Node.Id.AsyncAttribute => @panic("TODO"),
- ast.Node.Id.ParamDecl => @panic("TODO"),
- ast.Node.Id.FieldInitializer => @panic("TODO"),
+ ast.Node.Id.DocComment => return error.Unimplemented,
+ ast.Node.Id.SwitchCase => return error.Unimplemented,
+ ast.Node.Id.SwitchElse => return error.Unimplemented,
+ ast.Node.Id.Else => return error.Unimplemented,
+ ast.Node.Id.Payload => return error.Unimplemented,
+ ast.Node.Id.PointerPayload => return error.Unimplemented,
+ ast.Node.Id.PointerIndexPayload => return error.Unimplemented,
+ ast.Node.Id.StructField => return error.Unimplemented,
+ ast.Node.Id.UnionTag => return error.Unimplemented,
+ ast.Node.Id.EnumTag => return error.Unimplemented,
+ ast.Node.Id.ErrorTag => return error.Unimplemented,
+ ast.Node.Id.AsmInput => return error.Unimplemented,
+ ast.Node.Id.AsmOutput => return error.Unimplemented,
+ ast.Node.Id.AsyncAttribute => return error.Unimplemented,
+ ast.Node.Id.ParamDecl => return error.Unimplemented,
+ ast.Node.Id.FieldInitializer => return error.Unimplemented,
}
}
+ async fn genCall(irb: *Builder, suffix_op: *ast.Node.SuffixOp, call: *ast.Node.SuffixOp.Op.Call, scope: *Scope) !*Inst {
+ const fn_ref = try await (async irb.genNode(suffix_op.lhs, scope, LVal.None) catch unreachable);
+
+ const args = try irb.arena().alloc(*Inst, call.params.len);
+ var it = call.params.iterator(0);
+ var i: usize = 0;
+ while (it.next()) |arg_node_ptr| : (i += 1) {
+ args[i] = try await (async irb.genNode(arg_node_ptr.*, scope, LVal.None) catch unreachable);
+ }
+
+ //bool is_async = node->data.fn_call_expr.is_async;
+ //IrInstruction *async_allocator = nullptr;
+ //if (is_async) {
+ // if (node->data.fn_call_expr.async_allocator) {
+ // async_allocator = ir_gen_node(irb, node->data.fn_call_expr.async_allocator, scope);
+ // if (async_allocator == irb->codegen->invalid_instruction)
+ // return async_allocator;
+ // }
+ //}
+
+ return irb.build(Inst.Call, scope, Span.token(suffix_op.rtoken), Inst.Call.Params{
+ .fn_ref = fn_ref,
+ .args = args,
+ });
+ //IrInstruction *fn_call = ir_build_call(irb, scope, node, nullptr, fn_ref, arg_count, args, false, FnInlineAuto, is_async, async_allocator, nullptr);
+ //return ir_lval_wrap(irb, scope, fn_call, lval);
+ }
+
+ async fn genPtrType(
+ irb: *Builder,
+ prefix_op: *ast.Node.PrefixOp,
+ ptr_info: ast.Node.PrefixOp.PtrInfo,
+ scope: *Scope,
+ ) !*Inst {
+ // TODO port more logic
+
+ //assert(node->type == NodeTypePointerType);
+ //PtrLen ptr_len = (node->data.pointer_type.star_token->id == TokenIdStar ||
+ // node->data.pointer_type.star_token->id == TokenIdStarStar) ? PtrLenSingle : PtrLenUnknown;
+ //bool is_const = node->data.pointer_type.is_const;
+ //bool is_volatile = node->data.pointer_type.is_volatile;
+ //AstNode *expr_node = node->data.pointer_type.op_expr;
+ //AstNode *align_expr = node->data.pointer_type.align_expr;
+
+ //IrInstruction *align_value;
+ //if (align_expr != nullptr) {
+ // align_value = ir_gen_node(irb, align_expr, scope);
+ // if (align_value == irb->codegen->invalid_instruction)
+ // return align_value;
+ //} else {
+ // align_value = nullptr;
+ //}
+ const child_type = try await (async irb.genNode(prefix_op.rhs, scope, LVal.None) catch unreachable);
+
+ //uint32_t bit_offset_start = 0;
+ //if (node->data.pointer_type.bit_offset_start != nullptr) {
+ // if (!bigint_fits_in_bits(node->data.pointer_type.bit_offset_start, 32, false)) {
+ // Buf *val_buf = buf_alloc();
+ // bigint_append_buf(val_buf, node->data.pointer_type.bit_offset_start, 10);
+ // exec_add_error_node(irb->codegen, irb->exec, node,
+ // buf_sprintf("value %s too large for u32 bit offset", buf_ptr(val_buf)));
+ // return irb->codegen->invalid_instruction;
+ // }
+ // bit_offset_start = bigint_as_unsigned(node->data.pointer_type.bit_offset_start);
+ //}
+
+ //uint32_t bit_offset_end = 0;
+ //if (node->data.pointer_type.bit_offset_end != nullptr) {
+ // if (!bigint_fits_in_bits(node->data.pointer_type.bit_offset_end, 32, false)) {
+ // Buf *val_buf = buf_alloc();
+ // bigint_append_buf(val_buf, node->data.pointer_type.bit_offset_end, 10);
+ // exec_add_error_node(irb->codegen, irb->exec, node,
+ // buf_sprintf("value %s too large for u32 bit offset", buf_ptr(val_buf)));
+ // return irb->codegen->invalid_instruction;
+ // }
+ // bit_offset_end = bigint_as_unsigned(node->data.pointer_type.bit_offset_end);
+ //}
+
+ //if ((bit_offset_start != 0 || bit_offset_end != 0) && bit_offset_start >= bit_offset_end) {
+ // exec_add_error_node(irb->codegen, irb->exec, node,
+ // buf_sprintf("bit offset start must be less than bit offset end"));
+ // return irb->codegen->invalid_instruction;
+ //}
+
+ return irb.build(Inst.PtrType, scope, Span.node(&prefix_op.base), Inst.PtrType.Params{
+ .child_type = child_type,
+ .mut = Type.Pointer.Mut.Mut,
+ .vol = Type.Pointer.Vol.Non,
+ .size = Type.Pointer.Size.Many,
+ .alignment = null,
+ });
+ }
+
fn isCompTime(irb: *Builder, target_scope: *Scope) bool {
if (irb.is_comptime)
return true;
@@ -610,15 +1154,105 @@ pub const Builder = struct {
Scope.Id.CompTime => return true,
Scope.Id.FnDef => return false,
Scope.Id.Decls => unreachable,
+ Scope.Id.Root => unreachable,
Scope.Id.Block,
Scope.Id.Defer,
Scope.Id.DeferExpr,
- => scope = scope.parent orelse return false,
+ => scope = scope.parent.?,
}
}
}
- pub fn genBlock(irb: *Builder, block: *ast.Node.Block, parent_scope: *Scope) !*Instruction {
+ pub fn genIntLit(irb: *Builder, int_lit: *ast.Node.IntegerLiteral, scope: *Scope) !*Inst {
+ const int_token = irb.root_scope.tree.tokenSlice(int_lit.token);
+
+ var base: u8 = undefined;
+ var rest: []const u8 = undefined;
+ if (int_token.len >= 3 and int_token[0] == '0') {
+ base = switch (int_token[1]) {
+ 'b' => u8(2),
+ 'o' => u8(8),
+ 'x' => u8(16),
+ else => unreachable,
+ };
+ rest = int_token[2..];
+ } else {
+ base = 10;
+ rest = int_token;
+ }
+
+ const comptime_int_type = Type.ComptimeInt.get(irb.comp);
+ defer comptime_int_type.base.base.deref(irb.comp);
+
+ const int_val = Value.Int.createFromString(
+ irb.comp,
+ &comptime_int_type.base,
+ base,
+ rest,
+ ) catch |err| switch (err) {
+ error.OutOfMemory => return error.OutOfMemory,
+ error.InvalidBase => unreachable,
+ error.InvalidCharForDigit => unreachable,
+ error.DigitTooLargeForBase => unreachable,
+ };
+ errdefer int_val.base.deref(irb.comp);
+
+ const inst = try irb.build(Inst.Const, scope, Span.token(int_lit.token), Inst.Const.Params{});
+ inst.val = IrVal{ .KnownValue = &int_val.base };
+ return inst;
+ }
+
+ pub async fn genStrLit(irb: *Builder, str_lit: *ast.Node.StringLiteral, scope: *Scope) !*Inst {
+ const str_token = irb.root_scope.tree.tokenSlice(str_lit.token);
+ const src_span = Span.token(str_lit.token);
+
+ var bad_index: usize = undefined;
+ var buf = std.zig.parseStringLiteral(irb.comp.gpa(), str_token, &bad_index) catch |err| switch (err) {
+ error.OutOfMemory => return error.OutOfMemory,
+ error.InvalidCharacter => {
+ try irb.comp.addCompileError(
+ irb.root_scope,
+ src_span,
+ "invalid character in string literal: '{c}'",
+ str_token[bad_index],
+ );
+ return error.SemanticAnalysisFailed;
+ },
+ };
+ var buf_cleaned = false;
+ errdefer if (!buf_cleaned) irb.comp.gpa().free(buf);
+
+ if (str_token[0] == 'c') {
+ // first we add a null
+ buf = try irb.comp.gpa().realloc(u8, buf, buf.len + 1);
+ buf[buf.len - 1] = 0;
+
+ // next make an array value
+ const array_val = try await (async Value.Array.createOwnedBuffer(irb.comp, buf) catch unreachable);
+ buf_cleaned = true;
+ defer array_val.base.deref(irb.comp);
+
+ // then make a pointer value pointing at the first element
+ const ptr_val = try await (async Value.Ptr.createArrayElemPtr(
+ irb.comp,
+ array_val,
+ Type.Pointer.Mut.Const,
+ Type.Pointer.Size.Many,
+ 0,
+ ) catch unreachable);
+ defer ptr_val.base.deref(irb.comp);
+
+ return irb.buildConstValue(scope, src_span, &ptr_val.base);
+ } else {
+ const array_val = try await (async Value.Array.createOwnedBuffer(irb.comp, buf) catch unreachable);
+ buf_cleaned = true;
+ defer array_val.base.deref(irb.comp);
+
+ return irb.buildConstValue(scope, src_span, &array_val.base);
+ }
+ }
+
+ pub async fn genBlock(irb: *Builder, block: *ast.Node.Block, parent_scope: *Scope) !*Inst {
const block_scope = try Scope.Block.create(irb.comp, parent_scope);
const outer_block_scope = &block_scope.base;
@@ -636,7 +1270,7 @@ pub const Builder = struct {
}
if (block.label) |label| {
- block_scope.incoming_values = std.ArrayList(*Instruction).init(irb.arena());
+ block_scope.incoming_values = std.ArrayList(*Inst).init(irb.arena());
block_scope.incoming_blocks = std.ArrayList(*BasicBlock).init(irb.arena());
block_scope.end_block = try irb.createBasicBlock(parent_scope, c"BlockEnd");
block_scope.is_comptime = try irb.buildConstBool(
@@ -647,7 +1281,7 @@ pub const Builder = struct {
}
var is_continuation_unreachable = false;
- var noreturn_return_value: ?*Instruction = null;
+ var noreturn_return_value: ?*Inst = null;
var stmt_it = block.statements.iterator(0);
while (stmt_it.next()) |statement_node_ptr| {
@@ -655,7 +1289,7 @@ pub const Builder = struct {
if (statement_node.cast(ast.Node.Defer)) |defer_node| {
// defer starts a new scope
- const defer_token = irb.parsed_file.tree.tokens.at(defer_node.defer_token);
+ const defer_token = irb.root_scope.tree.tokens.at(defer_node.defer_token);
const kind = switch (defer_token.id) {
Token.Id.Keyword_defer => Scope.Defer.Kind.ScopeExit,
Token.Id.Keyword_errdefer => Scope.Defer.Kind.ErrorExit,
@@ -666,7 +1300,7 @@ pub const Builder = struct {
child_scope = &defer_child_scope.base;
continue;
}
- const statement_value = try irb.genNode(statement_node, child_scope, LVal.None);
+ const statement_value = try await (async irb.genNode(statement_node, child_scope, LVal.None) catch unreachable);
is_continuation_unreachable = statement_value.isNoReturn();
if (is_continuation_unreachable) {
@@ -674,16 +1308,19 @@ pub const Builder = struct {
noreturn_return_value = statement_value;
}
- if (statement_value.cast(Instruction.DeclVar)) |decl_var| {
+ if (statement_value.cast(Inst.DeclVar)) |decl_var| {
// variable declarations start a new scope
child_scope = decl_var.params.variable.child_scope;
} else if (!is_continuation_unreachable) {
// this statement's value must be void
_ = irb.build(
- Instruction.CheckVoidStmt,
+ Inst.CheckVoidStmt,
child_scope,
- statement_value.span,
- Instruction.CheckVoidStmt.Params{ .target = statement_value },
+ Span{
+ .first = statement_node.firstToken(),
+ .last = statement_node.lastToken(),
+ },
+ Inst.CheckVoidStmt.Params{ .target = statement_value },
);
}
}
@@ -695,7 +1332,7 @@ pub const Builder = struct {
}
try irb.setCursorAtEndAndAppendBlock(block_scope.end_block);
- return irb.build(Instruction.Phi, parent_scope, Span.token(block.rbrace), Instruction.Phi.Params{
+ return irb.build(Inst.Phi, parent_scope, Span.token(block.rbrace), Inst.Phi.Params{
.incoming_blocks = block_scope.incoming_blocks.toOwnedSlice(),
.incoming_values = block_scope.incoming_values.toOwnedSlice(),
});
@@ -706,26 +1343,216 @@ pub const Builder = struct {
try block_scope.incoming_values.append(
try irb.buildConstVoid(parent_scope, Span.token(block.rbrace), true),
);
- _ = try irb.genDefersForBlock(child_scope, outer_block_scope, Scope.Defer.Kind.ScopeExit);
+ _ = try await (async irb.genDefersForBlock(child_scope, outer_block_scope, Scope.Defer.Kind.ScopeExit) catch unreachable);
- _ = try irb.buildGen(Instruction.Br, parent_scope, Span.token(block.rbrace), Instruction.Br.Params{
+ _ = try irb.buildGen(Inst.Br, parent_scope, Span.token(block.rbrace), Inst.Br.Params{
.dest_block = block_scope.end_block,
.is_comptime = block_scope.is_comptime,
});
try irb.setCursorAtEndAndAppendBlock(block_scope.end_block);
- return irb.build(Instruction.Phi, parent_scope, Span.token(block.rbrace), Instruction.Phi.Params{
+ return irb.build(Inst.Phi, parent_scope, Span.token(block.rbrace), Inst.Phi.Params{
.incoming_blocks = block_scope.incoming_blocks.toOwnedSlice(),
.incoming_values = block_scope.incoming_values.toOwnedSlice(),
});
}
- _ = try irb.genDefersForBlock(child_scope, outer_block_scope, Scope.Defer.Kind.ScopeExit);
+ _ = try await (async irb.genDefersForBlock(child_scope, outer_block_scope, Scope.Defer.Kind.ScopeExit) catch unreachable);
return irb.buildConstVoid(child_scope, Span.token(block.rbrace), true);
}
- fn genDefersForBlock(
+ pub async fn genControlFlowExpr(
+ irb: *Builder,
+ control_flow_expr: *ast.Node.ControlFlowExpression,
+ scope: *Scope,
+ lval: LVal,
+ ) !*Inst {
+ switch (control_flow_expr.kind) {
+ ast.Node.ControlFlowExpression.Kind.Break => |arg| return error.Unimplemented,
+ ast.Node.ControlFlowExpression.Kind.Continue => |arg| return error.Unimplemented,
+ ast.Node.ControlFlowExpression.Kind.Return => {
+ const src_span = Span.token(control_flow_expr.ltoken);
+ if (scope.findFnDef() == null) {
+ try irb.comp.addCompileError(
+ irb.root_scope,
+ src_span,
+ "return expression outside function definition",
+ );
+ return error.SemanticAnalysisFailed;
+ }
+
+ if (scope.findDeferExpr()) |scope_defer_expr| {
+ if (!scope_defer_expr.reported_err) {
+ try irb.comp.addCompileError(
+ irb.root_scope,
+ src_span,
+ "cannot return from defer expression",
+ );
+ scope_defer_expr.reported_err = true;
+ }
+ return error.SemanticAnalysisFailed;
+ }
+
+ const outer_scope = irb.begin_scope.?;
+ const return_value = if (control_flow_expr.rhs) |rhs| blk: {
+ break :blk try await (async irb.genNode(rhs, scope, LVal.None) catch unreachable);
+ } else blk: {
+ break :blk try irb.buildConstVoid(scope, src_span, true);
+ };
+
+ const defer_counts = irb.countDefers(scope, outer_scope);
+ const have_err_defers = defer_counts.error_exit != 0;
+ if (have_err_defers or irb.comp.have_err_ret_tracing) {
+ const err_block = try irb.createBasicBlock(scope, c"ErrRetErr");
+ const ok_block = try irb.createBasicBlock(scope, c"ErrRetOk");
+ if (!have_err_defers) {
+ _ = try await (async irb.genDefersForBlock(scope, outer_scope, Scope.Defer.Kind.ScopeExit) catch unreachable);
+ }
+
+ const is_err = try irb.build(
+ Inst.TestErr,
+ scope,
+ src_span,
+ Inst.TestErr.Params{ .target = return_value },
+ );
+
+ const err_is_comptime = try irb.buildTestCompTime(scope, src_span, is_err);
+
+ _ = try irb.buildGen(Inst.CondBr, scope, src_span, Inst.CondBr.Params{
+ .condition = is_err,
+ .then_block = err_block,
+ .else_block = ok_block,
+ .is_comptime = err_is_comptime,
+ });
+
+ const ret_stmt_block = try irb.createBasicBlock(scope, c"RetStmt");
+
+ try irb.setCursorAtEndAndAppendBlock(err_block);
+ if (have_err_defers) {
+ _ = try await (async irb.genDefersForBlock(scope, outer_scope, Scope.Defer.Kind.ErrorExit) catch unreachable);
+ }
+ if (irb.comp.have_err_ret_tracing and !irb.isCompTime(scope)) {
+ _ = try irb.build(Inst.SaveErrRetAddr, scope, src_span, Inst.SaveErrRetAddr.Params{});
+ }
+ _ = try irb.build(Inst.Br, scope, src_span, Inst.Br.Params{
+ .dest_block = ret_stmt_block,
+ .is_comptime = err_is_comptime,
+ });
+
+ try irb.setCursorAtEndAndAppendBlock(ok_block);
+ if (have_err_defers) {
+ _ = try await (async irb.genDefersForBlock(scope, outer_scope, Scope.Defer.Kind.ScopeExit) catch unreachable);
+ }
+ _ = try irb.build(Inst.Br, scope, src_span, Inst.Br.Params{
+ .dest_block = ret_stmt_block,
+ .is_comptime = err_is_comptime,
+ });
+
+ try irb.setCursorAtEndAndAppendBlock(ret_stmt_block);
+ return irb.genAsyncReturn(scope, src_span, return_value, false);
+ } else {
+ _ = try await (async irb.genDefersForBlock(scope, outer_scope, Scope.Defer.Kind.ScopeExit) catch unreachable);
+ return irb.genAsyncReturn(scope, src_span, return_value, false);
+ }
+ },
+ }
+ }
+
+ pub async fn genIdentifier(irb: *Builder, identifier: *ast.Node.Identifier, scope: *Scope, lval: LVal) !*Inst {
+ const src_span = Span.token(identifier.token);
+ const name = irb.root_scope.tree.tokenSlice(identifier.token);
+
+ //if (buf_eql_str(variable_name, "_") && lval == LValPtr) {
+ // IrInstructionConst *const_instruction = ir_build_instruction(irb, scope, node);
+ // const_instruction->base.value.type = get_pointer_to_type(irb->codegen,
+ // irb->codegen->builtin_types.entry_void, false);
+ // const_instruction->base.value.special = ConstValSpecialStatic;
+ // const_instruction->base.value.data.x_ptr.special = ConstPtrSpecialDiscard;
+ // return &const_instruction->base;
+ //}
+
+ if (await (async irb.comp.getPrimitiveType(name) catch unreachable)) |result| {
+ if (result) |primitive_type| {
+ defer primitive_type.base.deref(irb.comp);
+ switch (lval) {
+ // if (lval == LValPtr) {
+ // return ir_build_ref(irb, scope, node, value, false, false);
+ LVal.Ptr => return error.Unimplemented,
+ LVal.None => return irb.buildConstValue(scope, src_span, &primitive_type.base),
+ }
+ }
+ } else |err| switch (err) {
+ error.Overflow => {
+ try irb.comp.addCompileError(irb.root_scope, src_span, "integer too large");
+ return error.SemanticAnalysisFailed;
+ },
+ error.OutOfMemory => return error.OutOfMemory,
+ }
+
+ //VariableTableEntry *var = find_variable(irb->codegen, scope, variable_name);
+ //if (var) {
+ // IrInstruction *var_ptr = ir_build_var_ptr(irb, scope, node, var);
+ // if (lval == LValPtr)
+ // return var_ptr;
+ // else
+ // return ir_build_load_ptr(irb, scope, node, var_ptr);
+ //}
+
+ if (await (async irb.findDecl(scope, name) catch unreachable)) |decl| {
+ return irb.build(Inst.DeclRef, scope, src_span, Inst.DeclRef.Params{
+ .decl = decl,
+ .lval = lval,
+ });
+ }
+
+ //if (node->owner->any_imports_failed) {
+ // // skip the error message since we had a failing import in this file
+ // // if an import breaks we don't need redundant undeclared identifier errors
+ // return irb->codegen->invalid_instruction;
+ //}
+
+ // TODO put a variable of same name with invalid type in global scope
+ // so that future references to this same name will find a variable with an invalid type
+
+ try irb.comp.addCompileError(irb.root_scope, src_span, "unknown identifier '{}'", name);
+ return error.SemanticAnalysisFailed;
+ }
+
+ const DeferCounts = struct {
+ scope_exit: usize,
+ error_exit: usize,
+ };
+
+ fn countDefers(irb: *Builder, inner_scope: *Scope, outer_scope: *Scope) DeferCounts {
+ var result = DeferCounts{ .scope_exit = 0, .error_exit = 0 };
+
+ var scope = inner_scope;
+ while (scope != outer_scope) {
+ switch (scope.id) {
+ Scope.Id.Defer => {
+ const defer_scope = @fieldParentPtr(Scope.Defer, "base", scope);
+ switch (defer_scope.kind) {
+ Scope.Defer.Kind.ScopeExit => result.scope_exit += 1,
+ Scope.Defer.Kind.ErrorExit => result.error_exit += 1,
+ }
+ scope = scope.parent orelse break;
+ },
+ Scope.Id.FnDef => break,
+
+ Scope.Id.CompTime,
+ Scope.Id.Block,
+ Scope.Id.Decls,
+ Scope.Id.Root,
+ => scope = scope.parent orelse break,
+
+ Scope.Id.DeferExpr => unreachable,
+ }
+ }
+ return result;
+ }
+
+ async fn genDefersForBlock(
irb: *Builder,
inner_scope: *Scope,
outer_scope: *Scope,
@@ -743,25 +1570,26 @@ pub const Builder = struct {
};
if (generate) {
const defer_expr_scope = defer_scope.defer_expr_scope;
- const instruction = try irb.genNode(
+ const instruction = try await (async irb.genNode(
defer_expr_scope.expr_node,
&defer_expr_scope.base,
LVal.None,
- );
+ ) catch unreachable);
if (instruction.isNoReturn()) {
is_noreturn = true;
} else {
_ = try irb.build(
- Instruction.CheckVoidStmt,
+ Inst.CheckVoidStmt,
&defer_expr_scope.base,
Span.token(defer_expr_scope.expr_node.lastToken()),
- Instruction.CheckVoidStmt.Params{ .target = instruction },
+ Inst.CheckVoidStmt.Params{ .target = instruction },
);
}
}
},
Scope.Id.FnDef,
Scope.Id.Decls,
+ Scope.Id.Root,
=> return is_noreturn,
Scope.Id.CompTime,
@@ -773,13 +1601,13 @@ pub const Builder = struct {
}
}
- pub fn lvalWrap(irb: *Builder, scope: *Scope, instruction: *Instruction, lval: LVal) !*Instruction {
+ pub fn lvalWrap(irb: *Builder, scope: *Scope, instruction: *Inst, lval: LVal) !*Inst {
switch (lval) {
LVal.None => return instruction,
LVal.Ptr => {
// We needed a pointer to a value, but we got a value. So we create
// an instruction which just makes a const pointer of it.
- return irb.build(Instruction.Ref, scope, instruction.span, Instruction.Ref.Params{
+ return irb.build(Inst.Ref, scope, instruction.span, Inst.Ref.Params{
.target = instruction,
.mut = Type.Pointer.Mut.Const,
.volatility = Type.Pointer.Vol.Non,
@@ -799,10 +1627,10 @@ pub const Builder = struct {
span: Span,
params: I.Params,
is_generated: bool,
- ) !*Instruction {
+ ) !*Inst {
const inst = try self.arena().create(I{
- .base = Instruction{
- .id = Instruction.typeToId(I),
+ .base = Inst{
+ .id = Inst.typeToId(I),
.is_generated = is_generated,
.scope = scope,
.debug_id = self.next_debug_id,
@@ -816,6 +1644,7 @@ pub const Builder = struct {
.child = null,
.parent = null,
.llvm_value = undefined,
+ .owner_bb = self.current_basic_block,
},
.params = params,
});
@@ -825,9 +1654,27 @@ pub const Builder = struct {
inline while (i < @memberCount(I.Params)) : (i += 1) {
const FieldType = comptime @typeOf(@field(I.Params(undefined), @memberName(I.Params, i)));
switch (FieldType) {
- *Instruction => @field(inst.params, @memberName(I.Params, i)).ref_count += 1,
- ?*Instruction => if (@field(inst.params, @memberName(I.Params, i))) |other| other.ref_count += 1,
- else => {},
+ *Inst => @field(inst.params, @memberName(I.Params, i)).ref(self),
+ *BasicBlock => @field(inst.params, @memberName(I.Params, i)).ref(self),
+ ?*Inst => if (@field(inst.params, @memberName(I.Params, i))) |other| other.ref(self),
+ []*Inst => {
+ // TODO https://github.com/ziglang/zig/issues/1269
+ for (@field(inst.params, @memberName(I.Params, i))) |other|
+ other.ref(self);
+ },
+ []*BasicBlock => {
+ // TODO https://github.com/ziglang/zig/issues/1269
+ for (@field(inst.params, @memberName(I.Params, i))) |other|
+ other.ref(self);
+ },
+ Type.Pointer.Mut,
+ Type.Pointer.Vol,
+ Type.Pointer.Size,
+ LVal,
+ *Decl,
+ => {},
+ // it's ok to add more types here, just make sure any instructions are ref'd appropriately
+ else => @compileError("unrecognized type in Params: " ++ @typeName(FieldType)),
}
}
@@ -842,7 +1689,7 @@ pub const Builder = struct {
scope: *Scope,
span: Span,
params: I.Params,
- ) !*Instruction {
+ ) !*Inst {
return self.buildExtra(I, scope, span, params, false);
}
@@ -852,21 +1699,95 @@ pub const Builder = struct {
scope: *Scope,
span: Span,
params: I.Params,
- ) !*Instruction {
+ ) !*Inst {
return self.buildExtra(I, scope, span, params, true);
}
- fn buildConstBool(self: *Builder, scope: *Scope, span: Span, x: bool) !*Instruction {
- const inst = try self.build(Instruction.Const, scope, span, Instruction.Const.Params{});
+ fn buildConstBool(self: *Builder, scope: *Scope, span: Span, x: bool) !*Inst {
+ const inst = try self.build(Inst.Const, scope, span, Inst.Const.Params{});
inst.val = IrVal{ .KnownValue = &Value.Bool.get(self.comp, x).base };
return inst;
}
- fn buildConstVoid(self: *Builder, scope: *Scope, span: Span, is_generated: bool) !*Instruction {
- const inst = try self.buildExtra(Instruction.Const, scope, span, Instruction.Const.Params{}, is_generated);
+ fn buildConstVoid(self: *Builder, scope: *Scope, span: Span, is_generated: bool) !*Inst {
+ const inst = try self.buildExtra(Inst.Const, scope, span, Inst.Const.Params{}, is_generated);
inst.val = IrVal{ .KnownValue = &Value.Void.get(self.comp).base };
return inst;
}
+
+ fn buildConstValue(self: *Builder, scope: *Scope, span: Span, v: *Value) !*Inst {
+ const inst = try self.build(Inst.Const, scope, span, Inst.Const.Params{});
+ inst.val = IrVal{ .KnownValue = v.getRef() };
+ return inst;
+ }
+
+ /// If the code is explicitly set to be comptime, then builds a const bool,
+ /// otherwise builds a TestCompTime instruction.
+ fn buildTestCompTime(self: *Builder, scope: *Scope, span: Span, target: *Inst) !*Inst {
+ if (self.isCompTime(scope)) {
+ return self.buildConstBool(scope, span, true);
+ } else {
+ return self.build(
+ Inst.TestCompTime,
+ scope,
+ span,
+ Inst.TestCompTime.Params{ .target = target },
+ );
+ }
+ }
+
+ fn genAsyncReturn(irb: *Builder, scope: *Scope, span: Span, result: *Inst, is_gen: bool) !*Inst {
+ _ = irb.buildGen(
+ Inst.AddImplicitReturnType,
+ scope,
+ span,
+ Inst.AddImplicitReturnType.Params{ .target = result },
+ );
+
+ if (!irb.is_async) {
+ return irb.buildExtra(
+ Inst.Return,
+ scope,
+ span,
+ Inst.Return.Params{ .return_value = result },
+ is_gen,
+ );
+ }
+ return error.Unimplemented;
+
+ //ir_build_store_ptr(irb, scope, node, irb->exec->coro_result_field_ptr, return_value);
+ //IrInstruction *promise_type_val = ir_build_const_type(irb, scope, node,
+ // get_optional_type(irb->codegen, irb->codegen->builtin_types.entry_promise));
+ //// TODO replace replacement_value with @intToPtr(?promise, 0x1) when it doesn't crash zig
+ //IrInstruction *replacement_value = irb->exec->coro_handle;
+ //IrInstruction *maybe_await_handle = ir_build_atomic_rmw(irb, scope, node,
+ // promise_type_val, irb->exec->coro_awaiter_field_ptr, nullptr, replacement_value, nullptr,
+ // AtomicRmwOp_xchg, AtomicOrderSeqCst);
+ //ir_build_store_ptr(irb, scope, node, irb->exec->await_handle_var_ptr, maybe_await_handle);
+ //IrInstruction *is_non_null = ir_build_test_nonnull(irb, scope, node, maybe_await_handle);
+ //IrInstruction *is_comptime = ir_build_const_bool(irb, scope, node, false);
+ //return ir_build_cond_br(irb, scope, node, is_non_null, irb->exec->coro_normal_final, irb->exec->coro_early_final,
+ // is_comptime);
+ //// the above blocks are rendered by ir_gen after the rest of codegen
+ }
+
+ async fn findDecl(irb: *Builder, scope: *Scope, name: []const u8) ?*Decl {
+ var s = scope;
+ while (true) {
+ switch (s.id) {
+ Scope.Id.Decls => {
+ const decls = @fieldParentPtr(Scope.Decls, "base", s);
+ const table = await (async decls.getTableReadOnly() catch unreachable);
+ if (table.get(name)) |entry| {
+ return entry.value;
+ }
+ },
+ Scope.Id.Root => return null,
+ else => {},
+ }
+ s = s.parent.?;
+ }
+ }
};
const Analyze = struct {
@@ -875,7 +1796,7 @@ const Analyze = struct {
const_predecessor_bb: ?*BasicBlock,
parent_basic_block: *BasicBlock,
instruction_index: usize,
- src_implicit_return_type_list: std.ArrayList(*Instruction),
+ src_implicit_return_type_list: std.ArrayList(*Inst),
explicit_return_type: ?*Type,
pub const Error = error{
@@ -889,8 +1810,8 @@ const Analyze = struct {
OutOfMemory,
};
- pub fn init(comp: *Compilation, parsed_file: *ParsedFile, explicit_return_type: ?*Type) !Analyze {
- var irb = try Builder.init(comp, parsed_file);
+ pub fn init(comp: *Compilation, root_scope: *Scope.Root, explicit_return_type: ?*Type) !Analyze {
+ var irb = try Builder.init(comp, root_scope, null);
errdefer irb.abort();
return Analyze{
@@ -899,7 +1820,7 @@ const Analyze = struct {
.const_predecessor_bb = null,
.parent_basic_block = undefined, // initialized with startBasicBlock
.instruction_index = undefined, // initialized with startBasicBlock
- .src_implicit_return_type_list = std.ArrayList(*Instruction).init(irb.arena()),
+ .src_implicit_return_type_list = std.ArrayList(*Inst).init(irb.arena()),
.explicit_return_type = explicit_return_type,
};
}
@@ -908,7 +1829,7 @@ const Analyze = struct {
self.irb.abort();
}
- pub fn getNewBasicBlock(self: *Analyze, old_bb: *BasicBlock, ref_old_instruction: ?*Instruction) !*BasicBlock {
+ pub fn getNewBasicBlock(self: *Analyze, old_bb: *BasicBlock, ref_old_instruction: ?*Inst) !*BasicBlock {
if (old_bb.child) |child| {
if (ref_old_instruction == null or child.ref_instruction != ref_old_instruction)
return child;
@@ -968,21 +1889,478 @@ const Analyze = struct {
}
fn addCompileError(self: *Analyze, span: Span, comptime fmt: []const u8, args: ...) !void {
- return self.irb.comp.addCompileError(self.irb.parsed_file, span, fmt, args);
+ return self.irb.comp.addCompileError(self.irb.root_scope, span, fmt, args);
}
- fn resolvePeerTypes(self: *Analyze, expected_type: ?*Type, peers: []const *Instruction) Analyze.Error!*Type {
+ fn resolvePeerTypes(self: *Analyze, expected_type: ?*Type, peers: []const *Inst) Analyze.Error!*Type {
// TODO actual implementation
return &Type.Void.get(self.irb.comp).base;
}
- fn implicitCast(self: *Analyze, target: *Instruction, optional_dest_type: ?*Type) Analyze.Error!*Instruction {
+ fn implicitCast(self: *Analyze, target: *Inst, optional_dest_type: ?*Type) Analyze.Error!*Inst {
const dest_type = optional_dest_type orelse return target;
- @panic("TODO implicitCast");
+ const from_type = target.getKnownType();
+ if (from_type == dest_type or from_type.id == Type.Id.NoReturn) return target;
+ return self.analyzeCast(target, target, dest_type);
}
- fn getCompTimeValOrNullUndefOk(self: *Analyze, target: *Instruction) ?*Value {
- @panic("TODO getCompTimeValOrNullUndefOk");
+ fn analyzeCast(ira: *Analyze, source_instr: *Inst, target: *Inst, dest_type: *Type) !*Inst {
+ const from_type = target.getKnownType();
+
+ //if (type_is_invalid(wanted_type) || type_is_invalid(actual_type)) {
+ // return ira->codegen->invalid_instruction;
+ //}
+
+ //// perfect match or non-const to const
+ //ConstCastOnly const_cast_result = types_match_const_cast_only(ira, wanted_type, actual_type,
+ // source_node, false);
+ //if (const_cast_result.id == ConstCastResultIdOk) {
+ // return ir_resolve_cast(ira, source_instr, value, wanted_type, CastOpNoop, false);
+ //}
+
+ //// widening conversion
+ //if (wanted_type->id == TypeTableEntryIdInt &&
+ // actual_type->id == TypeTableEntryIdInt &&
+ // wanted_type->data.integral.is_signed == actual_type->data.integral.is_signed &&
+ // wanted_type->data.integral.bit_count >= actual_type->data.integral.bit_count)
+ //{
+ // return ir_analyze_widen_or_shorten(ira, source_instr, value, wanted_type);
+ //}
+
+ //// small enough unsigned ints can get casted to large enough signed ints
+ //if (wanted_type->id == TypeTableEntryIdInt && wanted_type->data.integral.is_signed &&
+ // actual_type->id == TypeTableEntryIdInt && !actual_type->data.integral.is_signed &&
+ // wanted_type->data.integral.bit_count > actual_type->data.integral.bit_count)
+ //{
+ // return ir_analyze_widen_or_shorten(ira, source_instr, value, wanted_type);
+ //}
+
+ //// float widening conversion
+ //if (wanted_type->id == TypeTableEntryIdFloat &&
+ // actual_type->id == TypeTableEntryIdFloat &&
+ // wanted_type->data.floating.bit_count >= actual_type->data.floating.bit_count)
+ //{
+ // return ir_analyze_widen_or_shorten(ira, source_instr, value, wanted_type);
+ //}
+
+ //// cast from [N]T to []const T
+ //if (is_slice(wanted_type) && actual_type->id == TypeTableEntryIdArray) {
+ // TypeTableEntry *ptr_type = wanted_type->data.structure.fields[slice_ptr_index].type_entry;
+ // assert(ptr_type->id == TypeTableEntryIdPointer);
+ // if ((ptr_type->data.pointer.is_const || actual_type->data.array.len == 0) &&
+ // types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, actual_type->data.array.child_type,
+ // source_node, false).id == ConstCastResultIdOk)
+ // {
+ // return ir_analyze_array_to_slice(ira, source_instr, value, wanted_type);
+ // }
+ //}
+
+ //// cast from *const [N]T to []const T
+ //if (is_slice(wanted_type) &&
+ // actual_type->id == TypeTableEntryIdPointer &&
+ // actual_type->data.pointer.is_const &&
+ // actual_type->data.pointer.child_type->id == TypeTableEntryIdArray)
+ //{
+ // TypeTableEntry *ptr_type = wanted_type->data.structure.fields[slice_ptr_index].type_entry;
+ // assert(ptr_type->id == TypeTableEntryIdPointer);
+
+ // TypeTableEntry *array_type = actual_type->data.pointer.child_type;
+
+ // if ((ptr_type->data.pointer.is_const || array_type->data.array.len == 0) &&
+ // types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, array_type->data.array.child_type,
+ // source_node, false).id == ConstCastResultIdOk)
+ // {
+ // return ir_analyze_array_to_slice(ira, source_instr, value, wanted_type);
+ // }
+ //}
+
+ //// cast from [N]T to *const []const T
+ //if (wanted_type->id == TypeTableEntryIdPointer &&
+ // wanted_type->data.pointer.is_const &&
+ // is_slice(wanted_type->data.pointer.child_type) &&
+ // actual_type->id == TypeTableEntryIdArray)
+ //{
+ // TypeTableEntry *ptr_type =
+ // wanted_type->data.pointer.child_type->data.structure.fields[slice_ptr_index].type_entry;
+ // assert(ptr_type->id == TypeTableEntryIdPointer);
+ // if ((ptr_type->data.pointer.is_const || actual_type->data.array.len == 0) &&
+ // types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, actual_type->data.array.child_type,
+ // source_node, false).id == ConstCastResultIdOk)
+ // {
+ // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.pointer.child_type, value);
+ // if (type_is_invalid(cast1->value.type))
+ // return ira->codegen->invalid_instruction;
+
+ // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1);
+ // if (type_is_invalid(cast2->value.type))
+ // return ira->codegen->invalid_instruction;
+
+ // return cast2;
+ // }
+ //}
+
+ //// cast from [N]T to ?[]const T
+ //if (wanted_type->id == TypeTableEntryIdOptional &&
+ // is_slice(wanted_type->data.maybe.child_type) &&
+ // actual_type->id == TypeTableEntryIdArray)
+ //{
+ // TypeTableEntry *ptr_type =
+ // wanted_type->data.maybe.child_type->data.structure.fields[slice_ptr_index].type_entry;
+ // assert(ptr_type->id == TypeTableEntryIdPointer);
+ // if ((ptr_type->data.pointer.is_const || actual_type->data.array.len == 0) &&
+ // types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, actual_type->data.array.child_type,
+ // source_node, false).id == ConstCastResultIdOk)
+ // {
+ // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.maybe.child_type, value);
+ // if (type_is_invalid(cast1->value.type))
+ // return ira->codegen->invalid_instruction;
+
+ // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1);
+ // if (type_is_invalid(cast2->value.type))
+ // return ira->codegen->invalid_instruction;
+
+ // return cast2;
+ // }
+ //}
+
+ //// *[N]T to [*]T
+ //if (wanted_type->id == TypeTableEntryIdPointer &&
+ // wanted_type->data.pointer.ptr_len == PtrLenUnknown &&
+ // actual_type->id == TypeTableEntryIdPointer &&
+ // actual_type->data.pointer.ptr_len == PtrLenSingle &&
+ // actual_type->data.pointer.child_type->id == TypeTableEntryIdArray &&
+ // actual_type->data.pointer.alignment >= wanted_type->data.pointer.alignment &&
+ // types_match_const_cast_only(ira, wanted_type->data.pointer.child_type,
+ // actual_type->data.pointer.child_type->data.array.child_type, source_node,
+ // !wanted_type->data.pointer.is_const).id == ConstCastResultIdOk)
+ //{
+ // return ir_resolve_ptr_of_array_to_unknown_len_ptr(ira, source_instr, value, wanted_type);
+ //}
+
+ //// *[N]T to []T
+ //if (is_slice(wanted_type) &&
+ // actual_type->id == TypeTableEntryIdPointer &&
+ // actual_type->data.pointer.ptr_len == PtrLenSingle &&
+ // actual_type->data.pointer.child_type->id == TypeTableEntryIdArray)
+ //{
+ // TypeTableEntry *slice_ptr_type = wanted_type->data.structure.fields[slice_ptr_index].type_entry;
+ // assert(slice_ptr_type->id == TypeTableEntryIdPointer);
+ // if (types_match_const_cast_only(ira, slice_ptr_type->data.pointer.child_type,
+ // actual_type->data.pointer.child_type->data.array.child_type, source_node,
+ // !slice_ptr_type->data.pointer.is_const).id == ConstCastResultIdOk)
+ // {
+ // return ir_resolve_ptr_of_array_to_slice(ira, source_instr, value, wanted_type);
+ // }
+ //}
+
+ //// cast from T to ?T
+ //// note that the *T to ?*T case is handled via the "ConstCastOnly" mechanism
+ //if (wanted_type->id == TypeTableEntryIdOptional) {
+ // TypeTableEntry *wanted_child_type = wanted_type->data.maybe.child_type;
+ // if (types_match_const_cast_only(ira, wanted_child_type, actual_type, source_node,
+ // false).id == ConstCastResultIdOk)
+ // {
+ // return ir_analyze_maybe_wrap(ira, source_instr, value, wanted_type);
+ // } else if (actual_type->id == TypeTableEntryIdComptimeInt ||
+ // actual_type->id == TypeTableEntryIdComptimeFloat)
+ // {
+ // if (ir_num_lit_fits_in_other_type(ira, value, wanted_child_type, true)) {
+ // return ir_analyze_maybe_wrap(ira, source_instr, value, wanted_type);
+ // } else {
+ // return ira->codegen->invalid_instruction;
+ // }
+ // } else if (wanted_child_type->id == TypeTableEntryIdPointer &&
+ // wanted_child_type->data.pointer.is_const &&
+ // (actual_type->id == TypeTableEntryIdPointer || is_container(actual_type)))
+ // {
+ // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_child_type, value);
+ // if (type_is_invalid(cast1->value.type))
+ // return ira->codegen->invalid_instruction;
+
+ // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1);
+ // if (type_is_invalid(cast2->value.type))
+ // return ira->codegen->invalid_instruction;
+
+ // return cast2;
+ // }
+ //}
+
+ //// cast from null literal to maybe type
+ //if (wanted_type->id == TypeTableEntryIdOptional &&
+ // actual_type->id == TypeTableEntryIdNull)
+ //{
+ // return ir_analyze_null_to_maybe(ira, source_instr, value, wanted_type);
+ //}
+
+ //// cast from child type of error type to error type
+ //if (wanted_type->id == TypeTableEntryIdErrorUnion) {
+ // if (types_match_const_cast_only(ira, wanted_type->data.error_union.payload_type, actual_type,
+ // source_node, false).id == ConstCastResultIdOk)
+ // {
+ // return ir_analyze_err_wrap_payload(ira, source_instr, value, wanted_type);
+ // } else if (actual_type->id == TypeTableEntryIdComptimeInt ||
+ // actual_type->id == TypeTableEntryIdComptimeFloat)
+ // {
+ // if (ir_num_lit_fits_in_other_type(ira, value, wanted_type->data.error_union.payload_type, true)) {
+ // return ir_analyze_err_wrap_payload(ira, source_instr, value, wanted_type);
+ // } else {
+ // return ira->codegen->invalid_instruction;
+ // }
+ // }
+ //}
+
+ //// cast from [N]T to E![]const T
+ //if (wanted_type->id == TypeTableEntryIdErrorUnion &&
+ // is_slice(wanted_type->data.error_union.payload_type) &&
+ // actual_type->id == TypeTableEntryIdArray)
+ //{
+ // TypeTableEntry *ptr_type =
+ // wanted_type->data.error_union.payload_type->data.structure.fields[slice_ptr_index].type_entry;
+ // assert(ptr_type->id == TypeTableEntryIdPointer);
+ // if ((ptr_type->data.pointer.is_const || actual_type->data.array.len == 0) &&
+ // types_match_const_cast_only(ira, ptr_type->data.pointer.child_type, actual_type->data.array.child_type,
+ // source_node, false).id == ConstCastResultIdOk)
+ // {
+ // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.error_union.payload_type, value);
+ // if (type_is_invalid(cast1->value.type))
+ // return ira->codegen->invalid_instruction;
+
+ // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1);
+ // if (type_is_invalid(cast2->value.type))
+ // return ira->codegen->invalid_instruction;
+
+ // return cast2;
+ // }
+ //}
+
+ //// cast from error set to error union type
+ //if (wanted_type->id == TypeTableEntryIdErrorUnion &&
+ // actual_type->id == TypeTableEntryIdErrorSet)
+ //{
+ // return ir_analyze_err_wrap_code(ira, source_instr, value, wanted_type);
+ //}
+
+ //// cast from T to E!?T
+ //if (wanted_type->id == TypeTableEntryIdErrorUnion &&
+ // wanted_type->data.error_union.payload_type->id == TypeTableEntryIdOptional &&
+ // actual_type->id != TypeTableEntryIdOptional)
+ //{
+ // TypeTableEntry *wanted_child_type = wanted_type->data.error_union.payload_type->data.maybe.child_type;
+ // if (types_match_const_cast_only(ira, wanted_child_type, actual_type, source_node, false).id == ConstCastResultIdOk ||
+ // actual_type->id == TypeTableEntryIdNull ||
+ // actual_type->id == TypeTableEntryIdComptimeInt ||
+ // actual_type->id == TypeTableEntryIdComptimeFloat)
+ // {
+ // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.error_union.payload_type, value);
+ // if (type_is_invalid(cast1->value.type))
+ // return ira->codegen->invalid_instruction;
+
+ // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1);
+ // if (type_is_invalid(cast2->value.type))
+ // return ira->codegen->invalid_instruction;
+
+ // return cast2;
+ // }
+ //}
+
+ // cast from comptime-known integer to another integer where the value fits
+ if (target.isCompTime() and (from_type.id == Type.Id.Int or from_type.id == Type.Id.ComptimeInt)) cast: {
+ const target_val = target.val.KnownValue;
+ const from_int = &target_val.cast(Value.Int).?.big_int;
+ const fits = fits: {
+ if (dest_type.cast(Type.ComptimeInt)) |ctint| {
+ break :fits true;
+ }
+ if (dest_type.cast(Type.Int)) |int| {
+ break :fits from_int.fitsInTwosComp(int.key.is_signed, int.key.bit_count);
+ }
+ break :cast;
+ };
+ if (!fits) {
+ try ira.addCompileError(
+ source_instr.span,
+ "integer value '{}' cannot be stored in type '{}'",
+ from_int,
+ dest_type.name,
+ );
+ return error.SemanticAnalysisFailed;
+ }
+
+ const new_val = try target.copyVal(ira.irb.comp);
+ new_val.setType(dest_type, ira.irb.comp);
+ return ira.irb.buildConstValue(source_instr.scope, source_instr.span, new_val);
+ }
+
+ // cast from number literal to another type
+ // cast from number literal to *const integer
+ //if (actual_type->id == TypeTableEntryIdComptimeFloat ||
+ // actual_type->id == TypeTableEntryIdComptimeInt)
+ //{
+ // ensure_complete_type(ira->codegen, wanted_type);
+ // if (type_is_invalid(wanted_type))
+ // return ira->codegen->invalid_instruction;
+ // if (wanted_type->id == TypeTableEntryIdEnum) {
+ // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.enumeration.tag_int_type, value);
+ // if (type_is_invalid(cast1->value.type))
+ // return ira->codegen->invalid_instruction;
+
+ // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1);
+ // if (type_is_invalid(cast2->value.type))
+ // return ira->codegen->invalid_instruction;
+
+ // return cast2;
+ // } else if (wanted_type->id == TypeTableEntryIdPointer &&
+ // wanted_type->data.pointer.is_const)
+ // {
+ // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, wanted_type->data.pointer.child_type, value);
+ // if (type_is_invalid(cast1->value.type))
+ // return ira->codegen->invalid_instruction;
+
+ // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1);
+ // if (type_is_invalid(cast2->value.type))
+ // return ira->codegen->invalid_instruction;
+
+ // return cast2;
+ // } else if (ir_num_lit_fits_in_other_type(ira, value, wanted_type, true)) {
+ // CastOp op;
+ // if ((actual_type->id == TypeTableEntryIdComptimeFloat &&
+ // wanted_type->id == TypeTableEntryIdFloat) ||
+ // (actual_type->id == TypeTableEntryIdComptimeInt &&
+ // wanted_type->id == TypeTableEntryIdInt))
+ // {
+ // op = CastOpNumLitToConcrete;
+ // } else if (wanted_type->id == TypeTableEntryIdInt) {
+ // op = CastOpFloatToInt;
+ // } else if (wanted_type->id == TypeTableEntryIdFloat) {
+ // op = CastOpIntToFloat;
+ // } else {
+ // zig_unreachable();
+ // }
+ // return ir_resolve_cast(ira, source_instr, value, wanted_type, op, false);
+ // } else {
+ // return ira->codegen->invalid_instruction;
+ // }
+ //}
+
+ //// cast from typed number to integer or float literal.
+ //// works when the number is known at compile time
+ //if (instr_is_comptime(value) &&
+ // ((actual_type->id == TypeTableEntryIdInt && wanted_type->id == TypeTableEntryIdComptimeInt) ||
+ // (actual_type->id == TypeTableEntryIdFloat && wanted_type->id == TypeTableEntryIdComptimeFloat)))
+ //{
+ // return ir_analyze_number_to_literal(ira, source_instr, value, wanted_type);
+ //}
+
+ //// cast from union to the enum type of the union
+ //if (actual_type->id == TypeTableEntryIdUnion && wanted_type->id == TypeTableEntryIdEnum) {
+ // type_ensure_zero_bits_known(ira->codegen, actual_type);
+ // if (type_is_invalid(actual_type))
+ // return ira->codegen->invalid_instruction;
+
+ // if (actual_type->data.unionation.tag_type == wanted_type) {
+ // return ir_analyze_union_to_tag(ira, source_instr, value, wanted_type);
+ // }
+ //}
+
+ //// enum to union which has the enum as the tag type
+ //if (wanted_type->id == TypeTableEntryIdUnion && actual_type->id == TypeTableEntryIdEnum &&
+ // (wanted_type->data.unionation.decl_node->data.container_decl.auto_enum ||
+ // wanted_type->data.unionation.decl_node->data.container_decl.init_arg_expr != nullptr))
+ //{
+ // type_ensure_zero_bits_known(ira->codegen, wanted_type);
+ // if (wanted_type->data.unionation.tag_type == actual_type) {
+ // return ir_analyze_enum_to_union(ira, source_instr, value, wanted_type);
+ // }
+ //}
+
+ //// enum to &const union which has the enum as the tag type
+ //if (actual_type->id == TypeTableEntryIdEnum && wanted_type->id == TypeTableEntryIdPointer) {
+ // TypeTableEntry *union_type = wanted_type->data.pointer.child_type;
+ // if (union_type->data.unionation.decl_node->data.container_decl.auto_enum ||
+ // union_type->data.unionation.decl_node->data.container_decl.init_arg_expr != nullptr)
+ // {
+ // type_ensure_zero_bits_known(ira->codegen, union_type);
+ // if (union_type->data.unionation.tag_type == actual_type) {
+ // IrInstruction *cast1 = ir_analyze_cast(ira, source_instr, union_type, value);
+ // if (type_is_invalid(cast1->value.type))
+ // return ira->codegen->invalid_instruction;
+
+ // IrInstruction *cast2 = ir_analyze_cast(ira, source_instr, wanted_type, cast1);
+ // if (type_is_invalid(cast2->value.type))
+ // return ira->codegen->invalid_instruction;
+
+ // return cast2;
+ // }
+ // }
+ //}
+
+ //// cast from *T to *[1]T
+ //if (wanted_type->id == TypeTableEntryIdPointer && wanted_type->data.pointer.ptr_len == PtrLenSingle &&
+ // actual_type->id == TypeTableEntryIdPointer && actual_type->data.pointer.ptr_len == PtrLenSingle)
+ //{
+ // TypeTableEntry *array_type = wanted_type->data.pointer.child_type;
+ // if (array_type->id == TypeTableEntryIdArray && array_type->data.array.len == 1 &&
+ // types_match_const_cast_only(ira, array_type->data.array.child_type,
+ // actual_type->data.pointer.child_type, source_node,
+ // !wanted_type->data.pointer.is_const).id == ConstCastResultIdOk)
+ // {
+ // if (wanted_type->data.pointer.alignment > actual_type->data.pointer.alignment) {
+ // ErrorMsg *msg = ir_add_error(ira, source_instr, buf_sprintf("cast increases pointer alignment"));
+ // add_error_note(ira->codegen, msg, value->source_node,
+ // buf_sprintf("'%s' has alignment %" PRIu32, buf_ptr(&actual_type->name),
+ // actual_type->data.pointer.alignment));
+ // add_error_note(ira->codegen, msg, source_instr->source_node,
+ // buf_sprintf("'%s' has alignment %" PRIu32, buf_ptr(&wanted_type->name),
+ // wanted_type->data.pointer.alignment));
+ // return ira->codegen->invalid_instruction;
+ // }
+ // return ir_analyze_ptr_to_array(ira, source_instr, value, wanted_type);
+ // }
+ //}
+
+ //// cast from T to *T where T is zero bits
+ //if (wanted_type->id == TypeTableEntryIdPointer && wanted_type->data.pointer.ptr_len == PtrLenSingle &&
+ // types_match_const_cast_only(ira, wanted_type->data.pointer.child_type,
+ // actual_type, source_node, !wanted_type->data.pointer.is_const).id == ConstCastResultIdOk)
+ //{
+ // type_ensure_zero_bits_known(ira->codegen, actual_type);
+ // if (type_is_invalid(actual_type)) {
+ // return ira->codegen->invalid_instruction;
+ // }
+ // if (!type_has_bits(actual_type)) {
+ // return ir_get_ref(ira, source_instr, value, false, false);
+ // }
+ //}
+
+ //// cast from undefined to anything
+ //if (actual_type->id == TypeTableEntryIdUndefined) {
+ // return ir_analyze_undefined_to_anything(ira, source_instr, value, wanted_type);
+ //}
+
+ //// cast from something to const pointer of it
+ //if (!type_requires_comptime(actual_type)) {
+ // TypeTableEntry *const_ptr_actual = get_pointer_to_type(ira->codegen, actual_type, true);
+ // if (types_match_const_cast_only(ira, wanted_type, const_ptr_actual, source_node, false).id == ConstCastResultIdOk) {
+ // return ir_analyze_cast_ref(ira, source_instr, value, wanted_type);
+ // }
+ //}
+
+ try ira.addCompileError(
+ source_instr.span,
+ "expected type '{}', found '{}'",
+ dest_type.name,
+ from_type.name,
+ );
+ //ErrorMsg *parent_msg = ir_add_error_node(ira, source_instr->source_node,
+ // buf_sprintf("expected type '%s', found '%s'",
+ // buf_ptr(&wanted_type->name),
+ // buf_ptr(&actual_type->name)));
+ //report_recursive_error(ira, source_instr->source_node, &const_cast_result, parent_msg);
+ return error.SemanticAnalysisFailed;
+ }
+
+ fn getCompTimeValOrNullUndefOk(self: *Analyze, target: *Inst) ?*Value {
+ @panic("TODO");
}
fn getCompTimeRef(
@@ -991,9 +2369,8 @@ const Analyze = struct {
ptr_mut: Value.Ptr.Mut,
mut: Type.Pointer.Mut,
volatility: Type.Pointer.Vol,
- ptr_align: u32,
- ) Analyze.Error!*Instruction {
- @panic("TODO getCompTimeRef");
+ ) Analyze.Error!*Inst {
+ return error.Unimplemented;
}
};
@@ -1001,43 +2378,32 @@ pub async fn gen(
comp: *Compilation,
body_node: *ast.Node,
scope: *Scope,
- end_span: Span,
- parsed_file: *ParsedFile,
) !*Code {
- var irb = try Builder.init(comp, parsed_file);
+ var irb = try Builder.init(comp, scope.findRoot(), scope);
errdefer irb.abort();
const entry_block = try irb.createBasicBlock(scope, c"Entry");
- entry_block.ref(); // Entry block gets a reference because we enter it to begin.
+ entry_block.ref(&irb); // Entry block gets a reference because we enter it to begin.
try irb.setCursorAtEndAndAppendBlock(entry_block);
- const result = try irb.genNode(body_node, scope, LVal.None);
+ const result = try await (async irb.genNode(body_node, scope, LVal.None) catch unreachable);
if (!result.isNoReturn()) {
- _ = irb.buildGen(
- Instruction.AddImplicitReturnType,
- scope,
- end_span,
- Instruction.AddImplicitReturnType.Params{ .target = result },
- );
- _ = irb.buildGen(
- Instruction.Return,
- scope,
- end_span,
- Instruction.Return.Params{ .return_value = result },
- );
+ // no need for save_err_ret_addr because this cannot return error
+ _ = try irb.genAsyncReturn(scope, Span.token(body_node.lastToken()), result, true);
}
return irb.finish();
}
-pub async fn analyze(comp: *Compilation, parsed_file: *ParsedFile, old_code: *Code, expected_type: ?*Type) !*Code {
- var ira = try Analyze.init(comp, parsed_file, expected_type);
+pub async fn analyze(comp: *Compilation, old_code: *Code, expected_type: ?*Type) !*Code {
+ const old_entry_bb = old_code.basic_block_list.at(0);
+ const root_scope = old_entry_bb.scope.findRoot();
+
+ var ira = try Analyze.init(comp, root_scope, expected_type);
errdefer ira.abort();
- const old_entry_bb = old_code.basic_block_list.at(0);
-
const new_entry_bb = try ira.getNewBasicBlock(old_entry_bb, null);
- new_entry_bb.ref();
+ new_entry_bb.ref(&ira.irb);
ira.irb.current_basic_block = new_entry_bb;
@@ -1051,7 +2417,8 @@ pub async fn analyze(comp: *Compilation, parsed_file: *ParsedFile, old_code: *Co
continue;
}
- const return_inst = try old_instruction.analyze(&ira);
+ const return_inst = try await (async old_instruction.analyze(&ira) catch unreachable);
+ assert(return_inst.val != IrVal.Unknown); // at least the type should be known at this point
return_inst.linkToParent(old_instruction);
// Note: if we ever modify the above to handle error.CompileError by continuing analysis,
// then here we want to check if ira.isCompTime() and return early if true
diff --git a/src-self-hosted/libc_installation.zig b/src-self-hosted/libc_installation.zig
new file mode 100644
index 0000000000..3938c0d90c
--- /dev/null
+++ b/src-self-hosted/libc_installation.zig
@@ -0,0 +1,462 @@
+const std = @import("std");
+const builtin = @import("builtin");
+const event = std.event;
+const Target = @import("target.zig").Target;
+const c = @import("c.zig");
+
+/// See the render function implementation for documentation of the fields.
+pub const LibCInstallation = struct {
+ include_dir: []const u8,
+ lib_dir: ?[]const u8,
+ static_lib_dir: ?[]const u8,
+ msvc_lib_dir: ?[]const u8,
+ kernel32_lib_dir: ?[]const u8,
+ dynamic_linker_path: ?[]const u8,
+
+ pub const FindError = error{
+ OutOfMemory,
+ FileSystem,
+ UnableToSpawnCCompiler,
+ CCompilerExitCode,
+ CCompilerCrashed,
+ CCompilerCannotFindHeaders,
+ LibCRuntimeNotFound,
+ LibCStdLibHeaderNotFound,
+ LibCKernel32LibNotFound,
+ UnsupportedArchitecture,
+ };
+
+ pub fn parse(
+ self: *LibCInstallation,
+ allocator: *std.mem.Allocator,
+ libc_file: []const u8,
+ stderr: *std.io.OutStream(std.io.FileOutStream.Error),
+ ) !void {
+ self.initEmpty();
+
+ const keys = []const []const u8{
+ "include_dir",
+ "lib_dir",
+ "static_lib_dir",
+ "msvc_lib_dir",
+ "kernel32_lib_dir",
+ "dynamic_linker_path",
+ };
+ const FoundKey = struct {
+ found: bool,
+ allocated: ?[]u8,
+ };
+ var found_keys = [1]FoundKey{FoundKey{ .found = false, .allocated = null }} ** keys.len;
+ errdefer {
+ self.initEmpty();
+ for (found_keys) |found_key| {
+ if (found_key.allocated) |s| allocator.free(s);
+ }
+ }
+
+ const contents = try std.io.readFileAlloc(allocator, libc_file);
+ defer allocator.free(contents);
+
+ var it = std.mem.split(contents, "\n");
+ while (it.next()) |line| {
+ if (line.len == 0 or line[0] == '#') continue;
+ var line_it = std.mem.split(line, "=");
+ const name = line_it.next() orelse {
+ try stderr.print("missing equal sign after field name\n");
+ return error.ParseError;
+ };
+ const value = line_it.rest();
+ inline for (keys) |key, i| {
+ if (std.mem.eql(u8, name, key)) {
+ found_keys[i].found = true;
+ switch (@typeInfo(@typeOf(@field(self, key)))) {
+ builtin.TypeId.Optional => {
+ if (value.len == 0) {
+ @field(self, key) = null;
+ } else {
+ found_keys[i].allocated = try std.mem.dupe(allocator, u8, value);
+ @field(self, key) = found_keys[i].allocated;
+ }
+ },
+ else => {
+ if (value.len == 0) {
+ try stderr.print("field cannot be empty: {}\n", key);
+ return error.ParseError;
+ }
+ const dupe = try std.mem.dupe(allocator, u8, value);
+ found_keys[i].allocated = dupe;
+ @field(self, key) = dupe;
+ },
+ }
+ break;
+ }
+ }
+ }
+ for (found_keys) |found_key, i| {
+ if (!found_key.found) {
+ try stderr.print("missing field: {}\n", keys[i]);
+ return error.ParseError;
+ }
+ }
+ }
+
+ pub fn render(self: *const LibCInstallation, out: *std.io.OutStream(std.io.FileOutStream.Error)) !void {
+ @setEvalBranchQuota(4000);
+ try out.print(
+ \\# The directory that contains `stdlib.h`.
+ \\# On Linux, can be found with: `cc -E -Wp,-v -xc /dev/null`
+ \\include_dir={}
+ \\
+ \\# The directory that contains `crt1.o`.
+ \\# On Linux, can be found with `cc -print-file-name=crt1.o`.
+ \\# Not needed when targeting MacOS.
+ \\lib_dir={}
+ \\
+ \\# The directory that contains `crtbegin.o`.
+ \\# On Linux, can be found with `cc -print-file-name=crtbegin.o`.
+ \\# Not needed when targeting MacOS or Windows.
+ \\static_lib_dir={}
+ \\
+ \\# The directory that contains `vcruntime.lib`.
+ \\# Only needed when targeting Windows.
+ \\msvc_lib_dir={}
+ \\
+ \\# The directory that contains `kernel32.lib`.
+ \\# Only needed when targeting Windows.
+ \\kernel32_lib_dir={}
+ \\
+ \\# The full path to the dynamic linker, on the target system.
+ \\# Only needed when targeting Linux.
+ \\dynamic_linker_path={}
+ \\
+ ,
+ self.include_dir,
+ self.lib_dir orelse "",
+ self.static_lib_dir orelse "",
+ self.msvc_lib_dir orelse "",
+ self.kernel32_lib_dir orelse "",
+ self.dynamic_linker_path orelse Target(Target.Native).getDynamicLinkerPath(),
+ );
+ }
+
+ /// Finds the default, native libc.
+ pub async fn findNative(self: *LibCInstallation, loop: *event.Loop) !void {
+ self.initEmpty();
+ var group = event.Group(FindError!void).init(loop);
+ errdefer group.cancelAll();
+ var windows_sdk: ?*c.ZigWindowsSDK = null;
+ errdefer if (windows_sdk) |sdk| c.zig_free_windows_sdk(@ptrCast(?[*]c.ZigWindowsSDK, sdk));
+
+ switch (builtin.os) {
+ builtin.Os.windows => {
+ var sdk: *c.ZigWindowsSDK = undefined;
+ switch (c.zig_find_windows_sdk(@ptrCast(?[*]?[*]c.ZigWindowsSDK, &sdk))) {
+ c.ZigFindWindowsSdkError.None => {
+ windows_sdk = sdk;
+
+ if (sdk.msvc_lib_dir_ptr) |ptr| {
+ self.msvc_lib_dir = try std.mem.dupe(loop.allocator, u8, ptr[0..sdk.msvc_lib_dir_len]);
+ }
+ try group.call(findNativeKernel32LibDir, self, loop, sdk);
+ try group.call(findNativeIncludeDirWindows, self, loop, sdk);
+ try group.call(findNativeLibDirWindows, self, loop, sdk);
+ },
+ c.ZigFindWindowsSdkError.OutOfMemory => return error.OutOfMemory,
+ c.ZigFindWindowsSdkError.NotFound => return error.NotFound,
+ c.ZigFindWindowsSdkError.PathTooLong => return error.NotFound,
+ }
+ },
+ builtin.Os.linux => {
+ try group.call(findNativeIncludeDirLinux, self, loop);
+ try group.call(findNativeLibDirLinux, self, loop);
+ try group.call(findNativeStaticLibDir, self, loop);
+ try group.call(findNativeDynamicLinker, self, loop);
+ },
+ builtin.Os.macosx => {
+ self.include_dir = try std.mem.dupe(loop.allocator, u8, "/usr/include");
+ },
+ else => @compileError("unimplemented: find libc for this OS"),
+ }
+ return await (async group.wait() catch unreachable);
+ }
+
+ async fn findNativeIncludeDirLinux(self: *LibCInstallation, loop: *event.Loop) !void {
+ const cc_exe = std.os.getEnvPosix("CC") orelse "cc";
+ const argv = []const []const u8{
+ cc_exe,
+ "-E",
+ "-Wp,-v",
+ "-xc",
+ "/dev/null",
+ };
+ // TODO make this use event loop
+ const errorable_result = std.os.ChildProcess.exec(loop.allocator, argv, null, null, 1024 * 1024);
+ const exec_result = if (std.debug.runtime_safety) blk: {
+ break :blk errorable_result catch unreachable;
+ } else blk: {
+ break :blk errorable_result catch |err| switch (err) {
+ error.OutOfMemory => return error.OutOfMemory,
+ else => return error.UnableToSpawnCCompiler,
+ };
+ };
+ defer {
+ loop.allocator.free(exec_result.stdout);
+ loop.allocator.free(exec_result.stderr);
+ }
+
+ switch (exec_result.term) {
+ std.os.ChildProcess.Term.Exited => |code| {
+ if (code != 0) return error.CCompilerExitCode;
+ },
+ else => {
+ return error.CCompilerCrashed;
+ },
+ }
+
+ var it = std.mem.split(exec_result.stderr, "\n\r");
+ var search_paths = std.ArrayList([]const u8).init(loop.allocator);
+ defer search_paths.deinit();
+ while (it.next()) |line| {
+ if (line.len != 0 and line[0] == ' ') {
+ try search_paths.append(line);
+ }
+ }
+ if (search_paths.len == 0) {
+ return error.CCompilerCannotFindHeaders;
+ }
+
+ // search in reverse order
+ var path_i: usize = 0;
+ while (path_i < search_paths.len) : (path_i += 1) {
+ const search_path_untrimmed = search_paths.at(search_paths.len - path_i - 1);
+ const search_path = std.mem.trimLeft(u8, search_path_untrimmed, " ");
+ const stdlib_path = try std.os.path.join(loop.allocator, search_path, "stdlib.h");
+ defer loop.allocator.free(stdlib_path);
+
+ if (try fileExists(loop.allocator, stdlib_path)) {
+ self.include_dir = try std.mem.dupe(loop.allocator, u8, search_path);
+ return;
+ }
+ }
+
+ return error.LibCStdLibHeaderNotFound;
+ }
+
+ async fn findNativeIncludeDirWindows(self: *LibCInstallation, loop: *event.Loop, sdk: *c.ZigWindowsSDK) !void {
+ var search_buf: [2]Search = undefined;
+ const searches = fillSearch(&search_buf, sdk);
+
+ var result_buf = try std.Buffer.initSize(loop.allocator, 0);
+ defer result_buf.deinit();
+
+ for (searches) |search| {
+ result_buf.shrink(0);
+ const stream = &std.io.BufferOutStream.init(&result_buf).stream;
+ try stream.print("{}\\Include\\{}\\ucrt", search.path, search.version);
+
+ const stdlib_path = try std.os.path.join(loop.allocator, result_buf.toSliceConst(), "stdlib.h");
+ defer loop.allocator.free(stdlib_path);
+
+ if (try fileExists(loop.allocator, stdlib_path)) {
+ self.include_dir = result_buf.toOwnedSlice();
+ return;
+ }
+ }
+
+ return error.LibCStdLibHeaderNotFound;
+ }
+
+ async fn findNativeLibDirWindows(self: *LibCInstallation, loop: *event.Loop, sdk: *c.ZigWindowsSDK) FindError!void {
+ var search_buf: [2]Search = undefined;
+ const searches = fillSearch(&search_buf, sdk);
+
+ var result_buf = try std.Buffer.initSize(loop.allocator, 0);
+ defer result_buf.deinit();
+
+ for (searches) |search| {
+ result_buf.shrink(0);
+ const stream = &std.io.BufferOutStream.init(&result_buf).stream;
+ try stream.print("{}\\Lib\\{}\\ucrt\\", search.path, search.version);
+ switch (builtin.arch) {
+ builtin.Arch.i386 => try stream.write("x86"),
+ builtin.Arch.x86_64 => try stream.write("x64"),
+ builtin.Arch.aarch64 => try stream.write("arm"),
+ else => return error.UnsupportedArchitecture,
+ }
+ const ucrt_lib_path = try std.os.path.join(loop.allocator, result_buf.toSliceConst(), "ucrt.lib");
+ defer loop.allocator.free(ucrt_lib_path);
+ if (try fileExists(loop.allocator, ucrt_lib_path)) {
+ self.lib_dir = result_buf.toOwnedSlice();
+ return;
+ }
+ }
+ return error.LibCRuntimeNotFound;
+ }
+
+ async fn findNativeLibDirLinux(self: *LibCInstallation, loop: *event.Loop) FindError!void {
+ self.lib_dir = try await (async ccPrintFileName(loop, "crt1.o", true) catch unreachable);
+ }
+
+ async fn findNativeStaticLibDir(self: *LibCInstallation, loop: *event.Loop) FindError!void {
+ self.static_lib_dir = try await (async ccPrintFileName(loop, "crtbegin.o", true) catch unreachable);
+ }
+
+ async fn findNativeDynamicLinker(self: *LibCInstallation, loop: *event.Loop) FindError!void {
+ var dyn_tests = []DynTest{
+ DynTest{
+ .name = "ld-linux-x86-64.so.2",
+ .result = null,
+ },
+ DynTest{
+ .name = "ld-musl-x86_64.so.1",
+ .result = null,
+ },
+ };
+ var group = event.Group(FindError!void).init(loop);
+ errdefer group.cancelAll();
+ for (dyn_tests) |*dyn_test| {
+ try group.call(testNativeDynamicLinker, self, loop, dyn_test);
+ }
+ try await (async group.wait() catch unreachable);
+ for (dyn_tests) |*dyn_test| {
+ if (dyn_test.result) |result| {
+ self.dynamic_linker_path = result;
+ return;
+ }
+ }
+ }
+
+ const DynTest = struct {
+ name: []const u8,
+ result: ?[]const u8,
+ };
+
+ async fn testNativeDynamicLinker(self: *LibCInstallation, loop: *event.Loop, dyn_test: *DynTest) FindError!void {
+ if (await (async ccPrintFileName(loop, dyn_test.name, false) catch unreachable)) |result| {
+ dyn_test.result = result;
+ return;
+ } else |err| switch (err) {
+ error.LibCRuntimeNotFound => return,
+ else => return err,
+ }
+ }
+
+
+ async fn findNativeKernel32LibDir(self: *LibCInstallation, loop: *event.Loop, sdk: *c.ZigWindowsSDK) FindError!void {
+ var search_buf: [2]Search = undefined;
+ const searches = fillSearch(&search_buf, sdk);
+
+ var result_buf = try std.Buffer.initSize(loop.allocator, 0);
+ defer result_buf.deinit();
+
+ for (searches) |search| {
+ result_buf.shrink(0);
+ const stream = &std.io.BufferOutStream.init(&result_buf).stream;
+ try stream.print("{}\\Lib\\{}\\um\\", search.path, search.version);
+ switch (builtin.arch) {
+ builtin.Arch.i386 => try stream.write("x86\\"),
+ builtin.Arch.x86_64 => try stream.write("x64\\"),
+ builtin.Arch.aarch64 => try stream.write("arm\\"),
+ else => return error.UnsupportedArchitecture,
+ }
+ const kernel32_path = try std.os.path.join(loop.allocator, result_buf.toSliceConst(), "kernel32.lib");
+ defer loop.allocator.free(kernel32_path);
+ if (try fileExists(loop.allocator, kernel32_path)) {
+ self.kernel32_lib_dir = result_buf.toOwnedSlice();
+ return;
+ }
+ }
+ return error.LibCKernel32LibNotFound;
+ }
+
+ fn initEmpty(self: *LibCInstallation) void {
+ self.* = LibCInstallation{
+ .include_dir = ([*]const u8)(undefined)[0..0],
+ .lib_dir = null,
+ .static_lib_dir = null,
+ .msvc_lib_dir = null,
+ .kernel32_lib_dir = null,
+ .dynamic_linker_path = null,
+ };
+ }
+};
+
+/// caller owns returned memory
+async fn ccPrintFileName(loop: *event.Loop, o_file: []const u8, want_dirname: bool) ![]u8 {
+ const cc_exe = std.os.getEnvPosix("CC") orelse "cc";
+ const arg1 = try std.fmt.allocPrint(loop.allocator, "-print-file-name={}", o_file);
+ defer loop.allocator.free(arg1);
+ const argv = []const []const u8{ cc_exe, arg1 };
+
+ // TODO This simulates evented I/O for the child process exec
+ await (async loop.yield() catch unreachable);
+ const errorable_result = std.os.ChildProcess.exec(loop.allocator, argv, null, null, 1024 * 1024);
+ const exec_result = if (std.debug.runtime_safety) blk: {
+ break :blk errorable_result catch unreachable;
+ } else blk: {
+ break :blk errorable_result catch |err| switch (err) {
+ error.OutOfMemory => return error.OutOfMemory,
+ else => return error.UnableToSpawnCCompiler,
+ };
+ };
+ defer {
+ loop.allocator.free(exec_result.stdout);
+ loop.allocator.free(exec_result.stderr);
+ }
+ switch (exec_result.term) {
+ std.os.ChildProcess.Term.Exited => |code| {
+ if (code != 0) return error.CCompilerExitCode;
+ },
+ else => {
+ return error.CCompilerCrashed;
+ },
+ }
+ var it = std.mem.split(exec_result.stdout, "\n\r");
+ const line = it.next() orelse return error.LibCRuntimeNotFound;
+ const dirname = std.os.path.dirname(line) orelse return error.LibCRuntimeNotFound;
+
+ if (want_dirname) {
+ return std.mem.dupe(loop.allocator, u8, dirname);
+ } else {
+ return std.mem.dupe(loop.allocator, u8, line);
+ }
+}
+
+const Search = struct {
+ path: []const u8,
+ version: []const u8,
+};
+
+fn fillSearch(search_buf: *[2]Search, sdk: *c.ZigWindowsSDK) []Search {
+ var search_end: usize = 0;
+ if (sdk.path10_ptr) |path10_ptr| {
+ if (sdk.version10_ptr) |ver10_ptr| {
+ search_buf[search_end] = Search{
+ .path = path10_ptr[0..sdk.path10_len],
+ .version = ver10_ptr[0..sdk.version10_len],
+ };
+ search_end += 1;
+ }
+ }
+ if (sdk.path81_ptr) |path81_ptr| {
+ if (sdk.version81_ptr) |ver81_ptr| {
+ search_buf[search_end] = Search{
+ .path = path81_ptr[0..sdk.path81_len],
+ .version = ver81_ptr[0..sdk.version81_len],
+ };
+ search_end += 1;
+ }
+ }
+ return search_buf[0..search_end];
+}
+
+
+fn fileExists(allocator: *std.mem.Allocator, path: []const u8) !bool {
+ if (std.os.File.access(allocator, path)) |_| {
+ return true;
+ } else |err| switch (err) {
+ error.NotFound, error.PermissionDenied => return false,
+ error.OutOfMemory => return error.OutOfMemory,
+ else => return error.FileSystem,
+ }
+}
diff --git a/src-self-hosted/link.zig b/src-self-hosted/link.zig
new file mode 100644
index 0000000000..b9eefa9d4f
--- /dev/null
+++ b/src-self-hosted/link.zig
@@ -0,0 +1,724 @@
+const std = @import("std");
+const mem = std.mem;
+const c = @import("c.zig");
+const builtin = @import("builtin");
+const ObjectFormat = builtin.ObjectFormat;
+const Compilation = @import("compilation.zig").Compilation;
+const Target = @import("target.zig").Target;
+const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
+const assert = std.debug.assert;
+
+const Context = struct {
+ comp: *Compilation,
+ arena: std.heap.ArenaAllocator,
+ args: std.ArrayList([*]const u8),
+ link_in_crt: bool,
+
+ link_err: error{OutOfMemory}!void,
+ link_msg: std.Buffer,
+
+ libc: *LibCInstallation,
+ out_file_path: std.Buffer,
+};
+
+pub async fn link(comp: *Compilation) !void {
+ var ctx = Context{
+ .comp = comp,
+ .arena = std.heap.ArenaAllocator.init(comp.gpa()),
+ .args = undefined,
+ .link_in_crt = comp.haveLibC() and comp.kind == Compilation.Kind.Exe,
+ .link_err = {},
+ .link_msg = undefined,
+ .libc = undefined,
+ .out_file_path = undefined,
+ };
+ defer ctx.arena.deinit();
+ ctx.args = std.ArrayList([*]const u8).init(&ctx.arena.allocator);
+ ctx.link_msg = std.Buffer.initNull(&ctx.arena.allocator);
+
+ if (comp.link_out_file) |out_file| {
+ ctx.out_file_path = try std.Buffer.init(&ctx.arena.allocator, out_file);
+ } else {
+ ctx.out_file_path = try std.Buffer.init(&ctx.arena.allocator, comp.name.toSliceConst());
+ switch (comp.kind) {
+ Compilation.Kind.Exe => {
+ try ctx.out_file_path.append(comp.target.exeFileExt());
+ },
+ Compilation.Kind.Lib => {
+ try ctx.out_file_path.append(comp.target.libFileExt(comp.is_static));
+ },
+ Compilation.Kind.Obj => {
+ try ctx.out_file_path.append(comp.target.objFileExt());
+ },
+ }
+ }
+
+ // even though we're calling LLD as a library it thinks the first
+ // argument is its own exe name
+ try ctx.args.append(c"lld");
+
+ if (comp.haveLibC()) {
+ ctx.libc = ctx.comp.override_libc orelse blk: {
+ switch (comp.target) {
+ Target.Native => {
+ break :blk (await (async comp.event_loop_local.getNativeLibC() catch unreachable)) catch return error.LibCRequiredButNotProvidedOrFound;
+ },
+ else => return error.LibCRequiredButNotProvidedOrFound,
+ }
+ };
+ }
+
+ try constructLinkerArgs(&ctx);
+
+ if (comp.verbose_link) {
+ for (ctx.args.toSliceConst()) |arg, i| {
+ const space = if (i == 0) "" else " ";
+ std.debug.warn("{}{s}", space, arg);
+ }
+ std.debug.warn("\n");
+ }
+
+ const extern_ofmt = toExternObjectFormatType(comp.target.getObjectFormat());
+ const args_slice = ctx.args.toSlice();
+ // Not evented I/O. LLD does its own multithreading internally.
+ if (!ZigLLDLink(extern_ofmt, args_slice.ptr, args_slice.len, linkDiagCallback, @ptrCast(*c_void, &ctx))) {
+ if (!ctx.link_msg.isNull()) {
+ // TODO capture these messages and pass them through the system, reporting them through the
+ // event system instead of printing them directly here.
+ // perhaps try to parse and understand them.
+ std.debug.warn("{}\n", ctx.link_msg.toSliceConst());
+ }
+ return error.LinkFailed;
+ }
+}
+
+extern fn ZigLLDLink(
+ oformat: c.ZigLLVM_ObjectFormatType,
+ args: [*]const [*]const u8,
+ arg_count: usize,
+ append_diagnostic: extern fn (*c_void, [*]const u8, usize) void,
+ context: *c_void,
+) bool;
+
+extern fn linkDiagCallback(context: *c_void, ptr: [*]const u8, len: usize) void {
+ const ctx = @ptrCast(*Context, @alignCast(@alignOf(Context), context));
+ ctx.link_err = linkDiagCallbackErrorable(ctx, ptr[0..len]);
+}
+
+fn linkDiagCallbackErrorable(ctx: *Context, msg: []const u8) !void {
+ if (ctx.link_msg.isNull()) {
+ try ctx.link_msg.resize(0);
+ }
+ try ctx.link_msg.append(msg);
+}
+
+fn toExternObjectFormatType(ofmt: ObjectFormat) c.ZigLLVM_ObjectFormatType {
+ return switch (ofmt) {
+ ObjectFormat.unknown => c.ZigLLVM_UnknownObjectFormat,
+ ObjectFormat.coff => c.ZigLLVM_COFF,
+ ObjectFormat.elf => c.ZigLLVM_ELF,
+ ObjectFormat.macho => c.ZigLLVM_MachO,
+ ObjectFormat.wasm => c.ZigLLVM_Wasm,
+ };
+}
+
+fn constructLinkerArgs(ctx: *Context) !void {
+ switch (ctx.comp.target.getObjectFormat()) {
+ ObjectFormat.unknown => unreachable,
+ ObjectFormat.coff => return constructLinkerArgsCoff(ctx),
+ ObjectFormat.elf => return constructLinkerArgsElf(ctx),
+ ObjectFormat.macho => return constructLinkerArgsMachO(ctx),
+ ObjectFormat.wasm => return constructLinkerArgsWasm(ctx),
+ }
+}
+
+fn constructLinkerArgsElf(ctx: *Context) !void {
+ // TODO commented out code in this function
+ //if (g->linker_script) {
+ // lj->args.append("-T");
+ // lj->args.append(g->linker_script);
+ //}
+
+ //if (g->no_rosegment_workaround) {
+ // lj->args.append("--no-rosegment");
+ //}
+ try ctx.args.append(c"--gc-sections");
+
+ //lj->args.append("-m");
+ //lj->args.append(getLDMOption(&g->zig_target));
+
+ //bool is_lib = g->out_type == OutTypeLib;
+ //bool shared = !g->is_static && is_lib;
+ //Buf *soname = nullptr;
+ if (ctx.comp.is_static) {
+ if (ctx.comp.target.isArmOrThumb()) {
+ try ctx.args.append(c"-Bstatic");
+ } else {
+ try ctx.args.append(c"-static");
+ }
+ }
+ //} else if (shared) {
+ // lj->args.append("-shared");
+
+ // if (buf_len(&lj->out_file) == 0) {
+ // buf_appendf(&lj->out_file, "lib%s.so.%" ZIG_PRI_usize ".%" ZIG_PRI_usize ".%" ZIG_PRI_usize "",
+ // buf_ptr(g->root_out_name), g->version_major, g->version_minor, g->version_patch);
+ // }
+ // soname = buf_sprintf("lib%s.so.%" ZIG_PRI_usize "", buf_ptr(g->root_out_name), g->version_major);
+ //}
+
+ try ctx.args.append(c"-o");
+ try ctx.args.append(ctx.out_file_path.ptr());
+
+ if (ctx.link_in_crt) {
+ const crt1o = if (ctx.comp.is_static) "crt1.o" else "Scrt1.o";
+ const crtbegino = if (ctx.comp.is_static) "crtbeginT.o" else "crtbegin.o";
+ try addPathJoin(ctx, ctx.libc.lib_dir.?, crt1o);
+ try addPathJoin(ctx, ctx.libc.lib_dir.?, "crti.o");
+ try addPathJoin(ctx, ctx.libc.static_lib_dir.?, crtbegino);
+ }
+
+ //for (size_t i = 0; i < g->rpath_list.length; i += 1) {
+ // Buf *rpath = g->rpath_list.at(i);
+ // add_rpath(lj, rpath);
+ //}
+ //if (g->each_lib_rpath) {
+ // for (size_t i = 0; i < g->lib_dirs.length; i += 1) {
+ // const char *lib_dir = g->lib_dirs.at(i);
+ // for (size_t i = 0; i < g->link_libs_list.length; i += 1) {
+ // LinkLib *link_lib = g->link_libs_list.at(i);
+ // if (buf_eql_str(link_lib->name, "c")) {
+ // continue;
+ // }
+ // bool does_exist;
+ // Buf *test_path = buf_sprintf("%s/lib%s.so", lib_dir, buf_ptr(link_lib->name));
+ // if (os_file_exists(test_path, &does_exist) != ErrorNone) {
+ // zig_panic("link: unable to check if file exists: %s", buf_ptr(test_path));
+ // }
+ // if (does_exist) {
+ // add_rpath(lj, buf_create_from_str(lib_dir));
+ // break;
+ // }
+ // }
+ // }
+ //}
+
+ //for (size_t i = 0; i < g->lib_dirs.length; i += 1) {
+ // const char *lib_dir = g->lib_dirs.at(i);
+ // lj->args.append("-L");
+ // lj->args.append(lib_dir);
+ //}
+
+ if (ctx.comp.haveLibC()) {
+ try ctx.args.append(c"-L");
+ try ctx.args.append((try std.cstr.addNullByte(&ctx.arena.allocator, ctx.libc.lib_dir.?)).ptr);
+
+ try ctx.args.append(c"-L");
+ try ctx.args.append((try std.cstr.addNullByte(&ctx.arena.allocator, ctx.libc.static_lib_dir.?)).ptr);
+
+ if (!ctx.comp.is_static) {
+ const dl = blk: {
+ if (ctx.libc.dynamic_linker_path) |dl| break :blk dl;
+ if (ctx.comp.target.getDynamicLinkerPath()) |dl| break :blk dl;
+ return error.LibCMissingDynamicLinker;
+ };
+ try ctx.args.append(c"-dynamic-linker");
+ try ctx.args.append((try std.cstr.addNullByte(&ctx.arena.allocator, dl)).ptr);
+ }
+ }
+
+ //if (shared) {
+ // lj->args.append("-soname");
+ // lj->args.append(buf_ptr(soname));
+ //}
+
+ // .o files
+ for (ctx.comp.link_objects) |link_object| {
+ const link_obj_with_null = try std.cstr.addNullByte(&ctx.arena.allocator, link_object);
+ try ctx.args.append(link_obj_with_null.ptr);
+ }
+ try addFnObjects(ctx);
+
+ //if (g->out_type == OutTypeExe || g->out_type == OutTypeLib) {
+ // if (g->libc_link_lib == nullptr) {
+ // Buf *builtin_o_path = build_o(g, "builtin");
+ // lj->args.append(buf_ptr(builtin_o_path));
+ // }
+
+ // // sometimes libgcc is missing stuff, so we still build compiler_rt and rely on weak linkage
+ // Buf *compiler_rt_o_path = build_compiler_rt(g);
+ // lj->args.append(buf_ptr(compiler_rt_o_path));
+ //}
+
+ //for (size_t i = 0; i < g->link_libs_list.length; i += 1) {
+ // LinkLib *link_lib = g->link_libs_list.at(i);
+ // if (buf_eql_str(link_lib->name, "c")) {
+ // continue;
+ // }
+ // Buf *arg;
+ // if (buf_starts_with_str(link_lib->name, "/") || buf_ends_with_str(link_lib->name, ".a") ||
+ // buf_ends_with_str(link_lib->name, ".so"))
+ // {
+ // arg = link_lib->name;
+ // } else {
+ // arg = buf_sprintf("-l%s", buf_ptr(link_lib->name));
+ // }
+ // lj->args.append(buf_ptr(arg));
+ //}
+
+ // libc dep
+ if (ctx.comp.haveLibC()) {
+ if (ctx.comp.is_static) {
+ try ctx.args.append(c"--start-group");
+ try ctx.args.append(c"-lgcc");
+ try ctx.args.append(c"-lgcc_eh");
+ try ctx.args.append(c"-lc");
+ try ctx.args.append(c"-lm");
+ try ctx.args.append(c"--end-group");
+ } else {
+ try ctx.args.append(c"-lgcc");
+ try ctx.args.append(c"--as-needed");
+ try ctx.args.append(c"-lgcc_s");
+ try ctx.args.append(c"--no-as-needed");
+ try ctx.args.append(c"-lc");
+ try ctx.args.append(c"-lm");
+ try ctx.args.append(c"-lgcc");
+ try ctx.args.append(c"--as-needed");
+ try ctx.args.append(c"-lgcc_s");
+ try ctx.args.append(c"--no-as-needed");
+ }
+ }
+
+ // crt end
+ if (ctx.link_in_crt) {
+ try addPathJoin(ctx, ctx.libc.static_lib_dir.?, "crtend.o");
+ try addPathJoin(ctx, ctx.libc.lib_dir.?, "crtn.o");
+ }
+
+ if (ctx.comp.target != Target.Native) {
+ try ctx.args.append(c"--allow-shlib-undefined");
+ }
+
+ if (ctx.comp.target.getOs() == builtin.Os.zen) {
+ try ctx.args.append(c"-e");
+ try ctx.args.append(c"_start");
+
+ try ctx.args.append(c"--image-base=0x10000000");
+ }
+}
+
+fn addPathJoin(ctx: *Context, dirname: []const u8, basename: []const u8) !void {
+ const full_path = try std.os.path.join(&ctx.arena.allocator, dirname, basename);
+ const full_path_with_null = try std.cstr.addNullByte(&ctx.arena.allocator, full_path);
+ try ctx.args.append(full_path_with_null.ptr);
+}
+
+fn constructLinkerArgsCoff(ctx: *Context) !void {
+ try ctx.args.append(c"-NOLOGO");
+
+ if (!ctx.comp.strip) {
+ try ctx.args.append(c"-DEBUG");
+ }
+
+ switch (ctx.comp.target.getArch()) {
+ builtin.Arch.i386 => try ctx.args.append(c"-MACHINE:X86"),
+ builtin.Arch.x86_64 => try ctx.args.append(c"-MACHINE:X64"),
+ builtin.Arch.aarch64 => try ctx.args.append(c"-MACHINE:ARM"),
+ else => return error.UnsupportedLinkArchitecture,
+ }
+
+ if (ctx.comp.windows_subsystem_windows) {
+ try ctx.args.append(c"/SUBSYSTEM:windows");
+ } else if (ctx.comp.windows_subsystem_console) {
+ try ctx.args.append(c"/SUBSYSTEM:console");
+ }
+
+ const is_library = ctx.comp.kind == Compilation.Kind.Lib;
+
+ const out_arg = try std.fmt.allocPrint(&ctx.arena.allocator, "-OUT:{}\x00", ctx.out_file_path.toSliceConst());
+ try ctx.args.append(out_arg.ptr);
+
+ if (ctx.comp.haveLibC()) {
+ try ctx.args.append((try std.fmt.allocPrint(&ctx.arena.allocator, "-LIBPATH:{}\x00", ctx.libc.msvc_lib_dir.?)).ptr);
+ try ctx.args.append((try std.fmt.allocPrint(&ctx.arena.allocator, "-LIBPATH:{}\x00", ctx.libc.kernel32_lib_dir.?)).ptr);
+ try ctx.args.append((try std.fmt.allocPrint(&ctx.arena.allocator, "-LIBPATH:{}\x00", ctx.libc.lib_dir.?)).ptr);
+ }
+
+ if (ctx.link_in_crt) {
+ const lib_str = if (ctx.comp.is_static) "lib" else "";
+ const d_str = if (ctx.comp.build_mode == builtin.Mode.Debug) "d" else "";
+
+ if (ctx.comp.is_static) {
+ const cmt_lib_name = try std.fmt.allocPrint(&ctx.arena.allocator, "libcmt{}.lib\x00", d_str);
+ try ctx.args.append(cmt_lib_name.ptr);
+ } else {
+ const msvcrt_lib_name = try std.fmt.allocPrint(&ctx.arena.allocator, "msvcrt{}.lib\x00", d_str);
+ try ctx.args.append(msvcrt_lib_name.ptr);
+ }
+
+ const vcruntime_lib_name = try std.fmt.allocPrint(&ctx.arena.allocator, "{}vcruntime{}.lib\x00", lib_str, d_str);
+ try ctx.args.append(vcruntime_lib_name.ptr);
+
+ const crt_lib_name = try std.fmt.allocPrint(&ctx.arena.allocator, "{}ucrt{}.lib\x00", lib_str, d_str);
+ try ctx.args.append(crt_lib_name.ptr);
+
+ // Visual C++ 2015 Conformance Changes
+ // https://msdn.microsoft.com/en-us/library/bb531344.aspx
+ try ctx.args.append(c"legacy_stdio_definitions.lib");
+
+ // msvcrt depends on kernel32
+ try ctx.args.append(c"kernel32.lib");
+ } else {
+ try ctx.args.append(c"-NODEFAULTLIB");
+ if (!is_library) {
+ try ctx.args.append(c"-ENTRY:WinMainCRTStartup");
+ // TODO
+ //if (g->have_winmain) {
+ // lj->args.append("-ENTRY:WinMain");
+ //} else {
+ // lj->args.append("-ENTRY:WinMainCRTStartup");
+ //}
+ }
+ }
+
+ if (is_library and !ctx.comp.is_static) {
+ try ctx.args.append(c"-DLL");
+ }
+
+ //for (size_t i = 0; i < g->lib_dirs.length; i += 1) {
+ // const char *lib_dir = g->lib_dirs.at(i);
+ // lj->args.append(buf_ptr(buf_sprintf("-LIBPATH:%s", lib_dir)));
+ //}
+
+ for (ctx.comp.link_objects) |link_object| {
+ const link_obj_with_null = try std.cstr.addNullByte(&ctx.arena.allocator, link_object);
+ try ctx.args.append(link_obj_with_null.ptr);
+ }
+ try addFnObjects(ctx);
+
+ switch (ctx.comp.kind) {
+ Compilation.Kind.Exe, Compilation.Kind.Lib => {
+ if (!ctx.comp.haveLibC()) {
+ @panic("TODO");
+ //Buf *builtin_o_path = build_o(g, "builtin");
+ //lj->args.append(buf_ptr(builtin_o_path));
+ }
+
+ // msvc compiler_rt is missing some stuff, so we still build it and rely on weak linkage
+ // TODO
+ //Buf *compiler_rt_o_path = build_compiler_rt(g);
+ //lj->args.append(buf_ptr(compiler_rt_o_path));
+ },
+ Compilation.Kind.Obj => {},
+ }
+
+ //Buf *def_contents = buf_alloc();
+ //ZigList gen_lib_args = {0};
+ //for (size_t lib_i = 0; lib_i < g->link_libs_list.length; lib_i += 1) {
+ // LinkLib *link_lib = g->link_libs_list.at(lib_i);
+ // if (buf_eql_str(link_lib->name, "c")) {
+ // continue;
+ // }
+ // if (link_lib->provided_explicitly) {
+ // if (lj->codegen->zig_target.env_type == ZigLLVM_GNU) {
+ // Buf *arg = buf_sprintf("-l%s", buf_ptr(link_lib->name));
+ // lj->args.append(buf_ptr(arg));
+ // }
+ // else {
+ // lj->args.append(buf_ptr(link_lib->name));
+ // }
+ // } else {
+ // buf_resize(def_contents, 0);
+ // buf_appendf(def_contents, "LIBRARY %s\nEXPORTS\n", buf_ptr(link_lib->name));
+ // for (size_t exp_i = 0; exp_i < link_lib->symbols.length; exp_i += 1) {
+ // Buf *symbol_name = link_lib->symbols.at(exp_i);
+ // buf_appendf(def_contents, "%s\n", buf_ptr(symbol_name));
+ // }
+ // buf_appendf(def_contents, "\n");
+
+ // Buf *def_path = buf_alloc();
+ // os_path_join(g->cache_dir, buf_sprintf("%s.def", buf_ptr(link_lib->name)), def_path);
+ // os_write_file(def_path, def_contents);
+
+ // Buf *generated_lib_path = buf_alloc();
+ // os_path_join(g->cache_dir, buf_sprintf("%s.lib", buf_ptr(link_lib->name)), generated_lib_path);
+
+ // gen_lib_args.resize(0);
+ // gen_lib_args.append("link");
+
+ // coff_append_machine_arg(g, &gen_lib_args);
+ // gen_lib_args.append(buf_ptr(buf_sprintf("-DEF:%s", buf_ptr(def_path))));
+ // gen_lib_args.append(buf_ptr(buf_sprintf("-OUT:%s", buf_ptr(generated_lib_path))));
+ // Buf diag = BUF_INIT;
+ // if (!zig_lld_link(g->zig_target.oformat, gen_lib_args.items, gen_lib_args.length, &diag)) {
+ // fprintf(stderr, "%s\n", buf_ptr(&diag));
+ // exit(1);
+ // }
+ // lj->args.append(buf_ptr(generated_lib_path));
+ // }
+ //}
+}
+
+fn constructLinkerArgsMachO(ctx: *Context) !void {
+ try ctx.args.append(c"-demangle");
+
+ if (ctx.comp.linker_rdynamic) {
+ try ctx.args.append(c"-export_dynamic");
+ }
+
+ const is_lib = ctx.comp.kind == Compilation.Kind.Lib;
+ const shared = !ctx.comp.is_static and is_lib;
+ if (ctx.comp.is_static) {
+ try ctx.args.append(c"-static");
+ } else {
+ try ctx.args.append(c"-dynamic");
+ }
+
+ //if (is_lib) {
+ // if (!g->is_static) {
+ // lj->args.append("-dylib");
+
+ // Buf *compat_vers = buf_sprintf("%" ZIG_PRI_usize ".0.0", g->version_major);
+ // lj->args.append("-compatibility_version");
+ // lj->args.append(buf_ptr(compat_vers));
+
+ // Buf *cur_vers = buf_sprintf("%" ZIG_PRI_usize ".%" ZIG_PRI_usize ".%" ZIG_PRI_usize,
+ // g->version_major, g->version_minor, g->version_patch);
+ // lj->args.append("-current_version");
+ // lj->args.append(buf_ptr(cur_vers));
+
+ // // TODO getting an error when running an executable when doing this rpath thing
+ // //Buf *dylib_install_name = buf_sprintf("@rpath/lib%s.%" ZIG_PRI_usize ".dylib",
+ // // buf_ptr(g->root_out_name), g->version_major);
+ // //lj->args.append("-install_name");
+ // //lj->args.append(buf_ptr(dylib_install_name));
+
+ // if (buf_len(&lj->out_file) == 0) {
+ // buf_appendf(&lj->out_file, "lib%s.%" ZIG_PRI_usize ".%" ZIG_PRI_usize ".%" ZIG_PRI_usize ".dylib",
+ // buf_ptr(g->root_out_name), g->version_major, g->version_minor, g->version_patch);
+ // }
+ // }
+ //}
+
+ try ctx.args.append(c"-arch");
+ const darwin_arch_str = try std.cstr.addNullByte(
+ &ctx.arena.allocator,
+ ctx.comp.target.getDarwinArchString(),
+ );
+ try ctx.args.append(darwin_arch_str.ptr);
+
+ const platform = try DarwinPlatform.get(ctx.comp);
+ switch (platform.kind) {
+ DarwinPlatform.Kind.MacOS => try ctx.args.append(c"-macosx_version_min"),
+ DarwinPlatform.Kind.IPhoneOS => try ctx.args.append(c"-iphoneos_version_min"),
+ DarwinPlatform.Kind.IPhoneOSSimulator => try ctx.args.append(c"-ios_simulator_version_min"),
+ }
+ const ver_str = try std.fmt.allocPrint(&ctx.arena.allocator, "{}.{}.{}\x00", platform.major, platform.minor, platform.micro);
+ try ctx.args.append(ver_str.ptr);
+
+ if (ctx.comp.kind == Compilation.Kind.Exe) {
+ if (ctx.comp.is_static) {
+ try ctx.args.append(c"-no_pie");
+ } else {
+ try ctx.args.append(c"-pie");
+ }
+ }
+
+ try ctx.args.append(c"-o");
+ try ctx.args.append(ctx.out_file_path.ptr());
+
+ //for (size_t i = 0; i < g->rpath_list.length; i += 1) {
+ // Buf *rpath = g->rpath_list.at(i);
+ // add_rpath(lj, rpath);
+ //}
+ //add_rpath(lj, &lj->out_file);
+
+ if (shared) {
+ try ctx.args.append(c"-headerpad_max_install_names");
+ } else if (ctx.comp.is_static) {
+ try ctx.args.append(c"-lcrt0.o");
+ } else {
+ switch (platform.kind) {
+ DarwinPlatform.Kind.MacOS => {
+ if (platform.versionLessThan(10, 5)) {
+ try ctx.args.append(c"-lcrt1.o");
+ } else if (platform.versionLessThan(10, 6)) {
+ try ctx.args.append(c"-lcrt1.10.5.o");
+ } else if (platform.versionLessThan(10, 8)) {
+ try ctx.args.append(c"-lcrt1.10.6.o");
+ }
+ },
+ DarwinPlatform.Kind.IPhoneOS => {
+ if (ctx.comp.target.getArch() == builtin.Arch.aarch64) {
+ // iOS does not need any crt1 files for arm64
+ } else if (platform.versionLessThan(3, 1)) {
+ try ctx.args.append(c"-lcrt1.o");
+ } else if (platform.versionLessThan(6, 0)) {
+ try ctx.args.append(c"-lcrt1.3.1.o");
+ }
+ },
+ DarwinPlatform.Kind.IPhoneOSSimulator => {}, // no crt1.o needed
+ }
+ }
+
+ //for (size_t i = 0; i < g->lib_dirs.length; i += 1) {
+ // const char *lib_dir = g->lib_dirs.at(i);
+ // lj->args.append("-L");
+ // lj->args.append(lib_dir);
+ //}
+
+ for (ctx.comp.link_objects) |link_object| {
+ const link_obj_with_null = try std.cstr.addNullByte(&ctx.arena.allocator, link_object);
+ try ctx.args.append(link_obj_with_null.ptr);
+ }
+ try addFnObjects(ctx);
+
+ //// compiler_rt on darwin is missing some stuff, so we still build it and rely on LinkOnce
+ //if (g->out_type == OutTypeExe || g->out_type == OutTypeLib) {
+ // Buf *compiler_rt_o_path = build_compiler_rt(g);
+ // lj->args.append(buf_ptr(compiler_rt_o_path));
+ //}
+
+ if (ctx.comp.target == Target.Native) {
+ for (ctx.comp.link_libs_list.toSliceConst()) |lib| {
+ if (mem.eql(u8, lib.name, "c")) {
+ // on Darwin, libSystem has libc in it, but also you have to use it
+ // to make syscalls because the syscall numbers are not documented
+ // and change between versions.
+ // so we always link against libSystem
+ try ctx.args.append(c"-lSystem");
+ } else {
+ if (mem.indexOfScalar(u8, lib.name, '/') == null) {
+ const arg = try std.fmt.allocPrint(&ctx.arena.allocator, "-l{}\x00", lib.name);
+ try ctx.args.append(arg.ptr);
+ } else {
+ const arg = try std.cstr.addNullByte(&ctx.arena.allocator, lib.name);
+ try ctx.args.append(arg.ptr);
+ }
+ }
+ }
+ } else {
+ try ctx.args.append(c"-undefined");
+ try ctx.args.append(c"dynamic_lookup");
+ }
+
+ if (platform.kind == DarwinPlatform.Kind.MacOS) {
+ if (platform.versionLessThan(10, 5)) {
+ try ctx.args.append(c"-lgcc_s.10.4");
+ } else if (platform.versionLessThan(10, 6)) {
+ try ctx.args.append(c"-lgcc_s.10.5");
+ }
+ } else {
+ @panic("TODO");
+ }
+
+ //for (size_t i = 0; i < g->darwin_frameworks.length; i += 1) {
+ // lj->args.append("-framework");
+ // lj->args.append(buf_ptr(g->darwin_frameworks.at(i)));
+ //}
+}
+
+fn constructLinkerArgsWasm(ctx: *Context) void {
+ @panic("TODO");
+}
+
+fn addFnObjects(ctx: *Context) !void {
+ // at this point it's guaranteed nobody else has this lock, so we circumvent it
+ // and avoid having to be a coroutine
+ const fn_link_set = &ctx.comp.fn_link_set.private_data;
+
+ var it = fn_link_set.first;
+ while (it) |node| {
+ const fn_val = node.data orelse {
+ // handle the tombstone. See Value.Fn.destroy.
+ it = node.next;
+ fn_link_set.remove(node);
+ ctx.comp.gpa().destroy(node);
+ continue;
+ };
+ try ctx.args.append(fn_val.containing_object.ptr());
+ it = node.next;
+ }
+}
+
+const DarwinPlatform = struct {
+ kind: Kind,
+ major: u32,
+ minor: u32,
+ micro: u32,
+
+ const Kind = enum {
+ MacOS,
+ IPhoneOS,
+ IPhoneOSSimulator,
+ };
+
+ fn get(comp: *Compilation) !DarwinPlatform {
+ var result: DarwinPlatform = undefined;
+ const ver_str = switch (comp.darwin_version_min) {
+ Compilation.DarwinVersionMin.MacOS => |ver| blk: {
+ result.kind = Kind.MacOS;
+ break :blk ver;
+ },
+ Compilation.DarwinVersionMin.Ios => |ver| blk: {
+ result.kind = Kind.IPhoneOS;
+ break :blk ver;
+ },
+ Compilation.DarwinVersionMin.None => blk: {
+ assert(comp.target.getOs() == builtin.Os.macosx);
+ result.kind = Kind.MacOS;
+ break :blk "10.10";
+ },
+ };
+
+ var had_extra: bool = undefined;
+ try darwinGetReleaseVersion(ver_str, &result.major, &result.minor, &result.micro, &had_extra,);
+ if (had_extra or result.major != 10 or result.minor >= 100 or result.micro >= 100) {
+ return error.InvalidDarwinVersionString;
+ }
+
+ if (result.kind == Kind.IPhoneOS) {
+ switch (comp.target.getArch()) {
+ builtin.Arch.i386,
+ builtin.Arch.x86_64,
+ => result.kind = Kind.IPhoneOSSimulator,
+ else => {},
+ }
+ }
+ return result;
+ }
+
+ fn versionLessThan(self: DarwinPlatform, major: u32, minor: u32) bool {
+ if (self.major < major)
+ return true;
+ if (self.major > major)
+ return false;
+ if (self.minor < minor)
+ return true;
+ return false;
+ }
+};
+
+/// Parse (([0-9]+)(.([0-9]+)(.([0-9]+)?))?)? and return the
+/// grouped values as integers. Numbers which are not provided are set to 0.
+/// return true if the entire string was parsed (9.2), or all groups were
+/// parsed (10.3.5extrastuff).
+fn darwinGetReleaseVersion(str: []const u8, major: *u32, minor: *u32, micro: *u32, had_extra: *bool) !void {
+ major.* = 0;
+ minor.* = 0;
+ micro.* = 0;
+ had_extra.* = false;
+
+ if (str.len == 0)
+ return error.InvalidDarwinVersionString;
+
+ var start_pos: usize = 0;
+ for ([]*u32{major, minor, micro}) |v| {
+ const dot_pos = mem.indexOfScalarPos(u8, str, start_pos, '.');
+ const end_pos = dot_pos orelse str.len;
+ v.* = std.fmt.parseUnsigned(u32, str[start_pos..end_pos], 10) catch return error.InvalidDarwinVersionString;
+ start_pos = (dot_pos orelse return) + 1;
+ if (start_pos == str.len) return;
+ }
+ had_extra.* = true;
+}
diff --git a/src-self-hosted/llvm.zig b/src-self-hosted/llvm.zig
index 13480dc2c6..8bb45ac616 100644
--- a/src-self-hosted/llvm.zig
+++ b/src-self-hosted/llvm.zig
@@ -2,6 +2,12 @@ const builtin = @import("builtin");
const c = @import("c.zig");
const assert = @import("std").debug.assert;
+// we wrap the c module for 3 reasons:
+// 1. to avoid accidentally calling the non-thread-safe functions
+// 2. patch up some of the types to remove nullability
+// 3. some functions have been augmented by zig_llvm.cpp to be more powerful,
+// such as ZigLLVMTargetMachineEmitToFile
+
pub const AttributeIndex = c_uint;
pub const Bool = c_int;
@@ -12,25 +18,59 @@ pub const ValueRef = removeNullability(c.LLVMValueRef);
pub const TypeRef = removeNullability(c.LLVMTypeRef);
pub const BasicBlockRef = removeNullability(c.LLVMBasicBlockRef);
pub const AttributeRef = removeNullability(c.LLVMAttributeRef);
+pub const TargetRef = removeNullability(c.LLVMTargetRef);
+pub const TargetMachineRef = removeNullability(c.LLVMTargetMachineRef);
+pub const TargetDataRef = removeNullability(c.LLVMTargetDataRef);
+pub const DIBuilder = c.ZigLLVMDIBuilder;
+pub const ABIAlignmentOfType = c.LLVMABIAlignmentOfType;
pub const AddAttributeAtIndex = c.LLVMAddAttributeAtIndex;
pub const AddFunction = c.LLVMAddFunction;
+pub const AddGlobal = c.LLVMAddGlobal;
+pub const AddModuleCodeViewFlag = c.ZigLLVMAddModuleCodeViewFlag;
+pub const AddModuleDebugInfoFlag = c.ZigLLVMAddModuleDebugInfoFlag;
+pub const ArrayType = c.LLVMArrayType;
pub const ClearCurrentDebugLocation = c.ZigLLVMClearCurrentDebugLocation;
+pub const ConstAllOnes = c.LLVMConstAllOnes;
+pub const ConstArray = c.LLVMConstArray;
+pub const ConstBitCast = c.LLVMConstBitCast;
pub const ConstInt = c.LLVMConstInt;
+pub const ConstIntOfArbitraryPrecision = c.LLVMConstIntOfArbitraryPrecision;
+pub const ConstNeg = c.LLVMConstNeg;
+pub const ConstNull = c.LLVMConstNull;
pub const ConstStringInContext = c.LLVMConstStringInContext;
pub const ConstStructInContext = c.LLVMConstStructInContext;
+pub const CopyStringRepOfTargetData = c.LLVMCopyStringRepOfTargetData;
pub const CreateBuilderInContext = c.LLVMCreateBuilderInContext;
+pub const CreateCompileUnit = c.ZigLLVMCreateCompileUnit;
+pub const CreateDIBuilder = c.ZigLLVMCreateDIBuilder;
pub const CreateEnumAttribute = c.LLVMCreateEnumAttribute;
+pub const CreateFile = c.ZigLLVMCreateFile;
pub const CreateStringAttribute = c.LLVMCreateStringAttribute;
+pub const CreateTargetDataLayout = c.LLVMCreateTargetDataLayout;
+pub const CreateTargetMachine = c.LLVMCreateTargetMachine;
+pub const DIBuilderFinalize = c.ZigLLVMDIBuilderFinalize;
pub const DisposeBuilder = c.LLVMDisposeBuilder;
+pub const DisposeDIBuilder = c.ZigLLVMDisposeDIBuilder;
+pub const DisposeMessage = c.LLVMDisposeMessage;
pub const DisposeModule = c.LLVMDisposeModule;
+pub const DisposeTargetData = c.LLVMDisposeTargetData;
+pub const DisposeTargetMachine = c.LLVMDisposeTargetMachine;
pub const DoubleTypeInContext = c.LLVMDoubleTypeInContext;
pub const DumpModule = c.LLVMDumpModule;
pub const FP128TypeInContext = c.LLVMFP128TypeInContext;
pub const FloatTypeInContext = c.LLVMFloatTypeInContext;
pub const GetEnumAttributeKindForName = c.LLVMGetEnumAttributeKindForName;
+pub const GetHostCPUName = c.ZigLLVMGetHostCPUName;
pub const GetMDKindIDInContext = c.LLVMGetMDKindIDInContext;
+pub const GetNativeFeatures = c.ZigLLVMGetNativeFeatures;
+pub const GetUndef = c.LLVMGetUndef;
pub const HalfTypeInContext = c.LLVMHalfTypeInContext;
+pub const InitializeAllAsmParsers = c.LLVMInitializeAllAsmParsers;
+pub const InitializeAllAsmPrinters = c.LLVMInitializeAllAsmPrinters;
+pub const InitializeAllTargetInfos = c.LLVMInitializeAllTargetInfos;
+pub const InitializeAllTargetMCs = c.LLVMInitializeAllTargetMCs;
+pub const InitializeAllTargets = c.LLVMInitializeAllTargets;
pub const InsertBasicBlockInContext = c.LLVMInsertBasicBlockInContext;
pub const Int128TypeInContext = c.LLVMInt128TypeInContext;
pub const Int16TypeInContext = c.LLVMInt16TypeInContext;
@@ -47,13 +87,26 @@ pub const MDStringInContext = c.LLVMMDStringInContext;
pub const MetadataTypeInContext = c.LLVMMetadataTypeInContext;
pub const ModuleCreateWithNameInContext = c.LLVMModuleCreateWithNameInContext;
pub const PPCFP128TypeInContext = c.LLVMPPCFP128TypeInContext;
+pub const PointerType = c.LLVMPointerType;
+pub const SetAlignment = c.LLVMSetAlignment;
+pub const SetDataLayout = c.LLVMSetDataLayout;
+pub const SetGlobalConstant = c.LLVMSetGlobalConstant;
+pub const SetInitializer = c.LLVMSetInitializer;
+pub const SetLinkage = c.LLVMSetLinkage;
+pub const SetTarget = c.LLVMSetTarget;
+pub const SetUnnamedAddr = c.LLVMSetUnnamedAddr;
pub const StructTypeInContext = c.LLVMStructTypeInContext;
pub const TokenTypeInContext = c.LLVMTokenTypeInContext;
+pub const TypeOf = c.LLVMTypeOf;
pub const VoidTypeInContext = c.LLVMVoidTypeInContext;
pub const X86FP80TypeInContext = c.LLVMX86FP80TypeInContext;
pub const X86MMXTypeInContext = c.LLVMX86MMXTypeInContext;
-pub const ConstAllOnes = c.LLVMConstAllOnes;
-pub const ConstNull = c.LLVMConstNull;
+
+pub const ConstInBoundsGEP = LLVMConstInBoundsGEP;
+pub extern fn LLVMConstInBoundsGEP(ConstantVal: ValueRef, ConstantIndices: [*]ValueRef, NumIndices: c_uint) ?ValueRef;
+
+pub const GetTargetFromTriple = LLVMGetTargetFromTriple;
+extern fn LLVMGetTargetFromTriple(Triple: [*]const u8, T: *TargetRef, ErrorMessage: ?*[*]u8) Bool;
pub const VerifyModule = LLVMVerifyModule;
extern fn LLVMVerifyModule(M: ModuleRef, Action: VerifierFailureAction, OutMessage: *?[*]u8) Bool;
@@ -83,10 +136,66 @@ pub const PrintMessageAction = VerifierFailureAction.LLVMPrintMessageAction;
pub const ReturnStatusAction = VerifierFailureAction.LLVMReturnStatusAction;
pub const VerifierFailureAction = c.LLVMVerifierFailureAction;
+pub const CodeGenLevelNone = c.LLVMCodeGenOptLevel.LLVMCodeGenLevelNone;
+pub const CodeGenLevelLess = c.LLVMCodeGenOptLevel.LLVMCodeGenLevelLess;
+pub const CodeGenLevelDefault = c.LLVMCodeGenOptLevel.LLVMCodeGenLevelDefault;
+pub const CodeGenLevelAggressive = c.LLVMCodeGenOptLevel.LLVMCodeGenLevelAggressive;
+pub const CodeGenOptLevel = c.LLVMCodeGenOptLevel;
+
+pub const RelocDefault = c.LLVMRelocMode.LLVMRelocDefault;
+pub const RelocStatic = c.LLVMRelocMode.LLVMRelocStatic;
+pub const RelocPIC = c.LLVMRelocMode.LLVMRelocPIC;
+pub const RelocDynamicNoPic = c.LLVMRelocMode.LLVMRelocDynamicNoPic;
+pub const RelocMode = c.LLVMRelocMode;
+
+pub const CodeModelDefault = c.LLVMCodeModel.LLVMCodeModelDefault;
+pub const CodeModelJITDefault = c.LLVMCodeModel.LLVMCodeModelJITDefault;
+pub const CodeModelSmall = c.LLVMCodeModel.LLVMCodeModelSmall;
+pub const CodeModelKernel = c.LLVMCodeModel.LLVMCodeModelKernel;
+pub const CodeModelMedium = c.LLVMCodeModel.LLVMCodeModelMedium;
+pub const CodeModelLarge = c.LLVMCodeModel.LLVMCodeModelLarge;
+pub const CodeModel = c.LLVMCodeModel;
+
+pub const EmitAssembly = EmitOutputType.ZigLLVM_EmitAssembly;
+pub const EmitBinary = EmitOutputType.ZigLLVM_EmitBinary;
+pub const EmitLLVMIr = EmitOutputType.ZigLLVM_EmitLLVMIr;
+pub const EmitOutputType = c.ZigLLVM_EmitOutputType;
+
+pub const CCallConv = c.LLVMCCallConv;
+pub const FastCallConv = c.LLVMFastCallConv;
+pub const ColdCallConv = c.LLVMColdCallConv;
+pub const WebKitJSCallConv = c.LLVMWebKitJSCallConv;
+pub const AnyRegCallConv = c.LLVMAnyRegCallConv;
+pub const X86StdcallCallConv = c.LLVMX86StdcallCallConv;
+pub const X86FastcallCallConv = c.LLVMX86FastcallCallConv;
+pub const CallConv = c.LLVMCallConv;
+
+pub const FnInline = extern enum {
+ Auto,
+ Always,
+ Never,
+};
+
fn removeNullability(comptime T: type) type {
comptime assert(@typeId(T) == builtin.TypeId.Optional);
return T.Child;
}
pub const BuildRet = LLVMBuildRet;
-extern fn LLVMBuildRet(arg0: BuilderRef, V: ?ValueRef) ValueRef;
+extern fn LLVMBuildRet(arg0: BuilderRef, V: ?ValueRef) ?ValueRef;
+
+pub const TargetMachineEmitToFile = ZigLLVMTargetMachineEmitToFile;
+extern fn ZigLLVMTargetMachineEmitToFile(
+ targ_machine_ref: TargetMachineRef,
+ module_ref: ModuleRef,
+ filename: [*]const u8,
+ output_type: EmitOutputType,
+ error_message: *[*]u8,
+ is_debug: bool,
+ is_small: bool,
+) bool;
+
+pub const BuildCall = ZigLLVMBuildCall;
+extern fn ZigLLVMBuildCall(B: BuilderRef, Fn: ValueRef, Args: [*]ValueRef, NumArgs: c_uint, CC: c_uint, fn_inline: FnInline, Name: [*]const u8) ?ValueRef;
+
+pub const PrivateLinkage = c.LLVMLinkage.LLVMPrivateLinkage;
diff --git a/src-self-hosted/main.zig b/src-self-hosted/main.zig
index c9478954c5..37bb435c1b 100644
--- a/src-self-hosted/main.zig
+++ b/src-self-hosted/main.zig
@@ -18,6 +18,7 @@ const EventLoopLocal = @import("compilation.zig").EventLoopLocal;
const Compilation = @import("compilation.zig").Compilation;
const Target = @import("target.zig").Target;
const errmsg = @import("errmsg.zig");
+const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
var stderr_file: os.File = undefined;
var stderr: *io.OutStream(io.FileOutStream.Error) = undefined;
@@ -28,13 +29,14 @@ const usage =
\\
\\Commands:
\\
- \\ build-exe [source] Create executable from source or object files
- \\ build-lib [source] Create library from source or object files
- \\ build-obj [source] Create object from source or assembly
- \\ fmt [source] Parse file and render in canonical zig format
- \\ targets List available compilation targets
- \\ version Print version number and exit
- \\ zen Print zen of zig and exit
+ \\ build-exe [source] Create executable from source or object files
+ \\ build-lib [source] Create library from source or object files
+ \\ build-obj [source] Create object from source or assembly
+ \\ fmt [source] Parse file and render in canonical zig format
+ \\ libc [paths_file] Display native libc paths file or validate one
+ \\ targets List available compilation targets
+ \\ version Print version number and exit
+ \\ zen Print zen of zig and exit
\\
\\
;
@@ -85,6 +87,10 @@ pub fn main() !void {
.name = "fmt",
.exec = cmdFmt,
},
+ Command{
+ .name = "libc",
+ .exec = cmdLibC,
+ },
Command{
.name = "targets",
.exec = cmdTargets,
@@ -130,11 +136,10 @@ const usage_build_generic =
\\ --color [auto|off|on] Enable or disable colored error messages
\\
\\Compile Options:
+ \\ --libc [file] Provide a file which specifies libc paths
\\ --assembly [source] Add assembly file to build
- \\ --cache-dir [path] Override the cache directory
\\ --emit [filetype] Emit a specific file format as compilation output
\\ --enable-timing-info Print timing diagnostics
- \\ --libc-include-dir [path] Directory where libc stdlib.h resides
\\ --name [name] Override output name
\\ --output [file] Override destination path
\\ --output-h [file] Override generated header file path
@@ -163,12 +168,7 @@ const usage_build_generic =
\\
\\Link Options:
\\ --ar-path [path] Set the path to ar
- \\ --dynamic-linker [path] Set the path to ld.so
\\ --each-lib-rpath Add rpath for each used dynamic library
- \\ --libc-lib-dir [path] Directory where libc crt1.o resides
- \\ --libc-static-lib-dir [path] Directory where libc crtbegin.o resides
- \\ --msvc-lib-dir [path] (windows) directory where vcruntime.lib resides
- \\ --kernel32-lib-dir [path] (windows) directory where kernel32.lib resides
\\ --library [lib] Link against lib
\\ --forbid-library [lib] Make it an error to link against lib
\\ --library-path [dir] Add a directory to the library search path
@@ -203,14 +203,13 @@ const args_build_generic = []Flag{
}),
Flag.ArgMergeN("--assembly", 1),
- Flag.Arg1("--cache-dir"),
Flag.Option("--emit", []const []const u8{
"asm",
"bin",
"llvm-ir",
}),
Flag.Bool("--enable-timing-info"),
- Flag.Arg1("--libc-include-dir"),
+ Flag.Arg1("--libc"),
Flag.Arg1("--name"),
Flag.Arg1("--output"),
Flag.Arg1("--output-h"),
@@ -234,12 +233,7 @@ const args_build_generic = []Flag{
Flag.Arg1("-mllvm"),
Flag.Arg1("--ar-path"),
- Flag.Arg1("--dynamic-linker"),
Flag.Bool("--each-lib-rpath"),
- Flag.Arg1("--libc-lib-dir"),
- Flag.Arg1("--libc-static-lib-dir"),
- Flag.Arg1("--msvc-lib-dir"),
- Flag.Arg1("--kernel32-lib-dir"),
Flag.ArgMergeN("--library", 1),
Flag.ArgMergeN("--forbid-library", 1),
Flag.ArgMergeN("--library-path", 1),
@@ -363,6 +357,8 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co
}
};
+ const is_static = flags.present("static");
+
const assembly_files = flags.many("assembly");
const link_objects = flags.many("object");
if (root_source_file == null and link_objects.len == 0 and assembly_files.len == 0) {
@@ -375,21 +371,16 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co
os.exit(1);
}
- const rel_cache_dir = flags.single("cache-dir") orelse "zig-cache"[0..];
- const full_cache_dir = os.path.resolve(allocator, ".", rel_cache_dir) catch {
- try stderr.print("invalid cache dir: {}\n", rel_cache_dir);
- os.exit(1);
- };
- defer allocator.free(full_cache_dir);
-
const zig_lib_dir = introspect.resolveZigLibDir(allocator) catch os.exit(1);
defer allocator.free(zig_lib_dir);
+ var override_libc: LibCInstallation = undefined;
+
var loop: event.Loop = undefined;
try loop.initMultiThreaded(allocator);
defer loop.deinit();
- var event_loop_local = EventLoopLocal.init(&loop);
+ var event_loop_local = try EventLoopLocal.init(&loop);
defer event_loop_local.deinit();
var comp = try Compilation.create(
@@ -399,11 +390,20 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co
Target.Native,
out_type,
build_mode,
+ is_static,
zig_lib_dir,
- full_cache_dir,
);
defer comp.destroy();
+ if (flags.single("libc")) |libc_path| {
+ parseLibcPaths(loop.allocator, &override_libc, libc_path);
+ comp.override_libc = &override_libc;
+ }
+
+ for (flags.many("library")) |lib| {
+ _ = try comp.addLinkLib(lib, true);
+ }
+
comp.version_major = try std.fmt.parseUnsigned(u32, flags.single("ver-major") orelse "0", 10);
comp.version_minor = try std.fmt.parseUnsigned(u32, flags.single("ver-minor") orelse "0", 10);
comp.version_patch = try std.fmt.parseUnsigned(u32, flags.single("ver-patch") orelse "0", 10);
@@ -426,26 +426,6 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co
comp.clang_argv = clang_argv_buf.toSliceConst();
comp.strip = flags.present("strip");
- comp.is_static = flags.present("static");
-
- if (flags.single("libc-lib-dir")) |libc_lib_dir| {
- comp.libc_lib_dir = libc_lib_dir;
- }
- if (flags.single("libc-static-lib-dir")) |libc_static_lib_dir| {
- comp.libc_static_lib_dir = libc_static_lib_dir;
- }
- if (flags.single("libc-include-dir")) |libc_include_dir| {
- comp.libc_include_dir = libc_include_dir;
- }
- if (flags.single("msvc-lib-dir")) |msvc_lib_dir| {
- comp.msvc_lib_dir = msvc_lib_dir;
- }
- if (flags.single("kernel32-lib-dir")) |kernel32_lib_dir| {
- comp.kernel32_lib_dir = kernel32_lib_dir;
- }
- if (flags.single("dynamic-linker")) |dynamic_linker| {
- comp.dynamic_linker = dynamic_linker;
- }
comp.verbose_tokenize = flags.present("verbose-tokenize");
comp.verbose_ast_tree = flags.present("verbose-ast-tree");
@@ -481,9 +461,9 @@ fn buildOutputType(allocator: *Allocator, args: []const []const u8, out_type: Co
}
comp.emit_file_type = emit_type;
- comp.link_objects = link_objects;
comp.assembly_files = assembly_files;
- comp.link_out_file = flags.single("out-file");
+ comp.link_out_file = flags.single("output");
+ comp.link_objects = link_objects;
try comp.build();
const process_build_events_handle = try async processBuildEvents(comp, color);
@@ -497,7 +477,6 @@ async fn processBuildEvents(comp: *Compilation, color: errmsg.Color) void {
switch (build_event) {
Compilation.Event.Ok => {
- std.debug.warn("Build succeeded\n");
return;
},
Compilation.Event.Error => |err| {
@@ -506,7 +485,8 @@ async fn processBuildEvents(comp: *Compilation, color: errmsg.Color) void {
},
Compilation.Event.Fail => |msgs| {
for (msgs) |msg| {
- errmsg.printToFile(&stderr_file, msg, color) catch os.exit(1);
+ defer msg.destroy();
+ msg.printToFile(&stderr_file, color) catch os.exit(1);
}
},
}
@@ -577,6 +557,53 @@ const Fmt = struct {
}
};
+fn parseLibcPaths(allocator: *Allocator, libc: *LibCInstallation, libc_paths_file: []const u8) void {
+ libc.parse(allocator, libc_paths_file, stderr) catch |err| {
+ stderr.print(
+ "Unable to parse libc path file '{}': {}.\n" ++
+ "Try running `zig libc` to see an example for the native target.\n",
+ libc_paths_file,
+ @errorName(err),
+ ) catch os.exit(1);
+ os.exit(1);
+ };
+}
+
+fn cmdLibC(allocator: *Allocator, args: []const []const u8) !void {
+ switch (args.len) {
+ 0 => {},
+ 1 => {
+ var libc_installation: LibCInstallation = undefined;
+ parseLibcPaths(allocator, &libc_installation, args[0]);
+ return;
+ },
+ else => {
+ try stderr.print("unexpected extra parameter: {}\n", args[1]);
+ os.exit(1);
+ },
+ }
+
+ var loop: event.Loop = undefined;
+ try loop.initMultiThreaded(allocator);
+ defer loop.deinit();
+
+ var event_loop_local = try EventLoopLocal.init(&loop);
+ defer event_loop_local.deinit();
+
+ const handle = try async findLibCAsync(&event_loop_local);
+ defer cancel handle;
+
+ loop.run();
+}
+
+async fn findLibCAsync(event_loop_local: *EventLoopLocal) void {
+ const libc = (await (async event_loop_local.getNativeLibC() catch unreachable)) catch |err| {
+ stderr.print("unable to find libc: {}\n", @errorName(err)) catch os.exit(1);
+ os.exit(1);
+ };
+ libc.render(stdout) catch os.exit(1);
+}
+
fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void {
var flags = try Args.parse(allocator, args_fmt_spec, args);
defer flags.deinit();
@@ -620,10 +647,10 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void {
var error_it = tree.errors.iterator(0);
while (error_it.next()) |parse_error| {
- const msg = try errmsg.createFromParseError(allocator, parse_error, &tree, "");
- defer allocator.destroy(msg);
+ const msg = try errmsg.Msg.createFromParseError(allocator, parse_error, &tree, "");
+ defer msg.destroy();
- try errmsg.printToFile(&stderr_file, msg, color);
+ try msg.printToFile(&stderr_file, color);
}
if (tree.errors.len != 0) {
os.exit(1);
@@ -676,10 +703,10 @@ fn cmdFmt(allocator: *Allocator, args: []const []const u8) !void {
var error_it = tree.errors.iterator(0);
while (error_it.next()) |parse_error| {
- const msg = try errmsg.createFromParseError(allocator, parse_error, &tree, file_path);
- defer allocator.destroy(msg);
+ const msg = try errmsg.Msg.createFromParseError(allocator, parse_error, &tree, file_path);
+ defer msg.destroy();
- try errmsg.printToFile(&stderr_file, msg, color);
+ try msg.printToFile(&stderr_file, color);
}
if (tree.errors.len != 0) {
fmt.any_error = true;
diff --git a/src-self-hosted/package.zig b/src-self-hosted/package.zig
new file mode 100644
index 0000000000..720b279651
--- /dev/null
+++ b/src-self-hosted/package.zig
@@ -0,0 +1,29 @@
+const std = @import("std");
+const mem = std.mem;
+const assert = std.debug.assert;
+const Buffer = std.Buffer;
+
+pub const Package = struct {
+ root_src_dir: Buffer,
+ root_src_path: Buffer,
+
+ /// relative to root_src_dir
+ table: Table,
+
+ pub const Table = std.HashMap([]const u8, *Package, mem.hash_slice_u8, mem.eql_slice_u8);
+
+ /// makes internal copies of root_src_dir and root_src_path
+ /// allocator should be an arena allocator because Package never frees anything
+ pub fn create(allocator: *mem.Allocator, root_src_dir: []const u8, root_src_path: []const u8) !*Package {
+ return allocator.create(Package{
+ .root_src_dir = try Buffer.init(allocator, root_src_dir),
+ .root_src_path = try Buffer.init(allocator, root_src_path),
+ .table = Table.init(allocator),
+ });
+ }
+
+ pub fn add(self: *Package, name: []const u8, package: *Package) !void {
+ const entry = try self.table.put(try mem.dupe(self.table.allocator, u8, name), package);
+ assert(entry == null);
+ }
+};
diff --git a/src-self-hosted/parsed_file.zig b/src-self-hosted/parsed_file.zig
deleted file mode 100644
index d728c2fd18..0000000000
--- a/src-self-hosted/parsed_file.zig
+++ /dev/null
@@ -1,6 +0,0 @@
-const ast = @import("std").zig.ast;
-
-pub const ParsedFile = struct {
- tree: ast.Tree,
- realpath: []const u8,
-};
diff --git a/src-self-hosted/scope.zig b/src-self-hosted/scope.zig
index 4326617fa0..7a41083f44 100644
--- a/src-self-hosted/scope.zig
+++ b/src-self-hosted/scope.zig
@@ -8,6 +8,8 @@ const ast = std.zig.ast;
const Value = @import("value.zig").Value;
const ir = @import("ir.zig");
const Span = @import("errmsg.zig").Span;
+const assert = std.debug.assert;
+const event = std.event;
pub const Scope = struct {
id: Id,
@@ -23,7 +25,8 @@ pub const Scope = struct {
if (base.ref_count == 0) {
if (base.parent) |parent| parent.deref(comp);
switch (base.id) {
- Id.Decls => @fieldParentPtr(Decls, "base", base).destroy(),
+ Id.Root => @fieldParentPtr(Root, "base", base).destroy(comp),
+ Id.Decls => @fieldParentPtr(Decls, "base", base).destroy(comp),
Id.Block => @fieldParentPtr(Block, "base", base).destroy(comp),
Id.FnDef => @fieldParentPtr(FnDef, "base", base).destroy(comp),
Id.CompTime => @fieldParentPtr(CompTime, "base", base).destroy(comp),
@@ -33,6 +36,15 @@ pub const Scope = struct {
}
}
+ pub fn findRoot(base: *Scope) *Root {
+ var scope = base;
+ while (scope.parent) |parent| {
+ scope = parent;
+ }
+ assert(scope.id == Id.Root);
+ return @fieldParentPtr(Root, "base", scope);
+ }
+
pub fn findFnDef(base: *Scope) ?*FnDef {
var scope = base;
while (true) {
@@ -44,12 +56,33 @@ pub const Scope = struct {
Id.Defer,
Id.DeferExpr,
Id.CompTime,
+ Id.Root,
+ => scope = scope.parent orelse return null,
+ }
+ }
+ }
+
+ pub fn findDeferExpr(base: *Scope) ?*DeferExpr {
+ var scope = base;
+ while (true) {
+ switch (scope.id) {
+ Id.DeferExpr => return @fieldParentPtr(DeferExpr, "base", base),
+
+ Id.FnDef,
+ Id.Decls,
+ => return null,
+
+ Id.Block,
+ Id.Defer,
+ Id.CompTime,
+ Id.Root,
=> scope = scope.parent orelse return null,
}
}
}
pub const Id = enum {
+ Root,
Decls,
Block,
FnDef,
@@ -58,42 +91,82 @@ pub const Scope = struct {
DeferExpr,
};
+ pub const Root = struct {
+ base: Scope,
+ tree: *ast.Tree,
+ realpath: []const u8,
+
+ /// Creates a Root scope with 1 reference
+ /// Takes ownership of realpath
+ /// Takes ownership of tree, will deinit and destroy when done.
+ pub fn create(comp: *Compilation, tree: *ast.Tree, realpath: []u8) !*Root {
+ const self = try comp.gpa().create(Root{
+ .base = Scope{
+ .id = Id.Root,
+ .parent = null,
+ .ref_count = 1,
+ },
+ .tree = tree,
+ .realpath = realpath,
+ });
+ errdefer comp.gpa().destroy(self);
+
+ return self;
+ }
+
+ pub fn destroy(self: *Root, comp: *Compilation) void {
+ comp.gpa().free(self.tree.source);
+ self.tree.deinit();
+ comp.gpa().destroy(self.tree);
+ comp.gpa().free(self.realpath);
+ comp.gpa().destroy(self);
+ }
+ };
+
pub const Decls = struct {
base: Scope,
- table: Decl.Table,
+
+ /// The lock must be respected for writing. However once name_future resolves,
+ /// readers can freely access it.
+ table: event.Locked(Decl.Table),
+
+ /// Once this future is resolved, the table is complete and available for unlocked
+ /// read-only access. It does not mean all the decls are resolved; it means only that
+ /// the table has all the names. Each decl in the table has its own resolution state.
+ name_future: event.Future(void),
/// Creates a Decls scope with 1 reference
- pub fn create(comp: *Compilation, parent: ?*Scope) !*Decls {
- const self = try comp.a().create(Decls{
+ pub fn create(comp: *Compilation, parent: *Scope) !*Decls {
+ const self = try comp.gpa().create(Decls{
.base = Scope{
.id = Id.Decls,
.parent = parent,
.ref_count = 1,
},
- .table = undefined,
+ .table = event.Locked(Decl.Table).init(comp.loop, Decl.Table.init(comp.gpa())),
+ .name_future = event.Future(void).init(comp.loop),
});
- errdefer comp.a().destroy(self);
-
- self.table = Decl.Table.init(comp.a());
- errdefer self.table.deinit();
-
- if (parent) |p| p.ref();
-
+ parent.ref();
return self;
}
- pub fn destroy(self: *Decls) void {
+ pub fn destroy(self: *Decls, comp: *Compilation) void {
self.table.deinit();
- self.table.allocator.destroy(self);
+ comp.gpa().destroy(self);
+ }
+
+ pub async fn getTableReadOnly(self: *Decls) *Decl.Table {
+ _ = await (async self.name_future.get() catch unreachable);
+ return &self.table.private_data;
}
};
pub const Block = struct {
base: Scope,
- incoming_values: std.ArrayList(*ir.Instruction),
+ incoming_values: std.ArrayList(*ir.Inst),
incoming_blocks: std.ArrayList(*ir.BasicBlock),
end_block: *ir.BasicBlock,
- is_comptime: *ir.Instruction,
+ is_comptime: *ir.Inst,
safety: Safety,
@@ -125,8 +198,8 @@ pub const Scope = struct {
};
/// Creates a Block scope with 1 reference
- pub fn create(comp: *Compilation, parent: ?*Scope) !*Block {
- const self = try comp.a().create(Block{
+ pub fn create(comp: *Compilation, parent: *Scope) !*Block {
+ const self = try comp.gpa().create(Block{
.base = Scope{
.id = Id.Block,
.parent = parent,
@@ -138,14 +211,14 @@ pub const Scope = struct {
.is_comptime = undefined,
.safety = Safety.Auto,
});
- errdefer comp.a().destroy(self);
+ errdefer comp.gpa().destroy(self);
- if (parent) |p| p.ref();
+ parent.ref();
return self;
}
pub fn destroy(self: *Block, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
};
@@ -157,8 +230,8 @@ pub const Scope = struct {
/// Creates a FnDef scope with 1 reference
/// Must set the fn_val later
- pub fn create(comp: *Compilation, parent: ?*Scope) !*FnDef {
- const self = try comp.a().create(FnDef{
+ pub fn create(comp: *Compilation, parent: *Scope) !*FnDef {
+ const self = try comp.gpa().create(FnDef{
.base = Scope{
.id = Id.FnDef,
.parent = parent,
@@ -167,13 +240,13 @@ pub const Scope = struct {
.fn_val = undefined,
});
- if (parent) |p| p.ref();
+ parent.ref();
return self;
}
pub fn destroy(self: *FnDef, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
};
@@ -181,8 +254,8 @@ pub const Scope = struct {
base: Scope,
/// Creates a CompTime scope with 1 reference
- pub fn create(comp: *Compilation, parent: ?*Scope) !*CompTime {
- const self = try comp.a().create(CompTime{
+ pub fn create(comp: *Compilation, parent: *Scope) !*CompTime {
+ const self = try comp.gpa().create(CompTime{
.base = Scope{
.id = Id.CompTime,
.parent = parent,
@@ -190,12 +263,12 @@ pub const Scope = struct {
},
});
- if (parent) |p| p.ref();
+ parent.ref();
return self;
}
pub fn destroy(self: *CompTime, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
};
@@ -212,11 +285,11 @@ pub const Scope = struct {
/// Creates a Defer scope with 1 reference
pub fn create(
comp: *Compilation,
- parent: ?*Scope,
+ parent: *Scope,
kind: Kind,
defer_expr_scope: *DeferExpr,
) !*Defer {
- const self = try comp.a().create(Defer{
+ const self = try comp.gpa().create(Defer{
.base = Scope{
.id = Id.Defer,
.parent = parent,
@@ -225,42 +298,44 @@ pub const Scope = struct {
.defer_expr_scope = defer_expr_scope,
.kind = kind,
});
- errdefer comp.a().destroy(self);
+ errdefer comp.gpa().destroy(self);
defer_expr_scope.base.ref();
- if (parent) |p| p.ref();
+ parent.ref();
return self;
}
pub fn destroy(self: *Defer, comp: *Compilation) void {
self.defer_expr_scope.base.deref(comp);
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
};
pub const DeferExpr = struct {
base: Scope,
expr_node: *ast.Node,
+ reported_err: bool,
/// Creates a DeferExpr scope with 1 reference
- pub fn create(comp: *Compilation, parent: ?*Scope, expr_node: *ast.Node) !*DeferExpr {
- const self = try comp.a().create(DeferExpr{
+ pub fn create(comp: *Compilation, parent: *Scope, expr_node: *ast.Node) !*DeferExpr {
+ const self = try comp.gpa().create(DeferExpr{
.base = Scope{
.id = Id.DeferExpr,
.parent = parent,
.ref_count = 1,
},
.expr_node = expr_node,
+ .reported_err = false,
});
- errdefer comp.a().destroy(self);
+ errdefer comp.gpa().destroy(self);
- if (parent) |p| p.ref();
+ parent.ref();
return self;
}
pub fn destroy(self: *DeferExpr, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
};
};
diff --git a/src-self-hosted/target.zig b/src-self-hosted/target.zig
index 724d99ea23..0cc8d02a62 100644
--- a/src-self-hosted/target.zig
+++ b/src-self-hosted/target.zig
@@ -1,60 +1,562 @@
+const std = @import("std");
const builtin = @import("builtin");
-const c = @import("c.zig");
+const llvm = @import("llvm.zig");
+const CInt = @import("c_int.zig").CInt;
-pub const CrossTarget = struct {
- arch: builtin.Arch,
- os: builtin.Os,
- environ: builtin.Environ,
+pub const FloatAbi = enum {
+ Hard,
+ Soft,
+ SoftFp,
};
pub const Target = union(enum) {
Native,
- Cross: CrossTarget,
+ Cross: Cross,
- pub fn oFileExt(self: *const Target) []const u8 {
- const environ = switch (self.*) {
- Target.Native => builtin.environ,
- Target.Cross => |t| t.environ,
- };
- return switch (environ) {
- builtin.Environ.msvc => ".obj",
+ pub const Cross = struct {
+ arch: builtin.Arch,
+ os: builtin.Os,
+ environ: builtin.Environ,
+ object_format: builtin.ObjectFormat,
+ };
+
+ pub fn objFileExt(self: Target) []const u8 {
+ return switch (self.getObjectFormat()) {
+ builtin.ObjectFormat.coff => ".obj",
else => ".o",
};
}
- pub fn exeFileExt(self: *const Target) []const u8 {
+ pub fn exeFileExt(self: Target) []const u8 {
return switch (self.getOs()) {
builtin.Os.windows => ".exe",
else => "",
};
}
- pub fn getOs(self: *const Target) builtin.Os {
- return switch (self.*) {
- Target.Native => builtin.os,
- Target.Cross => |t| t.os,
+ pub fn libFileExt(self: Target, is_static: bool) []const u8 {
+ return switch (self.getOs()) {
+ builtin.Os.windows => if (is_static) ".lib" else ".dll",
+ else => if (is_static) ".a" else ".so",
};
}
- pub fn isDarwin(self: *const Target) bool {
+ pub fn getOs(self: Target) builtin.Os {
+ return switch (self) {
+ Target.Native => builtin.os,
+ @TagType(Target).Cross => |t| t.os,
+ };
+ }
+
+ pub fn getArch(self: Target) builtin.Arch {
+ return switch (self) {
+ Target.Native => builtin.arch,
+ @TagType(Target).Cross => |t| t.arch,
+ };
+ }
+
+ pub fn getEnviron(self: Target) builtin.Environ {
+ return switch (self) {
+ Target.Native => builtin.environ,
+ @TagType(Target).Cross => |t| t.environ,
+ };
+ }
+
+ pub fn getObjectFormat(self: Target) builtin.ObjectFormat {
+ return switch (self) {
+ Target.Native => builtin.object_format,
+ @TagType(Target).Cross => |t| t.object_format,
+ };
+ }
+
+ pub fn isWasm(self: Target) bool {
+ return switch (self.getArch()) {
+ builtin.Arch.wasm32, builtin.Arch.wasm64 => true,
+ else => false,
+ };
+ }
+
+ pub fn isDarwin(self: Target) bool {
return switch (self.getOs()) {
builtin.Os.ios, builtin.Os.macosx => true,
else => false,
};
}
- pub fn isWindows(self: *const Target) bool {
+ pub fn isWindows(self: Target) bool {
return switch (self.getOs()) {
builtin.Os.windows => true,
else => false,
};
}
-};
-pub fn initializeAll() void {
- c.LLVMInitializeAllTargets();
- c.LLVMInitializeAllTargetInfos();
- c.LLVMInitializeAllTargetMCs();
- c.LLVMInitializeAllAsmPrinters();
- c.LLVMInitializeAllAsmParsers();
-}
+ /// TODO expose the arch and subarch separately
+ pub fn isArmOrThumb(self: Target) bool {
+ return switch (self.getArch()) {
+ builtin.Arch.armv8_3a,
+ builtin.Arch.armv8_2a,
+ builtin.Arch.armv8_1a,
+ builtin.Arch.armv8,
+ builtin.Arch.armv8r,
+ builtin.Arch.armv8m_baseline,
+ builtin.Arch.armv8m_mainline,
+ builtin.Arch.armv7,
+ builtin.Arch.armv7em,
+ builtin.Arch.armv7m,
+ builtin.Arch.armv7s,
+ builtin.Arch.armv7k,
+ builtin.Arch.armv7ve,
+ builtin.Arch.armv6,
+ builtin.Arch.armv6m,
+ builtin.Arch.armv6k,
+ builtin.Arch.armv6t2,
+ builtin.Arch.armv5,
+ builtin.Arch.armv5te,
+ builtin.Arch.armv4t,
+ builtin.Arch.armebv8_3a,
+ builtin.Arch.armebv8_2a,
+ builtin.Arch.armebv8_1a,
+ builtin.Arch.armebv8,
+ builtin.Arch.armebv8r,
+ builtin.Arch.armebv8m_baseline,
+ builtin.Arch.armebv8m_mainline,
+ builtin.Arch.armebv7,
+ builtin.Arch.armebv7em,
+ builtin.Arch.armebv7m,
+ builtin.Arch.armebv7s,
+ builtin.Arch.armebv7k,
+ builtin.Arch.armebv7ve,
+ builtin.Arch.armebv6,
+ builtin.Arch.armebv6m,
+ builtin.Arch.armebv6k,
+ builtin.Arch.armebv6t2,
+ builtin.Arch.armebv5,
+ builtin.Arch.armebv5te,
+ builtin.Arch.armebv4t,
+ builtin.Arch.thumb,
+ builtin.Arch.thumbeb,
+ => true,
+ else => false,
+ };
+ }
+
+ pub fn initializeAll() void {
+ llvm.InitializeAllTargets();
+ llvm.InitializeAllTargetInfos();
+ llvm.InitializeAllTargetMCs();
+ llvm.InitializeAllAsmPrinters();
+ llvm.InitializeAllAsmParsers();
+ }
+
+ pub fn getTriple(self: Target, allocator: *std.mem.Allocator) !std.Buffer {
+ var result = try std.Buffer.initSize(allocator, 0);
+ errdefer result.deinit();
+
+ // LLVM WebAssembly output support requires the target to be activated at
+ // build type with -DCMAKE_LLVM_EXPIERMENTAL_TARGETS_TO_BUILD=WebAssembly.
+ //
+ // LLVM determines the output format based on the environment suffix,
+ // defaulting to an object based on the architecture. The default format in
+ // LLVM 6 sets the wasm arch output incorrectly to ELF. We need to
+ // explicitly set this ourself in order for it to work.
+ //
+ // This is fixed in LLVM 7 and you will be able to get wasm output by
+ // using the target triple `wasm32-unknown-unknown-unknown`.
+ const env_name = if (self.isWasm()) "wasm" else @tagName(self.getEnviron());
+
+ var out = &std.io.BufferOutStream.init(&result).stream;
+ try out.print("{}-unknown-{}-{}", @tagName(self.getArch()), @tagName(self.getOs()), env_name);
+
+ return result;
+ }
+
+ pub fn is64bit(self: Target) bool {
+ return self.getArchPtrBitWidth() == 64;
+ }
+
+ pub fn getArchPtrBitWidth(self: Target) u32 {
+ switch (self.getArch()) {
+ builtin.Arch.avr,
+ builtin.Arch.msp430,
+ => return 16,
+
+ builtin.Arch.arc,
+ builtin.Arch.armv8_3a,
+ builtin.Arch.armv8_2a,
+ builtin.Arch.armv8_1a,
+ builtin.Arch.armv8,
+ builtin.Arch.armv8r,
+ builtin.Arch.armv8m_baseline,
+ builtin.Arch.armv8m_mainline,
+ builtin.Arch.armv7,
+ builtin.Arch.armv7em,
+ builtin.Arch.armv7m,
+ builtin.Arch.armv7s,
+ builtin.Arch.armv7k,
+ builtin.Arch.armv7ve,
+ builtin.Arch.armv6,
+ builtin.Arch.armv6m,
+ builtin.Arch.armv6k,
+ builtin.Arch.armv6t2,
+ builtin.Arch.armv5,
+ builtin.Arch.armv5te,
+ builtin.Arch.armv4t,
+ builtin.Arch.armebv8_3a,
+ builtin.Arch.armebv8_2a,
+ builtin.Arch.armebv8_1a,
+ builtin.Arch.armebv8,
+ builtin.Arch.armebv8r,
+ builtin.Arch.armebv8m_baseline,
+ builtin.Arch.armebv8m_mainline,
+ builtin.Arch.armebv7,
+ builtin.Arch.armebv7em,
+ builtin.Arch.armebv7m,
+ builtin.Arch.armebv7s,
+ builtin.Arch.armebv7k,
+ builtin.Arch.armebv7ve,
+ builtin.Arch.armebv6,
+ builtin.Arch.armebv6m,
+ builtin.Arch.armebv6k,
+ builtin.Arch.armebv6t2,
+ builtin.Arch.armebv5,
+ builtin.Arch.armebv5te,
+ builtin.Arch.armebv4t,
+ builtin.Arch.hexagon,
+ builtin.Arch.le32,
+ builtin.Arch.mips,
+ builtin.Arch.mipsel,
+ builtin.Arch.nios2,
+ builtin.Arch.powerpc,
+ builtin.Arch.r600,
+ builtin.Arch.riscv32,
+ builtin.Arch.sparc,
+ builtin.Arch.sparcel,
+ builtin.Arch.tce,
+ builtin.Arch.tcele,
+ builtin.Arch.thumb,
+ builtin.Arch.thumbeb,
+ builtin.Arch.i386,
+ builtin.Arch.xcore,
+ builtin.Arch.nvptx,
+ builtin.Arch.amdil,
+ builtin.Arch.hsail,
+ builtin.Arch.spir,
+ builtin.Arch.kalimbav3,
+ builtin.Arch.kalimbav4,
+ builtin.Arch.kalimbav5,
+ builtin.Arch.shave,
+ builtin.Arch.lanai,
+ builtin.Arch.wasm32,
+ builtin.Arch.renderscript32,
+ => return 32,
+
+ builtin.Arch.aarch64,
+ builtin.Arch.aarch64_be,
+ builtin.Arch.mips64,
+ builtin.Arch.mips64el,
+ builtin.Arch.powerpc64,
+ builtin.Arch.powerpc64le,
+ builtin.Arch.riscv64,
+ builtin.Arch.x86_64,
+ builtin.Arch.nvptx64,
+ builtin.Arch.le64,
+ builtin.Arch.amdil64,
+ builtin.Arch.hsail64,
+ builtin.Arch.spir64,
+ builtin.Arch.wasm64,
+ builtin.Arch.renderscript64,
+ builtin.Arch.amdgcn,
+ builtin.Arch.bpfel,
+ builtin.Arch.bpfeb,
+ builtin.Arch.sparcv9,
+ builtin.Arch.s390x,
+ => return 64,
+ }
+ }
+
+ pub fn getFloatAbi(self: Target) FloatAbi {
+ return switch (self.getEnviron()) {
+ builtin.Environ.gnueabihf,
+ builtin.Environ.eabihf,
+ builtin.Environ.musleabihf,
+ => FloatAbi.Hard,
+ else => FloatAbi.Soft,
+ };
+ }
+
+ pub fn getDynamicLinkerPath(self: Target) ?[]const u8 {
+ const env = self.getEnviron();
+ const arch = self.getArch();
+ switch (env) {
+ builtin.Environ.android => {
+ if (self.is64bit()) {
+ return "/system/bin/linker64";
+ } else {
+ return "/system/bin/linker";
+ }
+ },
+ builtin.Environ.gnux32 => {
+ if (arch == builtin.Arch.x86_64) {
+ return "/libx32/ld-linux-x32.so.2";
+ }
+ },
+ builtin.Environ.musl,
+ builtin.Environ.musleabi,
+ builtin.Environ.musleabihf,
+ => {
+ if (arch == builtin.Arch.x86_64) {
+ return "/lib/ld-musl-x86_64.so.1";
+ }
+ },
+ else => {},
+ }
+ switch (arch) {
+ builtin.Arch.i386,
+ builtin.Arch.sparc,
+ builtin.Arch.sparcel,
+ => return "/lib/ld-linux.so.2",
+
+ builtin.Arch.aarch64 => return "/lib/ld-linux-aarch64.so.1",
+ builtin.Arch.aarch64_be => return "/lib/ld-linux-aarch64_be.so.1",
+
+ builtin.Arch.armv8_3a,
+ builtin.Arch.armv8_2a,
+ builtin.Arch.armv8_1a,
+ builtin.Arch.armv8,
+ builtin.Arch.armv8r,
+ builtin.Arch.armv8m_baseline,
+ builtin.Arch.armv8m_mainline,
+ builtin.Arch.armv7,
+ builtin.Arch.armv7em,
+ builtin.Arch.armv7m,
+ builtin.Arch.armv7s,
+ builtin.Arch.armv7k,
+ builtin.Arch.armv7ve,
+ builtin.Arch.armv6,
+ builtin.Arch.armv6m,
+ builtin.Arch.armv6k,
+ builtin.Arch.armv6t2,
+ builtin.Arch.armv5,
+ builtin.Arch.armv5te,
+ builtin.Arch.armv4t,
+ builtin.Arch.thumb,
+ => return switch (self.getFloatAbi()) {
+ FloatAbi.Hard => return "/lib/ld-linux-armhf.so.3",
+ else => return "/lib/ld-linux.so.3",
+ },
+
+ builtin.Arch.armebv8_3a,
+ builtin.Arch.armebv8_2a,
+ builtin.Arch.armebv8_1a,
+ builtin.Arch.armebv8,
+ builtin.Arch.armebv8r,
+ builtin.Arch.armebv8m_baseline,
+ builtin.Arch.armebv8m_mainline,
+ builtin.Arch.armebv7,
+ builtin.Arch.armebv7em,
+ builtin.Arch.armebv7m,
+ builtin.Arch.armebv7s,
+ builtin.Arch.armebv7k,
+ builtin.Arch.armebv7ve,
+ builtin.Arch.armebv6,
+ builtin.Arch.armebv6m,
+ builtin.Arch.armebv6k,
+ builtin.Arch.armebv6t2,
+ builtin.Arch.armebv5,
+ builtin.Arch.armebv5te,
+ builtin.Arch.armebv4t,
+ builtin.Arch.thumbeb,
+ => return switch (self.getFloatAbi()) {
+ FloatAbi.Hard => return "/lib/ld-linux-armhf.so.3",
+ else => return "/lib/ld-linux.so.3",
+ },
+
+ builtin.Arch.mips,
+ builtin.Arch.mipsel,
+ builtin.Arch.mips64,
+ builtin.Arch.mips64el,
+ => return null,
+
+ builtin.Arch.powerpc => return "/lib/ld.so.1",
+ builtin.Arch.powerpc64 => return "/lib64/ld64.so.2",
+ builtin.Arch.powerpc64le => return "/lib64/ld64.so.2",
+ builtin.Arch.s390x => return "/lib64/ld64.so.1",
+ builtin.Arch.sparcv9 => return "/lib64/ld-linux.so.2",
+ builtin.Arch.x86_64 => return "/lib64/ld-linux-x86-64.so.2",
+
+ builtin.Arch.arc,
+ builtin.Arch.avr,
+ builtin.Arch.bpfel,
+ builtin.Arch.bpfeb,
+ builtin.Arch.hexagon,
+ builtin.Arch.msp430,
+ builtin.Arch.nios2,
+ builtin.Arch.r600,
+ builtin.Arch.amdgcn,
+ builtin.Arch.riscv32,
+ builtin.Arch.riscv64,
+ builtin.Arch.tce,
+ builtin.Arch.tcele,
+ builtin.Arch.xcore,
+ builtin.Arch.nvptx,
+ builtin.Arch.nvptx64,
+ builtin.Arch.le32,
+ builtin.Arch.le64,
+ builtin.Arch.amdil,
+ builtin.Arch.amdil64,
+ builtin.Arch.hsail,
+ builtin.Arch.hsail64,
+ builtin.Arch.spir,
+ builtin.Arch.spir64,
+ builtin.Arch.kalimbav3,
+ builtin.Arch.kalimbav4,
+ builtin.Arch.kalimbav5,
+ builtin.Arch.shave,
+ builtin.Arch.lanai,
+ builtin.Arch.wasm32,
+ builtin.Arch.wasm64,
+ builtin.Arch.renderscript32,
+ builtin.Arch.renderscript64,
+ => return null,
+ }
+ }
+
+ pub fn llvmTargetFromTriple(triple: std.Buffer) !llvm.TargetRef {
+ var result: llvm.TargetRef = undefined;
+ var err_msg: [*]u8 = undefined;
+ if (llvm.GetTargetFromTriple(triple.ptr(), &result, &err_msg) != 0) {
+ std.debug.warn("triple: {s} error: {s}\n", triple.ptr(), err_msg);
+ return error.UnsupportedTarget;
+ }
+ return result;
+ }
+
+ pub fn cIntTypeSizeInBits(self: Target, id: CInt.Id) u32 {
+ const arch = self.getArch();
+ switch (self.getOs()) {
+ builtin.Os.freestanding => switch (self.getArch()) {
+ builtin.Arch.msp430 => switch (id) {
+ CInt.Id.Short,
+ CInt.Id.UShort,
+ CInt.Id.Int,
+ CInt.Id.UInt,
+ => return 16,
+ CInt.Id.Long,
+ CInt.Id.ULong,
+ => return 32,
+ CInt.Id.LongLong,
+ CInt.Id.ULongLong,
+ => return 64,
+ },
+ else => switch (id) {
+ CInt.Id.Short,
+ CInt.Id.UShort,
+ => return 16,
+ CInt.Id.Int,
+ CInt.Id.UInt,
+ => return 32,
+ CInt.Id.Long,
+ CInt.Id.ULong,
+ => return self.getArchPtrBitWidth(),
+ CInt.Id.LongLong,
+ CInt.Id.ULongLong,
+ => return 64,
+ },
+ },
+
+ builtin.Os.linux,
+ builtin.Os.macosx,
+ builtin.Os.openbsd,
+ builtin.Os.zen,
+ => switch (id) {
+ CInt.Id.Short,
+ CInt.Id.UShort,
+ => return 16,
+ CInt.Id.Int,
+ CInt.Id.UInt,
+ => return 32,
+ CInt.Id.Long,
+ CInt.Id.ULong,
+ => return self.getArchPtrBitWidth(),
+ CInt.Id.LongLong,
+ CInt.Id.ULongLong,
+ => return 64,
+ },
+
+ builtin.Os.windows => switch (id) {
+ CInt.Id.Short,
+ CInt.Id.UShort,
+ => return 16,
+ CInt.Id.Int,
+ CInt.Id.UInt,
+ => return 32,
+ CInt.Id.Long,
+ CInt.Id.ULong,
+ CInt.Id.LongLong,
+ CInt.Id.ULongLong,
+ => return 64,
+ },
+
+ builtin.Os.ananas,
+ builtin.Os.cloudabi,
+ builtin.Os.dragonfly,
+ builtin.Os.freebsd,
+ builtin.Os.fuchsia,
+ builtin.Os.ios,
+ builtin.Os.kfreebsd,
+ builtin.Os.lv2,
+ builtin.Os.netbsd,
+ builtin.Os.solaris,
+ builtin.Os.haiku,
+ builtin.Os.minix,
+ builtin.Os.rtems,
+ builtin.Os.nacl,
+ builtin.Os.cnk,
+ builtin.Os.aix,
+ builtin.Os.cuda,
+ builtin.Os.nvcl,
+ builtin.Os.amdhsa,
+ builtin.Os.ps4,
+ builtin.Os.elfiamcu,
+ builtin.Os.tvos,
+ builtin.Os.watchos,
+ builtin.Os.mesa3d,
+ builtin.Os.contiki,
+ builtin.Os.amdpal,
+ => @panic("TODO specify the C integer type sizes for this OS"),
+ }
+ }
+
+ pub fn getDarwinArchString(self: Target) []const u8 {
+ const arch = self.getArch();
+ switch (arch) {
+ builtin.Arch.aarch64 => return "arm64",
+ builtin.Arch.thumb,
+ builtin.Arch.armv8_3a,
+ builtin.Arch.armv8_2a,
+ builtin.Arch.armv8_1a,
+ builtin.Arch.armv8,
+ builtin.Arch.armv8r,
+ builtin.Arch.armv8m_baseline,
+ builtin.Arch.armv8m_mainline,
+ builtin.Arch.armv7,
+ builtin.Arch.armv7em,
+ builtin.Arch.armv7m,
+ builtin.Arch.armv7s,
+ builtin.Arch.armv7k,
+ builtin.Arch.armv7ve,
+ builtin.Arch.armv6,
+ builtin.Arch.armv6m,
+ builtin.Arch.armv6k,
+ builtin.Arch.armv6t2,
+ builtin.Arch.armv5,
+ builtin.Arch.armv5te,
+ builtin.Arch.armv4t,
+ => return "arm",
+ builtin.Arch.powerpc => return "ppc",
+ builtin.Arch.powerpc64 => return "ppc64",
+ builtin.Arch.powerpc64le => return "ppc64le",
+ else => return @tagName(arch),
+ }
+ }
+};
diff --git a/src-self-hosted/test.zig b/src-self-hosted/test.zig
index 3edb267ca9..47e45d1bb0 100644
--- a/src-self-hosted/test.zig
+++ b/src-self-hosted/test.zig
@@ -8,12 +8,14 @@ const assertOrPanic = std.debug.assertOrPanic;
const errmsg = @import("errmsg.zig");
const EventLoopLocal = @import("compilation.zig").EventLoopLocal;
-test "compile errors" {
- var ctx: TestContext = undefined;
+var ctx: TestContext = undefined;
+
+test "stage2" {
try ctx.init();
defer ctx.deinit();
try @import("../test/stage2/compile_errors.zig").addCases(&ctx);
+ try @import("../test/stage2/compare_output.zig").addCases(&ctx);
try ctx.run();
}
@@ -25,7 +27,6 @@ pub const TestContext = struct {
loop: std.event.Loop,
event_loop_local: EventLoopLocal,
zig_lib_dir: []u8,
- zig_cache_dir: []u8,
file_index: std.atomic.Int(usize),
group: std.event.Group(error!void),
any_err: error!void,
@@ -38,7 +39,6 @@ pub const TestContext = struct {
.loop = undefined,
.event_loop_local = undefined,
.zig_lib_dir = undefined,
- .zig_cache_dir = undefined,
.group = undefined,
.file_index = std.atomic.Int(usize).init(0),
};
@@ -46,7 +46,7 @@ pub const TestContext = struct {
try self.loop.initMultiThreaded(allocator);
errdefer self.loop.deinit();
- self.event_loop_local = EventLoopLocal.init(&self.loop);
+ self.event_loop_local = try EventLoopLocal.init(&self.loop);
errdefer self.event_loop_local.deinit();
self.group = std.event.Group(error!void).init(&self.loop);
@@ -55,16 +55,12 @@ pub const TestContext = struct {
self.zig_lib_dir = try introspect.resolveZigLibDir(allocator);
errdefer allocator.free(self.zig_lib_dir);
- self.zig_cache_dir = try introspect.resolveZigCacheDir(allocator);
- errdefer allocator.free(self.zig_cache_dir);
-
try std.os.makePath(allocator, tmp_dir_name);
errdefer std.os.deleteTree(allocator, tmp_dir_name) catch {};
}
fn deinit(self: *TestContext) void {
std.os.deleteTree(allocator, tmp_dir_name) catch {};
- allocator.free(self.zig_cache_dir);
allocator.free(self.zig_lib_dir);
self.event_loop_local.deinit();
self.loop.deinit();
@@ -107,8 +103,8 @@ pub const TestContext = struct {
Target.Native,
Compilation.Kind.Obj,
builtin.Mode.Debug,
+ true, // is_static
self.zig_lib_dir,
- self.zig_cache_dir,
);
errdefer comp.destroy();
@@ -117,6 +113,84 @@ pub const TestContext = struct {
try self.group.call(getModuleEvent, comp, source, path, line, column, msg);
}
+ fn testCompareOutputLibC(
+ self: *TestContext,
+ source: []const u8,
+ expected_output: []const u8,
+ ) !void {
+ var file_index_buf: [20]u8 = undefined;
+ const file_index = try std.fmt.bufPrint(file_index_buf[0..], "{}", self.file_index.incr());
+ const file1_path = try std.os.path.join(allocator, tmp_dir_name, file_index, file1);
+
+ const output_file = try std.fmt.allocPrint(allocator, "{}-out{}", file1_path, Target(Target.Native).exeFileExt());
+ if (std.os.path.dirname(file1_path)) |dirname| {
+ try std.os.makePath(allocator, dirname);
+ }
+
+ // TODO async I/O
+ try std.io.writeFile(allocator, file1_path, source);
+
+ var comp = try Compilation.create(
+ &self.event_loop_local,
+ "test",
+ file1_path,
+ Target.Native,
+ Compilation.Kind.Exe,
+ builtin.Mode.Debug,
+ false,
+ self.zig_lib_dir,
+ );
+ errdefer comp.destroy();
+
+ _ = try comp.addLinkLib("c", true);
+ comp.link_out_file = output_file;
+ try comp.build();
+
+ try self.group.call(getModuleEventSuccess, comp, output_file, expected_output);
+ }
+
+ async fn getModuleEventSuccess(
+ comp: *Compilation,
+ exe_file: []const u8,
+ expected_output: []const u8,
+ ) !void {
+ // TODO this should not be necessary
+ const exe_file_2 = try std.mem.dupe(allocator, u8, exe_file);
+
+ defer comp.destroy();
+ const build_event = await (async comp.events.get() catch unreachable);
+
+ switch (build_event) {
+ Compilation.Event.Ok => {
+ const argv = []const []const u8{exe_file_2};
+ // TODO use event loop
+ const child = try std.os.ChildProcess.exec(allocator, argv, null, null, 1024 * 1024);
+ switch (child.term) {
+ std.os.ChildProcess.Term.Exited => |code| {
+ if (code != 0) {
+ return error.BadReturnCode;
+ }
+ },
+ else => {
+ return error.Crashed;
+ },
+ }
+ if (!mem.eql(u8, child.stdout, expected_output)) {
+ return error.OutputMismatch;
+ }
+ },
+ Compilation.Event.Error => |err| return err,
+ Compilation.Event.Fail => |msgs| {
+ var stderr = try std.io.getStdErr();
+ try stderr.write("build incorrectly failed:\n");
+ for (msgs) |msg| {
+ defer msg.destroy();
+ try msg.printToFile(&stderr, errmsg.Color.Auto);
+ }
+ },
+ }
+ }
+
async fn getModuleEvent(
comp: *Compilation,
source: []const u8,
@@ -138,10 +212,10 @@ pub const TestContext = struct {
Compilation.Event.Fail => |msgs| {
assertOrPanic(msgs.len != 0);
for (msgs) |msg| {
- if (mem.endsWith(u8, msg.path, path) and mem.eql(u8, msg.text, text)) {
- const first_token = msg.tree.tokens.at(msg.span.first);
- const last_token = msg.tree.tokens.at(msg.span.first);
- const start_loc = msg.tree.tokenLocationPtr(0, first_token);
+ if (mem.endsWith(u8, msg.getRealPath(), path) and mem.eql(u8, msg.text, text)) {
+ const first_token = msg.getTree().tokens.at(msg.span.first);
+ const last_token = msg.getTree().tokens.at(msg.span.first);
+ const start_loc = msg.getTree().tokenLocationPtr(0, first_token);
if (start_loc.line + 1 == line and start_loc.column + 1 == column) {
return;
}
@@ -158,7 +232,8 @@ pub const TestContext = struct {
std.debug.warn("\n====found:========\n");
var stderr = try std.io.getStdErr();
for (msgs) |msg| {
- try errmsg.printToFile(&stderr, msg, errmsg.Color.Auto);
+ defer msg.destroy();
+ try msg.printToFile(&stderr, errmsg.Color.Auto);
}
std.debug.warn("============\n");
return error.TestFailed;
diff --git a/src-self-hosted/type.zig b/src-self-hosted/type.zig
index 670547cce2..217c1d50a7 100644
--- a/src-self-hosted/type.zig
+++ b/src-self-hosted/type.zig
@@ -4,11 +4,17 @@ const Scope = @import("scope.zig").Scope;
const Compilation = @import("compilation.zig").Compilation;
const Value = @import("value.zig").Value;
const llvm = @import("llvm.zig");
-const ObjectFile = @import("codegen.zig").ObjectFile;
+const event = std.event;
+const Allocator = std.mem.Allocator;
+const assert = std.debug.assert;
pub const Type = struct {
base: Value,
id: Id,
+ name: []const u8,
+ abi_alignment: AbiAlignment,
+
+ pub const AbiAlignment = event.Future(error{OutOfMemory}!u32);
pub const Id = builtin.TypeId;
@@ -42,33 +48,37 @@ pub const Type = struct {
}
}
- pub fn getLlvmType(base: *Type, ofile: *ObjectFile) (error{OutOfMemory}!llvm.TypeRef) {
+ pub fn getLlvmType(
+ base: *Type,
+ allocator: *Allocator,
+ llvm_context: llvm.ContextRef,
+ ) (error{OutOfMemory}!llvm.TypeRef) {
switch (base.id) {
- Id.Struct => return @fieldParentPtr(Struct, "base", base).getLlvmType(ofile),
- Id.Fn => return @fieldParentPtr(Fn, "base", base).getLlvmType(ofile),
+ Id.Struct => return @fieldParentPtr(Struct, "base", base).getLlvmType(allocator, llvm_context),
+ Id.Fn => return @fieldParentPtr(Fn, "base", base).getLlvmType(allocator, llvm_context),
Id.Type => unreachable,
Id.Void => unreachable,
- Id.Bool => return @fieldParentPtr(Bool, "base", base).getLlvmType(ofile),
+ Id.Bool => return @fieldParentPtr(Bool, "base", base).getLlvmType(allocator, llvm_context),
Id.NoReturn => unreachable,
- Id.Int => return @fieldParentPtr(Int, "base", base).getLlvmType(ofile),
- Id.Float => return @fieldParentPtr(Float, "base", base).getLlvmType(ofile),
- Id.Pointer => return @fieldParentPtr(Pointer, "base", base).getLlvmType(ofile),
- Id.Array => return @fieldParentPtr(Array, "base", base).getLlvmType(ofile),
+ Id.Int => return @fieldParentPtr(Int, "base", base).getLlvmType(allocator, llvm_context),
+ Id.Float => return @fieldParentPtr(Float, "base", base).getLlvmType(allocator, llvm_context),
+ Id.Pointer => return @fieldParentPtr(Pointer, "base", base).getLlvmType(allocator, llvm_context),
+ Id.Array => return @fieldParentPtr(Array, "base", base).getLlvmType(allocator, llvm_context),
Id.ComptimeFloat => unreachable,
Id.ComptimeInt => unreachable,
Id.Undefined => unreachable,
Id.Null => unreachable,
- Id.Optional => return @fieldParentPtr(Optional, "base", base).getLlvmType(ofile),
- Id.ErrorUnion => return @fieldParentPtr(ErrorUnion, "base", base).getLlvmType(ofile),
- Id.ErrorSet => return @fieldParentPtr(ErrorSet, "base", base).getLlvmType(ofile),
- Id.Enum => return @fieldParentPtr(Enum, "base", base).getLlvmType(ofile),
- Id.Union => return @fieldParentPtr(Union, "base", base).getLlvmType(ofile),
+ Id.Optional => return @fieldParentPtr(Optional, "base", base).getLlvmType(allocator, llvm_context),
+ Id.ErrorUnion => return @fieldParentPtr(ErrorUnion, "base", base).getLlvmType(allocator, llvm_context),
+ Id.ErrorSet => return @fieldParentPtr(ErrorSet, "base", base).getLlvmType(allocator, llvm_context),
+ Id.Enum => return @fieldParentPtr(Enum, "base", base).getLlvmType(allocator, llvm_context),
+ Id.Union => return @fieldParentPtr(Union, "base", base).getLlvmType(allocator, llvm_context),
Id.Namespace => unreachable,
Id.Block => unreachable,
- Id.BoundFn => return @fieldParentPtr(BoundFn, "base", base).getLlvmType(ofile),
+ Id.BoundFn => return @fieldParentPtr(BoundFn, "base", base).getLlvmType(allocator, llvm_context),
Id.ArgTuple => unreachable,
- Id.Opaque => return @fieldParentPtr(Opaque, "base", base).getLlvmType(ofile),
- Id.Promise => return @fieldParentPtr(Promise, "base", base).getLlvmType(ofile),
+ Id.Opaque => return @fieldParentPtr(Opaque, "base", base).getLlvmType(allocator, llvm_context),
+ Id.Promise => return @fieldParentPtr(Promise, "base", base).getLlvmType(allocator, llvm_context),
}
}
@@ -151,8 +161,49 @@ pub const Type = struct {
std.debug.warn("{}", @tagName(base.id));
}
- pub fn getAbiAlignment(base: *Type, comp: *Compilation) u32 {
- @panic("TODO getAbiAlignment");
+ fn init(base: *Type, comp: *Compilation, id: Id, name: []const u8) void {
+ base.* = Type{
+ .base = Value{
+ .id = Value.Id.Type,
+ .typ = &MetaType.get(comp).base,
+ .ref_count = std.atomic.Int(usize).init(1),
+ },
+ .id = id,
+ .name = name,
+ .abi_alignment = AbiAlignment.init(comp.loop),
+ };
+ }
+
+ /// If you happen to have an llvm context handy, use getAbiAlignmentInContext instead.
+ /// Otherwise, this one will grab one from the pool and then release it.
+ pub async fn getAbiAlignment(base: *Type, comp: *Compilation) !u32 {
+ if (await (async base.abi_alignment.start() catch unreachable)) |ptr| return ptr.*;
+
+ {
+ const held = try comp.event_loop_local.getAnyLlvmContext();
+ defer held.release(comp.event_loop_local);
+
+ const llvm_context = held.node.data;
+
+ base.abi_alignment.data = await (async base.resolveAbiAlignment(comp, llvm_context) catch unreachable);
+ }
+ base.abi_alignment.resolve();
+ return base.abi_alignment.data;
+ }
+
+ /// If you have an llvm conext handy, you can use it here.
+ pub async fn getAbiAlignmentInContext(base: *Type, comp: *Compilation, llvm_context: llvm.ContextRef) !u32 {
+ if (await (async base.abi_alignment.start() catch unreachable)) |ptr| return ptr.*;
+
+ base.abi_alignment.data = await (async base.resolveAbiAlignment(comp, llvm_context) catch unreachable);
+ base.abi_alignment.resolve();
+ return base.abi_alignment.data;
+ }
+
+ /// Lower level function that does the work. See getAbiAlignment.
+ async fn resolveAbiAlignment(base: *Type, comp: *Compilation, llvm_context: llvm.ContextRef) !u32 {
+ const llvm_type = try base.getLlvmType(comp.gpa(), llvm_context);
+ return @intCast(u32, llvm.ABIAlignmentOfType(comp.target_data_ref, llvm_type));
}
pub const Struct = struct {
@@ -160,10 +211,10 @@ pub const Type = struct {
decls: *Scope.Decls,
pub fn destroy(self: *Struct, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
- pub fn getLlvmType(self: *Struct, ofile: *ObjectFile) llvm.TypeRef {
+ pub fn getLlvmType(self: *Struct, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef {
@panic("TODO");
}
};
@@ -176,28 +227,23 @@ pub const Type = struct {
pub const Param = struct {
is_noalias: bool,
- typeof: *Type,
+ typ: *Type,
};
pub fn create(comp: *Compilation, return_type: *Type, params: []Param, is_var_args: bool) !*Fn {
- const result = try comp.a().create(Fn{
- .base = Type{
- .base = Value{
- .id = Value.Id.Type,
- .typeof = &MetaType.get(comp).base,
- .ref_count = std.atomic.Int(usize).init(1),
- },
- .id = builtin.TypeId.Fn,
- },
+ const result = try comp.gpa().create(Fn{
+ .base = undefined,
.return_type = return_type,
.params = params,
.is_var_args = is_var_args,
});
- errdefer comp.a().destroy(result);
+ errdefer comp.gpa().destroy(result);
+
+ result.base.init(comp, Id.Fn, "TODO fn type name");
result.return_type.base.ref();
for (result.params) |param| {
- param.typeof.base.ref();
+ param.typ.base.ref();
}
return result;
}
@@ -205,20 +251,20 @@ pub const Type = struct {
pub fn destroy(self: *Fn, comp: *Compilation) void {
self.return_type.base.deref(comp);
for (self.params) |param| {
- param.typeof.base.deref(comp);
+ param.typ.base.deref(comp);
}
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
- pub fn getLlvmType(self: *Fn, ofile: *ObjectFile) !llvm.TypeRef {
+ pub fn getLlvmType(self: *Fn, allocator: *Allocator, llvm_context: llvm.ContextRef) !llvm.TypeRef {
const llvm_return_type = switch (self.return_type.id) {
- Type.Id.Void => llvm.VoidTypeInContext(ofile.context) orelse return error.OutOfMemory,
- else => try self.return_type.getLlvmType(ofile),
+ Type.Id.Void => llvm.VoidTypeInContext(llvm_context) orelse return error.OutOfMemory,
+ else => try self.return_type.getLlvmType(allocator, llvm_context),
};
- const llvm_param_types = try ofile.a().alloc(llvm.TypeRef, self.params.len);
- defer ofile.a().free(llvm_param_types);
+ const llvm_param_types = try allocator.alloc(llvm.TypeRef, self.params.len);
+ defer allocator.free(llvm_param_types);
for (llvm_param_types) |*llvm_param_type, i| {
- llvm_param_type.* = try self.params[i].typeof.getLlvmType(ofile);
+ llvm_param_type.* = try self.params[i].typ.getLlvmType(allocator, llvm_context);
}
return llvm.FunctionType(
@@ -241,7 +287,7 @@ pub const Type = struct {
}
pub fn destroy(self: *MetaType, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
};
@@ -255,7 +301,7 @@ pub const Type = struct {
}
pub fn destroy(self: *Void, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
};
@@ -269,10 +315,10 @@ pub const Type = struct {
}
pub fn destroy(self: *Bool, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
- pub fn getLlvmType(self: *Bool, ofile: *ObjectFile) llvm.TypeRef {
+ pub fn getLlvmType(self: *Bool, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef {
@panic("TODO");
}
};
@@ -287,19 +333,89 @@ pub const Type = struct {
}
pub fn destroy(self: *NoReturn, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
};
pub const Int = struct {
base: Type,
+ key: Key,
+ garbage_node: std.atomic.Stack(*Int).Node,
- pub fn destroy(self: *Int, comp: *Compilation) void {
- comp.a().destroy(self);
+ pub const Key = struct {
+ bit_count: u32,
+ is_signed: bool,
+
+ pub fn hash(self: *const Key) u32 {
+ const rands = [2]u32{ 0xa4ba6498, 0x75fc5af7 };
+ return rands[@boolToInt(self.is_signed)] *% self.bit_count;
+ }
+
+ pub fn eql(self: *const Key, other: *const Key) bool {
+ return self.bit_count == other.bit_count and self.is_signed == other.is_signed;
+ }
+ };
+
+ pub fn get_u8(comp: *Compilation) *Int {
+ comp.u8_type.base.base.ref();
+ return comp.u8_type;
}
- pub fn getLlvmType(self: *Int, ofile: *ObjectFile) llvm.TypeRef {
- @panic("TODO");
+ pub async fn get(comp: *Compilation, key: Key) !*Int {
+ {
+ const held = await (async comp.int_type_table.acquire() catch unreachable);
+ defer held.release();
+
+ if (held.value.get(&key)) |entry| {
+ entry.value.base.base.ref();
+ return entry.value;
+ }
+ }
+
+ const self = try comp.gpa().create(Int{
+ .base = undefined,
+ .key = key,
+ .garbage_node = undefined,
+ });
+ errdefer comp.gpa().destroy(self);
+
+ const u_or_i = "ui"[@boolToInt(key.is_signed)];
+ const name = try std.fmt.allocPrint(comp.gpa(), "{c}{}", u_or_i, key.bit_count);
+ errdefer comp.gpa().free(name);
+
+ self.base.init(comp, Id.Int, name);
+
+ {
+ const held = await (async comp.int_type_table.acquire() catch unreachable);
+ defer held.release();
+
+ _ = try held.value.put(&self.key, self);
+ }
+ return self;
+ }
+
+ pub fn destroy(self: *Int, comp: *Compilation) void {
+ self.garbage_node = std.atomic.Stack(*Int).Node{
+ .data = self,
+ .next = undefined,
+ };
+ comp.registerGarbage(Int, &self.garbage_node);
+ }
+
+ pub async fn gcDestroy(self: *Int, comp: *Compilation) void {
+ {
+ const held = await (async comp.int_type_table.acquire() catch unreachable);
+ defer held.release();
+
+ _ = held.value.remove(&self.key).?;
+ }
+ // we allocated the name
+ comp.gpa().free(self.base.name);
+ comp.gpa().destroy(self);
+ }
+
+ pub fn getLlvmType(self: *Int, allocator: *Allocator, llvm_context: llvm.ContextRef) !llvm.TypeRef {
+ return llvm.IntTypeInContext(llvm_context, self.key.bit_count) orelse return error.OutOfMemory;
}
};
@@ -307,59 +423,239 @@ pub const Type = struct {
base: Type,
pub fn destroy(self: *Float, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
- pub fn getLlvmType(self: *Float, ofile: *ObjectFile) llvm.TypeRef {
+ pub fn getLlvmType(self: *Float, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef {
@panic("TODO");
}
};
pub const Pointer = struct {
base: Type,
- mut: Mut,
- vol: Vol,
- size: Size,
- alignment: u32,
+ key: Key,
+ garbage_node: std.atomic.Stack(*Pointer).Node,
+
+ pub const Key = struct {
+ child_type: *Type,
+ mut: Mut,
+ vol: Vol,
+ size: Size,
+ alignment: Align,
+
+ pub fn hash(self: *const Key) u32 {
+ const align_hash = switch (self.alignment) {
+ Align.Abi => 0xf201c090,
+ Align.Override => |x| x,
+ };
+ return hash_usize(@ptrToInt(self.child_type)) *%
+ hash_enum(self.mut) *%
+ hash_enum(self.vol) *%
+ hash_enum(self.size) *%
+ align_hash;
+ }
+
+ pub fn eql(self: *const Key, other: *const Key) bool {
+ if (self.child_type != other.child_type or
+ self.mut != other.mut or
+ self.vol != other.vol or
+ self.size != other.size or
+ @TagType(Align)(self.alignment) != @TagType(Align)(other.alignment))
+ {
+ return false;
+ }
+ switch (self.alignment) {
+ Align.Abi => return true,
+ Align.Override => |x| return x == other.alignment.Override,
+ }
+ }
+ };
pub const Mut = enum {
Mut,
Const,
};
+
pub const Vol = enum {
Non,
Volatile,
};
+
+ pub const Align = union(enum) {
+ Abi,
+ Override: u32,
+ };
+
pub const Size = builtin.TypeInfo.Pointer.Size;
pub fn destroy(self: *Pointer, comp: *Compilation) void {
- comp.a().destroy(self);
+ self.garbage_node = std.atomic.Stack(*Pointer).Node{
+ .data = self,
+ .next = undefined,
+ };
+ comp.registerGarbage(Pointer, &self.garbage_node);
}
- pub fn get(
+ pub async fn gcDestroy(self: *Pointer, comp: *Compilation) void {
+ {
+ const held = await (async comp.ptr_type_table.acquire() catch unreachable);
+ defer held.release();
+
+ _ = held.value.remove(&self.key).?;
+ }
+ self.key.child_type.base.deref(comp);
+ comp.gpa().destroy(self);
+ }
+
+ pub async fn getAlignAsInt(self: *Pointer, comp: *Compilation) u32 {
+ switch (self.key.alignment) {
+ Align.Abi => return await (async self.key.child_type.getAbiAlignment(comp) catch unreachable),
+ Align.Override => |alignment| return alignment,
+ }
+ }
+
+ pub async fn get(
comp: *Compilation,
- elem_type: *Type,
- mut: Mut,
- vol: Vol,
- size: Size,
- alignment: u32,
- ) *Pointer {
- @panic("TODO get pointer");
+ key: Key,
+ ) !*Pointer {
+ var normal_key = key;
+ switch (key.alignment) {
+ Align.Abi => {},
+ Align.Override => |alignment| {
+ const abi_align = try await (async key.child_type.getAbiAlignment(comp) catch unreachable);
+ if (abi_align == alignment) {
+ normal_key.alignment = Align.Abi;
+ }
+ },
+ }
+ {
+ const held = await (async comp.ptr_type_table.acquire() catch unreachable);
+ defer held.release();
+
+ if (held.value.get(&normal_key)) |entry| {
+ entry.value.base.base.ref();
+ return entry.value;
+ }
+ }
+
+ const self = try comp.gpa().create(Pointer{
+ .base = undefined,
+ .key = normal_key,
+ .garbage_node = undefined,
+ });
+ errdefer comp.gpa().destroy(self);
+
+ const size_str = switch (self.key.size) {
+ Size.One => "*",
+ Size.Many => "[*]",
+ Size.Slice => "[]",
+ };
+ const mut_str = switch (self.key.mut) {
+ Mut.Const => "const ",
+ Mut.Mut => "",
+ };
+ const vol_str = switch (self.key.vol) {
+ Vol.Volatile => "volatile ",
+ Vol.Non => "",
+ };
+ const name = switch (self.key.alignment) {
+ Align.Abi => try std.fmt.allocPrint(
+ comp.gpa(),
+ "{}{}{}{}",
+ size_str,
+ mut_str,
+ vol_str,
+ self.key.child_type.name,
+ ),
+ Align.Override => |alignment| try std.fmt.allocPrint(
+ comp.gpa(),
+ "{}align<{}> {}{}{}",
+ size_str,
+ alignment,
+ mut_str,
+ vol_str,
+ self.key.child_type.name,
+ ),
+ };
+ errdefer comp.gpa().free(name);
+
+ self.base.init(comp, Id.Pointer, name);
+
+ {
+ const held = await (async comp.ptr_type_table.acquire() catch unreachable);
+ defer held.release();
+
+ _ = try held.value.put(&self.key, self);
+ }
+ return self;
}
- pub fn getLlvmType(self: *Pointer, ofile: *ObjectFile) llvm.TypeRef {
- @panic("TODO");
+ pub fn getLlvmType(self: *Pointer, allocator: *Allocator, llvm_context: llvm.ContextRef) !llvm.TypeRef {
+ const elem_llvm_type = try self.key.child_type.getLlvmType(allocator, llvm_context);
+ return llvm.PointerType(elem_llvm_type, 0) orelse return error.OutOfMemory;
}
};
pub const Array = struct {
base: Type,
+ key: Key,
+ garbage_node: std.atomic.Stack(*Array).Node,
+
+ pub const Key = struct {
+ elem_type: *Type,
+ len: usize,
+
+ pub fn hash(self: *const Key) u32 {
+ return hash_usize(@ptrToInt(self.elem_type)) *% hash_usize(self.len);
+ }
+
+ pub fn eql(self: *const Key, other: *const Key) bool {
+ return self.elem_type == other.elem_type and self.len == other.len;
+ }
+ };
pub fn destroy(self: *Array, comp: *Compilation) void {
- comp.a().destroy(self);
+ self.key.elem_type.base.deref(comp);
+ comp.gpa().destroy(self);
}
- pub fn getLlvmType(self: *Array, ofile: *ObjectFile) llvm.TypeRef {
- @panic("TODO");
+ pub async fn get(comp: *Compilation, key: Key) !*Array {
+ key.elem_type.base.ref();
+ errdefer key.elem_type.base.deref(comp);
+
+ {
+ const held = await (async comp.array_type_table.acquire() catch unreachable);
+ defer held.release();
+
+ if (held.value.get(&key)) |entry| {
+ entry.value.base.base.ref();
+ return entry.value;
+ }
+ }
+
+ const self = try comp.gpa().create(Array{
+ .base = undefined,
+ .key = key,
+ .garbage_node = undefined,
+ });
+ errdefer comp.gpa().destroy(self);
+
+ const name = try std.fmt.allocPrint(comp.gpa(), "[{}]{}", key.len, key.elem_type.name);
+ errdefer comp.gpa().free(name);
+
+ self.base.init(comp, Id.Array, name);
+
+ {
+ const held = await (async comp.array_type_table.acquire() catch unreachable);
+ defer held.release();
+
+ _ = try held.value.put(&self.key, self);
+ }
+ return self;
+ }
+
+ pub fn getLlvmType(self: *Array, allocator: *Allocator, llvm_context: llvm.ContextRef) !llvm.TypeRef {
+ const elem_llvm_type = try self.key.elem_type.getLlvmType(allocator, llvm_context);
+ return llvm.ArrayType(elem_llvm_type, @intCast(c_uint, self.key.len)) orelse return error.OutOfMemory;
}
};
@@ -367,15 +663,21 @@ pub const Type = struct {
base: Type,
pub fn destroy(self: *ComptimeFloat, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
};
pub const ComptimeInt = struct {
base: Type,
+ /// Adds 1 reference to the resulting type
+ pub fn get(comp: *Compilation) *ComptimeInt {
+ comp.comptime_int_type.base.base.ref();
+ return comp.comptime_int_type;
+ }
+
pub fn destroy(self: *ComptimeInt, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
};
@@ -383,7 +685,7 @@ pub const Type = struct {
base: Type,
pub fn destroy(self: *Undefined, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
};
@@ -391,7 +693,7 @@ pub const Type = struct {
base: Type,
pub fn destroy(self: *Null, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
};
@@ -399,10 +701,10 @@ pub const Type = struct {
base: Type,
pub fn destroy(self: *Optional, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
- pub fn getLlvmType(self: *Optional, ofile: *ObjectFile) llvm.TypeRef {
+ pub fn getLlvmType(self: *Optional, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef {
@panic("TODO");
}
};
@@ -411,10 +713,10 @@ pub const Type = struct {
base: Type,
pub fn destroy(self: *ErrorUnion, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
- pub fn getLlvmType(self: *ErrorUnion, ofile: *ObjectFile) llvm.TypeRef {
+ pub fn getLlvmType(self: *ErrorUnion, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef {
@panic("TODO");
}
};
@@ -423,10 +725,10 @@ pub const Type = struct {
base: Type,
pub fn destroy(self: *ErrorSet, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
- pub fn getLlvmType(self: *ErrorSet, ofile: *ObjectFile) llvm.TypeRef {
+ pub fn getLlvmType(self: *ErrorSet, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef {
@panic("TODO");
}
};
@@ -435,10 +737,10 @@ pub const Type = struct {
base: Type,
pub fn destroy(self: *Enum, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
- pub fn getLlvmType(self: *Enum, ofile: *ObjectFile) llvm.TypeRef {
+ pub fn getLlvmType(self: *Enum, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef {
@panic("TODO");
}
};
@@ -447,10 +749,10 @@ pub const Type = struct {
base: Type,
pub fn destroy(self: *Union, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
- pub fn getLlvmType(self: *Union, ofile: *ObjectFile) llvm.TypeRef {
+ pub fn getLlvmType(self: *Union, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef {
@panic("TODO");
}
};
@@ -459,7 +761,7 @@ pub const Type = struct {
base: Type,
pub fn destroy(self: *Namespace, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
};
@@ -467,7 +769,7 @@ pub const Type = struct {
base: Type,
pub fn destroy(self: *Block, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
};
@@ -475,10 +777,10 @@ pub const Type = struct {
base: Type,
pub fn destroy(self: *BoundFn, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
- pub fn getLlvmType(self: *BoundFn, ofile: *ObjectFile) llvm.TypeRef {
+ pub fn getLlvmType(self: *BoundFn, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef {
@panic("TODO");
}
};
@@ -487,7 +789,7 @@ pub const Type = struct {
base: Type,
pub fn destroy(self: *ArgTuple, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
};
@@ -495,10 +797,10 @@ pub const Type = struct {
base: Type,
pub fn destroy(self: *Opaque, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
- pub fn getLlvmType(self: *Opaque, ofile: *ObjectFile) llvm.TypeRef {
+ pub fn getLlvmType(self: *Opaque, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef {
@panic("TODO");
}
};
@@ -507,11 +809,36 @@ pub const Type = struct {
base: Type,
pub fn destroy(self: *Promise, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
- pub fn getLlvmType(self: *Promise, ofile: *ObjectFile) llvm.TypeRef {
+ pub fn getLlvmType(self: *Promise, allocator: *Allocator, llvm_context: llvm.ContextRef) llvm.TypeRef {
@panic("TODO");
}
};
};
+
+fn hash_usize(x: usize) u32 {
+ return switch (@sizeOf(usize)) {
+ 4 => x,
+ 8 => @truncate(u32, x *% 0xad44ee2d8e3fc13d),
+ else => @compileError("implement this hash function"),
+ };
+}
+
+fn hash_enum(x: var) u32 {
+ const rands = []u32{
+ 0x85ebf64f,
+ 0x3fcb3211,
+ 0x240a4e8e,
+ 0x40bb0e3c,
+ 0x78be45af,
+ 0x1ca98e37,
+ 0xec56053a,
+ 0x906adc48,
+ 0xd4fe9763,
+ 0x54c80dac,
+ };
+ comptime assert(@memberCount(@typeOf(x)) < rands.len);
+ return rands[@enumToInt(x)];
+}
diff --git a/src-self-hosted/value.zig b/src-self-hosted/value.zig
index e3b91d2807..2005e3c119 100644
--- a/src-self-hosted/value.zig
+++ b/src-self-hosted/value.zig
@@ -4,12 +4,14 @@ const Scope = @import("scope.zig").Scope;
const Compilation = @import("compilation.zig").Compilation;
const ObjectFile = @import("codegen.zig").ObjectFile;
const llvm = @import("llvm.zig");
+const Buffer = std.Buffer;
+const assert = std.debug.assert;
/// Values are ref-counted, heap-allocated, and copy-on-write
/// If there is only 1 ref then write need not copy
pub const Value = struct {
id: Id,
- typeof: *Type,
+ typ: *Type,
ref_count: std.atomic.Int(usize),
/// Thread-safe
@@ -20,23 +22,37 @@ pub const Value = struct {
/// Thread-safe
pub fn deref(base: *Value, comp: *Compilation) void {
if (base.ref_count.decr() == 1) {
- base.typeof.base.deref(comp);
+ base.typ.base.deref(comp);
switch (base.id) {
Id.Type => @fieldParentPtr(Type, "base", base).destroy(comp),
Id.Fn => @fieldParentPtr(Fn, "base", base).destroy(comp),
+ Id.FnProto => @fieldParentPtr(FnProto, "base", base).destroy(comp),
Id.Void => @fieldParentPtr(Void, "base", base).destroy(comp),
Id.Bool => @fieldParentPtr(Bool, "base", base).destroy(comp),
Id.NoReturn => @fieldParentPtr(NoReturn, "base", base).destroy(comp),
Id.Ptr => @fieldParentPtr(Ptr, "base", base).destroy(comp),
+ Id.Int => @fieldParentPtr(Int, "base", base).destroy(comp),
+ Id.Array => @fieldParentPtr(Array, "base", base).destroy(comp),
}
}
}
+ pub fn setType(base: *Value, new_type: *Type, comp: *Compilation) void {
+ base.typ.base.deref(comp);
+ new_type.base.ref();
+ base.typ = new_type;
+ }
+
pub fn getRef(base: *Value) *Value {
base.ref();
return base;
}
+ pub fn cast(base: *Value, comptime T: type) ?*T {
+ if (base.id != @field(Id, @typeName(T))) return null;
+ return @fieldParentPtr(T, "base", base);
+ }
+
pub fn dump(base: *const Value) void {
std.debug.warn("{}", @tagName(base.id));
}
@@ -45,30 +61,117 @@ pub const Value = struct {
switch (base.id) {
Id.Type => unreachable,
Id.Fn => @panic("TODO"),
+ Id.FnProto => return @fieldParentPtr(FnProto, "base", base).getLlvmConst(ofile),
Id.Void => return null,
Id.Bool => return @fieldParentPtr(Bool, "base", base).getLlvmConst(ofile),
Id.NoReturn => unreachable,
- Id.Ptr => @panic("TODO"),
+ Id.Ptr => return @fieldParentPtr(Ptr, "base", base).getLlvmConst(ofile),
+ Id.Int => return @fieldParentPtr(Int, "base", base).getLlvmConst(ofile),
+ Id.Array => return @fieldParentPtr(Array, "base", base).getLlvmConst(ofile),
}
}
+ pub fn derefAndCopy(self: *Value, comp: *Compilation) (error{OutOfMemory}!*Value) {
+ if (self.ref_count.get() == 1) {
+ // ( ͡° ͜ʖ ͡°)
+ return self;
+ }
+
+ assert(self.ref_count.decr() != 1);
+ return self.copy(comp);
+ }
+
+ pub fn copy(base: *Value, comp: *Compilation) (error{OutOfMemory}!*Value) {
+ switch (base.id) {
+ Id.Type => unreachable,
+ Id.Fn => unreachable,
+ Id.FnProto => unreachable,
+ Id.Void => unreachable,
+ Id.Bool => unreachable,
+ Id.NoReturn => unreachable,
+ Id.Ptr => unreachable,
+ Id.Array => unreachable,
+ Id.Int => return &(try @fieldParentPtr(Int, "base", base).copy(comp)).base,
+ }
+ }
+
+ pub const Parent = union(enum) {
+ None,
+ BaseStruct: BaseStruct,
+ BaseArray: BaseArray,
+ BaseUnion: *Value,
+ BaseScalar: *Value,
+
+ pub const BaseStruct = struct {
+ val: *Value,
+ field_index: usize,
+ };
+
+ pub const BaseArray = struct {
+ val: *Value,
+ elem_index: usize,
+ };
+ };
+
pub const Id = enum {
Type,
Fn,
Void,
Bool,
NoReturn,
+ Array,
Ptr,
+ Int,
+ FnProto,
};
pub const Type = @import("type.zig").Type;
+ pub const FnProto = struct {
+ base: Value,
+
+ /// The main external name that is used in the .o file.
+ /// TODO https://github.com/ziglang/zig/issues/265
+ symbol_name: Buffer,
+
+ pub fn create(comp: *Compilation, fn_type: *Type.Fn, symbol_name: Buffer) !*FnProto {
+ const self = try comp.gpa().create(FnProto{
+ .base = Value{
+ .id = Value.Id.FnProto,
+ .typ = &fn_type.base,
+ .ref_count = std.atomic.Int(usize).init(1),
+ },
+ .symbol_name = symbol_name,
+ });
+ fn_type.base.base.ref();
+ return self;
+ }
+
+ pub fn destroy(self: *FnProto, comp: *Compilation) void {
+ self.symbol_name.deinit();
+ comp.gpa().destroy(self);
+ }
+
+ pub fn getLlvmConst(self: *FnProto, ofile: *ObjectFile) !?llvm.ValueRef {
+ const llvm_fn_type = try self.base.typ.getLlvmType(ofile.arena, ofile.context);
+ const llvm_fn = llvm.AddFunction(
+ ofile.module,
+ self.symbol_name.ptr(),
+ llvm_fn_type,
+ ) orelse return error.OutOfMemory;
+
+ // TODO port more logic from codegen.cpp:fn_llvm_value
+
+ return llvm_fn;
+ }
+ };
+
pub const Fn = struct {
base: Value,
/// The main external name that is used in the .o file.
/// TODO https://github.com/ziglang/zig/issues/265
- symbol_name: std.Buffer,
+ symbol_name: Buffer,
/// parent should be the top level decls or container decls
fndef_scope: *Scope.FnDef,
@@ -79,19 +182,33 @@ pub const Value = struct {
/// parent is child_scope
block_scope: *Scope.Block,
+ /// Path to the object file that contains this function
+ containing_object: Buffer,
+
+ link_set_node: *std.LinkedList(?*Value.Fn).Node,
+
/// Creates a Fn value with 1 ref
/// Takes ownership of symbol_name
- pub fn create(comp: *Compilation, fn_type: *Type.Fn, fndef_scope: *Scope.FnDef, symbol_name: std.Buffer) !*Fn {
- const self = try comp.a().create(Fn{
+ pub fn create(comp: *Compilation, fn_type: *Type.Fn, fndef_scope: *Scope.FnDef, symbol_name: Buffer) !*Fn {
+ const link_set_node = try comp.gpa().create(Compilation.FnLinkSet.Node{
+ .data = null,
+ .next = undefined,
+ .prev = undefined,
+ });
+ errdefer comp.gpa().destroy(link_set_node);
+
+ const self = try comp.gpa().create(Fn{
.base = Value{
.id = Value.Id.Fn,
- .typeof = &fn_type.base,
+ .typ = &fn_type.base,
.ref_count = std.atomic.Int(usize).init(1),
},
.fndef_scope = fndef_scope,
.child_scope = &fndef_scope.base,
.block_scope = undefined,
.symbol_name = symbol_name,
+ .containing_object = Buffer.initNull(comp.gpa()),
+ .link_set_node = link_set_node,
});
fn_type.base.base.ref();
fndef_scope.fn_val = self;
@@ -100,9 +217,19 @@ pub const Value = struct {
}
pub fn destroy(self: *Fn, comp: *Compilation) void {
+ // remove with a tombstone so that we do not have to grab a lock
+ if (self.link_set_node.data != null) {
+ // it's now the job of the link step to find this tombstone and
+ // deallocate it.
+ self.link_set_node.data = null;
+ } else {
+ comp.gpa().destroy(self.link_set_node);
+ }
+
+ self.containing_object.deinit();
self.fndef_scope.base.deref(comp);
self.symbol_name.deinit();
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
};
@@ -115,7 +242,7 @@ pub const Value = struct {
}
pub fn destroy(self: *Void, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
};
@@ -134,7 +261,7 @@ pub const Value = struct {
}
pub fn destroy(self: *Bool, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
pub fn getLlvmConst(self: *Bool, ofile: *ObjectFile) ?llvm.ValueRef {
@@ -156,12 +283,14 @@ pub const Value = struct {
}
pub fn destroy(self: *NoReturn, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
}
};
pub const Ptr = struct {
base: Value,
+ special: Special,
+ mut: Mut,
pub const Mut = enum {
CompTimeConst,
@@ -169,8 +298,268 @@ pub const Value = struct {
RunTime,
};
+ pub const Special = union(enum) {
+ Scalar: *Value,
+ BaseArray: BaseArray,
+ BaseStruct: BaseStruct,
+ HardCodedAddr: u64,
+ Discard,
+ };
+
+ pub const BaseArray = struct {
+ val: *Value,
+ elem_index: usize,
+ };
+
+ pub const BaseStruct = struct {
+ val: *Value,
+ field_index: usize,
+ };
+
+ pub async fn createArrayElemPtr(
+ comp: *Compilation,
+ array_val: *Array,
+ mut: Type.Pointer.Mut,
+ size: Type.Pointer.Size,
+ elem_index: usize,
+ ) !*Ptr {
+ array_val.base.ref();
+ errdefer array_val.base.deref(comp);
+
+ const elem_type = array_val.base.typ.cast(Type.Array).?.key.elem_type;
+ const ptr_type = try await (async Type.Pointer.get(comp, Type.Pointer.Key{
+ .child_type = elem_type,
+ .mut = mut,
+ .vol = Type.Pointer.Vol.Non,
+ .size = size,
+ .alignment = Type.Pointer.Align.Abi,
+ }) catch unreachable);
+ var ptr_type_consumed = false;
+ errdefer if (!ptr_type_consumed) ptr_type.base.base.deref(comp);
+
+ const self = try comp.gpa().create(Value.Ptr{
+ .base = Value{
+ .id = Value.Id.Ptr,
+ .typ = &ptr_type.base,
+ .ref_count = std.atomic.Int(usize).init(1),
+ },
+ .special = Special{
+ .BaseArray = BaseArray{
+ .val = &array_val.base,
+ .elem_index = 0,
+ },
+ },
+ .mut = Mut.CompTimeConst,
+ });
+ ptr_type_consumed = true;
+ errdefer comp.gpa().destroy(self);
+
+ return self;
+ }
+
pub fn destroy(self: *Ptr, comp: *Compilation) void {
- comp.a().destroy(self);
+ comp.gpa().destroy(self);
+ }
+
+ pub fn getLlvmConst(self: *Ptr, ofile: *ObjectFile) !?llvm.ValueRef {
+ const llvm_type = self.base.typ.getLlvmType(ofile.arena, ofile.context);
+ // TODO carefully port the logic from codegen.cpp:gen_const_val_ptr
+ switch (self.special) {
+ Special.Scalar => |scalar| @panic("TODO"),
+ Special.BaseArray => |base_array| {
+ // TODO put this in one .o file only, and after that, generate extern references to it
+ const array_llvm_value = (try base_array.val.getLlvmConst(ofile)).?;
+ const ptr_bit_count = ofile.comp.target_ptr_bits;
+ const usize_llvm_type = llvm.IntTypeInContext(ofile.context, ptr_bit_count) orelse return error.OutOfMemory;
+ const indices = []llvm.ValueRef{
+ llvm.ConstNull(usize_llvm_type) orelse return error.OutOfMemory,
+ llvm.ConstInt(usize_llvm_type, base_array.elem_index, 0) orelse return error.OutOfMemory,
+ };
+ return llvm.ConstInBoundsGEP(
+ array_llvm_value,
+ &indices,
+ @intCast(c_uint, indices.len),
+ ) orelse return error.OutOfMemory;
+ },
+ Special.BaseStruct => |base_struct| @panic("TODO"),
+ Special.HardCodedAddr => |addr| @panic("TODO"),
+ Special.Discard => unreachable,
+ }
+ }
+ };
+
+ pub const Array = struct {
+ base: Value,
+ special: Special,
+
+ pub const Special = union(enum) {
+ Undefined,
+ OwnedBuffer: []u8,
+ Explicit: Data,
+ };
+
+ pub const Data = struct {
+ parent: Parent,
+ elements: []*Value,
+ };
+
+ /// Takes ownership of buffer
+ pub async fn createOwnedBuffer(comp: *Compilation, buffer: []u8) !*Array {
+ const u8_type = Type.Int.get_u8(comp);
+ defer u8_type.base.base.deref(comp);
+
+ const array_type = try await (async Type.Array.get(comp, Type.Array.Key{
+ .elem_type = &u8_type.base,
+ .len = buffer.len,
+ }) catch unreachable);
+ errdefer array_type.base.base.deref(comp);
+
+ const self = try comp.gpa().create(Value.Array{
+ .base = Value{
+ .id = Value.Id.Array,
+ .typ = &array_type.base,
+ .ref_count = std.atomic.Int(usize).init(1),
+ },
+ .special = Special{ .OwnedBuffer = buffer },
+ });
+ errdefer comp.gpa().destroy(self);
+
+ return self;
+ }
+
+ pub fn destroy(self: *Array, comp: *Compilation) void {
+ switch (self.special) {
+ Special.Undefined => {},
+ Special.OwnedBuffer => |buf| {
+ comp.gpa().free(buf);
+ },
+ Special.Explicit => {},
+ }
+ comp.gpa().destroy(self);
+ }
+
+ pub fn getLlvmConst(self: *Array, ofile: *ObjectFile) !?llvm.ValueRef {
+ switch (self.special) {
+ Special.Undefined => {
+ const llvm_type = try self.base.typ.getLlvmType(ofile.arena, ofile.context);
+ return llvm.GetUndef(llvm_type);
+ },
+ Special.OwnedBuffer => |buf| {
+ const dont_null_terminate = 1;
+ const llvm_str_init = llvm.ConstStringInContext(
+ ofile.context,
+ buf.ptr,
+ @intCast(c_uint, buf.len),
+ dont_null_terminate,
+ ) orelse return error.OutOfMemory;
+ const str_init_type = llvm.TypeOf(llvm_str_init);
+ const global = llvm.AddGlobal(ofile.module, str_init_type, c"") orelse return error.OutOfMemory;
+ llvm.SetInitializer(global, llvm_str_init);
+ llvm.SetLinkage(global, llvm.PrivateLinkage);
+ llvm.SetGlobalConstant(global, 1);
+ llvm.SetUnnamedAddr(global, 1);
+ llvm.SetAlignment(global, llvm.ABIAlignmentOfType(ofile.comp.target_data_ref, str_init_type));
+ return global;
+ },
+ Special.Explicit => @panic("TODO"),
+ }
+
+ //{
+ // uint64_t len = type_entry->data.array.len;
+ // if (const_val->data.x_array.special == ConstArraySpecialUndef) {
+ // return LLVMGetUndef(type_entry->type_ref);
+ // }
+
+ // LLVMValueRef *values = allocate(len);
+ // LLVMTypeRef element_type_ref = type_entry->data.array.child_type->type_ref;
+ // bool make_unnamed_struct = false;
+ // for (uint64_t i = 0; i < len; i += 1) {
+ // ConstExprValue *elem_value = &const_val->data.x_array.s_none.elements[i];
+ // LLVMValueRef val = gen_const_val(g, elem_value, "");
+ // values[i] = val;
+ // make_unnamed_struct = make_unnamed_struct || is_llvm_value_unnamed_type(elem_value->type, val);
+ // }
+ // if (make_unnamed_struct) {
+ // return LLVMConstStruct(values, len, true);
+ // } else {
+ // return LLVMConstArray(element_type_ref, values, (unsigned)len);
+ // }
+ //}
+ }
+ };
+
+ pub const Int = struct {
+ base: Value,
+ big_int: std.math.big.Int,
+
+ pub fn createFromString(comp: *Compilation, typ: *Type, base: u8, value: []const u8) !*Int {
+ const self = try comp.gpa().create(Value.Int{
+ .base = Value{
+ .id = Value.Id.Int,
+ .typ = typ,
+ .ref_count = std.atomic.Int(usize).init(1),
+ },
+ .big_int = undefined,
+ });
+ typ.base.ref();
+ errdefer comp.gpa().destroy(self);
+
+ self.big_int = try std.math.big.Int.init(comp.gpa());
+ errdefer self.big_int.deinit();
+
+ try self.big_int.setString(base, value);
+
+ return self;
+ }
+
+ pub fn getLlvmConst(self: *Int, ofile: *ObjectFile) !?llvm.ValueRef {
+ switch (self.base.typ.id) {
+ Type.Id.Int => {
+ const type_ref = try self.base.typ.getLlvmType(ofile.arena, ofile.context);
+ if (self.big_int.len == 0) {
+ return llvm.ConstNull(type_ref);
+ }
+ const unsigned_val = if (self.big_int.len == 1) blk: {
+ break :blk llvm.ConstInt(type_ref, self.big_int.limbs[0], @boolToInt(false));
+ } else if (@sizeOf(std.math.big.Limb) == @sizeOf(u64)) blk: {
+ break :blk llvm.ConstIntOfArbitraryPrecision(
+ type_ref,
+ @intCast(c_uint, self.big_int.len),
+ @ptrCast([*]u64, self.big_int.limbs.ptr),
+ );
+ } else {
+ @compileError("std.math.Big.Int.Limb size does not match LLVM");
+ };
+ return if (self.big_int.positive) unsigned_val else llvm.ConstNeg(unsigned_val);
+ },
+ Type.Id.ComptimeInt => unreachable,
+ else => unreachable,
+ }
+ }
+
+ pub fn copy(old: *Int, comp: *Compilation) !*Int {
+ old.base.typ.base.ref();
+ errdefer old.base.typ.base.deref(comp);
+
+ const new = try comp.gpa().create(Value.Int{
+ .base = Value{
+ .id = Value.Id.Int,
+ .typ = old.base.typ,
+ .ref_count = std.atomic.Int(usize).init(1),
+ },
+ .big_int = undefined,
+ });
+ errdefer comp.gpa().destroy(new);
+
+ new.big_int = try old.big_int.clone();
+ errdefer new.big_int.deinit();
+
+ return new;
+ }
+
+ pub fn destroy(self: *Int, comp: *Compilation) void {
+ self.big_int.deinit();
+ comp.gpa().destroy(self);
}
};
};
diff --git a/src/analyze.cpp b/src/analyze.cpp
index 2ace893508..6bbe5f6037 100644
--- a/src/analyze.cpp
+++ b/src/analyze.cpp
@@ -1454,7 +1454,9 @@ static bool type_allowed_in_extern(CodeGen *g, TypeTableEntry *type_entry) {
case TypeTableEntryIdFn:
return type_entry->data.fn.fn_type_id.cc == CallingConventionC;
case TypeTableEntryIdPointer:
- return type_allowed_in_extern(g, type_entry->data.pointer.child_type);
+ if (type_size(g, type_entry) == 0)
+ return false;
+ return true;
case TypeTableEntryIdStruct:
return type_entry->data.structure.layout == ContainerLayoutExtern || type_entry->data.structure.layout == ContainerLayoutPacked;
case TypeTableEntryIdOptional:
@@ -4377,7 +4379,7 @@ bool handle_is_ptr(TypeTableEntry *type_entry) {
static ZigWindowsSDK *get_windows_sdk(CodeGen *g) {
if (g->win_sdk == nullptr) {
- if (os_find_windows_sdk(&g->win_sdk)) {
+ if (zig_find_windows_sdk(&g->win_sdk)) {
fprintf(stderr, "unable to determine windows sdk path\n");
exit(1);
}
@@ -4497,12 +4499,11 @@ void find_libc_lib_path(CodeGen *g) {
ZigWindowsSDK *sdk = get_windows_sdk(g);
if (g->msvc_lib_dir == nullptr) {
- Buf* vc_lib_dir = buf_alloc();
- if (os_get_win32_vcruntime_path(vc_lib_dir, g->zig_target.arch.arch)) {
+ if (sdk->msvc_lib_dir_ptr == nullptr) {
fprintf(stderr, "Unable to determine vcruntime path. --msvc-lib-dir");
exit(1);
}
- g->msvc_lib_dir = vc_lib_dir;
+ g->msvc_lib_dir = buf_create_from_mem(sdk->msvc_lib_dir_ptr, sdk->msvc_lib_dir_len);
}
if (g->libc_lib_dir == nullptr) {
diff --git a/src/codegen.cpp b/src/codegen.cpp
index 6cb89e6f7e..c288af4273 100644
--- a/src/codegen.cpp
+++ b/src/codegen.cpp
@@ -60,6 +60,33 @@ PackageTableEntry *new_anonymous_package(void) {
return new_package("", "");
}
+static const char *symbols_that_llvm_depends_on[] = {
+ "memcpy",
+ "memset",
+ "sqrt",
+ "powi",
+ "sin",
+ "cos",
+ "pow",
+ "exp",
+ "exp2",
+ "log",
+ "log10",
+ "log2",
+ "fma",
+ "fabs",
+ "minnum",
+ "maxnum",
+ "copysign",
+ "floor",
+ "ceil",
+ "trunc",
+ "rint",
+ "nearbyint",
+ "round",
+ // TODO probably all of compiler-rt needs to go here
+};
+
CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target, OutType out_type, BuildMode build_mode,
Buf *zig_lib_dir)
{
@@ -94,6 +121,10 @@ CodeGen *codegen_create(Buf *root_src_path, const ZigTarget *target, OutType out
g->want_h_file = (out_type == OutTypeObj || out_type == OutTypeLib);
buf_resize(&g->global_asm, 0);
+ for (size_t i = 0; i < array_length(symbols_that_llvm_depends_on); i += 1) {
+ g->external_prototypes.put(buf_create_from_str(symbols_that_llvm_depends_on[i]), nullptr);
+ }
+
if (root_src_path) {
Buf *src_basename = buf_alloc();
Buf *src_dir = buf_alloc();
@@ -7419,51 +7450,60 @@ static void gen_h_file(CodeGen *g) {
case TypeTableEntryIdPromise:
zig_unreachable();
case TypeTableEntryIdEnum:
- assert(type_entry->data.enumeration.layout == ContainerLayoutExtern);
- fprintf(out_h, "enum %s {\n", buf_ptr(&type_entry->name));
- for (uint32_t field_i = 0; field_i < type_entry->data.enumeration.src_field_count; field_i += 1) {
- TypeEnumField *enum_field = &type_entry->data.enumeration.fields[field_i];
- Buf *value_buf = buf_alloc();
- bigint_append_buf(value_buf, &enum_field->value, 10);
- fprintf(out_h, " %s = %s", buf_ptr(enum_field->name), buf_ptr(value_buf));
- if (field_i != type_entry->data.enumeration.src_field_count - 1) {
- fprintf(out_h, ",");
+ if (type_entry->data.enumeration.layout == ContainerLayoutExtern) {
+ fprintf(out_h, "enum %s {\n", buf_ptr(&type_entry->name));
+ for (uint32_t field_i = 0; field_i < type_entry->data.enumeration.src_field_count; field_i += 1) {
+ TypeEnumField *enum_field = &type_entry->data.enumeration.fields[field_i];
+ Buf *value_buf = buf_alloc();
+ bigint_append_buf(value_buf, &enum_field->value, 10);
+ fprintf(out_h, " %s = %s", buf_ptr(enum_field->name), buf_ptr(value_buf));
+ if (field_i != type_entry->data.enumeration.src_field_count - 1) {
+ fprintf(out_h, ",");
+ }
+ fprintf(out_h, "\n");
}
- fprintf(out_h, "\n");
+ fprintf(out_h, "};\n\n");
+ } else {
+ fprintf(out_h, "enum %s;\n", buf_ptr(&type_entry->name));
}
- fprintf(out_h, "};\n\n");
break;
case TypeTableEntryIdStruct:
- assert(type_entry->data.structure.layout == ContainerLayoutExtern);
- fprintf(out_h, "struct %s {\n", buf_ptr(&type_entry->name));
- for (uint32_t field_i = 0; field_i < type_entry->data.structure.src_field_count; field_i += 1) {
- TypeStructField *struct_field = &type_entry->data.structure.fields[field_i];
+ if (type_entry->data.structure.layout == ContainerLayoutExtern) {
+ fprintf(out_h, "struct %s {\n", buf_ptr(&type_entry->name));
+ for (uint32_t field_i = 0; field_i < type_entry->data.structure.src_field_count; field_i += 1) {
+ TypeStructField *struct_field = &type_entry->data.structure.fields[field_i];
- Buf *type_name_buf = buf_alloc();
- get_c_type(g, gen_h, struct_field->type_entry, type_name_buf);
+ Buf *type_name_buf = buf_alloc();
+ get_c_type(g, gen_h, struct_field->type_entry, type_name_buf);
+
+ if (struct_field->type_entry->id == TypeTableEntryIdArray) {
+ fprintf(out_h, " %s %s[%" ZIG_PRI_u64 "];\n", buf_ptr(type_name_buf),
+ buf_ptr(struct_field->name),
+ struct_field->type_entry->data.array.len);
+ } else {
+ fprintf(out_h, " %s %s;\n", buf_ptr(type_name_buf), buf_ptr(struct_field->name));
+ }
- if (struct_field->type_entry->id == TypeTableEntryIdArray) {
- fprintf(out_h, " %s %s[%" ZIG_PRI_u64 "];\n", buf_ptr(type_name_buf),
- buf_ptr(struct_field->name),
- struct_field->type_entry->data.array.len);
- } else {
- fprintf(out_h, " %s %s;\n", buf_ptr(type_name_buf), buf_ptr(struct_field->name));
}
-
+ fprintf(out_h, "};\n\n");
+ } else {
+ fprintf(out_h, "struct %s;\n", buf_ptr(&type_entry->name));
}
- fprintf(out_h, "};\n\n");
break;
case TypeTableEntryIdUnion:
- assert(type_entry->data.unionation.layout == ContainerLayoutExtern);
- fprintf(out_h, "union %s {\n", buf_ptr(&type_entry->name));
- for (uint32_t field_i = 0; field_i < type_entry->data.unionation.src_field_count; field_i += 1) {
- TypeUnionField *union_field = &type_entry->data.unionation.fields[field_i];
+ if (type_entry->data.unionation.layout == ContainerLayoutExtern) {
+ fprintf(out_h, "union %s {\n", buf_ptr(&type_entry->name));
+ for (uint32_t field_i = 0; field_i < type_entry->data.unionation.src_field_count; field_i += 1) {
+ TypeUnionField *union_field = &type_entry->data.unionation.fields[field_i];
- Buf *type_name_buf = buf_alloc();
- get_c_type(g, gen_h, union_field->type_entry, type_name_buf);
- fprintf(out_h, " %s %s;\n", buf_ptr(type_name_buf), buf_ptr(union_field->name));
+ Buf *type_name_buf = buf_alloc();
+ get_c_type(g, gen_h, union_field->type_entry, type_name_buf);
+ fprintf(out_h, " %s %s;\n", buf_ptr(type_name_buf), buf_ptr(union_field->name));
+ }
+ fprintf(out_h, "};\n\n");
+ } else {
+ fprintf(out_h, "union %s;\n", buf_ptr(&type_entry->name));
}
- fprintf(out_h, "};\n\n");
break;
case TypeTableEntryIdOpaque:
fprintf(out_h, "struct %s;\n\n", buf_ptr(&type_entry->name));
diff --git a/src/ir.cpp b/src/ir.cpp
index 0804134d2a..fe5fb77085 100644
--- a/src/ir.cpp
+++ b/src/ir.cpp
@@ -246,6 +246,8 @@ static void ir_ref_bb(IrBasicBlock *bb) {
static void ir_ref_instruction(IrInstruction *instruction, IrBasicBlock *cur_bb) {
assert(instruction->id != IrInstructionIdInvalid);
instruction->ref_count += 1;
+ if (instruction->owner_bb != cur_bb && !instr_is_comptime(instruction))
+ ir_ref_bb(instruction->owner_bb);
}
static void ir_ref_var(VariableTableEntry *var) {
@@ -2959,16 +2961,34 @@ static void ir_count_defers(IrBuilder *irb, Scope *inner_scope, Scope *outer_sco
results[ReturnKindUnconditional] = 0;
results[ReturnKindError] = 0;
- while (inner_scope != outer_scope) {
- assert(inner_scope);
- if (inner_scope->id == ScopeIdDefer) {
- AstNode *defer_node = inner_scope->source_node;
- assert(defer_node->type == NodeTypeDefer);
- ReturnKind defer_kind = defer_node->data.defer.kind;
- results[defer_kind] += 1;
+ Scope *scope = inner_scope;
+ while (scope != outer_scope) {
+ assert(scope);
+ switch (scope->id) {
+ case ScopeIdDefer: {
+ AstNode *defer_node = scope->source_node;
+ assert(defer_node->type == NodeTypeDefer);
+ ReturnKind defer_kind = defer_node->data.defer.kind;
+ results[defer_kind] += 1;
+ scope = scope->parent;
+ continue;
+ }
+ case ScopeIdDecls:
+ case ScopeIdFnDef:
+ return;
+ case ScopeIdBlock:
+ case ScopeIdVarDecl:
+ case ScopeIdLoop:
+ case ScopeIdSuspend:
+ case ScopeIdCompTime:
+ scope = scope->parent;
+ continue;
+ case ScopeIdDeferExpr:
+ case ScopeIdCImport:
+ case ScopeIdCoroPrelude:
+ zig_unreachable();
}
- inner_scope = inner_scope->parent;
}
}
@@ -2984,27 +3004,43 @@ static bool ir_gen_defers_for_block(IrBuilder *irb, Scope *inner_scope, Scope *o
if (!scope)
return is_noreturn;
- if (scope->id == ScopeIdDefer) {
- AstNode *defer_node = scope->source_node;
- assert(defer_node->type == NodeTypeDefer);
- ReturnKind defer_kind = defer_node->data.defer.kind;
- if (defer_kind == ReturnKindUnconditional ||
- (gen_error_defers && defer_kind == ReturnKindError))
- {
- AstNode *defer_expr_node = defer_node->data.defer.expr;
- Scope *defer_expr_scope = defer_node->data.defer.expr_scope;
- IrInstruction *defer_expr_value = ir_gen_node(irb, defer_expr_node, defer_expr_scope);
- if (defer_expr_value != irb->codegen->invalid_instruction) {
- if (defer_expr_value->value.type != nullptr && defer_expr_value->value.type->id == TypeTableEntryIdUnreachable) {
- is_noreturn = true;
- } else {
- ir_mark_gen(ir_build_check_statement_is_void(irb, defer_expr_scope, defer_expr_node, defer_expr_value));
+ switch (scope->id) {
+ case ScopeIdDefer: {
+ AstNode *defer_node = scope->source_node;
+ assert(defer_node->type == NodeTypeDefer);
+ ReturnKind defer_kind = defer_node->data.defer.kind;
+ if (defer_kind == ReturnKindUnconditional ||
+ (gen_error_defers && defer_kind == ReturnKindError))
+ {
+ AstNode *defer_expr_node = defer_node->data.defer.expr;
+ Scope *defer_expr_scope = defer_node->data.defer.expr_scope;
+ IrInstruction *defer_expr_value = ir_gen_node(irb, defer_expr_node, defer_expr_scope);
+ if (defer_expr_value != irb->codegen->invalid_instruction) {
+ if (defer_expr_value->value.type != nullptr && defer_expr_value->value.type->id == TypeTableEntryIdUnreachable) {
+ is_noreturn = true;
+ } else {
+ ir_mark_gen(ir_build_check_statement_is_void(irb, defer_expr_scope, defer_expr_node, defer_expr_value));
+ }
}
}
+ scope = scope->parent;
+ continue;
}
-
+ case ScopeIdDecls:
+ case ScopeIdFnDef:
+ return is_noreturn;
+ case ScopeIdBlock:
+ case ScopeIdVarDecl:
+ case ScopeIdLoop:
+ case ScopeIdSuspend:
+ case ScopeIdCompTime:
+ scope = scope->parent;
+ continue;
+ case ScopeIdDeferExpr:
+ case ScopeIdCImport:
+ case ScopeIdCoroPrelude:
+ zig_unreachable();
}
- scope = scope->parent;
}
return is_noreturn;
}
@@ -9408,7 +9444,7 @@ static IrInstruction *ir_analyze_maybe_wrap(IrAnalyze *ira, IrInstruction *sourc
if (type_is_invalid(casted_payload->value.type))
return ira->codegen->invalid_instruction;
- ConstExprValue *val = ir_resolve_const(ira, casted_payload, UndefBad);
+ ConstExprValue *val = ir_resolve_const(ira, casted_payload, UndefOk);
if (!val)
return ira->codegen->invalid_instruction;
@@ -13090,6 +13126,7 @@ static TypeTableEntry *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCall *cal
impl_fn->ir_executable.parent_exec = ira->new_irb.exec;
impl_fn->analyzed_executable.source_node = call_instruction->base.source_node;
impl_fn->analyzed_executable.parent_exec = ira->new_irb.exec;
+ impl_fn->analyzed_executable.backward_branch_quota = ira->new_irb.exec->backward_branch_quota;
impl_fn->analyzed_executable.is_generic_instantiation = true;
ira->codegen->fn_defs.append(impl_fn);
diff --git a/src/link.cpp b/src/link.cpp
index 2d9a79585f..f65c072bac 100644
--- a/src/link.cpp
+++ b/src/link.cpp
@@ -901,7 +901,7 @@ static void construct_linker_job_macho(LinkJob *lj) {
if (strchr(buf_ptr(link_lib->name), '/') == nullptr) {
Buf *arg = buf_sprintf("-l%s", buf_ptr(link_lib->name));
lj->args.append(buf_ptr(arg));
- } else {
+ } else {
lj->args.append(buf_ptr(link_lib->name));
}
}
diff --git a/src/os.cpp b/src/os.cpp
index d52295950d..91a591a7b6 100644
--- a/src/os.cpp
+++ b/src/os.cpp
@@ -26,7 +26,6 @@
#include
#include
#include
-#include "windows_com.hpp"
typedef SSIZE_T ssize_t;
#else
@@ -1115,249 +1114,10 @@ void os_stderr_set_color(TermColor color) {
#endif
}
-int os_find_windows_sdk(ZigWindowsSDK **out_sdk) {
-#if defined(ZIG_OS_WINDOWS)
- ZigWindowsSDK *result_sdk = allocate(1);
- buf_resize(&result_sdk->path10, 0);
- buf_resize(&result_sdk->path81, 0);
-
- HKEY key;
- HRESULT rc;
- rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots", 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY | KEY_ENUMERATE_SUB_KEYS, &key);
- if (rc != ERROR_SUCCESS) {
- return ErrorFileNotFound;
- }
-
- {
- DWORD tmp_buf_len = MAX_PATH;
- buf_resize(&result_sdk->path10, tmp_buf_len);
- rc = RegQueryValueEx(key, "KitsRoot10", NULL, NULL, (LPBYTE)buf_ptr(&result_sdk->path10), &tmp_buf_len);
- if (rc == ERROR_FILE_NOT_FOUND) {
- buf_resize(&result_sdk->path10, 0);
- } else {
- buf_resize(&result_sdk->path10, tmp_buf_len);
- }
- }
- {
- DWORD tmp_buf_len = MAX_PATH;
- buf_resize(&result_sdk->path81, tmp_buf_len);
- rc = RegQueryValueEx(key, "KitsRoot81", NULL, NULL, (LPBYTE)buf_ptr(&result_sdk->path81), &tmp_buf_len);
- if (rc == ERROR_FILE_NOT_FOUND) {
- buf_resize(&result_sdk->path81, 0);
- } else {
- buf_resize(&result_sdk->path81, tmp_buf_len);
- }
- }
-
- if (buf_len(&result_sdk->path10) != 0) {
- Buf *sdk_lib_dir = buf_sprintf("%s\\Lib\\*", buf_ptr(&result_sdk->path10));
-
- // enumerate files in sdk path looking for latest version
- WIN32_FIND_DATA ffd;
- HANDLE hFind = FindFirstFileA(buf_ptr(sdk_lib_dir), &ffd);
- if (hFind == INVALID_HANDLE_VALUE) {
- return ErrorFileNotFound;
- }
- int v0 = 0, v1 = 0, v2 = 0, v3 = 0;
- bool found_version_dir = false;
- for (;;) {
- if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
- int c0 = 0, c1 = 0, c2 = 0, c3 = 0;
- sscanf(ffd.cFileName, "%d.%d.%d.%d", &c0, &c1, &c2, &c3);
- if (c0 == 10 && c1 == 0 && c2 == 10240 && c3 == 0) {
- // Microsoft released 26624 as 10240 accidentally.
- // https://developer.microsoft.com/en-us/windows/downloads/sdk-archive
- c2 = 26624;
- }
- if ((c0 > v0) || (c1 > v1) || (c2 > v2) || (c3 > v3)) {
- v0 = c0, v1 = c1, v2 = c2, v3 = c3;
- buf_init_from_str(&result_sdk->version10, ffd.cFileName);
- found_version_dir = true;
- }
- }
- if (FindNextFile(hFind, &ffd) == 0) {
- FindClose(hFind);
- break;
- }
- }
- if (!found_version_dir) {
- buf_resize(&result_sdk->path10, 0);
- }
- }
-
- if (buf_len(&result_sdk->path81) != 0) {
- Buf *sdk_lib_dir = buf_sprintf("%s\\Lib\\winv*", buf_ptr(&result_sdk->path81));
-
- // enumerate files in sdk path looking for latest version
- WIN32_FIND_DATA ffd;
- HANDLE hFind = FindFirstFileA(buf_ptr(sdk_lib_dir), &ffd);
- if (hFind == INVALID_HANDLE_VALUE) {
- return ErrorFileNotFound;
- }
- int v0 = 0, v1 = 0;
- bool found_version_dir = false;
- for (;;) {
- if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
- int c0 = 0, c1 = 0;
- sscanf(ffd.cFileName, "winv%d.%d", &c0, &c1);
- if ((c0 > v0) || (c1 > v1)) {
- v0 = c0, v1 = c1;
- buf_init_from_str(&result_sdk->version81, ffd.cFileName);
- found_version_dir = true;
- }
- }
- if (FindNextFile(hFind, &ffd) == 0) {
- FindClose(hFind);
- break;
- }
- }
- if (!found_version_dir) {
- buf_resize(&result_sdk->path81, 0);
- }
- }
-
- *out_sdk = result_sdk;
- return 0;
-#else
- return ErrorFileNotFound;
-#endif
-}
-
-int os_get_win32_vcruntime_path(Buf* output_buf, ZigLLVM_ArchType platform_type) {
-#if defined(ZIG_OS_WINDOWS)
- buf_resize(output_buf, 0);
- //COM Smart Pointerse requires explicit scope
- {
- HRESULT rc;
- rc = CoInitializeEx(NULL, COINIT_MULTITHREADED);
- if (rc != S_OK) {
- goto com_done;
- }
-
- //This COM class is installed when a VS2017
- ISetupConfigurationPtr setup_config;
- rc = setup_config.CreateInstance(__uuidof(SetupConfiguration));
- if (rc != S_OK) {
- goto com_done;
- }
-
- IEnumSetupInstancesPtr all_instances;
- rc = setup_config->EnumInstances(&all_instances);
- if (rc != S_OK) {
- goto com_done;
- }
-
- ISetupInstance* curr_instance;
- ULONG found_inst;
- while ((rc = all_instances->Next(1, &curr_instance, &found_inst) == S_OK)) {
- BSTR bstr_inst_path;
- rc = curr_instance->GetInstallationPath(&bstr_inst_path);
- if (rc != S_OK) {
- goto com_done;
- }
- //BSTRs are UTF-16 encoded, so we need to convert the string & adjust the length
- UINT bstr_path_len = *((UINT*)bstr_inst_path - 1);
- ULONG tmp_path_len = bstr_path_len / 2 + 1;
- char* conv_path = (char*)bstr_inst_path;
- char *tmp_path = (char*)alloca(tmp_path_len);
- memset(tmp_path, 0, tmp_path_len);
- uint32_t c = 0;
- for (uint32_t i = 0; i < bstr_path_len; i += 2) {
- tmp_path[c] = conv_path[i];
- ++c;
- assert(c != tmp_path_len);
- }
-
- buf_append_str(output_buf, tmp_path);
- buf_append_char(output_buf, '\\');
-
- Buf* tmp_buf = buf_alloc();
- buf_append_buf(tmp_buf, output_buf);
- buf_append_str(tmp_buf, "VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt");
- FILE* tools_file = fopen(buf_ptr(tmp_buf), "r");
- if (!tools_file) {
- goto com_done;
- }
- memset(tmp_path, 0, tmp_path_len);
- fgets(tmp_path, tmp_path_len, tools_file);
- strtok(tmp_path, " \r\n");
- fclose(tools_file);
- buf_appendf(output_buf, "VC\\Tools\\MSVC\\%s\\lib\\", tmp_path);
- switch (platform_type) {
- case ZigLLVM_x86:
- buf_append_str(output_buf, "x86\\");
- break;
- case ZigLLVM_x86_64:
- buf_append_str(output_buf, "x64\\");
- break;
- case ZigLLVM_arm:
- buf_append_str(output_buf, "arm\\");
- break;
- default:
- zig_panic("Attemped to use vcruntime for non-supported platform.");
- }
- buf_resize(tmp_buf, 0);
- buf_append_buf(tmp_buf, output_buf);
- buf_append_str(tmp_buf, "vcruntime.lib");
-
- if (GetFileAttributesA(buf_ptr(tmp_buf)) != INVALID_FILE_ATTRIBUTES) {
- return 0;
- }
- }
- }
-
-com_done:;
- HKEY key;
- HRESULT rc;
- rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7", 0, KEY_QUERY_VALUE | KEY_WOW64_32KEY, &key);
- if (rc != ERROR_SUCCESS) {
- return ErrorFileNotFound;
- }
-
- DWORD dw_type = 0;
- DWORD cb_data = 0;
- rc = RegQueryValueEx(key, "14.0", NULL, &dw_type, NULL, &cb_data);
- if ((rc == ERROR_FILE_NOT_FOUND) || (REG_SZ != dw_type)) {
- return ErrorFileNotFound;
- }
-
- Buf* tmp_buf = buf_alloc_fixed(cb_data);
- RegQueryValueExA(key, "14.0", NULL, NULL, (LPBYTE)buf_ptr(tmp_buf), &cb_data);
- //RegQueryValueExA returns the length of the string INCLUDING the null terminator
- buf_resize(tmp_buf, cb_data-1);
- buf_append_str(tmp_buf, "VC\\Lib\\");
- switch (platform_type) {
- case ZigLLVM_x86:
- //x86 is in the root of the Lib folder
- break;
- case ZigLLVM_x86_64:
- buf_append_str(tmp_buf, "amd64\\");
- break;
- case ZigLLVM_arm:
- buf_append_str(tmp_buf, "arm\\");
- break;
- default:
- zig_panic("Attemped to use vcruntime for non-supported platform.");
- }
-
- buf_append_buf(output_buf, tmp_buf);
- buf_append_str(tmp_buf, "vcruntime.lib");
-
- if (GetFileAttributesA(buf_ptr(tmp_buf)) != INVALID_FILE_ATTRIBUTES) {
- return 0;
- } else {
- buf_resize(output_buf, 0);
- return ErrorFileNotFound;
- }
-#else
- return ErrorFileNotFound;
-#endif
-}
-
int os_get_win32_ucrt_lib_path(ZigWindowsSDK *sdk, Buf* output_buf, ZigLLVM_ArchType platform_type) {
#if defined(ZIG_OS_WINDOWS)
buf_resize(output_buf, 0);
- buf_appendf(output_buf, "%s\\Lib\\%s\\ucrt\\", buf_ptr(&sdk->path10), buf_ptr(&sdk->version10));
+ buf_appendf(output_buf, "%s\\Lib\\%s\\ucrt\\", sdk->path10_ptr, sdk->version10_ptr);
switch (platform_type) {
case ZigLLVM_x86:
buf_append_str(output_buf, "x86\\");
@@ -1389,7 +1149,7 @@ int os_get_win32_ucrt_lib_path(ZigWindowsSDK *sdk, Buf* output_buf, ZigLLVM_Arch
int os_get_win32_ucrt_include_path(ZigWindowsSDK *sdk, Buf* output_buf) {
#if defined(ZIG_OS_WINDOWS)
buf_resize(output_buf, 0);
- buf_appendf(output_buf, "%s\\Include\\%s\\ucrt", buf_ptr(&sdk->path10), buf_ptr(&sdk->version10));
+ buf_appendf(output_buf, "%s\\Include\\%s\\ucrt", sdk->path10_ptr, sdk->version10_ptr);
if (GetFileAttributesA(buf_ptr(output_buf)) != INVALID_FILE_ATTRIBUTES) {
return 0;
}
@@ -1406,7 +1166,7 @@ int os_get_win32_kern32_path(ZigWindowsSDK *sdk, Buf* output_buf, ZigLLVM_ArchTy
#if defined(ZIG_OS_WINDOWS)
{
buf_resize(output_buf, 0);
- buf_appendf(output_buf, "%s\\Lib\\%s\\um\\", buf_ptr(&sdk->path10), buf_ptr(&sdk->version10));
+ buf_appendf(output_buf, "%s\\Lib\\%s\\um\\", sdk->path10_ptr, sdk->version10_ptr);
switch (platform_type) {
case ZigLLVM_x86:
buf_append_str(output_buf, "x86\\");
@@ -1429,7 +1189,7 @@ int os_get_win32_kern32_path(ZigWindowsSDK *sdk, Buf* output_buf, ZigLLVM_ArchTy
}
{
buf_resize(output_buf, 0);
- buf_appendf(output_buf, "%s\\Lib\\%s\\um\\", buf_ptr(&sdk->path81), buf_ptr(&sdk->version81));
+ buf_appendf(output_buf, "%s\\Lib\\%s\\um\\", sdk->path81_ptr, sdk->version81_ptr);
switch (platform_type) {
case ZigLLVM_x86:
buf_append_str(output_buf, "x86\\");
diff --git a/src/os.hpp b/src/os.hpp
index b94e98ec3d..cfe4e8f3a2 100644
--- a/src/os.hpp
+++ b/src/os.hpp
@@ -12,6 +12,7 @@
#include "buffer.hpp"
#include "error.hpp"
#include "zig_llvm.h"
+#include "windows_sdk.h"
#include
#include
@@ -79,15 +80,6 @@ bool os_is_sep(uint8_t c);
int os_self_exe_path(Buf *out_path);
-struct ZigWindowsSDK {
- Buf path10;
- Buf version10;
- Buf path81;
- Buf version81;
-};
-
-int os_find_windows_sdk(ZigWindowsSDK **out_sdk);
-int os_get_win32_vcruntime_path(Buf *output_buf, ZigLLVM_ArchType platform_type);
int os_get_win32_ucrt_include_path(ZigWindowsSDK *sdk, Buf *output_buf);
int os_get_win32_ucrt_lib_path(ZigWindowsSDK *sdk, Buf *output_buf, ZigLLVM_ArchType platform_type);
int os_get_win32_kern32_path(ZigWindowsSDK *sdk, Buf *output_buf, ZigLLVM_ArchType platform_type);
diff --git a/src/tokenizer.cpp b/src/tokenizer.cpp
index f7f41af8a6..1d3db5567a 100644
--- a/src/tokenizer.cpp
+++ b/src/tokenizer.cpp
@@ -460,16 +460,21 @@ static const char* get_escape_shorthand(uint8_t c) {
static void invalid_char_error(Tokenize *t, uint8_t c) {
if (c == '\r') {
tokenize_error(t, "invalid carriage return, only '\\n' line endings are supported");
- } else if (isprint(c)) {
- tokenize_error(t, "invalid character: '%c'", c);
- } else {
- const char *sh = get_escape_shorthand(c);
- if (sh) {
- tokenize_error(t, "invalid character: '%s'", sh);
- } else {
- tokenize_error(t, "invalid character: '\\x%x'", c);
- }
+ return;
}
+
+ const char *sh = get_escape_shorthand(c);
+ if (sh) {
+ tokenize_error(t, "invalid character: '%s'", sh);
+ return;
+ }
+
+ if (isprint(c)) {
+ tokenize_error(t, "invalid character: '%c'", c);
+ return;
+ }
+
+ tokenize_error(t, "invalid character: '\\x%02x'", c);
}
void tokenize(Buf *buf, Tokenization *out) {
diff --git a/src/windows_sdk.cpp b/src/windows_sdk.cpp
new file mode 100644
index 0000000000..0f9d0fc301
--- /dev/null
+++ b/src/windows_sdk.cpp
@@ -0,0 +1,352 @@
+/*
+ * Copyright (c) 2018 Andrew Kelley
+ *
+ * This file is part of zig, which is MIT licensed.
+ * See http://opensource.org/licenses/MIT
+ */
+
+#include "windows_sdk.h"
+
+#if defined(_WIN32)
+
+#include "windows_com.hpp"
+#include
+#include
+
+struct ZigWindowsSDKPrivate {
+ ZigWindowsSDK base;
+};
+
+enum NativeArch {
+ NativeArchArm,
+ NativeArchi386,
+ NativeArchx86_64,
+};
+
+#if defined(_M_ARM) || defined(__arm_)
+static const NativeArch native_arch = NativeArchArm;
+#endif
+#if defined(_M_IX86) || defined(__i386__)
+static const NativeArch native_arch = NativeArchi386;
+#endif
+#if defined(_M_X64) || defined(__x86_64__)
+static const NativeArch native_arch = NativeArchx86_64;
+#endif
+
+void zig_free_windows_sdk(struct ZigWindowsSDK *sdk) {
+ if (sdk == nullptr) {
+ return;
+ }
+ free((void*)sdk->path10_ptr);
+ free((void*)sdk->version10_ptr);
+ free((void*)sdk->path81_ptr);
+ free((void*)sdk->version81_ptr);
+ free((void*)sdk->msvc_lib_dir_ptr);
+}
+
+static ZigFindWindowsSdkError find_msvc_lib_dir(ZigWindowsSDKPrivate *priv) {
+ //COM Smart Pointers requires explicit scope
+ {
+ HRESULT rc = CoInitializeEx(NULL, COINIT_MULTITHREADED);
+ if (rc != S_OK && rc != S_FALSE) {
+ goto com_done;
+ }
+
+ //This COM class is installed when a VS2017
+ ISetupConfigurationPtr setup_config;
+ rc = setup_config.CreateInstance(__uuidof(SetupConfiguration));
+ if (rc != S_OK) {
+ goto com_done;
+ }
+
+ IEnumSetupInstancesPtr all_instances;
+ rc = setup_config->EnumInstances(&all_instances);
+ if (rc != S_OK) {
+ goto com_done;
+ }
+
+ ISetupInstance* curr_instance;
+ ULONG found_inst;
+ while ((rc = all_instances->Next(1, &curr_instance, &found_inst) == S_OK)) {
+ BSTR bstr_inst_path;
+ rc = curr_instance->GetInstallationPath(&bstr_inst_path);
+ if (rc != S_OK) {
+ goto com_done;
+ }
+ //BSTRs are UTF-16 encoded, so we need to convert the string & adjust the length
+ //TODO call an actual function to do this
+ UINT bstr_path_len = *((UINT*)bstr_inst_path - 1);
+ ULONG tmp_path_len = bstr_path_len / 2 + 1;
+ char* conv_path = (char*)bstr_inst_path;
+ // TODO don't use alloca
+ char *tmp_path = (char*)alloca(tmp_path_len);
+ memset(tmp_path, 0, tmp_path_len);
+ uint32_t c = 0;
+ for (uint32_t i = 0; i < bstr_path_len; i += 2) {
+ tmp_path[c] = conv_path[i];
+ ++c;
+ assert(c != tmp_path_len);
+ }
+ char output_path[4096];
+ output_path[0] = 0;
+ char *out_append_ptr = output_path;
+
+ out_append_ptr += sprintf(out_append_ptr, "%s\\", tmp_path);
+
+ char tmp_buf[4096];
+ sprintf(tmp_buf, "%s%s", output_path, "VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt");
+ FILE* tools_file = fopen(tmp_buf, "rb");
+ if (!tools_file) {
+ goto com_done;
+ }
+ memset(tmp_path, 0, tmp_path_len);
+ fgets(tmp_path, tmp_path_len, tools_file);
+ strtok(tmp_path, " \r\n");
+ fclose(tools_file);
+ out_append_ptr += sprintf(out_append_ptr, "VC\\Tools\\MSVC\\%s\\lib\\", tmp_path);
+ switch (native_arch) {
+ case NativeArchi386:
+ out_append_ptr += sprintf(out_append_ptr, "x86\\");
+ break;
+ case NativeArchx86_64:
+ out_append_ptr += sprintf(out_append_ptr, "x64\\");
+ break;
+ case NativeArchArm:
+ out_append_ptr += sprintf(out_append_ptr, "arm\\");
+ break;
+ }
+ sprintf(tmp_buf, "%s%s", output_path, "vcruntime.lib");
+
+ if (GetFileAttributesA(tmp_buf) != INVALID_FILE_ATTRIBUTES) {
+ priv->base.msvc_lib_dir_ptr = strdup(output_path);
+ if (priv->base.msvc_lib_dir_ptr == nullptr) {
+ return ZigFindWindowsSdkErrorOutOfMemory;
+ }
+ priv->base.msvc_lib_dir_len = strlen(priv->base.msvc_lib_dir_ptr);
+ return ZigFindWindowsSdkErrorNone;
+ }
+ }
+ }
+
+com_done:;
+ HKEY key;
+ HRESULT rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\VisualStudio\\SxS\\VS7", 0,
+ KEY_QUERY_VALUE | KEY_WOW64_32KEY, &key);
+ if (rc != ERROR_SUCCESS) {
+ return ZigFindWindowsSdkErrorNotFound;
+ }
+
+ DWORD dw_type = 0;
+ DWORD cb_data = 0;
+ rc = RegQueryValueEx(key, "14.0", NULL, &dw_type, NULL, &cb_data);
+ if ((rc == ERROR_FILE_NOT_FOUND) || (REG_SZ != dw_type)) {
+ return ZigFindWindowsSdkErrorNotFound;
+ }
+
+ char tmp_buf[4096];
+
+ RegQueryValueExA(key, "14.0", NULL, NULL, (LPBYTE)tmp_buf, &cb_data);
+ // RegQueryValueExA returns the length of the string INCLUDING the null terminator
+ char *tmp_buf_append_ptr = tmp_buf + (cb_data - 1);
+ tmp_buf_append_ptr += sprintf(tmp_buf_append_ptr, "VC\\Lib\\");
+ switch (native_arch) {
+ case NativeArchi386:
+ //x86 is in the root of the Lib folder
+ break;
+ case NativeArchx86_64:
+ tmp_buf_append_ptr += sprintf(tmp_buf_append_ptr, "amd64\\");
+ break;
+ case NativeArchArm:
+ tmp_buf_append_ptr += sprintf(tmp_buf_append_ptr, "arm\\");
+ break;
+ }
+
+ char *output_path = strdup(tmp_buf);
+ if (output_path == nullptr) {
+ return ZigFindWindowsSdkErrorOutOfMemory;
+ }
+
+ tmp_buf_append_ptr += sprintf(tmp_buf_append_ptr, "vcruntime.lib");
+
+ if (GetFileAttributesA(tmp_buf) != INVALID_FILE_ATTRIBUTES) {
+ priv->base.msvc_lib_dir_ptr = output_path;
+ priv->base.msvc_lib_dir_len = strlen(output_path);
+ return ZigFindWindowsSdkErrorNone;
+ } else {
+ free(output_path);
+ return ZigFindWindowsSdkErrorNotFound;
+ }
+}
+
+static ZigFindWindowsSdkError find_10_version(ZigWindowsSDKPrivate *priv) {
+ if (priv->base.path10_ptr == nullptr)
+ return ZigFindWindowsSdkErrorNone;
+
+ char sdk_lib_dir[4096];
+ int n = snprintf(sdk_lib_dir, 4096, "%s\\Lib\\*", priv->base.path10_ptr);
+ if (n < 0 || n >= 4096) {
+ return ZigFindWindowsSdkErrorPathTooLong;
+ }
+
+ // enumerate files in sdk path looking for latest version
+ WIN32_FIND_DATA ffd;
+ HANDLE hFind = FindFirstFileA(sdk_lib_dir, &ffd);
+ if (hFind == INVALID_HANDLE_VALUE) {
+ return ZigFindWindowsSdkErrorNotFound;
+ }
+ int v0 = 0, v1 = 0, v2 = 0, v3 = 0;
+ for (;;) {
+ if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
+ int c0 = 0, c1 = 0, c2 = 0, c3 = 0;
+ sscanf(ffd.cFileName, "%d.%d.%d.%d", &c0, &c1, &c2, &c3);
+ if (c0 == 10 && c1 == 0 && c2 == 10240 && c3 == 0) {
+ // Microsoft released 26624 as 10240 accidentally.
+ // https://developer.microsoft.com/en-us/windows/downloads/sdk-archive
+ c2 = 26624;
+ }
+ if ((c0 > v0) || (c1 > v1) || (c2 > v2) || (c3 > v3)) {
+ v0 = c0, v1 = c1, v2 = c2, v3 = c3;
+ free((void*)priv->base.version10_ptr);
+ priv->base.version10_ptr = strdup(ffd.cFileName);
+ if (priv->base.version10_ptr == nullptr) {
+ FindClose(hFind);
+ return ZigFindWindowsSdkErrorOutOfMemory;
+ }
+ }
+ }
+ if (FindNextFile(hFind, &ffd) == 0) {
+ FindClose(hFind);
+ break;
+ }
+ }
+ priv->base.version10_len = strlen(priv->base.version10_ptr);
+ return ZigFindWindowsSdkErrorNone;
+}
+
+static ZigFindWindowsSdkError find_81_version(ZigWindowsSDKPrivate *priv) {
+ if (priv->base.path81_ptr == nullptr)
+ return ZigFindWindowsSdkErrorNone;
+
+ char sdk_lib_dir[4096];
+ int n = snprintf(sdk_lib_dir, 4096, "%s\\Lib\\winv*", priv->base.path81_ptr);
+ if (n < 0 || n >= 4096) {
+ return ZigFindWindowsSdkErrorPathTooLong;
+ }
+
+ // enumerate files in sdk path looking for latest version
+ WIN32_FIND_DATA ffd;
+ HANDLE hFind = FindFirstFileA(sdk_lib_dir, &ffd);
+ if (hFind == INVALID_HANDLE_VALUE) {
+ return ZigFindWindowsSdkErrorNotFound;
+ }
+ int v0 = 0, v1 = 0;
+ for (;;) {
+ if (ffd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
+ int c0 = 0, c1 = 0;
+ sscanf(ffd.cFileName, "winv%d.%d", &c0, &c1);
+ if ((c0 > v0) || (c1 > v1)) {
+ v0 = c0, v1 = c1;
+ free((void*)priv->base.version81_ptr);
+ priv->base.version81_ptr = strdup(ffd.cFileName);
+ if (priv->base.version81_ptr == nullptr) {
+ FindClose(hFind);
+ return ZigFindWindowsSdkErrorOutOfMemory;
+ }
+ }
+ }
+ if (FindNextFile(hFind, &ffd) == 0) {
+ FindClose(hFind);
+ break;
+ }
+ }
+ priv->base.version81_len = strlen(priv->base.version81_ptr);
+ return ZigFindWindowsSdkErrorNone;
+}
+
+ZigFindWindowsSdkError zig_find_windows_sdk(struct ZigWindowsSDK **out_sdk) {
+ ZigWindowsSDKPrivate *priv = (ZigWindowsSDKPrivate*)calloc(1, sizeof(ZigWindowsSDKPrivate));
+ if (priv == nullptr) {
+ return ZigFindWindowsSdkErrorOutOfMemory;
+ }
+
+ HKEY key;
+ HRESULT rc;
+ rc = RegOpenKeyEx(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots", 0,
+ KEY_QUERY_VALUE | KEY_WOW64_32KEY | KEY_ENUMERATE_SUB_KEYS, &key);
+ if (rc != ERROR_SUCCESS) {
+ zig_free_windows_sdk(&priv->base);
+ return ZigFindWindowsSdkErrorNotFound;
+ }
+
+ {
+ DWORD tmp_buf_len = MAX_PATH;
+ priv->base.path10_ptr = (const char *)calloc(tmp_buf_len, 1);
+ if (priv->base.path10_ptr == nullptr) {
+ zig_free_windows_sdk(&priv->base);
+ return ZigFindWindowsSdkErrorOutOfMemory;
+ }
+ rc = RegQueryValueEx(key, "KitsRoot10", NULL, NULL, (LPBYTE)priv->base.path10_ptr, &tmp_buf_len);
+ if (rc == ERROR_SUCCESS) {
+ priv->base.path10_len = tmp_buf_len - 1;
+ if (priv->base.path10_ptr[priv->base.path10_len - 1] == '\\') {
+ priv->base.path10_len -= 1;
+ }
+ } else {
+ free((void*)priv->base.path10_ptr);
+ priv->base.path10_ptr = nullptr;
+ }
+ }
+ {
+ DWORD tmp_buf_len = MAX_PATH;
+ priv->base.path81_ptr = (const char *)calloc(tmp_buf_len, 1);
+ if (priv->base.path81_ptr == nullptr) {
+ zig_free_windows_sdk(&priv->base);
+ return ZigFindWindowsSdkErrorOutOfMemory;
+ }
+ rc = RegQueryValueEx(key, "KitsRoot81", NULL, NULL, (LPBYTE)priv->base.path81_ptr, &tmp_buf_len);
+ if (rc == ERROR_SUCCESS) {
+ priv->base.path81_len = tmp_buf_len - 1;
+ if (priv->base.path81_ptr[priv->base.path81_len - 1] == '\\') {
+ priv->base.path81_len -= 1;
+ }
+ } else {
+ free((void*)priv->base.path81_ptr);
+ priv->base.path81_ptr = nullptr;
+ }
+ }
+
+ {
+ ZigFindWindowsSdkError err = find_10_version(priv);
+ if (err == ZigFindWindowsSdkErrorOutOfMemory) {
+ zig_free_windows_sdk(&priv->base);
+ return err;
+ }
+ }
+ {
+ ZigFindWindowsSdkError err = find_81_version(priv);
+ if (err == ZigFindWindowsSdkErrorOutOfMemory) {
+ zig_free_windows_sdk(&priv->base);
+ return err;
+ }
+ }
+
+ {
+ ZigFindWindowsSdkError err = find_msvc_lib_dir(priv);
+ if (err == ZigFindWindowsSdkErrorOutOfMemory) {
+ zig_free_windows_sdk(&priv->base);
+ return err;
+ }
+ }
+
+ *out_sdk = &priv->base;
+ return ZigFindWindowsSdkErrorNone;
+}
+
+#else
+
+void zig_free_windows_sdk(struct ZigWindowsSDK *sdk) {}
+ZigFindWindowsSdkError zig_find_windows_sdk(struct ZigWindowsSDK **out_sdk) {
+ return ZigFindWindowsSdkErrorNotFound;
+}
+
+#endif
diff --git a/src/windows_sdk.h b/src/windows_sdk.h
new file mode 100644
index 0000000000..2d531ad372
--- /dev/null
+++ b/src/windows_sdk.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2018 Andrew Kelley
+ *
+ * This file is part of zig, which is MIT licensed.
+ * See http://opensource.org/licenses/MIT
+ */
+
+#ifndef ZIG_WINDOWS_SDK_H
+#define ZIG_WINDOWS_SDK_H
+
+#ifdef __cplusplus
+#define ZIG_EXTERN_C extern "C"
+#else
+#define ZIG_EXTERN_C
+#endif
+
+#include
+
+struct ZigWindowsSDK {
+ const char *path10_ptr;
+ size_t path10_len;
+
+ const char *version10_ptr;
+ size_t version10_len;
+
+ const char *path81_ptr;
+ size_t path81_len;
+
+ const char *version81_ptr;
+ size_t version81_len;
+
+ const char *msvc_lib_dir_ptr;
+ size_t msvc_lib_dir_len;
+};
+
+enum ZigFindWindowsSdkError {
+ ZigFindWindowsSdkErrorNone,
+ ZigFindWindowsSdkErrorOutOfMemory,
+ ZigFindWindowsSdkErrorNotFound,
+ ZigFindWindowsSdkErrorPathTooLong,
+};
+
+ZIG_EXTERN_C enum ZigFindWindowsSdkError zig_find_windows_sdk(struct ZigWindowsSDK **out_sdk);
+
+ZIG_EXTERN_C void zig_free_windows_sdk(struct ZigWindowsSDK *sdk);
+
+#endif
diff --git a/src/zig_llvm.cpp b/src/zig_llvm.cpp
index f53e097577..ec46c37d96 100644
--- a/src/zig_llvm.cpp
+++ b/src/zig_llvm.cpp
@@ -455,6 +455,11 @@ ZigLLVMDIBuilder *ZigLLVMCreateDIBuilder(LLVMModuleRef module, bool allow_unreso
return reinterpret_cast(di_builder);
}
+void ZigLLVMDisposeDIBuilder(ZigLLVMDIBuilder *dbuilder) {
+ DIBuilder *di_builder = reinterpret_cast(dbuilder);
+ delete di_builder;
+}
+
void ZigLLVMSetCurrentDebugLocation(LLVMBuilderRef builder, int line, int column, ZigLLVMDIScope *scope) {
unwrap(builder)->SetCurrentDebugLocation(DebugLoc::get(
line, column, reinterpret_cast(scope)));
diff --git a/src/zig_llvm.h b/src/zig_llvm.h
index 5e404183b7..d026ae61a3 100644
--- a/src/zig_llvm.h
+++ b/src/zig_llvm.h
@@ -22,6 +22,9 @@
#define ZIG_EXTERN_C
#endif
+// ATTENTION: If you modify this file, be sure to update the corresponding
+// extern function declarations in the self-hosted compiler.
+
struct ZigLLVMDIType;
struct ZigLLVMDIBuilder;
struct ZigLLVMDICompileUnit;
@@ -39,7 +42,7 @@ struct ZigLLVMInsertionPoint;
ZIG_EXTERN_C void ZigLLVMInitializeLoopStrengthReducePass(LLVMPassRegistryRef R);
ZIG_EXTERN_C void ZigLLVMInitializeLowerIntrinsicsPass(LLVMPassRegistryRef R);
-/// Caller must free memory.
+/// Caller must free memory with LLVMDisposeMessage
ZIG_EXTERN_C char *ZigLLVMGetHostCPUName(void);
ZIG_EXTERN_C char *ZigLLVMGetNativeFeatures(void);
@@ -145,6 +148,7 @@ ZIG_EXTERN_C unsigned ZigLLVMTag_DW_enumeration_type(void);
ZIG_EXTERN_C unsigned ZigLLVMTag_DW_union_type(void);
ZIG_EXTERN_C struct ZigLLVMDIBuilder *ZigLLVMCreateDIBuilder(LLVMModuleRef module, bool allow_unresolved);
+ZIG_EXTERN_C void ZigLLVMDisposeDIBuilder(struct ZigLLVMDIBuilder *dbuilder);
ZIG_EXTERN_C void ZigLLVMAddModuleDebugInfoFlag(LLVMModuleRef module);
ZIG_EXTERN_C void ZigLLVMAddModuleCodeViewFlag(LLVMModuleRef module);
diff --git a/std/array_list.zig b/std/array_list.zig
index 8d7bde46a1..298026d11c 100644
--- a/std/array_list.zig
+++ b/std/array_list.zig
@@ -113,13 +113,12 @@ pub fn AlignedArrayList(comptime T: type, comptime A: u29) type {
return old_item;
}
- pub fn removeOrError(self: *Self, n: usize) !T {
- if (n >= self.len) return error.OutOfBounds;
- if (self.len - 1 == n) return self.pop();
-
- var old_item = self.at(n);
- try self.setOrError(n, self.pop());
- return old_item;
+ /// Removes the element at the specified index and returns it
+ /// or an error.OutOfBounds is returned. If no error then
+ /// the empty slot is filled from the end of the list.
+ pub fn swapRemoveOrError(self: *Self, i: usize) !T {
+ if (i >= self.len) return error.OutOfBounds;
+ return self.swapRemove(i);
}
pub fn appendSlice(self: *Self, items: []align(A) const T) !void {
@@ -192,7 +191,7 @@ pub fn AlignedArrayList(comptime T: type, comptime A: u29) type {
};
}
-test "basic ArrayList test" {
+test "std.ArrayList.basic" {
var bytes: [1024]u8 = undefined;
const allocator = &std.heap.FixedBufferAllocator.init(bytes[0..]).allocator;
@@ -279,7 +278,35 @@ test "std.ArrayList.swapRemove" {
assert(list.len == 4);
}
-test "iterator ArrayList test" {
+test "std.ArrayList.swapRemoveOrError" {
+ var list = ArrayList(i32).init(debug.global_allocator);
+ defer list.deinit();
+
+ // Test just after initialization
+ assertError(list.swapRemoveOrError(0), error.OutOfBounds);
+
+ // Test after adding one item and remote it
+ try list.append(1);
+ assert((try list.swapRemoveOrError(0)) == 1);
+ assertError(list.swapRemoveOrError(0), error.OutOfBounds);
+
+ // Test after adding two items and remote both
+ try list.append(1);
+ try list.append(2);
+ assert((try list.swapRemoveOrError(1)) == 2);
+ assert((try list.swapRemoveOrError(0)) == 1);
+ assertError(list.swapRemoveOrError(0), error.OutOfBounds);
+
+ // Test out of bounds with one item
+ try list.append(1);
+ assertError(list.swapRemoveOrError(1), error.OutOfBounds);
+
+ // Test out of bounds with two items
+ try list.append(2);
+ assertError(list.swapRemoveOrError(2), error.OutOfBounds);
+}
+
+test "std.ArrayList.iterator" {
var list = ArrayList(i32).init(debug.global_allocator);
defer list.deinit();
@@ -308,7 +335,7 @@ test "iterator ArrayList test" {
assert(it.next().? == 1);
}
-test "insert ArrayList test" {
+test "std.ArrayList.insert" {
var list = ArrayList(i32).init(debug.global_allocator);
defer list.deinit();
@@ -322,7 +349,7 @@ test "insert ArrayList test" {
assert(list.items[3] == 3);
}
-test "insertSlice ArrayList test" {
+test "std.ArrayList.insertSlice" {
var list = ArrayList(i32).init(debug.global_allocator);
defer list.deinit();
diff --git a/std/atomic/int.zig b/std/atomic/int.zig
index d51454c673..4103d52719 100644
--- a/std/atomic/int.zig
+++ b/std/atomic/int.zig
@@ -25,5 +25,9 @@ pub fn Int(comptime T: type) type {
pub fn get(self: *Self) T {
return @atomicLoad(T, &self.unprotected_value, AtomicOrder.SeqCst);
}
+
+ pub fn xchg(self: *Self, new_value: T) T {
+ return @atomicRmw(T, &self.unprotected_value, builtin.AtomicRmwOp.Xchg, new_value, AtomicOrder.SeqCst);
+ }
};
}
diff --git a/std/atomic/queue.zig b/std/atomic/queue.zig
index 1fd07714e8..df31c88d2a 100644
--- a/std/atomic/queue.zig
+++ b/std/atomic/queue.zig
@@ -51,6 +51,20 @@ pub fn Queue(comptime T: type) type {
return head;
}
+ pub fn unget(self: *Self, node: *Node) void {
+ while (@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) != 0) {}
+ defer assert(@atomicRmw(u8, &self.lock, builtin.AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst) == 1);
+
+ const opt_head = self.head;
+ self.head = node;
+ if (opt_head) |head| {
+ head.next = node;
+ } else {
+ assert(self.tail == null);
+ self.tail = node;
+ }
+ }
+
pub fn isEmpty(self: *Self) bool {
return @atomicLoad(?*Node, &self.head, builtin.AtomicOrder.SeqCst) != null;
}
diff --git a/std/buffer.zig b/std/buffer.zig
index aff7fa86ef..3b58002aba 100644
--- a/std/buffer.zig
+++ b/std/buffer.zig
@@ -54,6 +54,19 @@ pub const Buffer = struct {
return result;
}
+ pub fn allocPrint(allocator: *Allocator, comptime format: []const u8, args: ...) !Buffer {
+ const countSize = struct {
+ fn countSize(size: *usize, bytes: []const u8) (error{}!void) {
+ size.* += bytes.len;
+ }
+ }.countSize;
+ var size: usize = 0;
+ std.fmt.format(&size, error{}, countSize, format, args) catch |err| switch (err) {};
+ var self = try Buffer.initSize(allocator, size);
+ assert((std.fmt.bufPrint(self.list.items, format, args) catch unreachable).len == size);
+ return self;
+ }
+
pub fn deinit(self: *Buffer) void {
self.list.deinit();
}
diff --git a/std/c/darwin.zig b/std/c/darwin.zig
index 4189dfeadc..1bd1d6c4c9 100644
--- a/std/c/darwin.zig
+++ b/std/c/darwin.zig
@@ -30,7 +30,7 @@ pub extern "c" fn sysctl(name: [*]c_int, namelen: c_uint, oldp: ?*c_void, oldlen
pub extern "c" fn sysctlbyname(name: [*]const u8, oldp: ?*c_void, oldlenp: ?*usize, newp: ?*c_void, newlen: usize) c_int;
pub extern "c" fn sysctlnametomib(name: [*]const u8, mibp: ?*c_int, sizep: ?*usize) c_int;
-pub use @import("../os/darwin_errno.zig");
+pub use @import("../os/darwin/errno.zig");
pub const _errno = __error;
diff --git a/std/dwarf.zig b/std/dwarf.zig
index 76ed122447..2cf8ed953e 100644
--- a/std/dwarf.zig
+++ b/std/dwarf.zig
@@ -639,3 +639,40 @@ pub const LNE_define_file = 0x03;
pub const LNE_set_discriminator = 0x04;
pub const LNE_lo_user = 0x80;
pub const LNE_hi_user = 0xff;
+
+pub const LANG_C89 = 0x0001;
+pub const LANG_C = 0x0002;
+pub const LANG_Ada83 = 0x0003;
+pub const LANG_C_plus_plus = 0x0004;
+pub const LANG_Cobol74 = 0x0005;
+pub const LANG_Cobol85 = 0x0006;
+pub const LANG_Fortran77 = 0x0007;
+pub const LANG_Fortran90 = 0x0008;
+pub const LANG_Pascal83 = 0x0009;
+pub const LANG_Modula2 = 0x000a;
+pub const LANG_Java = 0x000b;
+pub const LANG_C99 = 0x000c;
+pub const LANG_Ada95 = 0x000d;
+pub const LANG_Fortran95 = 0x000e;
+pub const LANG_PLI = 0x000f;
+pub const LANG_ObjC = 0x0010;
+pub const LANG_ObjC_plus_plus = 0x0011;
+pub const LANG_UPC = 0x0012;
+pub const LANG_D = 0x0013;
+pub const LANG_Python = 0x0014;
+pub const LANG_Go = 0x0016;
+pub const LANG_C_plus_plus_11 = 0x001a;
+pub const LANG_Rust = 0x001c;
+pub const LANG_C11 = 0x001d;
+pub const LANG_C_plus_plus_14 = 0x0021;
+pub const LANG_Fortran03 = 0x0022;
+pub const LANG_Fortran08 = 0x0023;
+pub const LANG_lo_user = 0x8000;
+pub const LANG_hi_user = 0xffff;
+pub const LANG_Mips_Assembler = 0x8001;
+pub const LANG_Upc = 0x8765;
+pub const LANG_HP_Bliss = 0x8003;
+pub const LANG_HP_Basic91 = 0x8004;
+pub const LANG_HP_Pascal91 = 0x8005;
+pub const LANG_HP_IMacro = 0x8006;
+pub const LANG_HP_Assembler = 0x8007;
diff --git a/std/event/future.zig b/std/event/future.zig
index 0f27b4131b..f5d14d1ca6 100644
--- a/std/event/future.zig
+++ b/std/event/future.zig
@@ -6,15 +6,20 @@ const AtomicOrder = builtin.AtomicOrder;
const Lock = std.event.Lock;
const Loop = std.event.Loop;
-/// This is a value that starts out unavailable, until a value is put().
+/// This is a value that starts out unavailable, until resolve() is called
/// While it is unavailable, coroutines suspend when they try to get() it,
-/// and then are resumed when the value is put().
-/// At this point the value remains forever available, and another put() is not allowed.
+/// and then are resumed when resolve() is called.
+/// At this point the value remains forever available, and another resolve() is not allowed.
pub fn Future(comptime T: type) type {
return struct {
lock: Lock,
data: T,
- available: u8, // TODO make this a bool
+
+ /// TODO make this an enum
+ /// 0 - not started
+ /// 1 - started
+ /// 2 - finished
+ available: u8,
const Self = this;
const Queue = std.atomic.Queue(promise);
@@ -31,7 +36,7 @@ pub fn Future(comptime T: type) type {
/// available.
/// Thread-safe.
pub async fn get(self: *Self) *T {
- if (@atomicLoad(u8, &self.available, AtomicOrder.SeqCst) == 1) {
+ if (@atomicLoad(u8, &self.available, AtomicOrder.SeqCst) == 2) {
return &self.data;
}
const held = await (async self.lock.acquire() catch unreachable);
@@ -43,18 +48,36 @@ pub fn Future(comptime T: type) type {
/// Gets the data without waiting for it. If it's available, a pointer is
/// returned. Otherwise, null is returned.
pub fn getOrNull(self: *Self) ?*T {
- if (@atomicLoad(u8, &self.available, AtomicOrder.SeqCst) == 1) {
+ if (@atomicLoad(u8, &self.available, AtomicOrder.SeqCst) == 2) {
return &self.data;
} else {
return null;
}
}
+ /// If someone else has started working on the data, wait for them to complete
+ /// and return a pointer to the data. Otherwise, return null, and the caller
+ /// should start working on the data.
+ /// It's not required to call start() before resolve() but it can be useful since
+ /// this method is thread-safe.
+ pub async fn start(self: *Self) ?*T {
+ const state = @cmpxchgStrong(u8, &self.available, 0, 1, AtomicOrder.SeqCst, AtomicOrder.SeqCst) orelse return null;
+ switch (state) {
+ 1 => {
+ const held = await (async self.lock.acquire() catch unreachable);
+ held.release();
+ return &self.data;
+ },
+ 2 => return &self.data,
+ else => unreachable,
+ }
+ }
+
/// Make the data become available. May be called only once.
/// Before calling this, modify the `data` property.
pub fn resolve(self: *Self) void {
- const prev = @atomicRmw(u8, &self.available, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst);
- assert(prev == 0); // put() called twice
+ const prev = @atomicRmw(u8, &self.available, AtomicRmwOp.Xchg, 2, AtomicOrder.SeqCst);
+ assert(prev == 0 or prev == 1); // resolve() called twice
Lock.Held.release(Lock.Held{ .lock = &self.lock });
}
};
diff --git a/std/event/group.zig b/std/event/group.zig
index c286803b53..26c098399e 100644
--- a/std/event/group.zig
+++ b/std/event/group.zig
@@ -6,7 +6,7 @@ const AtomicRmwOp = builtin.AtomicRmwOp;
const AtomicOrder = builtin.AtomicOrder;
const assert = std.debug.assert;
-/// ReturnType should be `void` or `E!void`
+/// ReturnType must be `void` or `E!void`
pub fn Group(comptime ReturnType: type) type {
return struct {
coro_stack: Stack,
@@ -38,8 +38,17 @@ pub fn Group(comptime ReturnType: type) type {
self.alloc_stack.push(node);
}
+ /// Add a node to the group. Thread-safe. Cannot fail.
+ /// `node.data` should be the promise handle to add to the group.
+ /// The node's memory should be in the coroutine frame of
+ /// the handle that is in the node, or somewhere guaranteed to live
+ /// at least as long.
+ pub fn addNode(self: *Self, node: *Stack.Node) void {
+ self.coro_stack.push(node);
+ }
+
/// This is equivalent to an async call, but the async function is added to the group, instead
- /// of returning a promise. func must be async and have return type void.
+ /// of returning a promise. func must be async and have return type ReturnType.
/// Thread-safe.
pub fn call(self: *Self, comptime func: var, args: ...) (error{OutOfMemory}!void) {
const S = struct {
@@ -67,6 +76,7 @@ pub fn Group(comptime ReturnType: type) type {
/// Wait for all the calls and promises of the group to complete.
/// Thread-safe.
+ /// Safe to call any number of times.
pub async fn wait(self: *Self) ReturnType {
// TODO catch unreachable because the allocation can be grouped with
// the coro frame allocation
@@ -98,6 +108,8 @@ pub fn Group(comptime ReturnType: type) type {
}
/// Cancel all the outstanding promises. May only be called if wait was never called.
+ /// TODO These should be `cancelasync` not `cancel`.
+ /// See https://github.com/ziglang/zig/issues/1261
pub fn cancelAll(self: *Self) void {
while (self.coro_stack.pop()) |node| {
cancel node.data;
diff --git a/std/event/loop.zig b/std/event/loop.zig
index fc927592b9..cd805f891f 100644
--- a/std/event/loop.zig
+++ b/std/event/loop.zig
@@ -12,7 +12,6 @@ pub const Loop = struct {
next_tick_queue: std.atomic.Queue(promise),
os_data: OsData,
final_resume_node: ResumeNode,
- dispatch_lock: u8, // TODO make this a bool
pending_event_count: usize,
extra_threads: []*std.os.Thread,
@@ -74,11 +73,10 @@ pub const Loop = struct {
/// max(thread_count - 1, 0)
fn initInternal(self: *Loop, allocator: *mem.Allocator, thread_count: usize) !void {
self.* = Loop{
- .pending_event_count = 0,
+ .pending_event_count = 1,
.allocator = allocator,
.os_data = undefined,
.next_tick_queue = std.atomic.Queue(promise).init(),
- .dispatch_lock = 1, // start locked so threads go directly into epoll wait
.extra_threads = undefined,
.available_eventfd_resume_nodes = std.atomic.Stack(ResumeNode.EventFd).init(),
.eventfd_resume_nodes = undefined,
@@ -235,8 +233,6 @@ pub const Loop = struct {
}
},
builtin.Os.windows => {
- self.os_data.extra_thread_count = extra_thread_count;
-
self.os_data.io_port = try std.os.windowsCreateIoCompletionPort(
windows.INVALID_HANDLE_VALUE,
null,
@@ -306,7 +302,7 @@ pub const Loop = struct {
pub fn addFd(self: *Loop, fd: i32, resume_node: *ResumeNode) !void {
_ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
errdefer {
- _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst);
+ self.finishOneEvent();
}
try self.modFd(
fd,
@@ -326,7 +322,7 @@ pub const Loop = struct {
pub fn removeFd(self: *Loop, fd: i32) void {
self.removeFdNoCounter(fd);
- _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst);
+ self.finishOneEvent();
}
fn removeFdNoCounter(self: *Loop, fd: i32) void {
@@ -345,14 +341,70 @@ pub const Loop = struct {
}
}
+ fn dispatch(self: *Loop) void {
+ while (self.available_eventfd_resume_nodes.pop()) |resume_stack_node| {
+ const next_tick_node = self.next_tick_queue.get() orelse {
+ self.available_eventfd_resume_nodes.push(resume_stack_node);
+ return;
+ };
+ const eventfd_node = &resume_stack_node.data;
+ eventfd_node.base.handle = next_tick_node.data;
+ switch (builtin.os) {
+ builtin.Os.macosx => {
+ const kevent_array = (*[1]posix.Kevent)(&eventfd_node.kevent);
+ const eventlist = ([*]posix.Kevent)(undefined)[0..0];
+ _ = std.os.bsdKEvent(self.os_data.kqfd, kevent_array, eventlist, null) catch {
+ self.next_tick_queue.unget(next_tick_node);
+ self.available_eventfd_resume_nodes.push(resume_stack_node);
+ return;
+ };
+ },
+ builtin.Os.linux => {
+ // the pending count is already accounted for
+ const epoll_events = posix.EPOLLONESHOT | std.os.linux.EPOLLIN | std.os.linux.EPOLLOUT |
+ std.os.linux.EPOLLET;
+ self.modFd(
+ eventfd_node.eventfd,
+ eventfd_node.epoll_op,
+ epoll_events,
+ &eventfd_node.base,
+ ) catch {
+ self.next_tick_queue.unget(next_tick_node);
+ self.available_eventfd_resume_nodes.push(resume_stack_node);
+ return;
+ };
+ },
+ builtin.Os.windows => {
+ // this value is never dereferenced but we need it to be non-null so that
+ // the consumer code can decide whether to read the completion key.
+ // it has to do this for normal I/O, so we match that behavior here.
+ const overlapped = @intToPtr(?*windows.OVERLAPPED, 0x1);
+ std.os.windowsPostQueuedCompletionStatus(
+ self.os_data.io_port,
+ undefined,
+ eventfd_node.completion_key,
+ overlapped,
+ ) catch {
+ self.next_tick_queue.unget(next_tick_node);
+ self.available_eventfd_resume_nodes.push(resume_stack_node);
+ return;
+ };
+ },
+ else => @compileError("unsupported OS"),
+ }
+ }
+ }
+
/// Bring your own linked list node. This means it can't fail.
pub fn onNextTick(self: *Loop, node: *NextTickNode) void {
_ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Add, 1, AtomicOrder.SeqCst);
self.next_tick_queue.put(node);
+ self.dispatch();
}
pub fn run(self: *Loop) void {
- _ = @atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst);
+ self.finishOneEvent(); // the reference we start with
+
self.workerRun();
for (self.extra_threads) |extra_thread| {
extra_thread.wait();
@@ -392,110 +444,49 @@ pub const Loop = struct {
.next = undefined,
.data = p,
};
- loop.onNextTick(&my_tick_node);
+ self.onNextTick(&my_tick_node);
+ }
+ }
+
+ fn finishOneEvent(self: *Loop) void {
+ if (@atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst) == 1) {
+ // cause all the threads to stop
+ switch (builtin.os) {
+ builtin.Os.linux => {
+ // writing 8 bytes to an eventfd cannot fail
+ std.os.posixWrite(self.os_data.final_eventfd, wakeup_bytes) catch unreachable;
+ return;
+ },
+ builtin.Os.macosx => {
+ const final_kevent = (*[1]posix.Kevent)(&self.os_data.final_kevent);
+ const eventlist = ([*]posix.Kevent)(undefined)[0..0];
+ // cannot fail because we already added it and this just enables it
+ _ = std.os.bsdKEvent(self.os_data.kqfd, final_kevent, eventlist, null) catch unreachable;
+ return;
+ },
+ builtin.Os.windows => {
+ var i: usize = 0;
+ while (i < self.extra_threads.len + 1) : (i += 1) {
+ while (true) {
+ const overlapped = @intToPtr(?*windows.OVERLAPPED, 0x1);
+ std.os.windowsPostQueuedCompletionStatus(self.os_data.io_port, undefined, @ptrToInt(&self.final_resume_node), overlapped) catch continue;
+ break;
+ }
+ }
+ return;
+ },
+ else => @compileError("unsupported OS"),
+ }
}
}
fn workerRun(self: *Loop) void {
- start_over: while (true) {
- if (@atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 1, AtomicOrder.SeqCst) == 0) {
- while (self.next_tick_queue.get()) |next_tick_node| {
- const handle = next_tick_node.data;
- if (self.next_tick_queue.isEmpty()) {
- // last node, just resume it
- _ = @atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst);
- resume handle;
- _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst);
- continue :start_over;
- }
-
- // non-last node, stick it in the epoll/kqueue set so that
- // other threads can get to it
- if (self.available_eventfd_resume_nodes.pop()) |resume_stack_node| {
- const eventfd_node = &resume_stack_node.data;
- eventfd_node.base.handle = handle;
- switch (builtin.os) {
- builtin.Os.macosx => {
- const kevent_array = (*[1]posix.Kevent)(&eventfd_node.kevent);
- const eventlist = ([*]posix.Kevent)(undefined)[0..0];
- _ = std.os.bsdKEvent(self.os_data.kqfd, kevent_array, eventlist, null) catch {
- // fine, we didn't need it anyway
- _ = @atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst);
- self.available_eventfd_resume_nodes.push(resume_stack_node);
- resume handle;
- _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst);
- continue :start_over;
- };
- },
- builtin.Os.linux => {
- // the pending count is already accounted for
- const epoll_events = posix.EPOLLONESHOT | std.os.linux.EPOLLIN | std.os.linux.EPOLLOUT | std.os.linux.EPOLLET;
- self.modFd(eventfd_node.eventfd, eventfd_node.epoll_op, epoll_events, &eventfd_node.base) catch {
- // fine, we didn't need it anyway
- _ = @atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst);
- self.available_eventfd_resume_nodes.push(resume_stack_node);
- resume handle;
- _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst);
- continue :start_over;
- };
- },
- builtin.Os.windows => {
- // this value is never dereferenced but we need it to be non-null so that
- // the consumer code can decide whether to read the completion key.
- // it has to do this for normal I/O, so we match that behavior here.
- const overlapped = @intToPtr(?*windows.OVERLAPPED, 0x1);
- std.os.windowsPostQueuedCompletionStatus(self.os_data.io_port, undefined, eventfd_node.completion_key, overlapped) catch {
- // fine, we didn't need it anyway
- _ = @atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst);
- self.available_eventfd_resume_nodes.push(resume_stack_node);
- resume handle;
- _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst);
- continue :start_over;
- };
- },
- else => @compileError("unsupported OS"),
- }
- } else {
- // threads are too busy, can't add another eventfd to wake one up
- _ = @atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst);
- resume handle;
- _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst);
- continue :start_over;
- }
- }
-
- const pending_event_count = @atomicLoad(usize, &self.pending_event_count, AtomicOrder.SeqCst);
- if (pending_event_count == 0) {
- // cause all the threads to stop
- switch (builtin.os) {
- builtin.Os.linux => {
- // writing 8 bytes to an eventfd cannot fail
- std.os.posixWrite(self.os_data.final_eventfd, wakeup_bytes) catch unreachable;
- return;
- },
- builtin.Os.macosx => {
- const final_kevent = (*[1]posix.Kevent)(&self.os_data.final_kevent);
- const eventlist = ([*]posix.Kevent)(undefined)[0..0];
- // cannot fail because we already added it and this just enables it
- _ = std.os.bsdKEvent(self.os_data.kqfd, final_kevent, eventlist, null) catch unreachable;
- return;
- },
- builtin.Os.windows => {
- var i: usize = 0;
- while (i < self.os_data.extra_thread_count) : (i += 1) {
- while (true) {
- const overlapped = @intToPtr(?*windows.OVERLAPPED, 0x1);
- std.os.windowsPostQueuedCompletionStatus(self.os_data.io_port, undefined, @ptrToInt(&self.final_resume_node), overlapped) catch continue;
- break;
- }
- }
- return;
- },
- else => @compileError("unsupported OS"),
- }
- }
-
- _ = @atomicRmw(u8, &self.dispatch_lock, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst);
+ while (true) {
+ while (true) {
+ const next_tick_node = self.next_tick_queue.get() orelse break;
+ self.dispatch();
+ resume next_tick_node.data;
+ self.finishOneEvent();
}
switch (builtin.os) {
@@ -519,7 +510,7 @@ pub const Loop = struct {
}
resume handle;
if (resume_node_id == ResumeNode.Id.EventFd) {
- _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst);
+ self.finishOneEvent();
}
}
},
@@ -541,7 +532,7 @@ pub const Loop = struct {
}
resume handle;
if (resume_node_id == ResumeNode.Id.EventFd) {
- _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst);
+ self.finishOneEvent();
}
}
},
@@ -570,7 +561,7 @@ pub const Loop = struct {
}
resume handle;
if (resume_node_id == ResumeNode.Id.EventFd) {
- _ = @atomicRmw(usize, &self.pending_event_count, AtomicRmwOp.Sub, 1, AtomicOrder.SeqCst);
+ self.finishOneEvent();
}
},
else => @compileError("unsupported OS"),
diff --git a/std/event/tcp.zig b/std/event/tcp.zig
index 5151ecf934..416a8c07dc 100644
--- a/std/event/tcp.zig
+++ b/std/event/tcp.zig
@@ -125,8 +125,9 @@ pub async fn connect(loop: *Loop, _address: *const std.net.Address) !std.os.File
test "listen on a port, send bytes, receive bytes" {
if (builtin.os != builtin.Os.linux) {
// TODO build abstractions for other operating systems
- return;
+ return error.SkipZigTest;
}
+
const MyServer = struct {
tcp_server: Server,
diff --git a/std/fmt/index.zig b/std/fmt/index.zig
index c3c17f5322..2188cc5803 100644
--- a/std/fmt/index.zig
+++ b/std/fmt/index.zig
@@ -785,11 +785,15 @@ pub fn bufPrint(buf: []u8, comptime fmt: []const u8, args: ...) ![]u8 {
return buf[0 .. buf.len - context.remaining.len];
}
-pub fn allocPrint(allocator: *mem.Allocator, comptime fmt: []const u8, args: ...) ![]u8 {
+pub const AllocPrintError = error{OutOfMemory};
+
+pub fn allocPrint(allocator: *mem.Allocator, comptime fmt: []const u8, args: ...) AllocPrintError![]u8 {
var size: usize = 0;
format(&size, error{}, countSize, fmt, args) catch |err| switch (err) {};
const buf = try allocator.alloc(u8, size);
- return bufPrint(buf, fmt, args);
+ return bufPrint(buf, fmt, args) catch |err| switch (err) {
+ error.BufferTooSmall => unreachable, // we just counted the size above
+ };
}
fn countSize(size: *usize, bytes: []const u8) (error{}!void) {
diff --git a/std/index.zig b/std/index.zig
index 3b523f519f..2f4cfb7553 100644
--- a/std/index.zig
+++ b/std/index.zig
@@ -36,6 +36,8 @@ pub const sort = @import("sort.zig");
pub const unicode = @import("unicode.zig");
pub const zig = @import("zig/index.zig");
+pub const lazyInit = @import("lazy_init.zig").lazyInit;
+
test "std" {
// run tests from these
_ = @import("atomic/index.zig");
@@ -71,4 +73,5 @@ test "std" {
_ = @import("sort.zig");
_ = @import("unicode.zig");
_ = @import("zig/index.zig");
+ _ = @import("lazy_init.zig");
}
diff --git a/std/lazy_init.zig b/std/lazy_init.zig
new file mode 100644
index 0000000000..c46c067810
--- /dev/null
+++ b/std/lazy_init.zig
@@ -0,0 +1,85 @@
+const std = @import("index.zig");
+const builtin = @import("builtin");
+const assert = std.debug.assert;
+const AtomicRmwOp = builtin.AtomicRmwOp;
+const AtomicOrder = builtin.AtomicOrder;
+
+/// Thread-safe initialization of global data.
+/// TODO use a mutex instead of a spinlock
+pub fn lazyInit(comptime T: type) LazyInit(T) {
+ return LazyInit(T){
+ .data = undefined,
+ .state = 0,
+ };
+}
+
+fn LazyInit(comptime T: type) type {
+ return struct {
+ state: u8, // TODO make this an enum
+ data: Data,
+
+ const Self = this;
+
+ // TODO this isn't working for void, investigate and then remove this special case
+ const Data = if (@sizeOf(T) == 0) u8 else T;
+ const Ptr = if (T == void) void else *T;
+
+ /// Returns a usable pointer to the initialized data,
+ /// or returns null, indicating that the caller should
+ /// perform the initialization and then call resolve().
+ pub fn get(self: *Self) ?Ptr {
+ while (true) {
+ var state = @cmpxchgWeak(u8, &self.state, 0, 1, AtomicOrder.SeqCst, AtomicOrder.SeqCst) orelse return null;
+ switch (state) {
+ 0 => continue,
+ 1 => {
+ // TODO mutex instead of a spinlock
+ continue;
+ },
+ 2 => {
+ if (@sizeOf(T) == 0) {
+ return T(undefined);
+ } else {
+ return &self.data;
+ }
+ },
+ else => unreachable,
+ }
+ }
+ }
+
+ pub fn resolve(self: *Self) void {
+ const prev = @atomicRmw(u8, &self.state, AtomicRmwOp.Xchg, 2, AtomicOrder.SeqCst);
+ assert(prev == 1); // resolve() called twice
+ }
+ };
+}
+
+var global_number = lazyInit(i32);
+
+test "std.lazyInit" {
+ if (global_number.get()) |_| @panic("bad") else {
+ global_number.data = 1234;
+ global_number.resolve();
+ }
+ if (global_number.get()) |x| {
+ assert(x.* == 1234);
+ } else {
+ @panic("bad");
+ }
+ if (global_number.get()) |x| {
+ assert(x.* == 1234);
+ } else {
+ @panic("bad");
+ }
+}
+
+var global_void = lazyInit(void);
+
+test "std.lazyInit(void)" {
+ if (global_void.get()) |_| @panic("bad") else {
+ global_void.resolve();
+ }
+ assert(global_void.get() != null);
+ assert(global_void.get() != null);
+}
diff --git a/std/macho.zig b/std/macho.zig
index 33c170ff43..ddc4d334e4 100644
--- a/std/macho.zig
+++ b/std/macho.zig
@@ -141,7 +141,7 @@ pub fn loadSymbols(allocator: *mem.Allocator, in: *io.FileInStream) !SymbolTable
}
// Effectively a no-op, lld emits symbols in ascending order.
- std.sort.insertionSort(Symbol, symbols[0..nsyms], Symbol.addressLessThan);
+ std.sort.sort(Symbol, symbols[0..nsyms], Symbol.addressLessThan);
// Insert the sentinel. Since we don't know where the last function ends,
// we arbitrarily limit it to the start address + 4 KB.
diff --git a/std/math/big/int.zig b/std/math/big/int.zig
index caa9d0a7ed..41e1503d49 100644
--- a/std/math/big/int.zig
+++ b/std/math/big/int.zig
@@ -60,8 +60,9 @@ pub const Int = struct {
self.limbs = try self.allocator.realloc(Limb, self.limbs, capacity);
}
- pub fn deinit(self: Int) void {
+ pub fn deinit(self: *Int) void {
self.allocator.free(self.limbs);
+ self.* = undefined;
}
pub fn clone(other: Int) !Int {
@@ -115,13 +116,63 @@ pub const Int = struct {
return !r.isOdd();
}
- fn bitcount(self: Int) usize {
- const u_bit_count = (self.len - 1) * Limb.bit_count + (Limb.bit_count - @clz(self.limbs[self.len - 1]));
- return usize(@boolToInt(!self.positive)) + u_bit_count;
+ // Returns the number of bits required to represent the absolute value of self.
+ fn bitCountAbs(self: Int) usize {
+ return (self.len - 1) * Limb.bit_count + (Limb.bit_count - @clz(self.limbs[self.len - 1]));
}
+ // Returns the number of bits required to represent the integer in twos-complement form.
+ //
+ // If the integer is negative the value returned is the number of bits needed by a signed
+ // integer to represent the value. If positive the value is the number of bits for an
+ // unsigned integer. Any unsigned integer will fit in the signed integer with bitcount
+ // one greater than the returned value.
+ //
+ // e.g. -127 returns 8 as it will fit in an i8. 127 returns 7 since it fits in a u7.
+ fn bitCountTwosComp(self: Int) usize {
+ var bits = self.bitCountAbs();
+
+ // If the entire value has only one bit set (e.g. 0b100000000) then the negation in twos
+ // complement requires one less bit.
+ if (!self.positive) block: {
+ bits += 1;
+
+ if (@popCount(self.limbs[self.len - 1]) == 1) {
+ for (self.limbs[0 .. self.len - 1]) |limb| {
+ if (@popCount(limb) != 0) {
+ break :block;
+ }
+ }
+
+ bits -= 1;
+ }
+ }
+
+ return bits;
+ }
+
+ pub fn fitsInTwosComp(self: Int, is_signed: bool, bit_count: usize) bool {
+ if (self.eqZero()) {
+ return true;
+ }
+ if (!is_signed and !self.positive) {
+ return false;
+ }
+
+ const req_bits = self.bitCountTwosComp() + @boolToInt(self.positive and is_signed);
+ return bit_count >= req_bits;
+ }
+
+ pub fn fits(self: Int, comptime T: type) bool {
+ return self.fitsInTwosComp(T.is_signed, T.bit_count);
+ }
+
+ // Returns the approximate size of the integer in the given base. Negative values accomodate for
+ // the minus sign. This is used for determining the number of characters needed to print the
+ // value. It is inexact and will exceed the given value by 1-2 digits.
pub fn sizeInBase(self: Int, base: usize) usize {
- return (self.bitcount() / math.log2(base)) + 1;
+ const bit_count = usize(@boolToInt(!self.positive)) + self.bitCountAbs();
+ return (bit_count / math.log2(base)) + 1;
}
pub fn set(self: *Int, value: var) Allocator.Error!void {
@@ -189,9 +240,9 @@ pub const Int = struct {
pub fn to(self: Int, comptime T: type) ConvertError!T {
switch (@typeId(T)) {
TypeId.Int => {
- const UT = if (T.is_signed) @IntType(false, T.bit_count - 1) else T;
+ const UT = @IntType(false, T.bit_count);
- if (self.bitcount() > 8 * @sizeOf(UT)) {
+ if (self.bitCountTwosComp() > T.bit_count) {
return error.TargetTooSmall;
}
@@ -208,9 +259,17 @@ pub const Int = struct {
}
if (!T.is_signed) {
- return if (self.positive) r else error.NegativeIntoUnsigned;
+ return if (self.positive) @intCast(T, r) else error.NegativeIntoUnsigned;
} else {
- return if (self.positive) @intCast(T, r) else -@intCast(T, r);
+ if (self.positive) {
+ return @intCast(T, r);
+ } else {
+ if (math.cast(T, r)) |ok| {
+ return -ok;
+ } else |_| {
+ return @minValue(T);
+ }
+ }
}
},
else => {
@@ -274,6 +333,7 @@ pub const Int = struct {
self.positive = positive;
}
+ /// TODO make this call format instead of the other way around
pub fn toString(self: Int, allocator: *Allocator, base: u8) ![]const u8 {
if (base < 2 or base > 16) {
return error.InvalidBase;
@@ -356,6 +416,21 @@ pub const Int = struct {
return s;
}
+ /// for the std lib format function
+ /// TODO make this non-allocating
+ pub fn format(
+ self: Int,
+ comptime fmt: []const u8,
+ context: var,
+ comptime FmtError: type,
+ output: fn (@typeOf(context), []const u8) FmtError!void,
+ ) FmtError!void {
+ // TODO look at fmt and support other bases
+ const str = self.toString(self.allocator, 10) catch @panic("TODO make this non allocating");
+ defer self.allocator.free(str);
+ return output(context, str);
+ }
+
// returns -1, 0, 1 if |a| < |b|, |a| == |b| or |a| > |b| respectively.
pub fn cmpAbs(a: Int, b: Int) i8 {
if (a.len < b.len) {
@@ -1120,24 +1195,88 @@ test "big.int bitcount + sizeInBase" {
var a = try Int.init(al);
try a.set(0b100);
- debug.assert(a.bitcount() == 3);
+ debug.assert(a.bitCountAbs() == 3);
debug.assert(a.sizeInBase(2) >= 3);
debug.assert(a.sizeInBase(10) >= 1);
+ a.negate();
+ debug.assert(a.bitCountAbs() == 3);
+ debug.assert(a.sizeInBase(2) >= 4);
+ debug.assert(a.sizeInBase(10) >= 2);
+
try a.set(0xffffffff);
- debug.assert(a.bitcount() == 32);
+ debug.assert(a.bitCountAbs() == 32);
debug.assert(a.sizeInBase(2) >= 32);
debug.assert(a.sizeInBase(10) >= 10);
try a.shiftLeft(a, 5000);
- debug.assert(a.bitcount() == 5032);
+ debug.assert(a.bitCountAbs() == 5032);
debug.assert(a.sizeInBase(2) >= 5032);
a.positive = false;
- debug.assert(a.bitcount() == 5033);
+ debug.assert(a.bitCountAbs() == 5032);
debug.assert(a.sizeInBase(2) >= 5033);
}
+test "big.int bitcount/to" {
+ var a = try Int.init(al);
+
+ try a.set(0);
+ debug.assert(a.bitCountTwosComp() == 0);
+
+ // TODO: stack smashing
+ // debug.assert((try a.to(u0)) == 0);
+ // TODO: sigsegv
+ // debug.assert((try a.to(i0)) == 0);
+
+ try a.set(-1);
+ debug.assert(a.bitCountTwosComp() == 1);
+ debug.assert((try a.to(i1)) == -1);
+
+ try a.set(-8);
+ debug.assert(a.bitCountTwosComp() == 4);
+ debug.assert((try a.to(i4)) == -8);
+
+ try a.set(127);
+ debug.assert(a.bitCountTwosComp() == 7);
+ debug.assert((try a.to(u7)) == 127);
+
+ try a.set(-128);
+ debug.assert(a.bitCountTwosComp() == 8);
+ debug.assert((try a.to(i8)) == -128);
+
+ try a.set(-129);
+ debug.assert(a.bitCountTwosComp() == 9);
+ debug.assert((try a.to(i9)) == -129);
+}
+
+test "big.int fits" {
+ var a = try Int.init(al);
+
+ try a.set(0);
+ debug.assert(a.fits(u0));
+ debug.assert(a.fits(i0));
+
+ try a.set(255);
+ debug.assert(!a.fits(u0));
+ debug.assert(!a.fits(u1));
+ debug.assert(!a.fits(i8));
+ debug.assert(a.fits(u8));
+ debug.assert(a.fits(u9));
+ debug.assert(a.fits(i9));
+
+ try a.set(-128);
+ debug.assert(!a.fits(i7));
+ debug.assert(a.fits(i8));
+ debug.assert(a.fits(i9));
+ debug.assert(!a.fits(u9));
+
+ try a.set(0x1ffffffffeeeeeeee);
+ debug.assert(!a.fits(u32));
+ debug.assert(!a.fits(u64));
+ debug.assert(a.fits(u65));
+}
+
test "big.int string set" {
var a = try Int.init(al);
try a.setString(10, "120317241209124781241290847124");
diff --git a/std/mem.zig b/std/mem.zig
index 2a5b0366a9..43961a6d14 100644
--- a/std/mem.zig
+++ b/std/mem.zig
@@ -35,6 +35,7 @@ pub const Allocator = struct {
freeFn: fn (self: *Allocator, old_mem: []u8) void,
/// Call `destroy` with the result
+ /// TODO this is deprecated. use createOne instead
pub fn create(self: *Allocator, init: var) Error!*@typeOf(init) {
const T = @typeOf(init);
if (@sizeOf(T) == 0) return &(T{});
@@ -44,6 +45,14 @@ pub const Allocator = struct {
return ptr;
}
+ /// Call `destroy` with the result.
+ /// Returns undefined memory.
+ pub fn createOne(self: *Allocator, comptime T: type) Error!*T {
+ if (@sizeOf(T) == 0) return &(T{});
+ const slice = try self.alloc(T, 1);
+ return &slice[0];
+ }
+
/// `ptr` should be the return value of `create`
pub fn destroy(self: *Allocator, ptr: var) void {
const non_const_ptr = @intToPtr([*]u8, @ptrToInt(ptr));
@@ -149,13 +158,12 @@ pub fn copyBackwards(comptime T: type, dest: []T, source: []const T) void {
@setRuntimeSafety(false);
assert(dest.len >= source.len);
var i = source.len;
- while(i > 0){
+ while (i > 0) {
i -= 1;
dest[i] = source[i];
}
}
-
pub fn set(comptime T: type, dest: []T, value: T) void {
for (dest) |*d|
d.* = value;
diff --git a/std/os/darwin.zig b/std/os/darwin.zig
index 4134e382fc..cf67b01d5a 100644
--- a/std/os/darwin.zig
+++ b/std/os/darwin.zig
@@ -2,7 +2,7 @@ const std = @import("../index.zig");
const c = std.c;
const assert = std.debug.assert;
-pub use @import("darwin_errno.zig");
+pub use @import("darwin/errno.zig");
pub const PATH_MAX = 1024;
@@ -482,6 +482,92 @@ pub const NOTE_MACH_CONTINUOUS_TIME = 0x00000080;
/// data is mach absolute time units
pub const NOTE_MACHTIME = 0x00000100;
+pub const AF_UNSPEC: c_int = 0;
+pub const AF_LOCAL: c_int = 1;
+pub const AF_UNIX: c_int = AF_LOCAL;
+pub const AF_INET: c_int = 2;
+pub const AF_SYS_CONTROL: c_int = 2;
+pub const AF_IMPLINK: c_int = 3;
+pub const AF_PUP: c_int = 4;
+pub const AF_CHAOS: c_int = 5;
+pub const AF_NS: c_int = 6;
+pub const AF_ISO: c_int = 7;
+pub const AF_OSI: c_int = AF_ISO;
+pub const AF_ECMA: c_int = 8;
+pub const AF_DATAKIT: c_int = 9;
+pub const AF_CCITT: c_int = 10;
+pub const AF_SNA: c_int = 11;
+pub const AF_DECnet: c_int = 12;
+pub const AF_DLI: c_int = 13;
+pub const AF_LAT: c_int = 14;
+pub const AF_HYLINK: c_int = 15;
+pub const AF_APPLETALK: c_int = 16;
+pub const AF_ROUTE: c_int = 17;
+pub const AF_LINK: c_int = 18;
+pub const AF_XTP: c_int = 19;
+pub const AF_COIP: c_int = 20;
+pub const AF_CNT: c_int = 21;
+pub const AF_RTIP: c_int = 22;
+pub const AF_IPX: c_int = 23;
+pub const AF_SIP: c_int = 24;
+pub const AF_PIP: c_int = 25;
+pub const AF_ISDN: c_int = 28;
+pub const AF_E164: c_int = AF_ISDN;
+pub const AF_KEY: c_int = 29;
+pub const AF_INET6: c_int = 30;
+pub const AF_NATM: c_int = 31;
+pub const AF_SYSTEM: c_int = 32;
+pub const AF_NETBIOS: c_int = 33;
+pub const AF_PPP: c_int = 34;
+pub const AF_MAX: c_int = 40;
+
+pub const PF_UNSPEC: c_int = AF_UNSPEC;
+pub const PF_LOCAL: c_int = AF_LOCAL;
+pub const PF_UNIX: c_int = PF_LOCAL;
+pub const PF_INET: c_int = AF_INET;
+pub const PF_IMPLINK: c_int = AF_IMPLINK;
+pub const PF_PUP: c_int = AF_PUP;
+pub const PF_CHAOS: c_int = AF_CHAOS;
+pub const PF_NS: c_int = AF_NS;
+pub const PF_ISO: c_int = AF_ISO;
+pub const PF_OSI: c_int = AF_ISO;
+pub const PF_ECMA: c_int = AF_ECMA;
+pub const PF_DATAKIT: c_int = AF_DATAKIT;
+pub const PF_CCITT: c_int = AF_CCITT;
+pub const PF_SNA: c_int = AF_SNA;
+pub const PF_DECnet: c_int = AF_DECnet;
+pub const PF_DLI: c_int = AF_DLI;
+pub const PF_LAT: c_int = AF_LAT;
+pub const PF_HYLINK: c_int = AF_HYLINK;
+pub const PF_APPLETALK: c_int = AF_APPLETALK;
+pub const PF_ROUTE: c_int = AF_ROUTE;
+pub const PF_LINK: c_int = AF_LINK;
+pub const PF_XTP: c_int = AF_XTP;
+pub const PF_COIP: c_int = AF_COIP;
+pub const PF_CNT: c_int = AF_CNT;
+pub const PF_SIP: c_int = AF_SIP;
+pub const PF_IPX: c_int = AF_IPX;
+pub const PF_RTIP: c_int = AF_RTIP;
+pub const PF_PIP: c_int = AF_PIP;
+pub const PF_ISDN: c_int = AF_ISDN;
+pub const PF_KEY: c_int = AF_KEY;
+pub const PF_INET6: c_int = AF_INET6;
+pub const PF_NATM: c_int = AF_NATM;
+pub const PF_SYSTEM: c_int = AF_SYSTEM;
+pub const PF_NETBIOS: c_int = AF_NETBIOS;
+pub const PF_PPP: c_int = AF_PPP;
+pub const PF_MAX: c_int = AF_MAX;
+
+pub const SYSPROTO_EVENT: c_int = 1;
+pub const SYSPROTO_CONTROL: c_int = 2;
+
+pub const SOCK_STREAM: c_int = 1;
+pub const SOCK_DGRAM: c_int = 2;
+pub const SOCK_RAW: c_int = 3;
+pub const SOCK_RDM: c_int = 4;
+pub const SOCK_SEQPACKET: c_int = 5;
+pub const SOCK_MAXADDRLEN: c_int = 255;
+
fn wstatus(x: i32) i32 {
return x & 0o177;
}
diff --git a/std/os/darwin_errno.zig b/std/os/darwin/errno.zig
similarity index 100%
rename from std/os/darwin_errno.zig
rename to std/os/darwin/errno.zig
diff --git a/std/os/file.zig b/std/os/file.zig
index 055f185121..6998ba00d1 100644
--- a/std/os/file.zig
+++ b/std/os/file.zig
@@ -15,7 +15,7 @@ pub const File = struct {
/// The OS-specific file descriptor or file handle.
handle: os.FileHandle,
- const OpenError = os.WindowsOpenError || os.PosixOpenError;
+ pub const OpenError = os.WindowsOpenError || os.PosixOpenError;
/// `path` needs to be copied in memory to add a null terminating byte, hence the allocator.
/// Call close to clean up.
@@ -109,43 +109,42 @@ pub const File = struct {
Unexpected,
};
- pub fn access(allocator: *mem.Allocator, path: []const u8, file_mode: os.FileMode) AccessError!bool {
+ pub fn access(allocator: *mem.Allocator, path: []const u8) AccessError!void {
const path_with_null = try std.cstr.addNullByte(allocator, path);
defer allocator.free(path_with_null);
if (is_posix) {
- // mode is ignored and is always F_OK for now
const result = posix.access(path_with_null.ptr, posix.F_OK);
const err = posix.getErrno(result);
- if (err > 0) {
- return switch (err) {
- posix.EACCES => error.PermissionDenied,
- posix.EROFS => error.PermissionDenied,
- posix.ELOOP => error.PermissionDenied,
- posix.ETXTBSY => error.PermissionDenied,
- posix.ENOTDIR => error.NotFound,
- posix.ENOENT => error.NotFound,
+ switch (err) {
+ 0 => return,
+ posix.EACCES => return error.PermissionDenied,
+ posix.EROFS => return error.PermissionDenied,
+ posix.ELOOP => return error.PermissionDenied,
+ posix.ETXTBSY => return error.PermissionDenied,
+ posix.ENOTDIR => return error.NotFound,
+ posix.ENOENT => return error.NotFound,
- posix.ENAMETOOLONG => error.NameTooLong,
- posix.EINVAL => error.BadMode,
- posix.EFAULT => error.BadPathName,
- posix.EIO => error.Io,
- posix.ENOMEM => error.SystemResources,
- else => os.unexpectedErrorPosix(err),
- };
+ posix.ENAMETOOLONG => return error.NameTooLong,
+ posix.EINVAL => unreachable,
+ posix.EFAULT => return error.BadPathName,
+ posix.EIO => return error.Io,
+ posix.ENOMEM => return error.SystemResources,
+ else => return os.unexpectedErrorPosix(err),
}
- return true;
} else if (is_windows) {
if (os.windows.GetFileAttributesA(path_with_null.ptr) != os.windows.INVALID_FILE_ATTRIBUTES) {
- return true;
+ return;
}
const err = windows.GetLastError();
- return switch (err) {
- windows.ERROR.FILE_NOT_FOUND => error.NotFound,
- windows.ERROR.ACCESS_DENIED => error.PermissionDenied,
- else => os.unexpectedErrorWindows(err),
- };
+ switch (err) {
+ windows.ERROR.FILE_NOT_FOUND,
+ windows.ERROR.PATH_NOT_FOUND,
+ => return error.NotFound,
+ windows.ERROR.ACCESS_DENIED => return error.PermissionDenied,
+ else => return os.unexpectedErrorWindows(err),
+ }
} else {
@compileError("TODO implement access for this OS");
}
@@ -242,7 +241,7 @@ pub const File = struct {
},
Os.windows => {
var pos: windows.LARGE_INTEGER = undefined;
- if (windows.SetFilePointerEx(self.handle, 0, *pos, windows.FILE_CURRENT) == 0) {
+ if (windows.SetFilePointerEx(self.handle, 0, &pos, windows.FILE_CURRENT) == 0) {
const err = windows.GetLastError();
return switch (err) {
windows.ERROR.INVALID_PARAMETER => error.BadFd,
@@ -251,13 +250,7 @@ pub const File = struct {
}
assert(pos >= 0);
- if (@sizeOf(@typeOf(pos)) > @sizeOf(usize)) {
- if (pos > @maxValue(usize)) {
- return error.FilePosLargerThanPointerRange;
- }
- }
-
- return usize(pos);
+ return math.cast(usize, pos) catch error.FilePosLargerThanPointerRange;
},
else => @compileError("unsupported OS"),
}
@@ -289,7 +282,7 @@ pub const File = struct {
Unexpected,
};
- fn mode(self: *File) ModeError!os.FileMode {
+ pub fn mode(self: *File) ModeError!os.FileMode {
if (is_posix) {
var stat: posix.Stat = undefined;
const err = posix.getErrno(posix.fstat(self.handle, &stat));
@@ -364,7 +357,7 @@ pub const File = struct {
pub const WriteError = os.WindowsWriteError || os.PosixWriteError;
- fn write(self: *File, bytes: []const u8) WriteError!void {
+ pub fn write(self: *File, bytes: []const u8) WriteError!void {
if (is_posix) {
try os.posixWrite(self.handle, bytes);
} else if (is_windows) {
diff --git a/std/os/get_app_data_dir.zig b/std/os/get_app_data_dir.zig
new file mode 100644
index 0000000000..e8ae5dd490
--- /dev/null
+++ b/std/os/get_app_data_dir.zig
@@ -0,0 +1,69 @@
+const std = @import("../index.zig");
+const builtin = @import("builtin");
+const unicode = std.unicode;
+const mem = std.mem;
+const os = std.os;
+
+pub const GetAppDataDirError = error{
+ OutOfMemory,
+ AppDataDirUnavailable,
+};
+
+/// Caller owns returned memory.
+pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataDirError![]u8 {
+ switch (builtin.os) {
+ builtin.Os.windows => {
+ var dir_path_ptr: [*]u16 = undefined;
+ switch (os.windows.SHGetKnownFolderPath(
+ &os.windows.FOLDERID_LocalAppData,
+ os.windows.KF_FLAG_CREATE,
+ null,
+ &dir_path_ptr,
+ )) {
+ os.windows.S_OK => {
+ defer os.windows.CoTaskMemFree(@ptrCast(*c_void, dir_path_ptr));
+ const global_dir = unicode.utf16leToUtf8(allocator, utf16lePtrSlice(dir_path_ptr)) catch |err| switch (err) {
+ error.UnexpectedSecondSurrogateHalf => return error.AppDataDirUnavailable,
+ error.ExpectedSecondSurrogateHalf => return error.AppDataDirUnavailable,
+ error.DanglingSurrogateHalf => return error.AppDataDirUnavailable,
+ error.OutOfMemory => return error.OutOfMemory,
+ };
+ defer allocator.free(global_dir);
+ return os.path.join(allocator, global_dir, appname);
+ },
+ os.windows.E_OUTOFMEMORY => return error.OutOfMemory,
+ else => return error.AppDataDirUnavailable,
+ }
+ },
+ builtin.Os.macosx => {
+ const home_dir = os.getEnvPosix("HOME") orelse {
+ // TODO look in /etc/passwd
+ return error.AppDataDirUnavailable;
+ };
+ return os.path.join(allocator, home_dir, "Library", "Application Support", appname);
+ },
+ builtin.Os.linux => {
+ const home_dir = os.getEnvPosix("HOME") orelse {
+ // TODO look in /etc/passwd
+ return error.AppDataDirUnavailable;
+ };
+ return os.path.join(allocator, home_dir, ".local", "share", appname);
+ },
+ else => @compileError("Unsupported OS"),
+ }
+}
+
+fn utf16lePtrSlice(ptr: [*]const u16) []const u16 {
+ var index: usize = 0;
+ while (ptr[index] != 0) : (index += 1) {}
+ return ptr[0..index];
+}
+
+test "std.os.getAppDataDir" {
+ var buf: [512]u8 = undefined;
+ const allocator = &std.heap.FixedBufferAllocator.init(buf[0..]).allocator;
+
+ // We can't actually validate the result
+ _ = getAppDataDir(allocator, "zig") catch return;
+}
+
diff --git a/std/os/index.zig b/std/os/index.zig
index 79b2d2ff53..77fd2a78ad 100644
--- a/std/os/index.zig
+++ b/std/os/index.zig
@@ -11,13 +11,14 @@ const os = this;
test "std.os" {
_ = @import("child_process.zig");
_ = @import("darwin.zig");
- _ = @import("darwin_errno.zig");
+ _ = @import("darwin/errno.zig");
_ = @import("get_user_id.zig");
_ = @import("linux/index.zig");
_ = @import("path.zig");
_ = @import("test.zig");
_ = @import("time.zig");
_ = @import("windows/index.zig");
+ _ = @import("get_app_data_dir.zig");
}
pub const windows = @import("windows/index.zig");
@@ -76,6 +77,9 @@ pub const WindowsWriteError = windows_util.WriteError;
pub const FileHandle = if (is_windows) windows.HANDLE else i32;
+pub const getAppDataDir = @import("get_app_data_dir.zig").getAppDataDir;
+pub const GetAppDataDirError = @import("get_app_data_dir.zig").GetAppDataDirError;
+
const debug = std.debug;
const assert = debug.assert;
@@ -494,6 +498,7 @@ pub var linux_aux_raw = []usize{0} ** 38;
pub var posix_environ_raw: [][*]u8 = undefined;
/// Caller must free result when done.
+/// TODO make this go through libc when we have it
pub fn getEnvMap(allocator: *Allocator) !BufMap {
var result = BufMap.init(allocator);
errdefer result.deinit();
@@ -537,6 +542,7 @@ pub fn getEnvMap(allocator: *Allocator) !BufMap {
}
}
+/// TODO make this go through libc when we have it
pub fn getEnvPosix(key: []const u8) ?[]const u8 {
for (posix_environ_raw) |ptr| {
var line_i: usize = 0;
@@ -559,6 +565,7 @@ pub const GetEnvVarOwnedError = error{
};
/// Caller must free returned memory.
+/// TODO make this go through libc when we have it
pub fn getEnvVarOwned(allocator: *mem.Allocator, key: []const u8) GetEnvVarOwnedError![]u8 {
if (is_windows) {
const key_with_null = try cstr.addNullByte(allocator, key);
diff --git a/std/os/test.zig b/std/os/test.zig
index 52e6ffdc1c..9e795e8ad2 100644
--- a/std/os/test.zig
+++ b/std/os/test.zig
@@ -23,14 +23,14 @@ test "makePath, put some files in it, deleteTree" {
test "access file" {
try os.makePath(a, "os_test_tmp");
- if (os.File.access(a, "os_test_tmp/file.txt", os.default_file_mode)) |ok| {
- unreachable;
+ if (os.File.access(a, "os_test_tmp/file.txt")) |ok| {
+ @panic("expected error");
} else |err| {
assert(err == error.NotFound);
}
try io.writeFile(a, "os_test_tmp/file.txt", "");
- assert((try os.File.access(a, "os_test_tmp/file.txt", os.default_file_mode)) == true);
+ try os.File.access(a, "os_test_tmp/file.txt");
try os.deleteTree(a, "os_test_tmp");
}
diff --git a/std/os/windows/advapi32.zig b/std/os/windows/advapi32.zig
new file mode 100644
index 0000000000..dcb5a636ea
--- /dev/null
+++ b/std/os/windows/advapi32.zig
@@ -0,0 +1,30 @@
+use @import("index.zig");
+
+pub const PROV_RSA_FULL = 1;
+
+pub const REGSAM = ACCESS_MASK;
+pub const ACCESS_MASK = DWORD;
+pub const PHKEY = &HKEY;
+pub const HKEY = &HKEY__;
+pub const HKEY__ = extern struct {
+ unused: c_int,
+};
+pub const LSTATUS = LONG;
+
+pub extern "advapi32" stdcallcc fn CryptAcquireContextA(
+ phProv: *HCRYPTPROV,
+ pszContainer: ?LPCSTR,
+ pszProvider: ?LPCSTR,
+ dwProvType: DWORD,
+ dwFlags: DWORD,
+) BOOL;
+
+pub extern "advapi32" stdcallcc fn CryptReleaseContext(hProv: HCRYPTPROV, dwFlags: DWORD) BOOL;
+
+pub extern "advapi32" stdcallcc fn CryptGenRandom(hProv: HCRYPTPROV, dwLen: DWORD, pbBuffer: [*]BYTE) BOOL;
+
+pub extern "advapi32" stdcallcc fn RegOpenKeyExW(hKey: HKEY, lpSubKey: LPCWSTR, ulOptions: DWORD, samDesired: REGSAM,
+ phkResult: &HKEY,) LSTATUS;
+
+pub extern "advapi32" stdcallcc fn RegQueryValueExW(hKey: HKEY, lpValueName: LPCWSTR, lpReserved: LPDWORD,
+ lpType: LPDWORD, lpData: LPBYTE, lpcbData: LPDWORD,) LSTATUS;
diff --git a/std/os/windows/index.zig b/std/os/windows/index.zig
index d3525247c9..90ccfaf6c5 100644
--- a/std/os/windows/index.zig
+++ b/std/os/windows/index.zig
@@ -1,188 +1,19 @@
+const std = @import("../../index.zig");
+const assert = std.debug.assert;
+
+pub use @import("advapi32.zig");
+pub use @import("kernel32.zig");
+pub use @import("ole32.zig");
+pub use @import("shell32.zig");
+pub use @import("shlwapi.zig");
+pub use @import("user32.zig");
+
test "import" {
_ = @import("util.zig");
}
pub const ERROR = @import("error.zig");
-pub extern "advapi32" stdcallcc fn CryptAcquireContextA(
- phProv: *HCRYPTPROV,
- pszContainer: ?LPCSTR,
- pszProvider: ?LPCSTR,
- dwProvType: DWORD,
- dwFlags: DWORD,
-) BOOL;
-
-pub extern "advapi32" stdcallcc fn CryptReleaseContext(hProv: HCRYPTPROV, dwFlags: DWORD) BOOL;
-
-pub extern "advapi32" stdcallcc fn CryptGenRandom(hProv: HCRYPTPROV, dwLen: DWORD, pbBuffer: [*]BYTE) BOOL;
-
-pub extern "kernel32" stdcallcc fn CloseHandle(hObject: HANDLE) BOOL;
-
-pub extern "kernel32" stdcallcc fn CreateDirectoryA(
- lpPathName: LPCSTR,
- lpSecurityAttributes: ?*SECURITY_ATTRIBUTES,
-) BOOL;
-
-pub extern "kernel32" stdcallcc fn CreateFileA(
- lpFileName: LPCSTR,
- dwDesiredAccess: DWORD,
- dwShareMode: DWORD,
- lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES,
- dwCreationDisposition: DWORD,
- dwFlagsAndAttributes: DWORD,
- hTemplateFile: ?HANDLE,
-) HANDLE;
-
-pub extern "kernel32" stdcallcc fn CreatePipe(
- hReadPipe: *HANDLE,
- hWritePipe: *HANDLE,
- lpPipeAttributes: *const SECURITY_ATTRIBUTES,
- nSize: DWORD,
-) BOOL;
-
-pub extern "kernel32" stdcallcc fn CreateProcessA(
- lpApplicationName: ?LPCSTR,
- lpCommandLine: LPSTR,
- lpProcessAttributes: ?*SECURITY_ATTRIBUTES,
- lpThreadAttributes: ?*SECURITY_ATTRIBUTES,
- bInheritHandles: BOOL,
- dwCreationFlags: DWORD,
- lpEnvironment: ?*c_void,
- lpCurrentDirectory: ?LPCSTR,
- lpStartupInfo: *STARTUPINFOA,
- lpProcessInformation: *PROCESS_INFORMATION,
-) BOOL;
-
-pub extern "kernel32" stdcallcc fn CreateSymbolicLinkA(
- lpSymlinkFileName: LPCSTR,
- lpTargetFileName: LPCSTR,
- dwFlags: DWORD,
-) BOOLEAN;
-
-pub extern "kernel32" stdcallcc fn CreateIoCompletionPort(FileHandle: HANDLE, ExistingCompletionPort: ?HANDLE, CompletionKey: ULONG_PTR, NumberOfConcurrentThreads: DWORD) ?HANDLE;
-
-pub extern "kernel32" stdcallcc fn CreateThread(lpThreadAttributes: ?LPSECURITY_ATTRIBUTES, dwStackSize: SIZE_T, lpStartAddress: LPTHREAD_START_ROUTINE, lpParameter: ?LPVOID, dwCreationFlags: DWORD, lpThreadId: ?LPDWORD) ?HANDLE;
-
-pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: LPCSTR) BOOL;
-
-pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) noreturn;
-
-pub extern "kernel32" stdcallcc fn FindFirstFileA(lpFileName: LPCSTR, lpFindFileData: *WIN32_FIND_DATAA) HANDLE;
-pub extern "kernel32" stdcallcc fn FindClose(hFindFile: HANDLE) BOOL;
-pub extern "kernel32" stdcallcc fn FindNextFileA(hFindFile: HANDLE, lpFindFileData: *WIN32_FIND_DATAA) BOOL;
-
-pub extern "kernel32" stdcallcc fn FreeEnvironmentStringsA(penv: [*]u8) BOOL;
-
-pub extern "kernel32" stdcallcc fn GetCommandLineA() LPSTR;
-
-pub extern "kernel32" stdcallcc fn GetConsoleMode(in_hConsoleHandle: HANDLE, out_lpMode: *DWORD) BOOL;
-
-pub extern "kernel32" stdcallcc fn GetCurrentDirectoryA(nBufferLength: WORD, lpBuffer: ?LPSTR) DWORD;
-
-pub extern "kernel32" stdcallcc fn GetEnvironmentStringsA() ?[*]u8;
-
-pub extern "kernel32" stdcallcc fn GetEnvironmentVariableA(lpName: LPCSTR, lpBuffer: LPSTR, nSize: DWORD) DWORD;
-
-pub extern "kernel32" stdcallcc fn GetExitCodeProcess(hProcess: HANDLE, lpExitCode: *DWORD) BOOL;
-
-pub extern "kernel32" stdcallcc fn GetFileSizeEx(hFile: HANDLE, lpFileSize: *LARGE_INTEGER) BOOL;
-
-pub extern "kernel32" stdcallcc fn GetFileAttributesA(lpFileName: LPCSTR) DWORD;
-
-pub extern "kernel32" stdcallcc fn GetModuleFileNameA(hModule: ?HMODULE, lpFilename: LPSTR, nSize: DWORD) DWORD;
-
-pub extern "kernel32" stdcallcc fn GetLastError() DWORD;
-
-pub extern "kernel32" stdcallcc fn GetFileInformationByHandleEx(
- in_hFile: HANDLE,
- in_FileInformationClass: FILE_INFO_BY_HANDLE_CLASS,
- out_lpFileInformation: *c_void,
- in_dwBufferSize: DWORD,
-) BOOL;
-
-pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleA(
- hFile: HANDLE,
- lpszFilePath: LPSTR,
- cchFilePath: DWORD,
- dwFlags: DWORD,
-) DWORD;
-
-pub extern "kernel32" stdcallcc fn GetProcessHeap() ?HANDLE;
-pub extern "kernel32" stdcallcc fn GetQueuedCompletionStatus(CompletionPort: HANDLE, lpNumberOfBytesTransferred: LPDWORD, lpCompletionKey: *ULONG_PTR, lpOverlapped: *?*OVERLAPPED, dwMilliseconds: DWORD) BOOL;
-
-pub extern "kernel32" stdcallcc fn GetSystemInfo(lpSystemInfo: *SYSTEM_INFO) void;
-pub extern "kernel32" stdcallcc fn GetSystemTimeAsFileTime(*FILETIME) void;
-
-pub extern "kernel32" stdcallcc fn HeapCreate(flOptions: DWORD, dwInitialSize: SIZE_T, dwMaximumSize: SIZE_T) ?HANDLE;
-pub extern "kernel32" stdcallcc fn HeapDestroy(hHeap: HANDLE) BOOL;
-pub extern "kernel32" stdcallcc fn HeapReAlloc(hHeap: HANDLE, dwFlags: DWORD, lpMem: *c_void, dwBytes: SIZE_T) ?*c_void;
-pub extern "kernel32" stdcallcc fn HeapSize(hHeap: HANDLE, dwFlags: DWORD, lpMem: *const c_void) SIZE_T;
-pub extern "kernel32" stdcallcc fn HeapValidate(hHeap: HANDLE, dwFlags: DWORD, lpMem: *const c_void) BOOL;
-pub extern "kernel32" stdcallcc fn HeapCompact(hHeap: HANDLE, dwFlags: DWORD) SIZE_T;
-pub extern "kernel32" stdcallcc fn HeapSummary(hHeap: HANDLE, dwFlags: DWORD, lpSummary: LPHEAP_SUMMARY) BOOL;
-
-pub extern "kernel32" stdcallcc fn GetStdHandle(in_nStdHandle: DWORD) ?HANDLE;
-
-pub extern "kernel32" stdcallcc fn HeapAlloc(hHeap: HANDLE, dwFlags: DWORD, dwBytes: SIZE_T) ?*c_void;
-
-pub extern "kernel32" stdcallcc fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem: *c_void) BOOL;
-
-pub extern "kernel32" stdcallcc fn MoveFileExA(
- lpExistingFileName: LPCSTR,
- lpNewFileName: LPCSTR,
- dwFlags: DWORD,
-) BOOL;
-
-pub extern "kernel32" stdcallcc fn PostQueuedCompletionStatus(CompletionPort: HANDLE, dwNumberOfBytesTransferred: DWORD, dwCompletionKey: ULONG_PTR, lpOverlapped: ?*OVERLAPPED) BOOL;
-
-pub extern "kernel32" stdcallcc fn QueryPerformanceCounter(lpPerformanceCount: *LARGE_INTEGER) BOOL;
-
-pub extern "kernel32" stdcallcc fn QueryPerformanceFrequency(lpFrequency: *LARGE_INTEGER) BOOL;
-
-pub extern "kernel32" stdcallcc fn ReadFile(
- in_hFile: HANDLE,
- out_lpBuffer: *c_void,
- in_nNumberOfBytesToRead: DWORD,
- out_lpNumberOfBytesRead: *DWORD,
- in_out_lpOverlapped: ?*OVERLAPPED,
-) BOOL;
-
-pub extern "kernel32" stdcallcc fn RemoveDirectoryA(lpPathName: LPCSTR) BOOL;
-
-pub extern "kernel32" stdcallcc fn SetFilePointerEx(
- in_fFile: HANDLE,
- in_liDistanceToMove: LARGE_INTEGER,
- out_opt_ldNewFilePointer: ?*LARGE_INTEGER,
- in_dwMoveMethod: DWORD,
-) BOOL;
-
-pub extern "kernel32" stdcallcc fn SetHandleInformation(hObject: HANDLE, dwMask: DWORD, dwFlags: DWORD) BOOL;
-
-pub extern "kernel32" stdcallcc fn Sleep(dwMilliseconds: DWORD) void;
-
-pub extern "kernel32" stdcallcc fn TerminateProcess(hProcess: HANDLE, uExitCode: UINT) BOOL;
-
-pub extern "kernel32" stdcallcc fn WaitForSingleObject(hHandle: HANDLE, dwMilliseconds: DWORD) DWORD;
-
-pub extern "kernel32" stdcallcc fn WriteFile(
- in_hFile: HANDLE,
- in_lpBuffer: *const c_void,
- in_nNumberOfBytesToWrite: DWORD,
- out_lpNumberOfBytesWritten: ?*DWORD,
- in_out_lpOverlapped: ?*OVERLAPPED,
-) BOOL;
-
-//TODO: call unicode versions instead of relying on ANSI code page
-pub extern "kernel32" stdcallcc fn LoadLibraryA(lpLibFileName: LPCSTR) ?HMODULE;
-
-pub extern "kernel32" stdcallcc fn FreeLibrary(hModule: HMODULE) BOOL;
-
-pub extern "user32" stdcallcc fn MessageBoxA(hWnd: ?HANDLE, lpText: ?LPCTSTR, lpCaption: ?LPCTSTR, uType: UINT) c_int;
-
-pub extern "shlwapi" stdcallcc fn PathFileExistsA(pszPath: ?LPCTSTR) BOOL;
-
-pub const PROV_RSA_FULL = 1;
-
pub const BOOL = c_int;
pub const BOOLEAN = BYTE;
pub const BYTE = u8;
@@ -204,6 +35,7 @@ pub const LPSTR = [*]CHAR;
pub const LPTSTR = if (UNICODE) LPWSTR else LPSTR;
pub const LPVOID = *c_void;
pub const LPWSTR = [*]WCHAR;
+pub const LPCWSTR = [*]const WCHAR;
pub const PVOID = *c_void;
pub const PWSTR = [*]WCHAR;
pub const SIZE_T = usize;
@@ -439,3 +271,82 @@ pub const SYSTEM_INFO = extern struct {
wProcessorLevel: WORD,
wProcessorRevision: WORD,
};
+
+pub const HRESULT = c_long;
+
+pub const KNOWNFOLDERID = GUID;
+pub const GUID = extern struct {
+ Data1: c_ulong,
+ Data2: c_ushort,
+ Data3: c_ushort,
+ Data4: [8]u8,
+
+ pub fn parse(str: []const u8) GUID {
+ var guid: GUID = undefined;
+ var index: usize = 0;
+ assert(str[index] == '{');
+ index += 1;
+
+ guid.Data1 = std.fmt.parseUnsigned(c_ulong, str[index..index + 8], 16) catch unreachable;
+ index += 8;
+
+ assert(str[index] == '-');
+ index += 1;
+
+ guid.Data2 = std.fmt.parseUnsigned(c_ushort, str[index..index + 4], 16) catch unreachable;
+ index += 4;
+
+ assert(str[index] == '-');
+ index += 1;
+
+ guid.Data3 = std.fmt.parseUnsigned(c_ushort, str[index..index + 4], 16) catch unreachable;
+ index += 4;
+
+ assert(str[index] == '-');
+ index += 1;
+
+ guid.Data4[0] = std.fmt.parseUnsigned(u8, str[index..index + 2], 16) catch unreachable;
+ index += 2;
+ guid.Data4[1] = std.fmt.parseUnsigned(u8, str[index..index + 2], 16) catch unreachable;
+ index += 2;
+
+ assert(str[index] == '-');
+ index += 1;
+
+ var i: usize = 2;
+ while (i < guid.Data4.len) : (i += 1) {
+ guid.Data4[i] = std.fmt.parseUnsigned(u8, str[index..index + 2], 16) catch unreachable;
+ index += 2;
+ }
+
+ assert(str[index] == '}');
+ index += 1;
+ return guid;
+ }
+};
+
+pub const FOLDERID_LocalAppData = GUID.parse("{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}");
+
+pub const KF_FLAG_DEFAULT = 0;
+pub const KF_FLAG_NO_APPCONTAINER_REDIRECTION = 65536;
+pub const KF_FLAG_CREATE = 32768;
+pub const KF_FLAG_DONT_VERIFY = 16384;
+pub const KF_FLAG_DONT_UNEXPAND = 8192;
+pub const KF_FLAG_NO_ALIAS = 4096;
+pub const KF_FLAG_INIT = 2048;
+pub const KF_FLAG_DEFAULT_PATH = 1024;
+pub const KF_FLAG_NOT_PARENT_RELATIVE = 512;
+pub const KF_FLAG_SIMPLE_IDLIST = 256;
+pub const KF_FLAG_ALIAS_ONLY = -2147483648;
+
+pub const S_OK = 0;
+pub const E_NOTIMPL = @bitCast(c_long, c_ulong(0x80004001));
+pub const E_NOINTERFACE = @bitCast(c_long, c_ulong(0x80004002));
+pub const E_POINTER = @bitCast(c_long, c_ulong(0x80004003));
+pub const E_ABORT = @bitCast(c_long, c_ulong(0x80004004));
+pub const E_FAIL = @bitCast(c_long, c_ulong(0x80004005));
+pub const E_UNEXPECTED = @bitCast(c_long, c_ulong(0x8000FFFF));
+pub const E_ACCESSDENIED = @bitCast(c_long, c_ulong(0x80070005));
+pub const E_HANDLE = @bitCast(c_long, c_ulong(0x80070006));
+pub const E_OUTOFMEMORY = @bitCast(c_long, c_ulong(0x8007000E));
+pub const E_INVALIDARG = @bitCast(c_long, c_ulong(0x80070057));
diff --git a/std/os/windows/kernel32.zig b/std/os/windows/kernel32.zig
new file mode 100644
index 0000000000..fa3473ad05
--- /dev/null
+++ b/std/os/windows/kernel32.zig
@@ -0,0 +1,162 @@
+use @import("index.zig");
+
+pub extern "kernel32" stdcallcc fn CloseHandle(hObject: HANDLE) BOOL;
+
+pub extern "kernel32" stdcallcc fn CreateDirectoryA(
+ lpPathName: LPCSTR,
+ lpSecurityAttributes: ?*SECURITY_ATTRIBUTES,
+) BOOL;
+
+pub extern "kernel32" stdcallcc fn CreateFileA(
+ lpFileName: LPCSTR,
+ dwDesiredAccess: DWORD,
+ dwShareMode: DWORD,
+ lpSecurityAttributes: ?LPSECURITY_ATTRIBUTES,
+ dwCreationDisposition: DWORD,
+ dwFlagsAndAttributes: DWORD,
+ hTemplateFile: ?HANDLE,
+) HANDLE;
+
+pub extern "kernel32" stdcallcc fn CreatePipe(
+ hReadPipe: *HANDLE,
+ hWritePipe: *HANDLE,
+ lpPipeAttributes: *const SECURITY_ATTRIBUTES,
+ nSize: DWORD,
+) BOOL;
+
+pub extern "kernel32" stdcallcc fn CreateProcessA(
+ lpApplicationName: ?LPCSTR,
+ lpCommandLine: LPSTR,
+ lpProcessAttributes: ?*SECURITY_ATTRIBUTES,
+ lpThreadAttributes: ?*SECURITY_ATTRIBUTES,
+ bInheritHandles: BOOL,
+ dwCreationFlags: DWORD,
+ lpEnvironment: ?*c_void,
+ lpCurrentDirectory: ?LPCSTR,
+ lpStartupInfo: *STARTUPINFOA,
+ lpProcessInformation: *PROCESS_INFORMATION,
+) BOOL;
+
+pub extern "kernel32" stdcallcc fn CreateSymbolicLinkA(
+ lpSymlinkFileName: LPCSTR,
+ lpTargetFileName: LPCSTR,
+ dwFlags: DWORD,
+) BOOLEAN;
+
+pub extern "kernel32" stdcallcc fn CreateIoCompletionPort(FileHandle: HANDLE, ExistingCompletionPort: ?HANDLE, CompletionKey: ULONG_PTR, NumberOfConcurrentThreads: DWORD) ?HANDLE;
+
+pub extern "kernel32" stdcallcc fn CreateThread(lpThreadAttributes: ?LPSECURITY_ATTRIBUTES, dwStackSize: SIZE_T, lpStartAddress: LPTHREAD_START_ROUTINE, lpParameter: ?LPVOID, dwCreationFlags: DWORD, lpThreadId: ?LPDWORD) ?HANDLE;
+
+pub extern "kernel32" stdcallcc fn DeleteFileA(lpFileName: LPCSTR) BOOL;
+
+pub extern "kernel32" stdcallcc fn ExitProcess(exit_code: UINT) noreturn;
+
+pub extern "kernel32" stdcallcc fn FindFirstFileA(lpFileName: LPCSTR, lpFindFileData: *WIN32_FIND_DATAA) HANDLE;
+pub extern "kernel32" stdcallcc fn FindClose(hFindFile: HANDLE) BOOL;
+pub extern "kernel32" stdcallcc fn FindNextFileA(hFindFile: HANDLE, lpFindFileData: *WIN32_FIND_DATAA) BOOL;
+
+pub extern "kernel32" stdcallcc fn FreeEnvironmentStringsA(penv: [*]u8) BOOL;
+
+pub extern "kernel32" stdcallcc fn GetCommandLineA() LPSTR;
+
+pub extern "kernel32" stdcallcc fn GetConsoleMode(in_hConsoleHandle: HANDLE, out_lpMode: *DWORD) BOOL;
+
+pub extern "kernel32" stdcallcc fn GetCurrentDirectoryA(nBufferLength: WORD, lpBuffer: ?LPSTR) DWORD;
+
+pub extern "kernel32" stdcallcc fn GetEnvironmentStringsA() ?[*]u8;
+
+pub extern "kernel32" stdcallcc fn GetEnvironmentVariableA(lpName: LPCSTR, lpBuffer: LPSTR, nSize: DWORD) DWORD;
+
+pub extern "kernel32" stdcallcc fn GetExitCodeProcess(hProcess: HANDLE, lpExitCode: *DWORD) BOOL;
+
+pub extern "kernel32" stdcallcc fn GetFileSizeEx(hFile: HANDLE, lpFileSize: *LARGE_INTEGER) BOOL;
+
+pub extern "kernel32" stdcallcc fn GetFileAttributesA(lpFileName: LPCSTR) DWORD;
+
+pub extern "kernel32" stdcallcc fn GetModuleFileNameA(hModule: ?HMODULE, lpFilename: LPSTR, nSize: DWORD) DWORD;
+
+pub extern "kernel32" stdcallcc fn GetLastError() DWORD;
+
+pub extern "kernel32" stdcallcc fn GetFileInformationByHandleEx(
+ in_hFile: HANDLE,
+ in_FileInformationClass: FILE_INFO_BY_HANDLE_CLASS,
+ out_lpFileInformation: *c_void,
+ in_dwBufferSize: DWORD,
+) BOOL;
+
+pub extern "kernel32" stdcallcc fn GetFinalPathNameByHandleA(
+ hFile: HANDLE,
+ lpszFilePath: LPSTR,
+ cchFilePath: DWORD,
+ dwFlags: DWORD,
+) DWORD;
+
+pub extern "kernel32" stdcallcc fn GetProcessHeap() ?HANDLE;
+pub extern "kernel32" stdcallcc fn GetQueuedCompletionStatus(CompletionPort: HANDLE, lpNumberOfBytesTransferred: LPDWORD, lpCompletionKey: *ULONG_PTR, lpOverlapped: *?*OVERLAPPED, dwMilliseconds: DWORD) BOOL;
+
+pub extern "kernel32" stdcallcc fn GetSystemInfo(lpSystemInfo: *SYSTEM_INFO) void;
+pub extern "kernel32" stdcallcc fn GetSystemTimeAsFileTime(*FILETIME) void;
+
+pub extern "kernel32" stdcallcc fn HeapCreate(flOptions: DWORD, dwInitialSize: SIZE_T, dwMaximumSize: SIZE_T) ?HANDLE;
+pub extern "kernel32" stdcallcc fn HeapDestroy(hHeap: HANDLE) BOOL;
+pub extern "kernel32" stdcallcc fn HeapReAlloc(hHeap: HANDLE, dwFlags: DWORD, lpMem: *c_void, dwBytes: SIZE_T) ?*c_void;
+pub extern "kernel32" stdcallcc fn HeapSize(hHeap: HANDLE, dwFlags: DWORD, lpMem: *const c_void) SIZE_T;
+pub extern "kernel32" stdcallcc fn HeapValidate(hHeap: HANDLE, dwFlags: DWORD, lpMem: *const c_void) BOOL;
+pub extern "kernel32" stdcallcc fn HeapCompact(hHeap: HANDLE, dwFlags: DWORD) SIZE_T;
+pub extern "kernel32" stdcallcc fn HeapSummary(hHeap: HANDLE, dwFlags: DWORD, lpSummary: LPHEAP_SUMMARY) BOOL;
+
+pub extern "kernel32" stdcallcc fn GetStdHandle(in_nStdHandle: DWORD) ?HANDLE;
+
+pub extern "kernel32" stdcallcc fn HeapAlloc(hHeap: HANDLE, dwFlags: DWORD, dwBytes: SIZE_T) ?*c_void;
+
+pub extern "kernel32" stdcallcc fn HeapFree(hHeap: HANDLE, dwFlags: DWORD, lpMem: *c_void) BOOL;
+
+pub extern "kernel32" stdcallcc fn MoveFileExA(
+ lpExistingFileName: LPCSTR,
+ lpNewFileName: LPCSTR,
+ dwFlags: DWORD,
+) BOOL;
+
+pub extern "kernel32" stdcallcc fn PostQueuedCompletionStatus(CompletionPort: HANDLE, dwNumberOfBytesTransferred: DWORD, dwCompletionKey: ULONG_PTR, lpOverlapped: ?*OVERLAPPED) BOOL;
+
+pub extern "kernel32" stdcallcc fn QueryPerformanceCounter(lpPerformanceCount: *LARGE_INTEGER) BOOL;
+
+pub extern "kernel32" stdcallcc fn QueryPerformanceFrequency(lpFrequency: *LARGE_INTEGER) BOOL;
+
+pub extern "kernel32" stdcallcc fn ReadFile(
+ in_hFile: HANDLE,
+ out_lpBuffer: *c_void,
+ in_nNumberOfBytesToRead: DWORD,
+ out_lpNumberOfBytesRead: *DWORD,
+ in_out_lpOverlapped: ?*OVERLAPPED,
+) BOOL;
+
+pub extern "kernel32" stdcallcc fn RemoveDirectoryA(lpPathName: LPCSTR) BOOL;
+
+pub extern "kernel32" stdcallcc fn SetFilePointerEx(
+ in_fFile: HANDLE,
+ in_liDistanceToMove: LARGE_INTEGER,
+ out_opt_ldNewFilePointer: ?*LARGE_INTEGER,
+ in_dwMoveMethod: DWORD,
+) BOOL;
+
+pub extern "kernel32" stdcallcc fn SetHandleInformation(hObject: HANDLE, dwMask: DWORD, dwFlags: DWORD) BOOL;
+
+pub extern "kernel32" stdcallcc fn Sleep(dwMilliseconds: DWORD) void;
+
+pub extern "kernel32" stdcallcc fn TerminateProcess(hProcess: HANDLE, uExitCode: UINT) BOOL;
+
+pub extern "kernel32" stdcallcc fn WaitForSingleObject(hHandle: HANDLE, dwMilliseconds: DWORD) DWORD;
+
+pub extern "kernel32" stdcallcc fn WriteFile(
+ in_hFile: HANDLE,
+ in_lpBuffer: *const c_void,
+ in_nNumberOfBytesToWrite: DWORD,
+ out_lpNumberOfBytesWritten: ?*DWORD,
+ in_out_lpOverlapped: ?*OVERLAPPED,
+) BOOL;
+
+//TODO: call unicode versions instead of relying on ANSI code page
+pub extern "kernel32" stdcallcc fn LoadLibraryA(lpLibFileName: LPCSTR) ?HMODULE;
+
+pub extern "kernel32" stdcallcc fn FreeLibrary(hModule: HMODULE) BOOL;
diff --git a/std/os/windows/ole32.zig b/std/os/windows/ole32.zig
new file mode 100644
index 0000000000..84d8089d07
--- /dev/null
+++ b/std/os/windows/ole32.zig
@@ -0,0 +1,18 @@
+use @import("index.zig");
+
+pub extern "ole32.dll" stdcallcc fn CoTaskMemFree(pv: LPVOID) void;
+pub extern "ole32.dll" stdcallcc fn CoUninitialize() void;
+pub extern "ole32.dll" stdcallcc fn CoGetCurrentProcess() DWORD;
+pub extern "ole32.dll" stdcallcc fn CoInitializeEx(pvReserved: LPVOID, dwCoInit: DWORD) HRESULT;
+
+
+pub const COINIT_APARTMENTTHREADED = COINIT.COINIT_APARTMENTTHREADED;
+pub const COINIT_MULTITHREADED = COINIT.COINIT_MULTITHREADED;
+pub const COINIT_DISABLE_OLE1DDE = COINIT.COINIT_DISABLE_OLE1DDE;
+pub const COINIT_SPEED_OVER_MEMORY = COINIT.COINIT_SPEED_OVER_MEMORY;
+pub const COINIT = extern enum {
+ COINIT_APARTMENTTHREADED = 2,
+ COINIT_MULTITHREADED = 0,
+ COINIT_DISABLE_OLE1DDE = 4,
+ COINIT_SPEED_OVER_MEMORY = 8,
+};
diff --git a/std/os/windows/shell32.zig b/std/os/windows/shell32.zig
new file mode 100644
index 0000000000..f10466add3
--- /dev/null
+++ b/std/os/windows/shell32.zig
@@ -0,0 +1,4 @@
+use @import("index.zig");
+
+pub extern "shell32.dll" stdcallcc fn SHGetKnownFolderPath(rfid: *const KNOWNFOLDERID, dwFlags: DWORD, hToken: ?HANDLE, ppszPath: *[*]WCHAR) HRESULT;
+
diff --git a/std/os/windows/shlwapi.zig b/std/os/windows/shlwapi.zig
new file mode 100644
index 0000000000..6bccefaf98
--- /dev/null
+++ b/std/os/windows/shlwapi.zig
@@ -0,0 +1,4 @@
+use @import("index.zig");
+
+pub extern "shlwapi" stdcallcc fn PathFileExistsA(pszPath: ?LPCTSTR) BOOL;
+
diff --git a/std/os/windows/user32.zig b/std/os/windows/user32.zig
new file mode 100644
index 0000000000..37f9f6f3b8
--- /dev/null
+++ b/std/os/windows/user32.zig
@@ -0,0 +1,4 @@
+use @import("index.zig");
+
+pub extern "user32" stdcallcc fn MessageBoxA(hWnd: ?HANDLE, lpText: ?LPCTSTR, lpCaption: ?LPCTSTR, uType: UINT) c_int;
+
diff --git a/std/special/test_runner.zig b/std/special/test_runner.zig
index 76a54a5018..857739e82d 100644
--- a/std/special/test_runner.zig
+++ b/std/special/test_runner.zig
@@ -5,11 +5,25 @@ const test_fn_list = builtin.__zig_test_fn_slice;
const warn = std.debug.warn;
pub fn main() !void {
+ var ok_count: usize = 0;
+ var skip_count: usize = 0;
for (test_fn_list) |test_fn, i| {
warn("Test {}/{} {}...", i + 1, test_fn_list.len, test_fn.name);
- try test_fn.func();
-
- warn("OK\n");
+ if (test_fn.func()) |_| {
+ ok_count += 1;
+ warn("OK\n");
+ } else |err| switch (err) {
+ error.SkipZigTest => {
+ skip_count += 1;
+ warn("SKIP\n");
+ },
+ else => return err,
+ }
+ }
+ if (ok_count == test_fn_list.len) {
+ warn("All tests passed.\n");
+ } else {
+ warn("{} passed; {} skipped.\n", ok_count, skip_count);
}
}
diff --git a/std/unicode.zig b/std/unicode.zig
index 9c329acc68..8a9d4a9214 100644
--- a/std/unicode.zig
+++ b/std/unicode.zig
@@ -1,5 +1,8 @@
const std = @import("./index.zig");
+const builtin = @import("builtin");
const debug = std.debug;
+const assert = std.debug.assert;
+const mem = std.mem;
/// Returns how many bytes the UTF-8 representation would require
/// for the given codepoint.
@@ -441,3 +444,89 @@ fn testDecode(bytes: []const u8) !u32 {
debug.assert(bytes.len == length);
return utf8Decode(bytes);
}
+
+// TODO: make this API on top of a non-allocating Utf16LeView
+pub fn utf16leToUtf8(allocator: *mem.Allocator, utf16le: []const u16) ![]u8 {
+ var result = std.ArrayList(u8).init(allocator);
+ // optimistically guess that it will all be ascii.
+ try result.ensureCapacity(utf16le.len);
+
+ const utf16le_as_bytes = @sliceToBytes(utf16le);
+ var i: usize = 0;
+ var out_index: usize = 0;
+ while (i < utf16le_as_bytes.len) : (i += 2) {
+ // decode
+ const c0: u32 = mem.readIntLE(u16, utf16le_as_bytes[i..i + 2]);
+ var codepoint: u32 = undefined;
+ if (c0 & ~u32(0x03ff) == 0xd800) {
+ // surrogate pair
+ i += 2;
+ if (i >= utf16le_as_bytes.len) return error.DanglingSurrogateHalf;
+ const c1: u32 = mem.readIntLE(u16, utf16le_as_bytes[i..i + 2]);
+ if (c1 & ~u32(0x03ff) != 0xdc00) return error.ExpectedSecondSurrogateHalf;
+ codepoint = 0x10000 + (((c0 & 0x03ff) << 10) | (c1 & 0x03ff));
+ } else if (c0 & ~u32(0x03ff) == 0xdc00) {
+ return error.UnexpectedSecondSurrogateHalf;
+ } else {
+ codepoint = c0;
+ }
+
+ // encode
+ const utf8_len = utf8CodepointSequenceLength(codepoint) catch unreachable;
+ try result.resize(result.len + utf8_len);
+ _ = utf8Encode(codepoint, result.items[out_index..]) catch unreachable;
+ out_index += utf8_len;
+ }
+
+ return result.toOwnedSlice();
+}
+
+test "utf16leToUtf8" {
+ var utf16le: [2]u16 = undefined;
+ const utf16le_as_bytes = @sliceToBytes(utf16le[0..]);
+
+ {
+ mem.writeInt(utf16le_as_bytes[0..], u16('A'), builtin.Endian.Little);
+ mem.writeInt(utf16le_as_bytes[2..], u16('a'), builtin.Endian.Little);
+ const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le);
+ assert(mem.eql(u8, utf8, "Aa"));
+ }
+
+ {
+ mem.writeInt(utf16le_as_bytes[0..], u16(0x80), builtin.Endian.Little);
+ mem.writeInt(utf16le_as_bytes[2..], u16(0xffff), builtin.Endian.Little);
+ const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le);
+ assert(mem.eql(u8, utf8, "\xc2\x80" ++ "\xef\xbf\xbf"));
+ }
+
+ {
+ // the values just outside the surrogate half range
+ mem.writeInt(utf16le_as_bytes[0..], u16(0xd7ff), builtin.Endian.Little);
+ mem.writeInt(utf16le_as_bytes[2..], u16(0xe000), builtin.Endian.Little);
+ const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le);
+ assert(mem.eql(u8, utf8, "\xed\x9f\xbf" ++ "\xee\x80\x80"));
+ }
+
+ {
+ // smallest surrogate pair
+ mem.writeInt(utf16le_as_bytes[0..], u16(0xd800), builtin.Endian.Little);
+ mem.writeInt(utf16le_as_bytes[2..], u16(0xdc00), builtin.Endian.Little);
+ const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le);
+ assert(mem.eql(u8, utf8, "\xf0\x90\x80\x80"));
+ }
+
+ {
+ // largest surrogate pair
+ mem.writeInt(utf16le_as_bytes[0..], u16(0xdbff), builtin.Endian.Little);
+ mem.writeInt(utf16le_as_bytes[2..], u16(0xdfff), builtin.Endian.Little);
+ const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le);
+ assert(mem.eql(u8, utf8, "\xf4\x8f\xbf\xbf"));
+ }
+
+ {
+ mem.writeInt(utf16le_as_bytes[0..], u16(0xdbff), builtin.Endian.Little);
+ mem.writeInt(utf16le_as_bytes[2..], u16(0xdc00), builtin.Endian.Little);
+ const utf8 = try utf16leToUtf8(std.debug.global_allocator, utf16le);
+ assert(mem.eql(u8, utf8, "\xf4\x8f\xb0\x80"));
+ }
+}
diff --git a/std/zig/index.zig b/std/zig/index.zig
index 4dd68fa8b3..da84bc5bb0 100644
--- a/std/zig/index.zig
+++ b/std/zig/index.zig
@@ -2,6 +2,7 @@ const tokenizer = @import("tokenizer.zig");
pub const Token = tokenizer.Token;
pub const Tokenizer = tokenizer.Tokenizer;
pub const parse = @import("parse.zig").parse;
+pub const parseStringLiteral = @import("parse_string_literal.zig").parseStringLiteral;
pub const render = @import("render.zig").render;
pub const ast = @import("ast.zig");
@@ -10,4 +11,6 @@ test "std.zig tests" {
_ = @import("parse.zig");
_ = @import("render.zig");
_ = @import("tokenizer.zig");
+ _ = @import("parse_string_literal.zig");
}
+
diff --git a/std/zig/parse.zig b/std/zig/parse.zig
index 9842ba2a17..73d51e7870 100644
--- a/std/zig/parse.zig
+++ b/std/zig/parse.zig
@@ -2356,7 +2356,7 @@ pub fn parse(allocator: *mem.Allocator, source: []const u8) !ast.Tree {
const token = nextToken(&tok_it, &tree);
switch (token.ptr.id) {
Token.Id.IntegerLiteral => {
- _ = try createToCtxLiteral(arena, opt_ctx, ast.Node.StringLiteral, token.index);
+ _ = try createToCtxLiteral(arena, opt_ctx, ast.Node.IntegerLiteral, token.index);
continue;
},
Token.Id.FloatLiteral => {
diff --git a/std/zig/parse_string_literal.zig b/std/zig/parse_string_literal.zig
new file mode 100644
index 0000000000..00c92a7651
--- /dev/null
+++ b/std/zig/parse_string_literal.zig
@@ -0,0 +1,76 @@
+const std = @import("../index.zig");
+const assert = std.debug.assert;
+
+const State = enum {
+ Start,
+ Backslash,
+};
+
+pub const ParseStringLiteralError = error{
+ OutOfMemory,
+
+ /// When this is returned, index will be the position of the character.
+ InvalidCharacter,
+};
+
+/// caller owns returned memory
+pub fn parseStringLiteral(
+ allocator: *std.mem.Allocator,
+ bytes: []const u8,
+ bad_index: *usize, // populated if error.InvalidCharacter is returned
+) ParseStringLiteralError![]u8 {
+ const first_index = if (bytes[0] == 'c') usize(2) else usize(1);
+ assert(bytes[bytes.len - 1] == '"');
+
+ var list = std.ArrayList(u8).init(allocator);
+ errdefer list.deinit();
+
+ const slice = bytes[first_index..];
+ try list.ensureCapacity(slice.len - 1);
+
+ var state = State.Start;
+ for (slice) |b, index| {
+ switch (state) {
+ State.Start => switch (b) {
+ '\\' => state = State.Backslash,
+ '\n' => {
+ bad_index.* = index;
+ return error.InvalidCharacter;
+ },
+ '"' => return list.toOwnedSlice(),
+ else => try list.append(b),
+ },
+ State.Backslash => switch (b) {
+ 'x' => @panic("TODO"),
+ 'u' => @panic("TODO"),
+ 'U' => @panic("TODO"),
+ 'n' => {
+ try list.append('\n');
+ state = State.Start;
+ },
+ 'r' => {
+ try list.append('\r');
+ state = State.Start;
+ },
+ '\\' => {
+ try list.append('\\');
+ state = State.Start;
+ },
+ 't' => {
+ try list.append('\t');
+ state = State.Start;
+ },
+ '"' => {
+ try list.append('"');
+ state = State.Start;
+ },
+ else => {
+ bad_index.* = index;
+ return error.InvalidCharacter;
+ },
+ },
+ else => unreachable,
+ }
+ }
+ unreachable;
+}
diff --git a/std/zig/tokenizer.zig b/std/zig/tokenizer.zig
index 79f1871b64..3c7ab1f0a8 100644
--- a/std/zig/tokenizer.zig
+++ b/std/zig/tokenizer.zig
@@ -73,6 +73,7 @@ pub const Token = struct {
return null;
}
+ /// TODO remove this enum
const StrLitKind = enum {
Normal,
C,
diff --git a/test/cases/cast.zig b/test/cases/cast.zig
index 5688d90e11..63cc6313e1 100644
--- a/test/cases/cast.zig
+++ b/test/cases/cast.zig
@@ -468,3 +468,20 @@ test "@intCast i32 to u7" {
var z = x >> @intCast(u7, y);
assert(z == 0xff);
}
+
+test "implicit cast undefined to optional" {
+ assert(MakeType(void).getNull() == null);
+ assert(MakeType(void).getNonNull() != null);
+}
+
+fn MakeType(comptime T: type) type {
+ return struct {
+ fn getNull() ?T {
+ return null;
+ }
+
+ fn getNonNull() ?T {
+ return T(undefined);
+ }
+ };
+}
diff --git a/test/cases/defer.zig b/test/cases/defer.zig
index d2b00d1f91..7d4d1bc3d8 100644
--- a/test/cases/defer.zig
+++ b/test/cases/defer.zig
@@ -61,3 +61,18 @@ test "defer and labeled break" {
assert(i == 1);
}
+
+test "errdefer does not apply to fn inside fn" {
+ if (testNestedFnErrDefer()) |_| @panic("expected error") else |e| assert(e == error.Bad);
+}
+
+fn testNestedFnErrDefer() error!void {
+ var a: i32 = 0;
+ errdefer a += 1;
+ const S = struct {
+ fn baz() error {
+ return error.Bad;
+ }
+ };
+ return S.baz();
+}
diff --git a/test/cases/eval.zig b/test/cases/eval.zig
index 83d2e80176..9da475994d 100644
--- a/test/cases/eval.zig
+++ b/test/cases/eval.zig
@@ -642,3 +642,13 @@ test "@tagName of @typeId" {
const str = @tagName(@typeId(u8));
assert(std.mem.eql(u8, str, "Int"));
}
+
+test "setting backward branch quota just before a generic fn call" {
+ @setEvalBranchQuota(1001);
+ loopNTimes(1001);
+}
+
+fn loopNTimes(comptime n: usize) void {
+ comptime var i = 0;
+ inline while (i < n) : (i += 1) {}
+}
diff --git a/test/gen_h.zig b/test/gen_h.zig
index e6a757ea6d..b3aaa263d6 100644
--- a/test/gen_h.zig
+++ b/test/gen_h.zig
@@ -76,4 +76,51 @@ pub fn addCases(cases: *tests.GenHContext) void {
\\TEST_EXPORT void entry(struct Foo foo, uint8_t bar[]);
\\
);
+
+ cases.add("ptr to zig struct",
+ \\const S = struct {
+ \\ a: u8,
+ \\};
+ \\
+ \\export fn a(s: *S) u8 {
+ \\ return s.a;
+ \\}
+
+ ,
+ \\struct S;
+ \\TEST_EXPORT uint8_t a(struct S * s);
+ \\
+ );
+
+ cases.add("ptr to zig union",
+ \\const U = union(enum) {
+ \\ A: u8,
+ \\ B: u16,
+ \\};
+ \\
+ \\export fn a(s: *U) u8 {
+ \\ return s.A;
+ \\}
+
+ ,
+ \\union U;
+ \\TEST_EXPORT uint8_t a(union U * s);
+ \\
+ );
+
+ cases.add("ptr to zig enum",
+ \\const E = enum(u8) {
+ \\ A,
+ \\ B,
+ \\};
+ \\
+ \\export fn a(s: *E) u8 {
+ \\ return @enumToInt(s.*);
+ \\}
+
+ ,
+ \\enum E;
+ \\TEST_EXPORT uint8_t a(enum E * s);
+ \\
+ );
}
diff --git a/test/stage2/compare_output.zig b/test/stage2/compare_output.zig
new file mode 100644
index 0000000000..35adcbb96b
--- /dev/null
+++ b/test/stage2/compare_output.zig
@@ -0,0 +1,12 @@
+const std = @import("std");
+const TestContext = @import("../../src-self-hosted/test.zig").TestContext;
+
+pub fn addCases(ctx: *TestContext) !void {
+ try ctx.testCompareOutputLibC(
+ \\extern fn puts([*]const u8) void;
+ \\export fn main() c_int {
+ \\ puts(c"Hello, world!");
+ \\ return 0;
+ \\}
+ , "Hello, world!" ++ std.cstr.line_sep);
+}
diff --git a/test/stage2/compile_errors.zig b/test/stage2/compile_errors.zig
index 1dca908e69..2cecd78653 100644
--- a/test/stage2/compile_errors.zig
+++ b/test/stage2/compile_errors.zig
@@ -9,4 +9,22 @@ pub fn addCases(ctx: *TestContext) !void {
try ctx.testCompileError(
\\fn() void {}
, "1.zig", 1, 1, "missing function name");
+
+ try ctx.testCompileError(
+ \\comptime {
+ \\ return;
+ \\}
+ , "1.zig", 2, 5, "return expression outside function definition");
+
+ try ctx.testCompileError(
+ \\export fn entry() void {
+ \\ defer return;
+ \\}
+ , "1.zig", 2, 11, "cannot return from defer expression");
+
+ try ctx.testCompileError(
+ \\export fn entry() c_int {
+ \\ return 36893488147419103232;
+ \\}
+ , "1.zig", 2, 12, "integer value '36893488147419103232' cannot be stored in type 'c_int'");
}
diff --git a/test/tests.zig b/test/tests.zig
index 3a72f58753..aa5eed17ee 100644
--- a/test/tests.zig
+++ b/test/tests.zig
@@ -89,12 +89,13 @@ pub fn addCompileErrorTests(b: *build.Builder, test_filter: ?[]const u8, modes:
return cases.step;
}
-pub fn addBuildExampleTests(b: *build.Builder, test_filter: ?[]const u8) *build.Step {
+pub fn addBuildExampleTests(b: *build.Builder, test_filter: ?[]const u8, modes: []const Mode) *build.Step {
const cases = b.allocator.create(BuildExamplesContext{
.b = b,
.step = b.step("test-build-examples", "Build the examples"),
.test_index = 0,
.test_filter = test_filter,
+ .modes = modes,
}) catch unreachable;
build_examples.addCases(cases);
@@ -697,6 +698,7 @@ pub const BuildExamplesContext = struct {
step: *build.Step,
test_index: usize,
test_filter: ?[]const u8,
+ modes: []const Mode,
pub fn addC(self: *BuildExamplesContext, root_src: []const u8) void {
self.addAllArgs(root_src, true);
@@ -739,12 +741,7 @@ pub const BuildExamplesContext = struct {
pub fn addAllArgs(self: *BuildExamplesContext, root_src: []const u8, link_libc: bool) void {
const b = self.b;
- for ([]Mode{
- Mode.Debug,
- Mode.ReleaseSafe,
- Mode.ReleaseFast,
- Mode.ReleaseSmall,
- }) |mode| {
+ for (self.modes) |mode| {
const annotated_case_name = fmt.allocPrint(self.b.allocator, "build {} ({})", root_src, @tagName(mode)) catch unreachable;
if (self.test_filter) |filter| {
if (mem.indexOf(u8, annotated_case_name, filter) == null) continue;
|