From 30ed5a235adfa3c99bb5a4d29e778dbedec5434c Mon Sep 17 00:00:00 2001 From: David Gonzalez Martin Date: Thu, 6 Jun 2024 20:20:22 -0600 Subject: [PATCH] Implement 'assert' builtin --- TODOLIST | 4 +- bootstrap/compiler.zig | 73 +++++++++++++++++++++++++++++++ build.zig | 14 ++++++ build/new_test.zig | 17 +++++++ retest/standalone/assert/main.nat | 5 +++ 5 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 build/new_test.zig create mode 100644 retest/standalone/assert/main.nat diff --git a/TODOLIST b/TODOLIST index 28f6ed6..2426953 100644 --- a/TODOLIST +++ b/TODOLIST @@ -1,6 +1,4 @@ -arrays assert -c abi orelse size trailing zeroes @@ -10,3 +8,5 @@ returns inside loops (else conditional) returns inside loops (non-conditional) function pointers for loops +arrays +c abi diff --git a/bootstrap/compiler.zig b/bootstrap/compiler.zig index ad687bd..7f15558 100644 --- a/bootstrap/compiler.zig +++ b/bootstrap/compiler.zig @@ -193,6 +193,38 @@ const Parser = struct{ } } + fn parse_intrinsic(parser: *Parser, analyzer: *Analyzer, thread: *Thread, file: *File) void { + const src = file.source_code; + parser.expect_character(src, '#'); + + // TODO: make it more efficient + const identifier = parser.parse_raw_identifier(src); + if (identifier[0] == '"') { + unreachable; + } else { + const intrinsic_id = inline for (@typeInfo(Intrinsic).Enum.fields) |i_field| { + if (byte_equal(i_field.name, identifier)) { + break @field(Intrinsic, i_field.name); + } + } else { + exit_with_error("Unknown intrinsic"); + }; + + switch (intrinsic_id) { + .assert => { + const condition = parser.parse_condition(analyzer, thread, file); + const assert_true_block = create_basic_block(thread); + const assert_false_block = create_basic_block(thread); + _ = emit_branch(analyzer, thread, condition, assert_true_block, assert_false_block); + analyzer.current_basic_block = assert_false_block; + emit_unreachable(analyzer, thread); + analyzer.current_basic_block = assert_true_block; + }, + else => |t| @panic(@tagName(t)), + } + } + } + fn parse_raw_identifier(parser: *Parser, file: []const u8) []const u8 { const identifier_start = parser.i; const is_string_literal_identifier = file[identifier_start] == '"'; @@ -1215,6 +1247,7 @@ const Parser = struct{ }, else => |t| @panic(@tagName(t)), }; + return compare; } @@ -1509,6 +1542,12 @@ const Keyword = enum{ @"break", }; +const Intrinsic = enum{ + assert, + trap, + @"unreachable", +}; + fn parse_keyword(identifier: []const u8) u32 { assert(identifier.len > 0); if (identifier[0] != '"') { @@ -1730,6 +1769,7 @@ const Instruction = struct{ ret, ret_void, store, + @"unreachable", }; const id_to_instruction_map = std.EnumArray(Id, type).init(.{ @@ -1745,6 +1785,7 @@ const Instruction = struct{ .ret = Return, .ret_void = void, .store = Store, + .@"unreachable" = Unreachable, }); fn get_payload(instruction: *Instruction, comptime id: Id) *id_to_instruction_map.get(id) { @@ -1846,6 +1887,10 @@ const Return = struct{ value: *Value, }; +const Unreachable = struct{ + instruction: Instruction, +}; + const Import = struct { global_declaration: GlobalDeclaration, hash: u32, @@ -1891,6 +1936,7 @@ const Thread = struct{ local_symbols: PinnedArray(LocalSymbol) = .{}, argument_symbols: PinnedArray(ArgumentSymbol) = .{}, global_variables: PinnedArray(GlobalVariable) = .{}, + unreachables: PinnedArray(Unreachable) = .{}, analyzed_file_count: u32 = 0, assigned_file_count: u32 = 0, llvm: struct { @@ -3231,6 +3277,10 @@ fn worker_thread(thread_index: u32, cpu_count: *u32) void { _ = llvm_phi_nodes.append(phi_node); break :block phi_node.toValue(); }, + .@"unreachable" => block: { + const ur = builder.createUnreachable(); + break :block ur.toValue(); + }, else => |t| @panic(@tagName(t)), }; @@ -3837,6 +3887,11 @@ pub fn analyze_local_block(thread: *Thread, analyzer: *Analyzer, parser: *Parser parser.i = statement_start_ch_index; } }, + '#' => { + parser.parse_intrinsic(analyzer, thread, file); + parser.skip_space(src); + parser.expect_character(src, ';'); + }, else => {}, } @@ -4518,6 +4573,24 @@ fn typecheck(expected: *Type, have: *Type) TypecheckResult { } } +fn emit_unreachable(analyzer: *Analyzer, thread: *Thread) void { + assert(!analyzer.current_basic_block.is_terminated); + const ur = thread.unreachables.append(.{ + .instruction = .{ + .value = .{ + .sema = .{ + .id = .instruction, + .thread = thread.get_index(), + .resolved = true, + }, + }, + .id = .@"unreachable", + }, + }); + _ = analyzer.current_basic_block.instructions.append(&ur.instruction); + analyzer.current_basic_block.is_terminated = true; +} + const JumpEmission = struct { jump: *Jump, basic_block: *BasicBlock, diff --git a/build.zig b/build.zig index e9b8e4f..84254f6 100644 --- a/build.zig +++ b/build.zig @@ -599,10 +599,22 @@ pub fn build(b: *std.Build) !void { b.installArtifact(test_runner); test_command.step.dependOn(b.getInstallStep()); + const new_test = b.addExecutable(.{ + .name = "new_test", + .target = native_target, + .root_source_file = b.path("build/new_test.zig"), + .optimize = .ReleaseSmall, + }); + b.default_step.dependOn(&new_test.step); + + const new_test_command = b.addRunArtifact(new_test); + new_test_command.step.dependOn(b.getInstallStep()); + if (b.args) |args| { run_command.addArgs(args); debug_command.addArgs(args); test_command.addArgs(args); + new_test_command.addArgs(args); } const run_step = b.step("run", "Test the Nativity compiler"); @@ -611,6 +623,8 @@ pub fn build(b: *std.Build) !void { debug_step.dependOn(&debug_command.step); const test_step = b.step("test", "Test the Nativity compiler"); test_step.dependOn(&test_command.step); + const new_test_step = b.step("new_test", "Script to make a new test"); + new_test_step.dependOn(&new_test_command.step); const test_all = b.step("test_all", "Test all"); test_all.dependOn(&test_command.step); diff --git a/build/new_test.zig b/build/new_test.zig new file mode 100644 index 0000000..36e822f --- /dev/null +++ b/build/new_test.zig @@ -0,0 +1,17 @@ +const std = @import("std"); +pub fn main () !void { + var arena = std.heap.ArenaAllocator.init(std.heap.page_allocator); + const allocator = arena.allocator(); + const args = try std.process.argsAlloc(allocator); + if (args.len < 2) return; + + const test_name = args[1]; + try std.fs.cwd().makeDir(try std.mem.concat(allocator, u8, &.{"retest/standalone/", test_name})); + try std.fs.cwd().writeFile(.{ + .sub_path = try std.mem.concat(allocator, u8, &.{"retest/standalone/", test_name, "/main.nat"}), + .data = + \\fn[cc(.c)] main[export]() s32 { + \\ return 0; + \\} + }); +} diff --git a/retest/standalone/assert/main.nat b/retest/standalone/assert/main.nat new file mode 100644 index 0000000..1c98bc3 --- /dev/null +++ b/retest/standalone/assert/main.nat @@ -0,0 +1,5 @@ +fn[cc(.c)] main[export]() s32 { + >n: s32 = 1; + #assert(n); + return n - n; +}