From 1ecf7fa908f952ae29a52dac26bf79ccadb45fb1 Mon Sep 17 00:00:00 2001 From: David Gonzalez Martin Date: Tue, 20 Feb 2024 19:54:05 -0600 Subject: [PATCH] Pass all tests on Apple M1 --- .vscode/launch.json | 2 +- bootstrap/Compilation.zig | 905 +++++++++++++++++++++---- bootstrap/backend/llvm.zig | 243 ++++--- bootstrap/frontend/parser.zig | 10 +- build.zig | 56 +- lib/std/builtin.nat | 1 + lib/std/os.nat | 81 ++- lib/std/os/linux.nat | 2 +- lib/std/os/macos.nat | 57 +- test/standalone/self_exe_path/main.nat | 2 +- 10 files changed, 1064 insertions(+), 295 deletions(-) diff --git a/.vscode/launch.json b/.vscode/launch.json index 8e4f676..db8a929 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -12,7 +12,7 @@ "args": [ "exe", "-main_source_file", - "test/standalone/first/main.nat" + "test/standalone/hello_world/main.nat" ], "cwd": "${workspaceFolder}", }, diff --git a/bootstrap/Compilation.zig b/bootstrap/Compilation.zig index 0c05ad4..710279d 100644 --- a/bootstrap/Compilation.zig +++ b/bootstrap/Compilation.zig @@ -711,6 +711,7 @@ pub const Instruction = union(enum) { slice_var_to_const, slice_to_nullable, slice_to_not_null, + slice_coerce_to_zero_termination, truncate, pointer_to_array_to_pointer_to_many, }; @@ -841,6 +842,7 @@ pub const V = struct { bool: bool, comptime_int: ComptimeInt, constant_int: ConstantInt, + function_declaration: Type.Index, enum_value: Enum.Field.Index, function_definition: Function.Definition.Index, global: *Debug.Declaration.Global, @@ -893,6 +895,7 @@ pub const V = struct { .function_definition => |function_definition_index| unit.function_definitions.get(function_definition_index).type, .comptime_int => .comptime_int, .constant_struct => |constant_struct| unit.constant_structs.get(constant_struct).type, + .function_declaration => |function_type| function_type, else => |t| @panic(@tagName(t)), }; } @@ -925,6 +928,7 @@ pub const Debug = struct { pub const Attribute = enum { @"export", + @"extern", }; pub const List = BlockList(@This(), enum {}); @@ -1545,6 +1549,19 @@ pub const Builder = struct { return undefined; }, + .@"error" => { + assert(argument_node_list.len == 1); + // TODO: type + // const value = try builder.resolveComptimeValue(unit, context, Type.Expect.none, .{}, argument_node_list[0], null); + const argument_node = unit.getNode(argument_node_list[0]); + switch (argument_node.id) { + .string_literal => { + const error_message = try unit.fixupStringLiteral(context, argument_node.token); + std.debug.panic("Compile error: {s}", .{error_message}); + }, + else => |t| @panic(@tagName(t)), + } + }, else => |t| @panic(@tagName(t)), } } @@ -1770,6 +1787,17 @@ pub const Builder = struct { }, } }, + .function_declaration => |function_type| { + switch (unit.getNode(declaration_node_index).id) { + .function_prototype => try unit.external_functions.putNoClobber(context.allocator, function_type, global_declaration), + else => { + const actual_function_declaration = unit.external_functions.get(function_type).?; + global_declaration.initial_value = .{ + .global = actual_function_declaration, + }; + }, + } + }, .type => |type_index| { assert(declaration.type == .type); unit.type_declarations.put(context.allocator, type_index, global_declaration) catch { @@ -1904,15 +1932,12 @@ pub const Builder = struct { const function_prototype_node_index = node.left; const body_node_index = node.right; - const function_prototype_index = try builder.resolveFunctionPrototype(unit, context, function_prototype_node_index, global_attributes.contains(.@"export")); - const function_prototype_type_index = try unit.types.append(context.allocator, .{ - .function = function_prototype_index, - }); + const function_type_index = try builder.resolveFunctionPrototype(unit, context, function_prototype_node_index, global_attributes); const old_function = builder.current_function; const token_debug_info = builder.getTokenDebugInfo(unit, node.token); builder.current_function = try unit.function_definitions.append(context.allocator, .{ - .type = function_prototype_type_index, + .type = function_type_index, .body = undefined, .scope = .{ .scope = Debug.Scope{ @@ -1942,6 +1967,7 @@ pub const Builder = struct { const body_node = unit.getNode(body_node_index); try builder.insertDebugCheckPoint(unit, context, body_node.token); + const function_prototype_index = unit.types.get(function_type_index).function; // Get argument declarations into scope const function_prototype_node = unit.getNode(function_prototype_node_index); if (function_prototype_node.left != .null) { @@ -2070,6 +2096,7 @@ pub const Builder = struct { .none => unreachable, .type => |type_index| switch (unit.types.get(type_index).*) { .type => .right, + .integer => .right, else => |t| @panic(@tagName(t)), }, else => unreachable, @@ -2147,6 +2174,16 @@ pub const Builder = struct { else => |t| @panic(@tagName(t)), } }, + .function_prototype => { + const function_prototype = try builder.resolveFunctionPrototype(unit, context, node_index, global_attributes); + if (global_attributes.contains(.@"extern")) { + return .{ + .function_declaration = function_prototype, + }; + } else { + unreachable; + } + }, else => |t| @panic(@tagName(t)), } } @@ -2205,6 +2242,7 @@ pub const Builder = struct { pointer_to_nullable, slice_var_to_const, slice_to_nullable, + slice_coerce_to_zero_termination, materialize_int, optional_wrap, sign_extend, @@ -2254,6 +2292,13 @@ pub const Builder = struct { else => |t| @panic(@tagName(t)), } }, + .slice => |source_slice| { + switch (destination_pointer.many) { + true => unreachable, + false => unreachable, + } + _ = source_slice; // autofix + }, else => |t| @panic(@tagName(t)), } }, @@ -2282,7 +2327,7 @@ pub const Builder = struct { unreachable; } } else { - unreachable; + @panic("Signedness mismatch"); } }, .comptime_int => { @@ -2320,7 +2365,15 @@ pub const Builder = struct { return .slice_to_nullable; } } + } else { + if (destination_slice.termination == .none) { + return .slice_coerce_to_zero_termination; + } else { + unreachable; + } } + } else { + unreachable; } unreachable; @@ -2362,6 +2415,29 @@ pub const Builder = struct { else => |t| @panic(@tagName(t)), } }, + .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)), + // } + }, else => |t| @panic(@tagName(t)), } } @@ -2559,6 +2635,23 @@ pub const Builder = struct { .type = expected_type_index, }; }, + .slice_coerce_to_zero_termination => { + const cast_to_zero_termination = try unit.instructions.append(context.allocator, .{ + .cast = .{ + .id = .slice_coerce_to_zero_termination, + .value = v, + .type = expected_type_index, + }, + }); + try builder.appendInstruction(unit, context, cast_to_zero_termination); + + return .{ + .value = .{ + .runtime = cast_to_zero_termination, + }, + .type = expected_type_index, + }; + }, .slice_var_to_const => { const cast_to_const = try unit.instructions.append(context.allocator, .{ .cast = .{ @@ -2620,25 +2713,35 @@ pub const Builder = struct { if (destination_integer_type.bit_count < number_bit_count) { unreachable; } + return .{ + .value = .{ + .@"comptime" = .{ + .constant_int = .{ + .value = ct_int.value, + }, + }, + }, + .type = expected_type_index, + }; }, .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 = expected_type_index, + }; } - unreachable; }, } - - return .{ - .value = .{ - .@"comptime" = .{ - .constant_int = .{ - .value = ct_int.value, - }, - }, - }, - .type = expected_type_index, - }; }, .optional_wrap => { const optional_type_index = expected_type_index; @@ -2707,10 +2810,11 @@ pub const Builder = struct { .termination = expected_array_descriptor.termination, .type = expected_array_descriptor.type, }); - if (array_type == lookup.declaration.type) { - return v; - } else { - unreachable; + + const typecheck_result = try builder.typecheck(unit, context, array_type, lookup.declaration.type); + switch (typecheck_result) { + .success => return v, + else => |t| @panic(@tagName(t)), } }, else => |t| @panic(@tagName(t)), @@ -2881,6 +2985,7 @@ pub const Builder = struct { const len = switch (len_node.id) { else => switch (try builder.resolveComptimeValue(unit, context, Type.Expect{ .type = .usize }, .{}, attribute_node_list[0], null)) { .comptime_int => |ct_int| ct_int.value, + .constant_int => |constant_int| constant_int.value, else => |t| @panic(@tagName(t)), }, .discard => size_hint orelse unreachable, @@ -3061,21 +3166,14 @@ pub const Builder = struct { }; break :blk r; }, - .function_prototype => blk: { - const is_export = false; - const function_prototype_index = try builder.resolveFunctionPrototype(unit, context, node_index, is_export); - const function_type = try unit.types.append(context.allocator, .{ - .function = function_prototype_index, - }); - break :blk function_type; - }, + .function_prototype => try builder.resolveFunctionPrototype(unit, context, node_index, .{}), else => |t| @panic(@tagName(t)), }; return result; } - fn resolveFunctionPrototype(builder: *Builder, unit: *Unit, context: *const Context, node_index: Node.Index, is_export: bool) !Function.Prototype.Index { + fn resolveFunctionPrototype(builder: *Builder, unit: *Unit, context: *const Context, node_index: Node.Index, global_attributes: Debug.Declaration.Global.Attributes) !Type.Index { const node = unit.getNode(node_index); assert(node.id == .function_prototype); const attribute_and_return_type_node_list = unit.getNodeList(node.right); @@ -3090,7 +3188,7 @@ pub const Builder = struct { // .@"export" = false, .naked = false, }, - .calling_convention = switch (is_export) { + .calling_convention = switch (global_attributes.contains(.@"export") or global_attributes.contains(.@"extern")) { true => .c, false => .auto, }, @@ -3130,7 +3228,11 @@ pub const Builder = struct { function_prototype.return_type = try builder.resolveType(unit, context, return_type_node_index); - return function_prototype_index; + const function_prototype_type_index = try unit.types.append(context.allocator, .{ + .function = function_prototype_index, + }); + + return function_prototype_type_index; } fn resolveContainerType(builder: *Builder, unit: *Unit, context: *const Context, container_node_index: Node.Index, container_type: ContainerType, maybe_global: ?*Debug.Declaration.Global) !Type.Index { @@ -3342,6 +3444,7 @@ pub const Builder = struct { const attribute_node = unit.getNode(attribute_node_index); switch (attribute_node.id) { .symbol_attribute_export => res.setPresent(.@"export", true), + .symbol_attribute_extern => res.setPresent(.@"extern", true), else => |t| @panic(@tagName(t)), } } @@ -3485,6 +3588,7 @@ pub const Builder = struct { } fn emitIntegerCompare(builder: *Builder, unit: *Unit, context: *const Context, left_value: V, right_value: V, integer: Type.Integer, compare_node_id: Node.Id) anyerror!V { + assert(left_value.type == right_value.type); const compare = try unit.instructions.append(context.allocator, .{ .integer_compare = .{ .left = left_value, @@ -3597,6 +3701,57 @@ pub const Builder = struct { .bit_count = 1, .signedness = .unsigned, }, cmp_node_id), + .pointer => |pointer| b: { + const Pair = struct{ + left: V, + right: V, + }; + + const pair: Pair = if (left_value.type == right_value.type) .{ .left = left_value, .right = right_value } else switch (unit.types.get(right_value.type).*) { + .pointer => |right_pointer| blk: { + assert(pointer.type == right_pointer.type); + assert(pointer.mutability == right_pointer.mutability); + assert(pointer.termination == right_pointer.termination); + assert(pointer.many == right_pointer.many); + assert(pointer.nullable != right_pointer.nullable); + + if (pointer.nullable) { + // Left nullable + unreachable; + } else { + // Right nullable, then we cast the left side to optional + const cast = try unit.instructions.append(context.allocator, .{ + .cast = .{ + .id = .pointer_to_nullable, + .value = left_value, + .type = right_value.type, + }, + }); + try builder.appendInstruction(unit, context, cast); + + const new_left_value = V{ + .value = .{ + .runtime = cast, + }, + .type = right_value.type, + }; + + break :blk .{ + .left = new_left_value, + .right = right_value, + }; + } + }, + else => |t| @panic(@tagName(t)), + }; + + const compare = try builder.emitIntegerCompare(unit, context, pair.left, pair.right, .{ + .bit_count = 64, + .signedness = .unsigned, + }, cmp_node_id); + + break :b compare; + }, else => |t| @panic(@tagName(t)), }; }, @@ -3623,6 +3778,7 @@ pub const Builder = struct { const left_type = left_value.type; switch (unit.types.get(left_type).*) { .integer => {}, + .comptime_int => {}, else => |t| @panic(@tagName(t)), } @@ -3691,6 +3847,33 @@ pub const Builder = struct { }); break :b i; }, + .comptime_int => { + const left = left_value.value.@"comptime".comptime_int; + const right = right_value.value.@"comptime".comptime_int; + switch (binary_operation_id) { + .add => { + assert(left.signedness == right.signedness); + assert(left.signedness == .unsigned); + if (true) unreachable; + const value = left.value + right.value; + break :block switch (type_expect) { + .none => V{ + .value = .{ + .@"comptime" = .{ + .comptime_int = .{ + .value = value, + .signedness = left.signedness, + }, + }, + }, + .type = .comptime_int, + }, + else => |t| @panic(@tagName(t)), + }; + }, + else => |t| @panic(@tagName(t)), + } + }, else => |t| @panic(@tagName(t)), }; @@ -3718,6 +3901,17 @@ pub const Builder = struct { }, .type = type_index, }, + .comptime_int => V{ + .value = .{ + .@"comptime" = .{ + .comptime_int = .{ + .value = integer, + .signedness = .unsigned, + }, + }, + }, + .type = type_index, + }, else => |t| @panic(@tagName(t)), }, .none => V{ @@ -3888,22 +4082,24 @@ pub const Builder = struct { .type = type_index, }; }, - .pointer => |pointer| blk: { - assert(pointer.nullable); - - break :blk .{ - .value = .{ - .@"comptime" = .null_pointer, - }, - .type = type_index, - }; + .pointer => |pointer| .{ + .value = .{ + .@"comptime" = .null_pointer, + }, + .type = if (pointer.nullable) type_index else blk: { + var p = pointer; + p.nullable = true; + const nullable_pointer = try unit.getPointerType(context, p); + break :blk nullable_pointer; + }, }, else => |t| @panic(@tagName(t)), }, else => |t| @panic(@tagName(t)), }, .slice => blk: { - const expression_to_slice = try builder.resolveRuntimeValue(unit, context, Type.Expect.none, node.left, .right); + const expression_to_slice = try builder.resolveRuntimeValue(unit, context, Type.Expect.none, node.left, .left); + const range_node = unit.getNode(node.right); assert(range_node.id == .range); const range_start: V = try builder.resolveRuntimeValue(unit, context, Type.Expect{ .type = .usize }, range_node.left, .right); @@ -3925,23 +4121,137 @@ pub const Builder = struct { .type = .usize, }; }, - .pointer => |pointer| switch (unit.types.get(pointer.type).*) { - .array => |array| .{ - .value = .{ - .@"comptime" = .{ - .constant_int = .{ - .value = array.count, + .pointer => |pointer| switch (pointer.many) { + true => unreachable, + false => switch (unit.types.get(pointer.type).*) { + .array => |array| .{ + .value = .{ + .@"comptime" = .{ + .constant_int = .{ + .value = array.count, + }, }, }, + .type = .usize, }, - .type = .usize, + .slice => |slice| b: { + _ = slice; // autofix + assert(!pointer.many); + const gep = try unit.instructions.append(context.allocator, .{ + .get_element_pointer = .{ + .pointer = expression_to_slice.value.runtime, + .is_struct = true, + .base_type = pointer.type, + .index = .{ + .value = .{ + .@"comptime" = .{ + .constant_int = .{ + .value = 1, + }, + }, + }, + .type = .u32, + }, + + }, + }); + try builder.appendInstruction(unit, context, gep); + + const load = try unit.instructions.append(context.allocator, .{ + .load = .{ + .value = .{ + .value = .{ + .runtime = gep, + }, + .type = try unit.getPointerType(context, .{ + .type = .usize, + .termination = .none, + .many = false, + .nullable = false, + .mutability = .@"const", + }), + }, + .type = .usize, + }, + }); + try builder.appendInstruction(unit, context, load); + + break :b V{ + .value = .{ + .runtime = load, + }, + .type = .usize, + }; + }, + .pointer => |child_pointer| b: { + assert(!child_pointer.many); + switch (unit.types.get(child_pointer.type).*) { + .array => |array| { + break :b V{ + .value = .{ + .@"comptime" = .{ + .constant_int = .{ + .value = array.count, + }, + }, + }, + .type = .usize, + }; + }, + else => |t| @panic(@tagName(t)), + } + @panic("Range end of many-item pointer is unknown"); + }, + else => |t| @panic(@tagName(t)), }, - else => |t| @panic(@tagName(t)), }, else => |t| @panic(@tagName(t)), }, else => try builder.resolveRuntimeValue(unit, context, Type.Expect{ .type = .usize }, range_node.right, .right), }; + + const len_expression: V = b: { + if (range_start.value == .@"comptime" and range_end.value == .@"comptime") { + const end = switch (range_end.value.@"comptime") { + .constant_int => |constant_int| constant_int.value, + else => |t| @panic(@tagName(t)), + }; + const start = switch (range_start.value.@"comptime") { + .constant_int => |constant_int| constant_int.value, + else => |t| @panic(@tagName(t)), + }; + const len = end - start; + break :b V{ + .value = .{ + .@"comptime" = .{ + .constant_int = .{ + .value = len, + }, + }, + }, + .type = .usize, + }; + } else { + const range_compute = try unit.instructions.append(context.allocator, .{ + .integer_binary_operation = .{ + .left = range_end, + .right = range_start, + .id = .sub, + .signedness = .unsigned, + }, + }); + + try builder.appendInstruction(unit, context, range_compute); + + break :b .{ + .value = .{ + .runtime = range_compute, + }, + .type = .usize, + }; + } + }; + switch (unit.types.get(expression_to_slice.type).*) { .slice => |slice| { const extract_value = try unit.instructions.append(context.allocator, .{ @@ -3982,17 +4292,6 @@ pub const Builder = struct { }); try builder.appendInstruction(unit, context, slice_builder); - const range_compute = try unit.instructions.append(context.allocator, .{ - .integer_binary_operation = .{ - .left = range_end, - .right = range_start, - .id = .sub, - .signedness = .unsigned, - }, - }); - - try builder.appendInstruction(unit, context, range_compute); - const final_slice = try unit.instructions.append(context.allocator, .{ .insert_value = .{ .expression = V{ @@ -4002,12 +4301,7 @@ pub const Builder = struct { .type = expression_to_slice.type, }, .index = 1, - .new_value = .{ - .value = .{ - .runtime = range_compute, - }, - .type = .usize, - }, + .new_value = len_expression, }, }); @@ -4070,24 +4364,7 @@ pub const Builder = struct { .type = expression_to_slice.type, }, .index = 1, - .new_value = b: { - const range_compute = try unit.instructions.append(context.allocator, .{ - .integer_binary_operation = .{ - .id = .sub, - .left = range_end, - .right = range_start, - .signedness = .unsigned, - }, - }); - try builder.appendInstruction(unit, context, range_compute); - - break :b V{ - .value = .{ - .runtime = range_compute, - }, - .type = .usize, - }; - }, + .new_value = len_expression, }, }); try builder.appendInstruction(unit, context, final_slice); @@ -4116,11 +4393,20 @@ pub const Builder = struct { const pointer_type = try unit.getPointerType(context, .{ .type = array.type, - .termination = .none, + .termination = array.termination, .mutability = pointer.mutability, - .many = false, + .many = true, .nullable = false, }); + + const slice_type = try unit.getSliceType(context, .{ + .child_type = array.type, + .child_pointer_type = pointer_type, + .termination = array.termination, + .mutability = pointer.mutability, + .nullable = false, + }); + const slice_builder = try unit.instructions.append(context.allocator, .{ .insert_value = .{ .expression = V{ @@ -4129,6 +4415,7 @@ pub const Builder = struct { }, .type = switch (type_expect) { .type => |type_index| type_index, + .none => slice_type, else => |t| @panic(@tagName(t)), }, }, @@ -4143,17 +4430,6 @@ pub const Builder = struct { }); try builder.appendInstruction(unit, context, slice_builder); - const array_len_value = V{ - .value = .{ - .@"comptime" = .{ - .constant_int = .{ - .value = array.count, - }, - }, - }, - .type = .usize, - }; - const final_slice = try unit.instructions.append(context.allocator, .{ .insert_value = .{ .expression = V{ @@ -4163,47 +4439,7 @@ pub const Builder = struct { .type = expression_to_slice.type, }, .index = 1, - .new_value = switch (range_start.value) { - .runtime => b: { - const range_compute = try unit.instructions.append(context.allocator, .{ - .integer_binary_operation = .{ - .id = .sub, - .left = array_len_value, - .right = range_start, - .signedness = .unsigned, - }, - }); - try builder.appendInstruction(unit, context, range_compute); - - break :b V{ - .value = .{ - .runtime = range_compute, - }, - .type = .usize, - }; - }, - .@"comptime" => |ct| b: { - const range_start_value = switch (ct) { - .constant_int => |const_int| const_int.value, - else => |t| @panic(@tagName(t)), - }; - - const range = array.count - range_start_value; - - break :b V{ - .value = .{ - .@"comptime" = .{ - .constant_int = .{ - .value = range, - }, - }, - }, - .type = .usize, - }; - }, - else => |t| @panic(@tagName(t)), - }, - // }, + .new_value = len_expression, }, }); try builder.appendInstruction(unit, context, final_slice); @@ -4214,18 +4450,268 @@ pub const Builder = struct { }, .type = switch (type_expect) { .type => |type_index| type_index, + .none => slice_type, + else => |t| @panic(@tagName(t)), + }, + }; + }, + .pointer => |child_pointer| switch (child_pointer.many) { + true => { + assert(!child_pointer.nullable); + const load = try unit.instructions.append(context.allocator, .{ + .load = .{ + .value = expression_to_slice, + .type = pointer.type, + }, + }); + try builder.appendInstruction(unit, context, load); + + const pointer_gep = try unit.instructions.append(context.allocator, .{ + .get_element_pointer = .{ + .pointer = load, + .base_type = child_pointer.type, + .is_struct = false, + .index = range_start, + }, + }); + try builder.appendInstruction(unit, context, pointer_gep); + + const pointer_type = try unit.getPointerType(context, .{ + .type = child_pointer.type, + .termination = child_pointer.termination, + .mutability = child_pointer.mutability, + .many = true, + .nullable = false, + }); + + const slice_type = try unit.getSliceType(context, .{ + .child_type = child_pointer.type, + .child_pointer_type = pointer_type, + .termination = child_pointer.termination, + .mutability = child_pointer.mutability, + .nullable = false, + }); + + const slice_builder = try unit.instructions.append(context.allocator, .{ + .insert_value = .{ + .expression = V{ + .value = .{ + .@"comptime" = .undefined, + }, + .type = switch (type_expect) { + .type => |type_index| type_index, + .none => slice_type, + else => |t| @panic(@tagName(t)), + }, + }, + .index = 0, + .new_value = .{ + .value = .{ + .runtime = pointer_gep, + }, + .type = pointer_type, + }, + }, + }); + try builder.appendInstruction(unit, context, slice_builder); + + const final_slice = try unit.instructions.append(context.allocator, .{ + .insert_value = .{ + .expression = V{ + .value = .{ + .runtime = slice_builder, + }, + .type = expression_to_slice.type, + }, + .index = 1, + .new_value = len_expression, + }, + }); + try builder.appendInstruction(unit, context, final_slice); + + break :blk .{ + .value = .{ + .runtime = final_slice, + }, + .type = switch (type_expect) { + .type => |type_index| type_index, + .none => slice_type, + else => |t| @panic(@tagName(t)), + }, + }; + }, + false => switch (unit.types.get(child_pointer.type).*) { + .array => |array| { + const load = try unit.instructions.append(context.allocator, .{ + .load = .{ + .value = expression_to_slice, + .type = pointer.type, + }, + }); + try builder.appendInstruction(unit, context, load); + + const pointer_gep = try unit.instructions.append(context.allocator, .{ + .get_element_pointer = .{ + .pointer = load, + .base_type = array.type, + .is_struct = false, + .index = range_start, + }, + }); + try builder.appendInstruction(unit, context, pointer_gep); + + const pointer_type = try unit.getPointerType(context, .{ + .type = array.type, + .termination = array.termination, + .mutability = child_pointer.mutability, + .many = true, + .nullable = false, + }); + + const slice_type = try unit.getSliceType(context, .{ + .child_type = array.type, + .child_pointer_type = pointer_type, + .termination = array.termination, + .mutability = child_pointer.mutability, + .nullable = false, + }); + + const slice_builder = try unit.instructions.append(context.allocator, .{ + .insert_value = .{ + .expression = V{ + .value = .{ + .@"comptime" = .undefined, + }, + .type = switch (type_expect) { + .type => |type_index| type_index, + .none => slice_type, + else => |t| @panic(@tagName(t)), + }, + }, + .index = 0, + .new_value = .{ + .value = .{ + .runtime = pointer_gep, + }, + .type = pointer_type, + }, + }, + }); + try builder.appendInstruction(unit, context, slice_builder); + + const final_slice = try unit.instructions.append(context.allocator, .{ + .insert_value = .{ + .expression = V{ + .value = .{ + .runtime = slice_builder, + }, + .type = slice_type, + }, + .index = 1, + .new_value = len_expression, + }, + }); + try builder.appendInstruction(unit, context, final_slice); + + break :blk .{ + .value = .{ + .runtime = final_slice, + }, + .type = switch (type_expect) { + .type => |type_index| type_index, + .none => slice_type, + else => |t| @panic(@tagName(t)), + }, + }; + }, + else => |t| @panic(@tagName(t)), + }, + }, + .slice => |slice| { + const load = try unit.instructions.append(context.allocator, .{ + .load = .{ + .value = expression_to_slice, + .type = pointer.type, + }, + }); + try builder.appendInstruction(unit, context, load); + + const extract_pointer = try unit.instructions.append(context.allocator, .{ + .extract_value = .{ + .expression = .{ + .value = .{ + .runtime = load, + }, + .type = pointer.type, + }, + .index = 0, + }, + }); + try builder.appendInstruction(unit, context, extract_pointer); + + const pointer_gep = try unit.instructions.append(context.allocator, .{ + .get_element_pointer = .{ + .pointer = extract_pointer, + .base_type = slice.child_type, + .is_struct = false, + .index = range_start, + }, + }); + try builder.appendInstruction(unit, context, pointer_gep); + + const slice_builder = try unit.instructions.append(context.allocator, .{ + .insert_value = .{ + .expression = V{ + .value = .{ + .@"comptime" = .undefined, + }, + .type = switch (type_expect) { + .type => |type_index| type_index, + .none => pointer.type, + else => |t| @panic(@tagName(t)), + }, + }, + .index = 0, + .new_value = .{ + .value = .{ + .runtime = pointer_gep, + }, + .type = slice.child_pointer_type, + }, + }, + }); + try builder.appendInstruction(unit, context, slice_builder); + + const final_slice = try unit.instructions.append(context.allocator, .{ + .insert_value = .{ + .expression = V{ + .value = .{ + .runtime = slice_builder, + }, + .type = pointer.type, + }, + .index = 1, + .new_value = len_expression, + }, + }); + try builder.appendInstruction(unit, context, final_slice); + + break :blk .{ + .value = .{ + .runtime = final_slice, + }, + .type = switch (type_expect) { + .type => |type_index| type_index, + .none => pointer.type, else => |t| @panic(@tagName(t)), }, }; }, else => |t| @panic(@tagName(t)), }, - // else => |t| @panic(@tagName(t)), - // }, }, else => |t| @panic(@tagName(t)), } - if (true) break :blk undefined; }, .keyword_false, .keyword_true => .{ .value = .{ @@ -4769,6 +5255,42 @@ pub const Builder = struct { .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)), @@ -4896,6 +5418,22 @@ pub const Builder = struct { }, else => |t| @panic(@tagName(t)), }, + .none => { + return .{ + .value = .{ + .@"comptime" = .{ + .comptime_int = .{ + .value = ct_int.value, + .signedness = switch (ct_int.signedness) { + .unsigned => .signed, + .signed => .unsigned, + }, + }, + }, + }, + .type = .comptime_int, + }; + }, else => |t| @panic(@tagName(t)), }, else => |t| @panic(@tagName(t)), @@ -5237,7 +5775,7 @@ pub const Builder = struct { if (container_scope.lookupDeclaration(right_identifier_hash, look_in_parent_scopes)) |lookup| { const global = try builder.referenceGlobalDeclaration(unit, context, lookup.scope, lookup.declaration); switch (global.initial_value) { - .function_definition => break :blk .{ + .function_definition, .function_declaration => break :blk .{ .value = .{ .@"comptime" = .{ .global = global, @@ -5248,7 +5786,7 @@ pub const Builder = struct { else => |t| @panic(@tagName(t)), } } else { - unreachable; + std.debug.panic("Right identifier in field-access-like call expression not found: '{s}'", .{right_identifier}); } }, else => |t| @panic(@tagName(t)), @@ -6664,6 +7202,7 @@ pub const Builder = struct { .many = false, .nullable = false, }); + break :blk switch (side) { .left => switch (global.initial_value) { .type => |ti| .{ @@ -6723,7 +7262,7 @@ pub const Builder = struct { if (enum_field.name == identifier_hash) { break enum_field_index; } - } else @panic("identifier not found"); + } else std.debug.panic("Right identifier '{s}' not found", .{identifier}); break :blk V{ .value = .{ .@"comptime" = .{ @@ -6733,6 +7272,25 @@ pub const Builder = struct { .type = type_index, }; }, + .@"struct" => |struct_index| { + const struct_type = unit.structs.get(struct_index); + const field_index = for (struct_type.fields.items) |enum_field_index| { + const enum_field = unit.struct_fields.get(enum_field_index); + if (enum_field.name == identifier_hash) { + break enum_field_index; + } + } else std.debug.panic("Right identifier '{s}' not found", .{identifier}); + _ = field_index; + unreachable; + // break :blk V{ + // .value = .{ + // .@"comptime" = .{ + // .enum_value = field_index, + // }, + // }, + // .type = type_index, + // }; + }, else => |t| @panic(@tagName(t)), }; @@ -6758,6 +7316,17 @@ pub const Builder = struct { }, .type = type_index, }, + .none => V{ + .value = .{ + .@"comptime" = .{ + .comptime_int = .{ + .value = array.count, + .signedness = .unsigned, + }, + }, + }, + .type = .comptime_int, + }, else => |t| @panic(@tagName(t)), }; }, @@ -7106,6 +7675,46 @@ pub const Builder = struct { .type = ti, }; }, + .materialize_int => { + const destination_integer_type = unit.types.get(ti).integer; + const ct_int = result.value.@"comptime".comptime_int; + + switch (ct_int.signedness) { + .unsigned => { + const number_bit_count = @bitSizeOf(@TypeOf(ct_int.value)) - @clz(ct_int.value); + if (destination_integer_type.bit_count < number_bit_count) { + unreachable; + } + return .{ + .value = .{ + .@"comptime" = .{ + .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, + }; + } + }, + } + }, else => |t| @panic(@tagName(t)), } }, @@ -7221,6 +7830,7 @@ pub const Unit = struct { code_to_emit: AutoArrayHashMap(Function.Definition.Index, *Debug.Declaration.Global) = .{}, data_to_emit: ArrayList(*Debug.Declaration.Global) = .{}, + external_functions: AutoArrayHashMap(Type.Index, *Debug.Declaration.Global) = .{}, type_declarations: AutoHashMap(Type.Index, *Debug.Declaration.Global) = .{}, struct_type_map: AutoHashMap(Struct.Index, Type.Index) = .{}, scope: Debug.Scope.Global = .{ @@ -7679,6 +8289,9 @@ pub const Unit = struct { } try builder.analyzePackage(unit, context, main_package); + for (unit.external_functions.values()) |function_declaration| { + logln(.compilation, .ir, "External function: {s}", .{ unit.getIdentifier(function_declaration.declaration.name) }); + } for (unit.code_to_emit.values()) |function_declaration| { const function_definition_index = function_declaration.initial_value.function_definition; diff --git a/bootstrap/backend/llvm.zig b/bootstrap/backend/llvm.zig index 0810260..6ec2aa5 100644 --- a/bootstrap/backend/llvm.zig +++ b/bootstrap/backend/llvm.zig @@ -32,6 +32,7 @@ pub const LLVM = struct { debug_type_map: AutoHashMap(Compilation.Type.Index, *LLVM.DebugInfo.Type) = .{}, type_name_map: AutoHashMap(Compilation.Type.Index, []const u8) = .{}, type_map: AutoHashMap(Compilation.Type.Index, *LLVM.Type) = .{}, + function_declaration_map: AutoArrayHashMap(*Compilation.Debug.Declaration.Global, *LLVM.Value.Constant.Function) = .{}, function_definition_map: AutoArrayHashMap(*Compilation.Debug.Declaration.Global, *LLVM.Value.Constant.Function) = .{}, llvm_instruction_map: AutoHashMap(Compilation.Instruction.Index, *LLVM.Value) = .{}, llvm_value_map: AutoArrayHashMap(Compilation.V, *LLVM.Value) = .{}, @@ -47,7 +48,7 @@ pub const LLVM = struct { return_phi_node: ?*LLVM.Value.Instruction.PhiNode = null, scope: *LLVM.DebugInfo.Scope = undefined, file: *LLVM.DebugInfo.File = undefined, - subprogram: *LLVM.DebugInfo.Subprogram = undefined, + // subprogram: *LLVM.DebugInfo.Subprogram = undefined, arg_index: u32 = 0, tag_count: c_uint = 0, inside_branch: bool = false, @@ -1281,13 +1282,13 @@ pub const LLVM = struct { } fn renderTypeName(llvm: *LLVM, unit: *Compilation.Unit, context: *const Compilation.Context, sema_type_index: Compilation.Type.Index) ![]const u8 { - const gop = try llvm.type_name_map.getOrPut(context.allocator, sema_type_index); - if (gop.found_existing) { - return gop.value_ptr.*; + if (llvm.type_name_map.get(sema_type_index)) |result| { + return result; } else { if (unit.type_declarations.get(sema_type_index)) |global_declaration| { - gop.value_ptr.* = unit.getIdentifier(global_declaration.declaration.name); - return gop.value_ptr.*; + const result = unit.getIdentifier(global_declaration.declaration.name); + try llvm.type_name_map.putNoClobber(context.allocator, sema_type_index, result); + return result; } else { const sema_type = unit.types.get(sema_type_index); const result: []const u8 = switch (sema_type.*) { @@ -2241,6 +2242,114 @@ pub const LLVM = struct { const call = llvm.builder.createCall(intrinsic_type, intrinsic_function.toValue(), intrinsic_arguments.ptr, intrinsic_arguments.len, name.ptr, name.len, null) orelse return LLVM.Value.Instruction.Error.call; return call.toValue(); } + + fn emitFunctionDeclaration(llvm: *LLVM, unit: *Compilation.Unit, context: *const Compilation.Context, declaration: *Compilation.Debug.Declaration.Global) !void { + const function_type = try llvm.getType(unit, context, declaration.declaration.type); + const is_export = declaration.attributes.contains(.@"export"); + const is_extern = declaration.attributes.contains(.@"extern"); + const export_or_extern = is_export or is_extern; + + const linkage: LLVM.Linkage = switch (export_or_extern) { + true => .@"extern", + false => .internal, + }; + // TODO: Check name collision + const mangle_name = !export_or_extern; + _ = mangle_name; // autofix + const name = unit.getIdentifier(declaration.declaration.name); + const function = llvm.module.createFunction(function_type.toFunction() orelse unreachable, linkage, address_space, name.ptr, name.len) orelse return Error.function; + + const function_prototype = unit.function_prototypes.get(unit.types.get(declaration.declaration.type).function); + switch (unit.types.get(function_prototype.return_type).*) { + .noreturn => { + function.addAttributeKey(.NoReturn); + }, + else => {}, + } + + if (function_prototype.attributes.naked) { + function.addAttributeKey(.Naked); + } + + const calling_convention = getCallingConvention(function_prototype.calling_convention); + function.setCallingConvention(calling_convention); + + switch (declaration.initial_value) { + .function_declaration => try llvm.function_declaration_map.putNoClobber(context.allocator, declaration, function), + .function_definition => try llvm.function_definition_map.putNoClobber(context.allocator, declaration, function), + else => unreachable, + } + + if (unit.descriptor.generate_debug_information) { + const debug_file = try llvm.getDebugInfoFile(unit, context, declaration.declaration.scope.file); + var parameter_types = try ArrayList(*LLVM.DebugInfo.Type).initCapacity(context.allocator, function_prototype.argument_types.len); + for (function_prototype.argument_types) |argument_type_index| { + const argument_type = try llvm.getDebugType(unit, context, argument_type_index); + parameter_types.appendAssumeCapacity(argument_type); + } + + const subroutine_type_flags = LLVM.DebugInfo.Node.Flags{ + .visibility = .none, + .forward_declaration = is_extern, + .apple_block = false, + .block_by_ref_struct = false, + .virtual = false, + .artificial = false, + .explicit = false, + .prototyped = false, + .objective_c_class_complete = false, + .object_pointer = false, + .vector = false, + .static_member = false, + .lvalue_reference = false, + .rvalue_reference = false, + .reserved = false, + .inheritance = .none, + .introduced_virtual = false, + .bit_field = false, + .no_return = false, + .type_pass_by_value = false, + .type_pass_by_reference = false, + .enum_class = false, + .thunk = false, + .non_trivial = false, + .big_endian = false, + .little_endian = false, + .all_calls_described = false, + }; + const subroutine_type_calling_convention = LLVM.DebugInfo.CallingConvention.none; + const subroutine_type = llvm.debug_info_builder.createSubroutineType(parameter_types.items.ptr, parameter_types.items.len, subroutine_type_flags, subroutine_type_calling_convention) orelse unreachable; + const scope_line = 0; + const subprogram_flags = LLVM.DebugInfo.Subprogram.Flags{ + .virtuality = .none, + .local_to_unit = !export_or_extern, + .definition = !is_extern, + .optimized = false, + .pure = false, + .elemental = false, + .recursive = false, + .main_subprogram = false, + .deleted = false, + .object_c_direct = false, + }; + const subprogram_declaration = null; + const function_name = unit.getIdentifier(declaration.declaration.name); + const subprogram = llvm.debug_info_builder.createFunction(debug_file.toScope(), function_name.ptr, function_name.len, function_name.ptr, function_name.len, debug_file, declaration.declaration.line + 1, subroutine_type, scope_line, subroutine_type_flags, subprogram_flags, subprogram_declaration) orelse unreachable; + function.setSubprogram(subprogram); + + switch (declaration.initial_value) { + .function_declaration => {}, + .function_definition => |function_definition_index| { + const function_definition = unit.function_definitions.get(function_definition_index); + const scope = subprogram.toLocalScope().toScope(); + + try llvm.scope_map.putNoClobber(context.allocator, &function_definition.scope.scope, scope); + }, + else => |t| @panic(@tagName(t)), + } + } + } + }; fn getCallingConvention(calling_convention: Compilation.Function.CallingConvention) LLVM.Value.Constant.Function.CallingConvention { @@ -2305,49 +2414,26 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo try llvm.scope_map.putNoClobber(context.allocator, &unit.scope.scope, llvm.scope); } + for (unit.external_functions.values()) |external_function_declaration| { + try llvm.emitFunctionDeclaration(unit, context, external_function_declaration); + } + const functions = unit.code_to_emit.values(); { var function_i: usize = functions.len; + // Emit it in reverse order so the code goes the right order, from entry point to leaves while (function_i > 0) { function_i -= 1; const function_declaration = functions[function_i]; - const function_definition_index = function_declaration.getFunctionDefinitionIndex(); - const function_definition = unit.function_definitions.get(function_definition_index); - const function_type = try llvm.getType(unit, context, function_definition.type); - const is_export = function_declaration.attributes.contains(.@"export"); - const linkage: LLVM.Linkage = switch (is_export) { - true => .@"extern", - false => .internal, - }; - // TODO: Check name collision - const mangle_name = !is_export; - _ = mangle_name; // autofix - const name = unit.getIdentifier(function_declaration.declaration.name); - const function = llvm.module.createFunction(function_type.toFunction() orelse unreachable, linkage, address_space, name.ptr, name.len) orelse return Error.function; - - const function_prototype = unit.function_prototypes.get(unit.types.get(function_definition.type).function); - switch (unit.types.get(function_prototype.return_type).*) { - .noreturn => { - function.addAttributeKey(.NoReturn); - }, - else => {}, - } - - if (function_prototype.attributes.naked) { - function.addAttributeKey(.Naked); - } - - const calling_convention = getCallingConvention(function_prototype.calling_convention); - function.setCallingConvention(calling_convention); - - try llvm.function_definition_map.putNoClobber(context.allocator, function_declaration, function); + try llvm.emitFunctionDeclaration(unit, context, function_declaration); } } // First, cache all the global variables for (unit.data_to_emit.items) |global_declaration| { const name = unit.getIdentifier(global_declaration.declaration.name); + switch (global_declaration.initial_value) { .string_literal => |hash| { const string_literal = unit.string_literal_values.get(hash).?; @@ -2402,68 +2488,11 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo llvm.function = function; llvm.sema_function = function_declaration; llvm.inside_branch = false; - const function_prototype = unit.function_prototypes.get(unit.types.get(function_definition.type).function); - const function_name = unit.getIdentifier(function_declaration.declaration.name); if (unit.descriptor.generate_debug_information) { - const debug_file = try llvm.getDebugInfoFile(unit, context, function_declaration.declaration.scope.file); - var parameter_types = try ArrayList(*LLVM.DebugInfo.Type).initCapacity(context.allocator, function_prototype.argument_types.len); - for (function_prototype.argument_types) |argument_type_index| { - const argument_type = try llvm.getDebugType(unit, context, argument_type_index); - parameter_types.appendAssumeCapacity(argument_type); - } - const subroutine_type_flags = LLVM.DebugInfo.Node.Flags{ - .visibility = .none, - .forward_declaration = false, - .apple_block = false, - .block_by_ref_struct = false, - .virtual = false, - .artificial = false, - .explicit = false, - .prototyped = false, - .objective_c_class_complete = false, - .object_pointer = false, - .vector = false, - .static_member = false, - .lvalue_reference = false, - .rvalue_reference = false, - .reserved = false, - .inheritance = .none, - .introduced_virtual = false, - .bit_field = false, - .no_return = false, - .type_pass_by_value = false, - .type_pass_by_reference = false, - .enum_class = false, - .thunk = false, - .non_trivial = false, - .big_endian = false, - .little_endian = false, - .all_calls_described = false, - }; - const subroutine_type_calling_convention = LLVM.DebugInfo.CallingConvention.none; - const subroutine_type = llvm.debug_info_builder.createSubroutineType(parameter_types.items.ptr, parameter_types.items.len, subroutine_type_flags, subroutine_type_calling_convention) orelse unreachable; - const scope_line = 0; - const subprogram_flags = LLVM.DebugInfo.Subprogram.Flags{ - .virtuality = .none, - .local_to_unit = true, - .definition = true, - .optimized = false, - .pure = false, - .elemental = false, - .recursive = false, - .main_subprogram = false, - .deleted = false, - .object_c_direct = false, - }; - const subprogram_declaration = null; - const subprogram = llvm.debug_info_builder.createFunction(debug_file.toScope(), function_name.ptr, function_name.len, function_name.ptr, function_name.len, debug_file, function_declaration.declaration.line + 1, subroutine_type, scope_line, subroutine_type_flags, subprogram_flags, subprogram_declaration) orelse unreachable; - llvm.function.setSubprogram(subprogram); + const subprogram = llvm.function.getSubprogram() orelse unreachable; llvm.file = subprogram.getFile() orelse unreachable; - llvm.subprogram = subprogram; llvm.scope = subprogram.toLocalScope().toScope(); - - try llvm.scope_map.putNoClobber(context.allocator, &function_definition.scope.scope, llvm.scope); } llvm.arg_index = 0; @@ -2496,7 +2525,7 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo .pop_scope => |pop_scope| { const new = try llvm.getScope(unit, context, pop_scope.new); if (pop_scope.new.kind == .function) { - assert(new.toSubprogram() orelse unreachable == llvm.subprogram); + assert(new.toSubprogram() orelse unreachable == llvm.function.getSubprogram() orelse unreachable); } llvm.scope = new; var scope = pop_scope.old; @@ -2504,7 +2533,7 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo scope = scope.parent.?; } const subprogram_scope = try llvm.getScope(unit, context, scope); - assert(llvm.subprogram == subprogram_scope.toSubprogram() orelse unreachable); + assert(llvm.function.getSubprogram() orelse unreachable == subprogram_scope.toSubprogram() orelse unreachable); }, .debug_checkpoint => |debug_checkpoint| { const scope = try llvm.getScope(unit, context, debug_checkpoint.scope); @@ -2600,6 +2629,7 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo .slice => {}, .array => {}, } + const declaration_type = try llvm.getType(unit, context, stack_slot.type); const alloca_array_size = null; const declaration_alloca = llvm.builder.createAlloca(declaration_type, address_space, alloca_array_size, "", "".len) orelse return LLVM.Value.Instruction.Error.alloca; @@ -2630,6 +2660,7 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo .enum_to_int, .slice_to_nullable, .slice_to_not_null, + .slice_coerce_to_zero_termination, .pointer_to_nullable, .pointer_const_to_var, .pointer_to_array_to_pointer_to_many, @@ -2676,7 +2707,6 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo }; const value_type = try llvm.getType(unit, context, load.type); - // const value_type = try llvm.getType(unit, context, load.value.type); const is_volatile = false; const load_i = llvm.builder.createLoad(value_type, value, is_volatile, "", "".len) orelse return LLVM.Value.Instruction.Error.load; try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, load_i.toValue()); @@ -2722,11 +2752,18 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo switch (call.callable.value) { .@"comptime" => |ct| switch (ct) { .global => |call_function_declaration| { - const call_function_definition_index = call_function_declaration.getFunctionDefinitionIndex(); - const callee = llvm.function_definition_map.get(call_function_declaration).?; - const call_function_definition = unit.function_definitions.get(call_function_definition_index); - const call_function_prototype = unit.function_prototypes.get(unit.types.get(call_function_definition.type).function); - assert(call_function_definition.type == call.function_type); + const call_function_type = call_function_declaration.declaration.type; + // const call_function_definition_index = call_function_declaration.getFunctionDefinitionIndex(); + // const callee = llvm.function_definition_map.get(call_function_declaration).?; + const call_function_prototype = unit.function_prototypes.get(unit.types.get(call_function_type).function); + assert(call_function_type == call.function_type); + + const callee = switch (call_function_declaration.initial_value) { + .function_definition => llvm.function_definition_map.get(call_function_declaration).?, + .function_declaration => llvm.function_declaration_map.get(call_function_declaration).?, + else => |t| @panic(@tagName(t)), + }; + for (call.arguments, arguments) |argument_value, *argument| { argument.* = try llvm.emitRightValue(unit, context, argument_value); @@ -3062,7 +3099,7 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo } if (unit.descriptor.generate_debug_information) { - llvm.debug_info_builder.finalizeSubprogram(llvm.subprogram, llvm.function); + llvm.debug_info_builder.finalizeSubprogram(llvm.function.getSubprogram() orelse unreachable, llvm.function); } const verify_function = true; diff --git a/bootstrap/frontend/parser.zig b/bootstrap/frontend/parser.zig index 63f14e8..cdae575 100644 --- a/bootstrap/frontend/parser.zig +++ b/bootstrap/frontend/parser.zig @@ -304,7 +304,7 @@ const Analyzer = struct { if (equal(u8, identifier_name, enum_field.name)) { const attribute = @field(Compilation.Debug.Declaration.Global.Attribute, enum_field.name); const attribute_node = switch (attribute) { - .@"export" => try analyzer.addNode(.{ + .@"export", .@"extern", => try analyzer.addNode(.{ .id = @field(Node.Id, "symbol_attribute_" ++ @tagName(attribute)), .token = identifier, .left = .null, @@ -313,7 +313,7 @@ const Analyzer = struct { }; break attribute_node; } - } else @panic("Not known attribute"); + } else panic("Unknown symbol attribute: {s}", .{identifier_name}); try list.append(analyzer.allocator, attribute_node); switch (analyzer.peekToken()) { @@ -400,7 +400,7 @@ const Analyzer = struct { }; break attribute_node; } - } else @panic("Not known attribute"); + } else panic("Unknown function attribute: {s}", .{identifier_name}); try attribute_and_return_type_node_list.append(analyzer.allocator, attribute_node); @@ -1128,7 +1128,8 @@ const Analyzer = struct { fn primaryExpression(analyzer: *Analyzer) !Node.Index { const result = switch (analyzer.peekToken()) { .identifier => switch (analyzer.peekTokenAhead(1)) { - .operator_colon => unreachable, + // TODO: tags + // .operator_colon => unreachable, else => try analyzer.curlySuffixExpression(), }, .string_literal, @@ -1349,6 +1350,7 @@ const Analyzer = struct { .operator_right_parenthesis, .operator_left_brace, .operator_assign, + .operator_semicolon, => return node_index, else => |t| @panic(@tagName(t)), } diff --git a/build.zig b/build.zig index 9ee6c38..3df229f 100644 --- a/build.zig +++ b/build.zig @@ -5,7 +5,7 @@ pub fn build(b: *std.Build) !void { const self_hosted_ci = b.option(bool, "self_hosted_ci", "This option enables the self-hosted CI behavior") orelse false; const third_party_ci = b.option(bool, "third_party_ci", "This option enables the third-party CI behavior") orelse false; const is_ci = self_hosted_ci or third_party_ci; - const print_stack_trace = b.option(bool, "print_stack_trace", "This option enables printing stack traces inside the compiler") orelse is_ci; + const print_stack_trace = b.option(bool, "print_stack_trace", "This option enables printing stack traces inside the compiler") orelse is_ci or @import("builtin").os.tag == .macos; const native_target = b.resolveTargetQuery(.{}); const optimization = b.standardOptimizeOption(.{}); var target_query = b.standardTargetOptionsQueryOnly(.{}); @@ -14,6 +14,32 @@ pub fn build(b: *std.Build) !void { target_query.abi = .musl; } const target = b.resolveTargetQuery(target_query); + const compiler_options = b.addOptions(); + compiler_options.addOption(bool, "print_stack_trace", print_stack_trace); + + const compiler = b.addExecutable(.{ + .name = "nat", + .root_source_file = .{ .path = "bootstrap/main.zig" }, + .target = target, + .optimize = optimization, + }); + compiler.root_module.addOptions("configuration", compiler_options); + compiler.formatted_panics = print_stack_trace; + compiler.root_module.unwind_tables = print_stack_trace; + compiler.root_module.omit_frame_pointer = false; + compiler.want_lto = false; + + compiler.linkLibC(); + compiler.linkSystemLibrary("c++"); + + // TODO: + // if (target.result.os.tag == .windows) { + // compiler.linkSystemLibrary("ole32"); + // compiler.linkSystemLibrary("version"); + // compiler.linkSystemLibrary("uuid"); + // compiler.linkSystemLibrary("msvcrt-os"); + // } + const llvm_version = "17.0.6"; var fetcher_run: ?*std.Build.Step.Run = null; const llvm_path = b.option([]const u8, "llvm_path", "LLVM prefix path") orelse blk: { @@ -69,30 +95,6 @@ pub fn build(b: *std.Build) !void { }; } }; - const compiler_options = b.addOptions(); - compiler_options.addOption(bool, "print_stack_trace", print_stack_trace); - - const compiler = b.addExecutable(.{ - .name = "nat", - .root_source_file = .{ .path = "bootstrap/main.zig" }, - .target = target, - .optimize = optimization, - }); - compiler.root_module.addOptions("configuration", compiler_options); - compiler.formatted_panics = print_stack_trace; - compiler.root_module.unwind_tables = print_stack_trace; - compiler.root_module.omit_frame_pointer = false; - compiler.want_lto = false; - - compiler.linkLibC(); - compiler.linkSystemLibrary("c++"); - - if (target.result.os.tag == .windows) { - compiler.linkSystemLibrary("ole32"); - compiler.linkSystemLibrary("version"); - compiler.linkSystemLibrary("uuid"); - compiler.linkSystemLibrary("msvcrt-os"); - } if (fetcher_run) |fr| { compiler.step.dependOn(&fr.step); @@ -408,13 +410,9 @@ pub fn build(b: *std.Build) !void { test_command.step.dependOn(b.getInstallStep()); if (b.args) |args| { - std.debug.print("Args: {s}", .{args}); run_command.addArgs(args); debug_command.addArgs(args); test_command.addArgs(args); - for (debug_command.argv.items, 0..) |arg, i| { - std.debug.print("Arg #{}: {s}\n", .{i, arg.bytes}); - } } const run_step = b.step("run", "Test the Nativity compiler"); diff --git a/lib/std/builtin.nat b/lib/std/builtin.nat index 1c815e3..5733d24 100644 --- a/lib/std/builtin.nat +++ b/lib/std/builtin.nat @@ -7,6 +7,7 @@ const Os = enum{ const Cpu = enum{ x86_64, + aarch64, }; const Abi = enum{ diff --git a/lib/std/os.nat b/lib/std/os.nat index c904db1..3a8c4ec 100644 --- a/lib/std/os.nat +++ b/lib/std/os.nat @@ -11,17 +11,20 @@ const exit = fn(exit_code: s32) noreturn { switch (current) { .linux => _ = #syscall(#cast(linux.Syscall.exit_group), #cast(exit_code)), .macos => macos.exit(exit_code), - .windows => windows.ExitProcess(exit_code), + .windows => windows.ExitProcess(#cast(exit_code)), } - - unreachable; } const max_file_operation_byte_count = switch (current) { .linux => 0x7ffff000, + .macos => 0x7fffffff, else => #error("OS not supported"), }; +const unwrap_syscall_signed = fn(syscall_result: ssize) bool { + return syscall_result >= 0; +} + const FileDescriptor = struct{ handle: system.FileDescriptor, @@ -54,6 +57,15 @@ const FileDescriptor = struct{ return null; } }, + .macos => { + const raw_result = macos.write(file_descriptor.handle, bytes.ptr, bytes.len); + if (unwrap_syscall_signed(raw_result)) { + const bytes_written: usize = #cast(raw_result); + return bytes_written; + } else { + return null; + } + }, else => #error("OS not supported"), } } @@ -90,16 +102,27 @@ const MapFlags = struct(u32){ const allocate_virtual_memory = fn(address: ?[&]u8, length: usize, general_protection_flags: ProtectionFlags, general_map_flags: MapFlags) ?[&]u8 { const protection_flags = system.get_protection_flags(flags = general_protection_flags); const map_flags = system.get_map_flags(flags = general_map_flags); + const fd = -1; + const offset = 0; switch (current) { .linux => { - if (linux.unwrap_syscall(syscall_result = linux.mmap(address, length, protection_flags, map_flags, fd = -1, offset = 0))) |result_address| { + if (linux.unwrap_syscall(syscall_result = linux.mmap(address, length, protection_flags, map_flags, fd, offset))) |result_address| { const pointer: [&]u8 = #cast(result_address); return pointer; } else { return null; } }, + .macos => { + const result = macos.mmap(address, length, protection_flags, map_flags, fd, offset); + if (result != macos.MAP_FAILED) { + const result_address: [&]u8 = #cast(result); + return result_address; + } else { + return null; + } + }, else => #error("OS not supported"), } @@ -114,6 +137,11 @@ const free_virtual_memory = fn(bytes_ptr: [&]const u8, bytes_len: usize) bool { return false; } }, + .macos => { + const raw_result = macos.munmap(bytes_ptr, bytes_len); + const result = unwrap_syscall_signed(raw_result); + return result; + }, else => #error("OS not supported"), } } @@ -136,10 +164,11 @@ const readlink = fn(file_path: [&:0]const u8, buffer: []u8) ?[]u8 { const max_path_byte_count = switch (current) { .linux => 0x1000, + .macos => 1024, else => #error("OS not supported"), }; -const current_executable_path = fn(buffer: []u8) ?[]u8 { +const current_executable_path = fn(buffer: [:0]u8) ?[]u8 { switch (current) { .linux => { if (readlink(file_path = "/proc/self/exe", buffer)) |bytes| { @@ -148,6 +177,31 @@ const current_executable_path = fn(buffer: []u8) ?[]u8 { return null; } }, + .macos => { + var symlink_path_buffer: [max_path_byte_count:0]u8 = undefined; + var symlink_path_len: u32 = symlink_path_buffer.len + 1; + const ns_result = macos._NSGetExecutablePath(symlink_path_buffer.&, symlink_path_len.&); + if (ns_result == 0) { + const symlink_path = symlink_path_buffer[0..symlink_path_len]; + const result = macos.realpath(symlink_path.ptr, buffer.ptr); + if (result != null) { + var i: usize = 0; + while (i < buffer.len) { + if (result[i] == 0) { + break; + } + i += 1; + } + #assert(i < buffer.len); + + return result[0..i]; + } else { + return null; + } + } else { + return null; + } + }, else => #error("OS not supported"), } } @@ -158,7 +212,7 @@ const Process = struct{ const duplicate_process = fn () ?Process.Id { switch (current) { - .linux => { + .linux => { if (linux.unwrap_syscall(syscall_result = linux.fork())) |fork_result| { const unsigned: u32 = #cast(fork_result); const signed: s32 = #cast(unsigned); @@ -167,14 +221,25 @@ const duplicate_process = fn () ?Process.Id { return null; } }, + .macos => { + const fork_result = macos.fork(); + if (unwrap_syscall_signed(fork_result)) { + return fork_result; + } else { + return null; + } + }, else => #error("OS not supported"), } } -const execute = fn(path: [&:0]const u8, argv: [&:null]const ?[&:0]const u8, env: [&:null]const ?[&:null]const u8) usize { +const execute = fn(path: [&:0]const u8, argv: [&:null]const ?[&:0]const u8, env: [&:null]const ?[&:null]const u8) void { switch (current) { .linux => { - return linux.execve(path, argv, env); + _ = linux.execve(path, argv, env); + }, + .macos => { + _ = macos.execve(path, argv, env); }, else => #error("OS not supported"), } diff --git a/lib/std/os/linux.nat b/lib/std/os/linux.nat index f883d73..e2a7c9c 100644 --- a/lib/std/os/linux.nat +++ b/lib/std/os/linux.nat @@ -494,7 +494,7 @@ const memfd_create = fn(name: [&:0]const u8, flags: u32) usize { const unwrap_syscall = fn(syscall_result: usize) ?usize { const signed_syscall_result: ssize = #cast(syscall_result); - if (signed_syscall_result >= 0) { + if (std.os.unwrap_syscall_signed(signed_syscall_result)) { return syscall_result; } else { return null; diff --git a/lib/std/os/macos.nat b/lib/std/os/macos.nat index abec54e..0556d16 100644 --- a/lib/std/os/macos.nat +++ b/lib/std/os/macos.nat @@ -1,3 +1,56 @@ +const std = #import("std"); + const FileDescriptor = s32; -const write = fn (file_descriptor: FileDescriptor, bytes_ptr: [&]const u8, bytes_len: usize) ssize extern; -const exit = fn (exit_code: u32) noreturn extern; +const ProcessId = s32; +const MAP_FAILED = 0xffffffffffffffff; + +const MapFlags = struct(u32){ + shared: bool, + private: bool, + reserved: u2 = 0, + fixed: bool, + reserved0: bool = 0, + noreserve: bool, + reserved1: u2 = 0, + has_semaphore: bool, + no_cache: bool, + reserved2: u1 = 0, + anonymous: bool, + reserved3: u19 = 0, +}; + +const ProtectionFlags = struct(u32) { + read: bool, + write: bool, + execute: bool, +}; + +const get_protection_flags = fn(flags: std.os.ProtectionFlags) ProtectionFlags { + return ProtectionFlags{ + .read = flags.read, + .write = flags.write, + .execute = flags.execute, + }; +} + +const get_map_flags = fn(flags: std.os.MapFlags) MapFlags{ + return MapFlags{ + .shared = false, + .private = true, + .fixed = false, + .noreserve = false, + .has_semaphore = false, + .no_cache = false, + .anonymous = true, + }; +} + +const write :: extern = fn (file_descriptor: FileDescriptor, bytes_ptr: [&]const u8, bytes_len: usize) ssize; +const exit :: extern = fn (exit_code: s32) noreturn; +const fork :: extern = fn () ProcessId; +const mmap :: extern = fn (address: ?[&]const u8, length: usize, protection_flags: ProtectionFlags, map_flags: MapFlags, file_descriptor: FileDescriptor, offset: u64) usize; +const munmap :: extern = fn (address: [&]const u8, length: usize) s32; +const execve :: extern = fn(path: [&:0]const u8, argv: [&:null]const ?[&:0]const u8, env: [&:null]const ?[&:null]const u8) s32; +const realpath :: extern = fn(path: [&:0]const u8, resolved_path: [&:0]u8) [&:0]u8; + +const _NSGetExecutablePath :: extern = fn (buffer: [&:0]u8, buffer_size: &u32) s32; diff --git a/test/standalone/self_exe_path/main.nat b/test/standalone/self_exe_path/main.nat index 3e1906f..a0f0def 100644 --- a/test/standalone/self_exe_path/main.nat +++ b/test/standalone/self_exe_path/main.nat @@ -2,7 +2,7 @@ const std = #import("std"); const print = std.print; const main = fn() s32 { - var buffer: [std.os.max_path_byte_count + 1]u8 = undefined; + var buffer: [std.os.max_path_byte_count:0]u8 = undefined; if (std.os.current_executable_path(buffer = buffer.&)) |bytes| { print(bytes); print(bytes = "\n");