From 6ae42e05c577b8f68245117f49d6a582eb6372ec Mon Sep 17 00:00:00 2001 From: David Gonzalez Martin Date: Tue, 20 Feb 2024 22:55:34 -0600 Subject: [PATCH] Make build work --- bootstrap/Compilation.zig | 532 +++++++++++------- bootstrap/backend/llvm.zig | 61 +- bootstrap/main.zig | 6 +- build/test_runner.zig | 130 ++++- lib/std/build.nat | 10 +- lib/std/os.nat | 2 +- lib/std/os/linux.nat | 3 +- lib/std/start.nat | 2 + .../integral => test/build}/first/.gitignore | 0 .../integral => test/build}/first/build.nat | 0 .../build}/first/src/main.nat | 0 .../build}/link_libc/.gitignore | 0 .../build}/link_libc/build.nat | 0 .../build}/link_libc/src/main.nat | 0 test/standalone/null_terminated/main.nat | 7 + test/standalone/size/main.nat | 6 + test/standalone/zero_terminated/main.nat | 7 + 17 files changed, 530 insertions(+), 236 deletions(-) rename {todo_test/integral => test/build}/first/.gitignore (100%) rename {todo_test/integral => test/build}/first/build.nat (100%) rename {todo_test/integral => test/build}/first/src/main.nat (100%) rename {todo_test/integral => test/build}/link_libc/.gitignore (100%) rename {todo_test/integral => test/build}/link_libc/build.nat (100%) rename {todo_test/integral => test/build}/link_libc/src/main.nat (100%) create mode 100644 test/standalone/null_terminated/main.nat create mode 100644 test/standalone/size/main.nat create mode 100644 test/standalone/zero_terminated/main.nat diff --git a/bootstrap/Compilation.zig b/bootstrap/Compilation.zig index e912026..5871140 100644 --- a/bootstrap/Compilation.zig +++ b/bootstrap/Compilation.zig @@ -36,7 +36,7 @@ fn reportUnterminatedArgumentError(string: []const u8) noreturn { std.debug.panic("Unterminated argument: {s}", .{string}); } -pub fn buildExecutable(allocator: Allocator, arguments: [][:0]u8) !void { +pub fn createContext(allocator: Allocator) !*const Context{ const context: *Context = try allocator.create(Context); const self_exe_path = try std.fs.selfExePathAlloc(allocator); @@ -52,6 +52,42 @@ pub fn buildExecutable(allocator: Allocator, arguments: [][:0]u8) !void { try context.build_directory.makePath(cache_dir_name); try context.build_directory.makePath(installation_dir_name); + return context; +} + +pub fn compileBuildExecutable(context: *const Context, arguments: [][:0]u8) !void { + _ = arguments; // autofix + const unit = try context.allocator.create(Unit); + const target_query = try std.Target.Query.parse(.{}); + const target = try std.zig.system.resolveTargetQuery(target_query); + unit.* = .{ + .descriptor = .{ + .main_package_path = "build.nat", + .target = target, + .only_parse = false, + .executable_path = "nat/build", + .link_libc = @import("builtin").os.tag == .macos, + .generate_debug_information = true, + .name = "build", + }, + }; + + try unit.compile(context); + const result = try std.ChildProcess.run(.{ + .allocator = context.allocator, + .argv = &.{ "nat/build", "-compiler_path", context.executable_absolute_path }, + }); + switch (result.term) { + .Exited => |exit_code| { + if (exit_code != 0) @panic("Bad exit code"); + }, + .Signal => @panic("Signaled"), + .Stopped => @panic("Stopped"), + .Unknown => @panic("Unknown"), + } +} + +pub fn buildExecutable(context: *const Context, arguments: [][:0]u8) !void { var maybe_executable_path: ?[]const u8 = null; var maybe_main_package_path: ?[]const u8 = null; var target_triplet: []const u8 = switch (@import("builtin").os.tag) { @@ -63,121 +99,112 @@ pub fn buildExecutable(allocator: Allocator, arguments: [][:0]u8) !void { var link_libc = false; var maybe_executable_name: ?[]const u8 = null; const generate_debug_information = true; - var is_build = false; - if (arguments.len == 0) { - is_build = true; - } else if (equal(u8, arguments[0], "init")) { - if (arguments.len == 1) { - unreachable; - } else { - @panic("Init does not take arguments"); - } - } else { - var i: usize = 0; - while (i < arguments.len) : (i += 1) { - const current_argument = arguments[i]; - if (equal(u8, current_argument, "-o")) { - if (i + 1 != arguments.len) { - maybe_executable_path = arguments[i + 1]; - assert(maybe_executable_path.?.len != 0); - i += 1; - } else { - reportUnterminatedArgumentError(current_argument); - } - } else if (equal(u8, current_argument, "-target")) { - if (i + 1 != arguments.len) { - target_triplet = arguments[i + 1]; - i += 1; - } else { - reportUnterminatedArgumentError(current_argument); - } - } else if (equal(u8, current_argument, "-log")) { - if (i + 1 != arguments.len) { - i += 1; + if (arguments.len == 0) return error.InvalidInput; - var log_argument_iterator = std.mem.splitScalar(u8, arguments[i], ','); + var i: usize = 0; + while (i < arguments.len) : (i += 1) { + const current_argument = arguments[i]; + if (equal(u8, current_argument, "-o")) { + if (i + 1 != arguments.len) { + maybe_executable_path = arguments[i + 1]; + assert(maybe_executable_path.?.len != 0); + i += 1; + } else { + reportUnterminatedArgumentError(current_argument); + } + } else if (equal(u8, current_argument, "-target")) { + if (i + 1 != arguments.len) { + target_triplet = arguments[i + 1]; + i += 1; + } else { + reportUnterminatedArgumentError(current_argument); + } + } else if (equal(u8, current_argument, "-log")) { + if (i + 1 != arguments.len) { + i += 1; - while (log_argument_iterator.next()) |log_argument| { - var log_argument_splitter = std.mem.splitScalar(u8, log_argument, '.'); - const log_scope_candidate = log_argument_splitter.next() orelse unreachable; - var recognized_scope = false; + var log_argument_iterator = std.mem.splitScalar(u8, arguments[i], ','); - inline for (@typeInfo(LoggerScope).Enum.fields) |logger_scope_enum_field| { - const log_scope = @field(LoggerScope, logger_scope_enum_field.name); + while (log_argument_iterator.next()) |log_argument| { + var log_argument_splitter = std.mem.splitScalar(u8, log_argument, '.'); + const log_scope_candidate = log_argument_splitter.next() orelse unreachable; + var recognized_scope = false; - if (equal(u8, @tagName(log_scope), log_scope_candidate)) { - const LogScope = getLoggerScopeType(log_scope); + inline for (@typeInfo(LoggerScope).Enum.fields) |logger_scope_enum_field| { + const log_scope = @field(LoggerScope, logger_scope_enum_field.name); - if (log_argument_splitter.next()) |particular_log_candidate| { - var recognized_particular = false; - inline for (@typeInfo(LogScope.Logger).Enum.fields) |particular_log_field| { - const particular_log = @field(LogScope.Logger, particular_log_field.name); + if (equal(u8, @tagName(log_scope), log_scope_candidate)) { + const LogScope = getLoggerScopeType(log_scope); - if (equal(u8, particular_log_candidate, @tagName(particular_log))) { - LogScope.Logger.bitset.setPresent(particular_log, true); - recognized_particular = true; - } - } else if (!recognized_particular) std.debug.panic("Unrecognized particular log \"{s}\" in scope {s}", .{ particular_log_candidate, @tagName(log_scope) }); - } else { - // LogScope.Logger.bitset = @TypeOf(LogScope.Logger.bitset).initFull(); - } + if (log_argument_splitter.next()) |particular_log_candidate| { + var recognized_particular = false; + inline for (@typeInfo(LogScope.Logger).Enum.fields) |particular_log_field| { + const particular_log = @field(LogScope.Logger, particular_log_field.name); - logger_bitset.setPresent(log_scope, true); - - recognized_scope = true; + if (equal(u8, particular_log_candidate, @tagName(particular_log))) { + LogScope.Logger.bitset.setPresent(particular_log, true); + recognized_particular = true; + } + } else if (!recognized_particular) std.debug.panic("Unrecognized particular log \"{s}\" in scope {s}", .{ particular_log_candidate, @tagName(log_scope) }); + } else { + // LogScope.Logger.bitset = @TypeOf(LogScope.Logger.bitset).initFull(); } - } else if (!recognized_scope) std.debug.panic("Unrecognized log scope: {s}", .{log_scope_candidate}); - } - } else { - reportUnterminatedArgumentError(current_argument); - } - } else if (equal(u8, current_argument, "-parse")) { - if (i + 1 != arguments.len) { - i += 1; - const arg = arguments[i]; - maybe_main_package_path = arg; - maybe_only_parse = true; - } else { - reportUnterminatedArgumentError(current_argument); - } - } else if (equal(u8, current_argument, "-link_libc")) { - if (i + 1 != arguments.len) { - i += 1; + logger_bitset.setPresent(log_scope, true); - const arg = arguments[i]; - if (std.mem.eql(u8, arg, "true")) { - link_libc = true; - } else if (std.mem.eql(u8, arg, "false")) { - link_libc = false; - } else { - unreachable; - } - } else { - reportUnterminatedArgumentError(current_argument); - } - } else if (equal(u8, current_argument, "-main_source_file")) { - if (i + 1 != arguments.len) { - i += 1; - - const arg = arguments[i]; - maybe_main_package_path = arg; - } else { - reportUnterminatedArgumentError(current_argument); - } - } else if (equal(u8, current_argument, "-name")) { - if (i + 1 != arguments.len) { - i += 1; - - const arg = arguments[i]; - maybe_executable_name = arg; - } else { - reportUnterminatedArgumentError(current_argument); + recognized_scope = true; + } + } else if (!recognized_scope) std.debug.panic("Unrecognized log scope: {s}", .{log_scope_candidate}); } } else { - std.debug.panic("Unrecognized argument: {s}", .{current_argument}); + reportUnterminatedArgumentError(current_argument); } + } else if (equal(u8, current_argument, "-parse")) { + if (i + 1 != arguments.len) { + i += 1; + + const arg = arguments[i]; + maybe_main_package_path = arg; + maybe_only_parse = true; + } else { + reportUnterminatedArgumentError(current_argument); + } + } else if (equal(u8, current_argument, "-link_libc")) { + if (i + 1 != arguments.len) { + i += 1; + + const arg = arguments[i]; + if (std.mem.eql(u8, arg, "true")) { + link_libc = true; + } else if (std.mem.eql(u8, arg, "false")) { + link_libc = false; + } else { + unreachable; + } + } else { + reportUnterminatedArgumentError(current_argument); + } + } else if (equal(u8, current_argument, "-main_source_file")) { + if (i + 1 != arguments.len) { + i += 1; + + const arg = arguments[i]; + maybe_main_package_path = arg; + } else { + reportUnterminatedArgumentError(current_argument); + } + } else if (equal(u8, current_argument, "-name")) { + if (i + 1 != arguments.len) { + i += 1; + + const arg = arguments[i]; + maybe_executable_name = arg; + } else { + reportUnterminatedArgumentError(current_argument); + } + } else { + std.debug.panic("Unrecognized argument: {s}", .{current_argument}); } } @@ -190,25 +217,13 @@ pub fn buildExecutable(allocator: Allocator, arguments: [][:0]u8) !void { file.close(); break :blk path; - } else blk: { - const build_file = "build.nat"; - const file = std.fs.cwd().openFile(build_file, .{}) catch return error.main_package_path_not_specified; - file.close(); - is_build = true; + } else unreachable; - break :blk build_file; - }; - - const executable_name = if (is_build) b: { - assert(maybe_executable_name == null); - break :b "build"; - } else b: { - break :b if (maybe_executable_name) |name| name else std.fs.path.basename(main_package_path[0 .. main_package_path.len - "/main.nat".len]); - }; + const executable_name = if (maybe_executable_name) |name| name else std.fs.path.basename(main_package_path[0 .. main_package_path.len - "/main.nat".len]); const executable_path = maybe_executable_path orelse blk: { assert(executable_name.len > 0); - const result = try std.mem.concat(allocator, u8, &.{ "nat/", executable_name }); + const result = try std.mem.concat(context.allocator, u8, &.{ "nat/", executable_name }); break :blk result; }; @@ -218,7 +233,6 @@ pub fn buildExecutable(allocator: Allocator, arguments: [][:0]u8) !void { .main_package_path = main_package_path, .executable_path = executable_path, .target = target, - .is_build = is_build, .only_parse = only_parse, .link_libc = switch (target.os.tag) { .linux => link_libc, @@ -372,6 +386,12 @@ fn getTypeBitSize(ty: *Type, unit: *Unit) u32 { } }, .pointer => 64, + .@"enum" => |enum_index| b: { + const enum_type = unit.enums.get(enum_index); + const backing_type = unit.types.get(enum_type.backing_type); + break :b getTypeBitSize(backing_type, unit); + }, + .slice => 2 * @bitSizeOf(usize), else => |t| @panic(@tagName(t)), }; } @@ -397,7 +417,7 @@ pub const Type = union(enum) { fn getByteSize(ty: *Type, unit: *Unit) u32 { _ = unit; // autofix return switch (ty.*) { - .integer => |integer| integer.bit_count, + .integer => |integer| @divExact(integer.bit_count, @bitSizeOf(u8)), else => |t| @panic(@tagName(t)), }; } @@ -1312,6 +1332,18 @@ pub const Builder = struct { .pointer => |source_pointer| { if (destination_pointer.type == source_pointer.type) { if (destination_pointer.mutability == source_pointer.mutability) { + if (destination_pointer.nullable != source_pointer.nullable) { + std.debug.print("Dst: {} Src: {}\n",.{destination_pointer.nullable, source_pointer.nullable}); + if (destination_pointer.nullable) { + assert(destination_pointer.termination != source_pointer.termination); + unreachable; + } else { + unreachable; + } + } + if (destination_pointer.termination != source_pointer.termination) { + unreachable; + } unreachable; } else { break :b .pointer_const_to_var; @@ -1435,6 +1467,7 @@ pub const Builder = struct { else => |t| @panic(@tagName(t)), }; }, + .none => .usize, else => |t| @panic(@tagName(t)), }; @@ -2255,10 +2288,16 @@ pub const Builder = struct { return .pointer_to_nullable; } + } else { + if (destination_pointer.termination != .none) { + unreachable; + } else { + unreachable; + } } } - unreachable; + std.debug.panic("Pointer unknown typecheck:\nDst: {}\n Src: {}", .{destination_pointer, source_pointer}); }, else => |t| @panic(@tagName(t)), } @@ -2387,27 +2426,20 @@ pub const Builder = struct { } }, .array => |destination_array| { - switch (source.*) { - .array => |source_array| { - assert(destination_array.type == source_array.type); - assert(destination_array.count == source_array.count); - if (destination_array.termination != source_array.termination) { - if (destination_array.termination == .none) { - unreachable; - } else { - std.debug.panic("Expected {s} array termination, got {s}", .{@tagName(destination_array.termination), @tagName(source_array.termination)}); - } - } else unreachable; - }, - else => |t| @panic(@tagName(t)), - } - // .array => |array| switch (unit.types.get(array_type).*) { - // .array => |expected_array| { - // }, - // else => |t| @panic(@tagName(t)), - // }, - // else => |t| @panic(@tagName(t)), - // } + switch (source.*) { + .array => |source_array| { + assert(destination_array.type == source_array.type); + assert(destination_array.count == source_array.count); + if (destination_array.termination != source_array.termination) { + if (destination_array.termination == .none) { + unreachable; + } else { + std.debug.panic("Expected {s} array termination, got {s}", .{@tagName(destination_array.termination), @tagName(source_array.termination)}); + } + } else unreachable; + }, + else => |t| @panic(@tagName(t)), + } }, else => |t| @panic(@tagName(t)), } @@ -3928,6 +3960,19 @@ pub const Builder = struct { const result = try builder.resolveContainerLiteral(unit, context, initialization_nodes, container_type_index); break :block result; }, + .anonymous_container_literal => block: { + switch (type_expect) { + .type => |type_index| { + assert(node.left == .null); + assert(node.right != .null); + const initialization_nodes = unit.getNodeList(node.right); + const result = try builder.resolveContainerLiteral(unit, context, initialization_nodes, type_index); + break :block result; + }, + else => |t| @panic(@tagName(t)), + } + unreachable; + }, .enum_literal => block: { switch (type_expect) { .type => |type_index| { @@ -5191,52 +5236,15 @@ pub const Builder = struct { .type = gep_type, }; }, - .pointer => |child_pointer| switch (unit.types.get(child_pointer.type).*) { - .array => |array| b: { + .pointer => |child_pointer| switch (child_pointer.many) { + true => b: { const load = try unit.instructions.append(context.allocator, .{ .load = .{ .value = array_like_expression, .type = pointer.type, }, - }); + }); try builder.appendInstruction(unit, context, load); - - const gep = try unit.instructions.append(context.allocator, .{ - .get_element_pointer = .{ - .pointer = load, - .base_type = array.type, - .is_struct = false, - .index = index, - }, - }); - try builder.appendInstruction(unit, context, gep); - - const gep_type = try unit.getPointerType(context, .{ - .type = array.type, - .termination = .none, - .mutability = pointer.mutability, - .many = false, - .nullable = false, - }); - - break :b .{ - .value = .{ - .runtime = gep, - }, - .type = gep_type, - }; - }, - .integer => b: { - assert(child_pointer.many); - - const load = try unit.instructions.append(context.allocator, .{ - .load = .{ - .value = array_like_expression, - .type = pointer.type, - }, - }); - try builder.appendInstruction(unit, context, load); - const gep = try unit.instructions.append(context.allocator, .{ .get_element_pointer = .{ .pointer = load, @@ -5249,8 +5257,8 @@ pub const Builder = struct { const gep_type = try unit.getPointerType(context, .{ .type = child_pointer.type, - .termination = .none, - .mutability = pointer.mutability, + .termination = child_pointer.termination, + .mutability = child_pointer.mutability, .many = false, .nullable = false, }); @@ -5262,7 +5270,79 @@ pub const Builder = struct { .type = gep_type, }; }, - else => |t| @panic(@tagName(t)), + false => switch (unit.types.get(child_pointer.type).*) { + .array => |array| b: { + const load = try unit.instructions.append(context.allocator, .{ + .load = .{ + .value = array_like_expression, + .type = pointer.type, + }, + }); + try builder.appendInstruction(unit, context, load); + + const gep = try unit.instructions.append(context.allocator, .{ + .get_element_pointer = .{ + .pointer = load, + .base_type = array.type, + .is_struct = false, + .index = index, + }, + }); + try builder.appendInstruction(unit, context, gep); + + const gep_type = try unit.getPointerType(context, .{ + .type = array.type, + .termination = .none, + .mutability = pointer.mutability, + .many = false, + .nullable = false, + }); + + break :b .{ + .value = .{ + .runtime = gep, + }, + .type = gep_type, + }; + }, + .integer => b: { + assert(child_pointer.many); + + const load = try unit.instructions.append(context.allocator, .{ + .load = .{ + .value = array_like_expression, + .type = pointer.type, + }, + }); + try builder.appendInstruction(unit, context, load); + + const gep = try unit.instructions.append(context.allocator, .{ + .get_element_pointer = .{ + .pointer = load, + .base_type = child_pointer.type, + .is_struct = false, + .index = index, + }, + }); + try builder.appendInstruction(unit, context, gep); + + const gep_type = try unit.getPointerType(context, .{ + .type = child_pointer.type, + .termination = .none, + .mutability = pointer.mutability, + .many = false, + .nullable = false, + }); + + break :b .{ + .value = .{ + .runtime = gep, + }, + .type = gep_type, + }; + }, + else => |t| @panic(@tagName(t)), + }, }, else => |t| @panic(@tagName(t)), }, @@ -5658,7 +5738,11 @@ pub const Builder = struct { if (array_type.count != expression_element_count) @panic("Array element count mismatch"); var is_comptime = true; - var values = try ArrayList(V).initCapacity(context.allocator, nodes.len); + const is_terminated = switch (array_type.termination) { + .none => false, + else => true, + }; + var values = try ArrayList(V).initCapacity(context.allocator, nodes.len + @intFromBool(is_terminated)); for (nodes) |node_index| { const value = try builder.resolveRuntimeValue(unit, context, Type.Expect{ .type = array_type.type }, node_index, .right); // assert(value.value == .@"comptime"); @@ -5666,6 +5750,26 @@ pub const Builder = struct { values.appendAssumeCapacity(value); } + switch (array_type.termination) { + .none => {}, + .zero => values.appendAssumeCapacity(.{ + .value = .{ + .@"comptime" = .{ + .constant_int = .{ + .value = 0, + }, + }, + }, + .type = array_type.type, + }), + .null => values.appendAssumeCapacity(.{ + .value = .{ + .@"comptime" = .null_pointer, + }, + .type = array_type.type, + }), + } + if (is_comptime) { const constant_array = try unit.constant_arrays.append(context.allocator, .{ .values = blk: { @@ -7662,30 +7766,47 @@ pub const Builder = struct { .constant_int = .{ .value = ct_int.value, }, + }, + }, + .type = ti, + }; + }, + .signed => { + if (destination_integer_type.signedness == .unsigned) { + unreachable; + } else { + const value = -@as(i64, @intCast(ct_int.value)); + return .{ + .value = .{ + .@"comptime" = .{ + .constant_int = .{ + .value = @bitCast(value), + }, }, }, .type = ti, }; - }, - .signed => { - if (destination_integer_type.signedness == .unsigned) { - unreachable; - } else { - const value = -@as(i64, @intCast(ct_int.value)); - return .{ - .value = .{ - .@"comptime" = .{ - .constant_int = .{ - .value = @bitCast(value), - }, - }, - }, - .type = ti, - }; - } - }, - } - }, + } + }, + } + }, + .pointer_to_nullable => { + const cast = try unit.instructions.append(context.allocator, .{ + .cast = .{ + .id = .pointer_to_nullable, + .value = result, + .type = ti, + }, + }); + try builder.appendInstruction(unit, context, cast); + + return .{ + .value = .{ + .runtime = cast, + }, + .type = ti, + }; + }, else => |t| @panic(@tagName(t)), } }, @@ -8513,7 +8634,6 @@ pub const Descriptor = struct { main_package_path: []const u8, executable_path: []const u8, target: std.Target, - is_build: bool, only_parse: bool, link_libc: bool, generate_debug_information: bool, diff --git a/bootstrap/backend/llvm.zig b/bootstrap/backend/llvm.zig index 11b071f..968be85 100644 --- a/bootstrap/backend/llvm.zig +++ b/bootstrap/backend/llvm.zig @@ -1255,7 +1255,11 @@ pub const LLVM = struct { }, .array => |array| blk: { const element_type = try llvm.getType(unit, context, array.type); - const array_type = LLVM.Type.Array.get(element_type, array.count) orelse return Type.Error.array; + const extra_element = switch (array.termination) { + .none => false, + else => true, + }; + const array_type = LLVM.Type.Array.get(element_type, array.count + @intFromBool(extra_element)) orelse return Type.Error.array; break :blk array_type.toType(); }, else => |t| @panic(@tagName(t)), @@ -2223,6 +2227,7 @@ pub const LLVM = struct { }; list.appendAssumeCapacity(value); } + const result = array_type.getConstant(list.items.ptr, list.items.len) orelse unreachable; return result; } @@ -3193,17 +3198,55 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo var arguments = ArrayList([*:0]const u8){}; try arguments.append(context.allocator, driver_program); - try arguments.append(context.allocator, object_file_path.ptr); + try arguments.append(context.allocator, "--error-limit=0"); + try arguments.append(context.allocator, "-o"); try arguments.append(context.allocator, destination_file_path.ptr); - if (format == .macho) { - try arguments.append(context.allocator, "-dynamic"); - try arguments.appendSlice(context.allocator, &.{ "-platform_version", "macos", "13.4.1", "13.3" }); - try arguments.appendSlice(context.allocator, &.{ "-arch", "arm64" }); - try arguments.appendSlice(context.allocator, &.{ "-syslibroot", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk" }); - try arguments.appendSlice(context.allocator, &.{ "-e", "_main" }); - try arguments.append(context.allocator, "-lSystem"); + try arguments.append(context.allocator, object_file_path.ptr); + + switch (unit.descriptor.target.os.tag) { + .macos => { + try arguments.append(context.allocator, "-dynamic"); + try arguments.appendSlice(context.allocator, &.{ "-platform_version", "macos", "13.4.1", "13.3" }); + try arguments.append(context.allocator, "-arch"); + try arguments.append(context.allocator, switch (unit.descriptor.target.cpu.arch) { + .aarch64 => "arm64", + else => |t| @panic(@tagName(t)), + }); + try arguments.appendSlice(context.allocator, &.{ "-syslibroot", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk" }); + try arguments.appendSlice(context.allocator, &.{ "-e", "_main" }); + try arguments.append(context.allocator, "-lSystem"); + }, + .linux => { + try arguments.appendSlice(context.allocator, &.{"--entry", "_start"}); + try arguments.append(context.allocator, "-m"); + try arguments.append(context.allocator, switch (unit.descriptor.target.cpu.arch) { + .x86_64 => "elf_x86_64", + else => |t| @panic(@tagName(t)), + }); + + if (unit.descriptor.link_libc) { + try arguments.append(context.allocator, "/usr/lib/crt1.o"); + try arguments.append(context.allocator, "/usr/lib/crti.o"); + try arguments.appendSlice(context.allocator, &.{"-L", "/usr/lib"}); + try arguments.appendSlice(context.allocator, &.{"-dynamic-linker", "/lib64/ld-linux-x86-64.so.2"}); + try arguments.append(context.allocator, "--as-needed"); + try arguments.append(context.allocator, "-lm"); + try arguments.append(context.allocator, "-lpthread"); + try arguments.append(context.allocator, "-lc"); + try arguments.append(context.allocator, "-ldl"); + try arguments.append(context.allocator, "-lrt"); + try arguments.append(context.allocator, "-lutil"); + try arguments.append(context.allocator, "/usr/lib/crtn.o"); + } + + // if (unit.descriptor.link_libc) { + // try arguments.appendSlice(context.allocator, &.{ "-lc" }); + // } + }, + .windows => {}, + else => |t| @panic(@tagName(t)), } var stdout_ptr: [*]const u8 = undefined; diff --git a/bootstrap/main.zig b/bootstrap/main.zig index af90707..e562f00 100644 --- a/bootstrap/main.zig +++ b/bootstrap/main.zig @@ -29,7 +29,8 @@ pub fn main() !void { const command_arguments = arguments[2..]; if (equal(u8, command, "build")) { - todo(); + const context = try Compilation.createContext(allocator); + try Compilation.compileBuildExecutable(context, command_arguments); } else if (equal(u8, command, "clang") or equal(u8, command, "-cc1") or equal(u8, command, "-cc1as")) { const exit_code = try clangMain(allocator, arguments); std.process.exit(exit_code); @@ -40,7 +41,8 @@ pub fn main() !void { // TODO: transform our arguments to Clang and invoke it todo(); } else if (equal(u8, command, "exe")) { - try Compilation.buildExecutable(allocator, command_arguments); + const context = try Compilation.createContext(allocator); + try Compilation.buildExecutable(context, command_arguments); } else if (equal(u8, command, "lib")) { todo(); } else if (equal(u8, command, "obj")) { diff --git a/build/test_runner.zig b/build/test_runner.zig index dfca186..fe623db 100644 --- a/build/test_runner.zig +++ b/build/test_runner.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const Allocator = std.mem.Allocator; const TestError = error{ junk_in_test_directory, @@ -9,36 +10,38 @@ const TestError = error{ fail, }; -pub fn main() !void { - std.debug.print("\n",.{}); - var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); - const allocator = arena.allocator(); - const standalone_test_dir_path = "test/standalone"; - var standalone_test_dir = try std.fs.cwd().openDir(standalone_test_dir_path, .{ +fn collectDirectoryDirEntries(allocator: Allocator, path: []const u8) ![]const []const u8{ + var dir = try std.fs.cwd().openDir(path, .{ .iterate = true, }); - var standalone_iterator = standalone_test_dir.iterate(); - var standalone_test_names = std.ArrayListUnmanaged([]const u8){}; + var dir_iterator = dir.iterate(); + var dir_entries = std.ArrayListUnmanaged([]const u8){}; - while (try standalone_iterator.next()) |entry| { + while (try dir_iterator.next()) |entry| { switch (entry.kind) { - .directory => try standalone_test_names.append(allocator, try allocator.dupe(u8, entry.name)), + .directory => try dir_entries.append(allocator, try allocator.dupe(u8, entry.name)), else => return error.junk_in_test_directory, } } - standalone_test_dir.close(); + dir.close(); - const total_compilation_count = standalone_test_names.items.len; + return dir_entries.items; +} + +fn runStandaloneTests(allocator: Allocator) !void { + const standalone_test_dir_path = "test/standalone"; + const standalone_test_names = try collectDirectoryDirEntries(allocator, standalone_test_dir_path); + + const total_compilation_count = standalone_test_names.len; var ran_compilation_count: usize = 0; var failed_compilation_count: usize = 0; var ran_test_count: usize = 0; var failed_test_count: usize = 0; - const total_test_count = standalone_test_names.items.len; + const total_test_count = standalone_test_names.len; - for (standalone_test_names.items) |standalone_test_name| { - std.debug.assert(!std.mem.eql(u8, standalone_test_name, "else_ifrld")); + for (standalone_test_names) |standalone_test_name| { std.debug.print("{s}... ", .{standalone_test_name}); const source_file_path = try std.mem.concat(allocator, u8, &.{standalone_test_dir_path, "/", standalone_test_name, "/main.nat"}); const compile_run = try std.ChildProcess.run(.{ @@ -106,3 +109,100 @@ pub fn main() !void { return error.fail; } } + +fn runBuildTests(allocator: Allocator) !void { + std.debug.print("\n[BUILD TESTS]\n\n", .{}); + const test_dir_path = "test/build"; + const test_names = try collectDirectoryDirEntries(allocator, test_dir_path); + const test_dir_realpath = try std.fs.cwd().realpathAlloc(allocator, test_dir_path); + const compiler_realpath = try std.fs.cwd().realpathAlloc(allocator, "zig-out/bin/nat"); + try std.os.chdir(test_dir_realpath); + + const total_compilation_count = test_names.len; + var ran_compilation_count: usize = 0; + var failed_compilation_count: usize = 0; + + var ran_test_count: usize = 0; + var failed_test_count: usize = 0; + const total_test_count = test_names.len; + + for (test_names) |test_name| { + std.debug.print("{s}... ", .{test_name}); + try std.os.chdir(test_name); + + const compile_run = try std.ChildProcess.run(.{ + .allocator = allocator, + // TODO: delete -main_source_file? + .argv = &.{compiler_realpath, "build"}, + }); + + ran_compilation_count += 1; + + const compilation_result: TestError!bool = switch (compile_run.term) { + .Exited => |exit_code| if (exit_code == 0) true else error.abnormal_exit_code, + .Signal => error.signaled, + .Stopped => error.stopped, + .Unknown => error.unknown, + }; + + const compilation_success = compilation_result catch b: { + failed_compilation_count += 1; + break :b false; + }; + + std.debug.print("[COMPILATION {s}] ", .{if (compilation_success) "\x1b[32mOK\x1b[0m" else "\x1b[31mFAILED\x1b[0m"}); + if (compile_run.stdout.len > 0) { + std.debug.print("STDOUT:\n\n{s}\n\n", .{compile_run.stdout}); + } + if (compile_run.stderr.len > 0) { + std.debug.print("STDERR:\n\n{s}\n\n", .{compile_run.stderr}); + } + + if (compilation_success) { + const test_path = try std.mem.concat(allocator, u8, &.{"nat/", test_name}); + const test_run = try std.ChildProcess.run(.{ + .allocator = allocator, + // TODO: delete -main_source_file? + .argv = &.{ test_path }, + }); + ran_test_count += 1; + const test_result: TestError!bool = switch (test_run.term) { + .Exited => |exit_code| if (exit_code == 0) true else error.abnormal_exit_code, + .Signal => error.signaled, + .Stopped => error.stopped, + .Unknown => error.unknown, + }; + + const test_success = test_result catch b: { + failed_test_count += 1; + break :b false; + }; + std.debug.print("[TEST {s}]\n", .{if (test_success) "\x1b[32mOK\x1b[0m" else "\x1b[31mFAILED\x1b[0m"}); + if (test_run.stdout.len > 0) { + std.debug.print("STDOUT:\n\n{s}\n\n", .{test_run.stdout}); + } + if (test_run.stderr.len > 0) { + std.debug.print("STDERR:\n\n{s}\n\n", .{test_run.stderr}); + } + } else { + std.debug.print("\n", .{}); + } + + try std.os.chdir(test_dir_realpath); + } + + std.debug.print("\nTOTAL COMPILATIONS: {}. FAILED: {}\n", .{total_compilation_count, failed_compilation_count}); + std.debug.print("TOTAL TESTS: {}. RAN: {}. FAILED: {}\n", .{total_test_count, ran_test_count, failed_test_count}); + + if (failed_compilation_count > 0 or failed_test_count > 0) { + return error.fail; + } +} + +pub fn main() !void { + std.debug.print("\n",.{}); + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + const allocator = arena.allocator(); + try runStandaloneTests(allocator); + try runBuildTests(allocator); +} diff --git a/lib/std/build.nat b/lib/std/build.nat index 5f5089a..f062065 100644 --- a/lib/std/build.nat +++ b/lib/std/build.nat @@ -7,7 +7,7 @@ const Executable = struct{ target: Target, main_source_path: [:0]const u8, link_libc: bool = false, - name: []const u8, + name: [:0]const u8, const compile = fn(executable: Executable) bool { const argument_count = std.start.argument_count; @@ -22,7 +22,13 @@ const Executable = struct{ const compile_with_compiler_path = fn(executable: Executable, compiler_path: [&:0]const u8) bool { if (std.os.duplicate_process()) |pid| { if (pid == 0) { - const argv = [_:null] ?[&:0]const u8{ compiler_path, "-main_source_file", #cast(executable.main_source_path.ptr), "-link_libc", if (executable.link_libc) "true" else "false", "-name", #cast(executable.name.ptr) }; + var link_libc_arg: [&:0]const u8 = undefined; + if (executable.link_libc) { + link_libc_arg = "true"; + } else { + link_libc_arg = "false"; + } + const argv = [_:null] ?[&:0]const u8{ compiler_path, "exe", "-main_source_file", executable.main_source_path.ptr, "-link_libc", link_libc_arg, "-name", executable.name.ptr }; std.os.execute(path = compiler_path, argv = argv.&, env = std.start.environment_values); return true; } else { diff --git a/lib/std/os.nat b/lib/std/os.nat index 433cc5e..9b28aa6 100644 --- a/lib/std/os.nat +++ b/lib/std/os.nat @@ -406,7 +406,7 @@ const waitpid = fn(pid: Process.Id, flags: u32) ?u32 { while (true) { const raw_syscall_result = linux.waitpid(pid, status = status.&, flags, resource_usage = 0); const signed_syscall_result: ssize = #cast(raw_syscall_result); - if (raw_syscall_result != -4) { + if (signed_syscall_result != -4) { if (linux.unwrap_syscall(syscall_result = raw_syscall_result)) |_| { return status; } else { diff --git a/lib/std/os/linux.nat b/lib/std/os/linux.nat index e2a7c9c..29ab0de 100644 --- a/lib/std/os/linux.nat +++ b/lib/std/os/linux.nat @@ -478,7 +478,8 @@ const pipe2 = fn (pipe_pointer: &[2]s32, flags: u32) usize { } const waitpid = fn(pid: ProcessId, status: &u32, flags: u32, resource_usage: usize) usize { - const result = #syscall(#cast(Syscall.wait4), pid, #cast(status), flags, resource_usage); + const pid_unsigned: u32 = #cast(pid); + const result = #syscall(#cast(Syscall.wait4), pid_unsigned, #cast(status), flags, resource_usage); return result; } diff --git a/lib/std/start.nat b/lib/std/start.nat index 042579d..53c1f43 100644 --- a/lib/std/start.nat +++ b/lib/std/start.nat @@ -27,8 +27,10 @@ const start :: export = fn (argc_argv_address: usize) noreturn { argument_count = argument_count_ptr.@; argument_address_iterator += #size(usize); argument_values = #cast(argument_address_iterator); + const argv = argument_values; argument_address_iterator += #size(usize) * (argument_count + 1); environment_values = #cast(argument_address_iterator); + const env = environment_values; const result = #import("main").main(); std.os.exit(exit_code = result); } diff --git a/todo_test/integral/first/.gitignore b/test/build/first/.gitignore similarity index 100% rename from todo_test/integral/first/.gitignore rename to test/build/first/.gitignore diff --git a/todo_test/integral/first/build.nat b/test/build/first/build.nat similarity index 100% rename from todo_test/integral/first/build.nat rename to test/build/first/build.nat diff --git a/todo_test/integral/first/src/main.nat b/test/build/first/src/main.nat similarity index 100% rename from todo_test/integral/first/src/main.nat rename to test/build/first/src/main.nat diff --git a/todo_test/integral/link_libc/.gitignore b/test/build/link_libc/.gitignore similarity index 100% rename from todo_test/integral/link_libc/.gitignore rename to test/build/link_libc/.gitignore diff --git a/todo_test/integral/link_libc/build.nat b/test/build/link_libc/build.nat similarity index 100% rename from todo_test/integral/link_libc/build.nat rename to test/build/link_libc/build.nat diff --git a/todo_test/integral/link_libc/src/main.nat b/test/build/link_libc/src/main.nat similarity index 100% rename from todo_test/integral/link_libc/src/main.nat rename to test/build/link_libc/src/main.nat diff --git a/test/standalone/null_terminated/main.nat b/test/standalone/null_terminated/main.nat new file mode 100644 index 0000000..faeab4e --- /dev/null +++ b/test/standalone/null_terminated/main.nat @@ -0,0 +1,7 @@ +const std = #import("std"); +const assert = std.assert; +const main = fn() s32 { + var foo = [2:null] ?[&:0]const u8 {"Hi", "Ho"}; + assert(foo[2] == null); + return 0; +} diff --git a/test/standalone/size/main.nat b/test/standalone/size/main.nat new file mode 100644 index 0000000..a73cfc8 --- /dev/null +++ b/test/standalone/size/main.nat @@ -0,0 +1,6 @@ +const std = #import("std"); +const assert = std.assert; +const main = fn () s32 { + assert(#size(usize) == 8); + return 0; +} diff --git a/test/standalone/zero_terminated/main.nat b/test/standalone/zero_terminated/main.nat new file mode 100644 index 0000000..02520e6 --- /dev/null +++ b/test/standalone/zero_terminated/main.nat @@ -0,0 +1,7 @@ +const std = #import("std"); +const assert = std.assert; +const main = fn () s32 { + var a = [2:0]u8 {1, 2}; + assert(a[2] == 0); + return 0; +}