diff --git a/bootstrap/Compilation.zig b/bootstrap/Compilation.zig index a4ccb36..fcbb817 100644 --- a/bootstrap/Compilation.zig +++ b/bootstrap/Compilation.zig @@ -359,7 +359,9 @@ pub const Type = union(enum) { @"enum": Enum.Index, function: Function.Prototype.Index, integer: Type.Integer, - pointer: Pointer, + pointer: Type.Pointer, + slice: Type.Slice, + array: Type.Array, fn getByteSize(ty: *Type, unit: *Unit) u32 { _ = unit; // autofix @@ -380,6 +382,7 @@ pub const Type = union(enum) { const Expect = union(enum){ none, type: Type.Index, + optional, }; const Integer = struct { @@ -392,8 +395,7 @@ pub const Type = union(enum) { }; }; - - const Pointer = struct{ + pub const Pointer = struct{ type: Type.Index, termination: Termination, mutability: Mutability, @@ -401,6 +403,19 @@ pub const Type = union(enum) { nullable: bool, }; + const Slice = struct{ + type: Type.Index, + termination: Termination, + mutability: Mutability, + nullable: bool, + }; + + const Array = struct{ + count: usize, + type: Type.Index, + termination: Termination, + }; + pub const Termination = enum { none, null, @@ -506,6 +521,7 @@ pub const Type = union(enum) { pub const Instruction = union(enum) { argument_declaration: *Debug.Declaration.Argument, + branch: Branch, block: Debug.Block.Index, // TODO call: Instruction.Call, @@ -517,11 +533,22 @@ pub const Instruction = union(enum) { }, debug_checkpoint: DebugCheckPoint, debug_declare_local_variable: DebugDeclareLocalVariable, + extract_value: ExtractValue, + insert_value: InsertValue, + get_element_pointer: GEP, global: *Debug.Declaration.Global, inline_assembly: InlineAssembly.Index, + integer_compare: IntegerCompare, integer_binary_operation: Instruction.IntegerBinaryOperation, - // TODO: should we join these two? + jump: Jump, load: Load, + umin: Min, + smin: Min, + // optional_wrap: V, + // optional_unwrap_unchecked: Instruction.Index, + // optional_unwrap_checked: V, + // optional_check: Instruction.Index, + phi: Phi, pop_scope: Instruction.Scope, push_scope: Instruction.Scope, ret: V, @@ -531,6 +558,46 @@ pub const Instruction = union(enum) { syscall: Syscall, @"unreachable", + const Phi = struct{ + values: ArrayList(V) = .{}, + basic_blocks: ArrayList(BasicBlock.Index) = .{}, + type: Type.Index, + }; + + const Min = struct{ + left: V, + right: V, + type: Type.Index, + }; + + const GEP = struct{ + pointer: Instruction.Index, + base_type: Type.Index, + index: V, + }; + + const ExtractValue = struct{ + expression: V, + index: u32, + }; + + const InsertValue = struct{ + expression: V, + index: u32, + new_value: V, + }; + + const Branch = struct{ + condition: Instruction.Index, + from: BasicBlock.Index, + taken: BasicBlock.Index, + not_taken: BasicBlock.Index, + }; + const Jump = struct{ + from: BasicBlock.Index, + to: BasicBlock.Index, + }; + const DebugDeclareLocalVariable = struct{ variable: *Debug.Declaration.Local, stack: Instruction.Index, @@ -550,6 +617,26 @@ pub const Instruction = union(enum) { arguments: []const V, }; + const IntegerCompare = struct{ + left: V, + right: V, + type: Type.Index, + id: Id, + + const Id = enum{ + equal, + not_equal, + unsigned_less, + unsigned_less_equal, + unsigned_greater, + unsigned_greater_equal, + signed_less, + signed_less_equal, + signed_greater, + signed_greater_equal, + }; + }; + const IntegerBinaryOperation = struct{ left: V, right: V, @@ -589,6 +676,7 @@ pub const Instruction = union(enum) { bitcast, enum_to_int, int_to_pointer, + pointer_to_int, sign_extend, zero_extend, pointer_var_to_const, @@ -669,15 +757,19 @@ pub const Function = struct{ }; pub const Struct = struct{ - fields: ArrayList(Field) = .{}, + fields: ArrayList(Struct.Field.Index) = .{}, scope: Debug.Scope.Global, backing_type: Type.Index, type: Type.Index, + optional: bool, pub const Field = struct{ name: u32, - type: u32, - value: V.Comptime, + type: Type.Index, + default_value: ?V.Comptime, + + const List = BlockList(@This(), enum{}); + pub usingnamespace @This().List.Index; }; const List = BlockList(@This(), enum{}); @@ -714,9 +806,29 @@ pub const V = struct{ undefined, type: Type.Index, bool: bool, - integer: u64, + comptime_int: ComptimeInt, + constant_int: ConstantInt, enum_value: Enum.Field.Index, function_definition: Function.Definition.Index, + constant_struct: ConstantStruct.Index, + string_literal: u32, + + const ConstantStruct = struct{ + fields: []const V.Comptime, + type: Type.Index, + + pub const List = BlockList(@This(), enum{}); + pub usingnamespace List.Index; + }; + + const ComptimeInt = struct{ + value: u64, + signedness: Type.Integer.Signedness, + }; + + const ConstantInt = struct{ + value: u64, + }; fn getType(v: Comptime, unit: *Unit) Type.Index{ return switch (v) { @@ -724,6 +836,7 @@ pub const V = struct{ .bool => .bool, .enum_value => |enum_field_index| unit.enum_fields.get(enum_field_index).parent, .function_definition => |function_definition_index| unit.function_definitions.get(function_definition_index).type, + .comptime_int => .comptime_int, else => |t| @panic(@tagName(t)), }; } @@ -897,7 +1010,7 @@ pub const IntrinsicId = enum{ zero_extend, }; -pub const BinaryOperationId = enum { +pub const ArithmeticLogicIntegerInstruction = enum { add, sub, mul, @@ -908,12 +1021,6 @@ pub const BinaryOperationId = enum { bit_or, shift_left, shift_right, - compare_equal, - compare_not_equal, - compare_greater, - compare_greater_equal, - compare_less, - compare_less_equal, }; pub const Builder = struct { @@ -921,6 +1028,9 @@ pub const Builder = struct { current_file: Debug.File.Index = .null, current_function: Function.Definition.Index = .null, current_basic_block: BasicBlock.Index = .null, + exit_blocks: ArrayList(BasicBlock.Index) = .{}, + return_phi: Instruction.Index = .null, + return_block: BasicBlock.Index = .null, last_check_point: struct{ line: u32 = 0, column: u32 = 0, @@ -929,6 +1039,66 @@ pub const Builder = struct { generate_debug_info: bool, emit_ir: bool, + fn processStringLiteral(builder: *Builder, unit: *Unit, context: *const Context, token_index: Token.Index) !*Debug.Declaration.Global { + const string = try unit.fixupStringLiteral(context, token_index); + const possible_id = unit.string_literal_values.size; + const hash = data_structures.hash(string); + const gop = try unit.string_literal_globals.getOrPut(context.allocator, hash); + + if (gop.found_existing) { + return gop.value_ptr.*; + } else { + const string_name = try std.fmt.allocPrint(context.allocator, "__anon_str_{}", .{possible_id}); + const identifier = try unit.processIdentifier(context, string_name); + try unit.string_literal_values.putNoClobber(context.allocator, hash, string); + const token_debug_info = builder.getTokenDebugInfo(unit, token_index); + + const string_global_index = try unit.global_declarations.append(context.allocator, .{ + .declaration = .{ + .scope = builder.current_scope, + .name = identifier, + .type = blk: { + const length = string.len; + const array_type = try unit.getArrayType(context, .{ + .type = .u8, + .count = length, + .termination = .zero, + }); + const string_type = try unit.getPointerType(context, .{ + .type = array_type, + .termination = .none, + .mutability = .@"const", + .many = true, + .nullable = false, + }); + _ = string_type; // autofix + + break :blk array_type; + }, + .line = token_debug_info.line, + .column = token_debug_info.column, + .mutability = .@"const", + .kind = .global, + }, + .initial_value = .{ + .string_literal = hash, + }, + .type_node_index = .null, + .attributes = Debug.Declaration.Global.Attributes.initMany(&.{ + .@"export", + }), + }); + + const string_global = unit.global_declarations.get(string_global_index); + + gop.value_ptr.* = string_global; + + try unit.data_to_emit.append(context.allocator, string_global); + + return string_global; + } + } + fn resolveIntrinsic(builder: *Builder, unit: *Unit, context: *const Context, type_expect: Type.Expect, node_index: Node.Index) anyerror!V { const node = unit.getNode(node_index); const intrinsic_id: IntrinsicId = @enumFromInt(Node.unwrap(node.right)); @@ -1042,7 +1212,7 @@ pub const Builder = struct { switch (source_type.*) { .integer => |source_integer| { _ = source_integer; // autofix - // TODO: + // TODO: break :b .int_to_pointer; }, else => |t| @panic(@tagName(t)), @@ -1050,8 +1220,25 @@ pub const Builder = struct { }, .integer => |destination_integer| { switch (source_type.*) { - .@"enum" => { - break :b .enum_to_int; + .@"enum" => |enum_index| { + const enum_type = unit.enums.get(enum_index); + if (enum_type.backing_type == type_index) { + break :b .enum_to_int; + } else { + const source_integer = unit.types.get(enum_type.backing_type).integer; + if (destination_integer.bit_count < source_integer.bit_count) { + unreachable; + } else if (destination_integer.bit_count > source_integer.bit_count) { + assert(destination_integer.signedness != source_integer.signedness); + break :b switch (destination_integer.signedness) { + .signed => .sign_extend, + .unsigned => .zero_extend, + }; + } else { + assert(destination_integer.signedness != source_integer.signedness); + break :b .bitcast; + } + } }, .integer => |source_integer| { if (destination_integer.bit_count < source_integer.bit_count) { @@ -1060,13 +1247,23 @@ pub const Builder = struct { assert(destination_integer.signedness != source_integer.signedness); break :b switch (destination_integer.signedness) { .signed => .sign_extend, - .unsigned => .sign_extend, + .unsigned => .zero_extend, }; } else { assert(destination_integer.signedness != source_integer.signedness); break :b .bitcast; } }, + .pointer => { + if (destination_integer.signedness == .signed) { + unreachable; + } + if (destination_integer.bit_count < 64) { + unreachable; + } + + break :b .pointer_to_int; + }, else => |t| @panic(@tagName(t)), } }, @@ -1114,7 +1311,10 @@ pub const Builder = struct { return .{ .value = .{ .@"comptime" = .{ - .integer = integer_value, + .comptime_int = .{ + .value = integer_value, + .signedness = .unsigned, + }, }, }, .type = integer_type, @@ -1151,6 +1351,45 @@ pub const Builder = struct { @panic("Syscall argument mismatch"); } }, + .min => { + assert(argument_node_list.len == 2); + const expected_type = switch (type_expect) { + .type => |type_index| { + const left = try builder.resolveRuntimeValue(unit, context, type_expect, argument_node_list[0], .right); + const right = try builder.resolveRuntimeValue(unit, context, type_expect, argument_node_list[1], .right); + switch (unit.types.get(type_index).*) { + .integer => |integer| { + const min_descriptor = Instruction.Min{ + .left = left, + .right = right, + .type = type_index, + }; + const instruction: Instruction = switch (integer.signedness) { + .unsigned => .{ + .umin = min_descriptor, + }, + .signed => .{ + .smin = min_descriptor, + }, + }; + const min = try unit.instructions.append(context.allocator, instruction); + try builder.appendInstruction(unit, context, min); + + return .{ + .value = .{ + .runtime = min, + }, + .type = type_index, + }; + }, + else => |t| @panic(@tagName(t)), + } + }, + else => |t| @panic(@tagName(t)), + }; + _ = expected_type; // autofix + unreachable; + }, else => |t| @panic(@tagName(t)), } } @@ -1215,6 +1454,18 @@ pub const Builder = struct { builder.current_scope = &unit.scope.scope; defer builder.current_scope = old_scope; + const old_exit_blocks = builder.exit_blocks; + builder.exit_blocks = .{}; + defer builder.exit_blocks = old_exit_blocks; + + const old_return_phi = builder.return_phi; + builder.return_phi = .null; + defer builder.return_phi = old_return_phi; + + const old_return_block = builder.return_block; + builder.return_block = .null; + defer builder.return_block = old_return_block; + const file = unit.files.get(file_index); assert(file.status == .parsed); @@ -1286,8 +1537,13 @@ pub const Builder = struct { fn appendInstruction(builder: *Builder, unit: *Unit, context: *const Context, instruction_index: Instruction.Index) !void { const basic_block = unit.basic_blocks.get(builder.current_basic_block); - assert(!basic_block.terminated); - try basic_block.instructions.append(context.allocator, instruction_index); + if (!basic_block.terminated) { + try basic_block.instructions.append(context.allocator, instruction_index); + } else { + const instruction = unit.instructions.get(instruction_index); + assert(instruction.* == .pop_scope); + try basic_block.instructions.insert(context.allocator, basic_block.instructions.items.len - 1, instruction_index); + } } const If = struct{ @@ -1340,6 +1596,12 @@ pub const Builder = struct { .function_definition => { try unit.code_to_emit.append(context.allocator, global_declaration); }, + .type => |type_index| { + assert(declaration.type == .type); + unit.type_declarations.put(context.allocator, type_index, global_declaration) catch { + assert(unit.type_declarations.get(type_index).? == global_declaration); + }; + }, else => { if (global_declaration.attributes.contains(.@"export") or declaration.mutability == .@"var") { try unit.data_to_emit.append(context.allocator, global_declaration); @@ -1414,6 +1676,7 @@ pub const Builder = struct { switch (type_expect) { .none => {}, .type => |type_index| if (type_index != .type) @panic("expected type"), + .optional => unreachable, } if (arguments.len != 1) { @panic("Import argument mismatch"); @@ -1425,7 +1688,7 @@ pub const Builder = struct { @panic("Import expected a string literal as an argument"); } - const string_literal_bytes = unit.tokenStringLiteral(argument_node.token); + const string_literal_bytes = try unit.fixupStringLiteral(context, argument_node.token); const import_file = try unit.importFile(context, builder.current_file, string_literal_bytes); @@ -1442,6 +1705,10 @@ pub const Builder = struct { return import_file.file.index; } + const ComptimeEvaluationError = error{ + cannot_evaluate, + }; + fn resolveComptimeValue(builder: *Builder, unit: *Unit, context: *const Context, type_expect: Type.Expect, global_attributes: Debug.Declaration.Global.Attributes, node_index: Node.Index) anyerror!V.Comptime{ const node = unit.getNode(node_index); switch (node.id) { @@ -1475,6 +1742,18 @@ pub const Builder = struct { defer builder.current_basic_block = current_basic_block; builder.current_basic_block = .null; + const old_exit_blocks = builder.exit_blocks; + defer builder.exit_blocks = old_exit_blocks; + builder.exit_blocks = .{}; + + const old_phi_node = builder.return_phi; + defer builder.return_phi = old_phi_node; + builder.return_phi = .null; + + const old_return_block = builder.return_block; + defer builder.return_block = old_return_block; + builder.return_block = .null; + const function_prototype_node_index = node.left; const body_node_index = node.right; @@ -1499,12 +1778,16 @@ pub const Builder = struct { }, }, }); + + // if (@intFromEnum(builder.current_function) == 7) { + // @breakpoint(); + // } defer builder.current_function = old_function; const function = unit.function_definitions.get(builder.current_function); builder.last_check_point = .{}; - assert(builder.current_scope.kind == .file_container or builder.current_scope.kind == .file); + assert(builder.current_scope.kind == .file_container or builder.current_scope.kind == .file or builder.current_scope.kind == .container); try builder.pushScope(unit, context, &function.scope.scope); defer builder.popScope(unit, context) catch unreachable; @@ -1564,32 +1847,6 @@ pub const Builder = struct { if (body_node.id == .block) { function.body = try builder.resolveBlock(unit, context, body_node_index); - logln(.compilation, .ir, "Function #{}", .{Function.Definition.unwrap(builder.current_function)}); - - for (function.basic_blocks.items) |basic_block_index| { - const basic_block = unit.basic_blocks.get(basic_block_index); - logln(.compilation, .ir, "[#{}]:", .{BasicBlock.unwrap(basic_block_index)}); - - for (basic_block.instructions.items) |instruction_index| { - const instruction = unit.instructions.get(instruction_index); - log(.compilation, .ir, " %{}: {s} ", .{Instruction.unwrap(instruction_index), @tagName(instruction.*)}); - - switch (instruction.*) { - .debug_checkpoint => |checkpoint| { - log(.compilation, .ir, "{}, {}", .{checkpoint.line, checkpoint.column}); - }, - .argument_declaration => |arg|{ - log(.compilation, .ir, "\"{s}\"", .{unit.getIdentifier(arg.declaration.name)}); - }, - .cast => |cast| { - log(.compilation, .ir, "{s}", .{@tagName(cast.id)}); - }, - else => {} - } - logln(.compilation, .ir, "", .{}); - } - } - const function_definition_index = builder.current_function; return .{ @@ -1601,20 +1858,23 @@ pub const Builder = struct { }, .number_literal => switch (std.zig.parseNumberLiteral(unit.getExpectedTokenBytes(node.token, .number_literal))) { .int => |integer| { - const type_index = switch (type_expect) { - .type => |type_index| b: { - const ty = unit.types.get(type_index); - break :b switch (ty.*) { - .integer => type_index, - else => |t| @panic(@tagName(t)), - }; - }, - .none => Type.Index.comptime_int, - }; - _ = type_index; // autofix + // const type_index = switch (type_expect) { + // .type => |type_index| b: { + // const ty = unit.types.get(type_index); + // break :b switch (ty.*) { + // .integer => type_index, + // else => |t| @panic(@tagName(t)), + // }; + // }, + // .none => Type.Index.comptime_int, + // }; + // _ = type_index; // autofix return .{ - .integer = integer, + .comptime_int = .{ + .value = integer, + .signedness = .unsigned, + }, }; }, else => |t| @panic(@tagName(t)), @@ -1628,6 +1888,12 @@ pub const Builder = struct { .type = type_index, }; }, + .struct_type => { + const type_index = try builder.resolveContainerType(unit, context, node_index, .@"struct"); + return .{ + .type = type_index, + }; + }, .@"switch" => { const result = try builder.resolveSwitch(unit, context, type_expect, node_index); switch (result.value) { @@ -1638,9 +1904,30 @@ pub const Builder = struct { else => unreachable, } }, + .identifier => { + const identifier = unit.getExpectedTokenBytes(node.token, .identifier); + const resolved_value = try builder.resolveIdentifier(unit, context, type_expect, identifier, .left); + return switch (resolved_value.value) { + .@"comptime" => |ct| ct, + .runtime => return error.cannot_evaluate, + else => unreachable, + }; + }, + .signed_integer_type => { + const result = try builder.resolveIntegerType(unit, context, node_index); + return .{ + .type = result, + }; + }, + .compare_greater_equal => { + const left = try builder.resolveComptimeValue(unit, context, Type.Expect.none, .{}, node.left); + const left_type = left.getType(unit); + const right = try builder.resolveComptimeValue(unit, context, Type.Expect{ .type = left_type }, .{}, node.right); + _ = right; // autofix + unreachable; + }, else => |t| @panic(@tagName(t)), } - unreachable; } fn referenceArgumentDeclaration(builder: *Builder, unit: *Unit, context: *const Context, scope: *Debug.Scope, declaration: *Debug.Declaration) !V { @@ -1687,6 +1974,8 @@ pub const Builder = struct { const TypeCheckResult = enum{ success, pointer_var_to_const, + materialize_int, + optional_wrap, }; const TypecheckError = error{ @@ -1734,11 +2023,36 @@ pub const Builder = struct { unreachable; }, + .comptime_int => { + return .materialize_int; + }, else => |t| @panic(@tagName(t)), } }, + .@"struct" => |destination_struct_index| { + const destination_struct = unit.structs.get(destination_struct_index); + if (destination_struct.optional) { + if (unit.optionals.get(source_type_index)) |optional_type_index| { + _ = optional_type_index; // autofix + return .optional_wrap; + } else { + unreachable; + } + } else { + unreachable; + } + + }, + // .optional => |destination_optional_element_type_index| { + // if (destination_optional_element_type_index == source_type_index) { + // return .optional_wrap; + // } else { + // unreachable; + // } + // }, else => |t| @panic(@tagName(t)), } + unreachable; } } @@ -1803,14 +2117,97 @@ pub const Builder = struct { try builder.appendInstruction(unit, context, cast_to_const); return .{ - .value =.{ + .value = .{ .runtime = cast_to_const, }, .type = expected_type_index, }; }, + .materialize_int => { + const destination_integer_type = unit.types.get(expected_type_index).integer; + const ct_int = v.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; + } + }, + .signed => { + if (destination_integer_type.signedness == .unsigned) { + unreachable; + } + unreachable; + }, + } + + return .{ + .value = .{ + .@"comptime" = .{ + .constant_int = .{ + .value = ct_int.value, + }, + }, + }, + .type = expected_type_index, + }; + }, + .optional_wrap => { + const optional_type_index = expected_type_index; + const optional_undefined = V{ + .value = .{ + .@"comptime" = .undefined, + }, + .type = optional_type_index, + }; + + const value_to_wrap = v; + _ = value_to_wrap; // autofix + + const insert_value_to_optional = try unit.instructions.append(context.allocator, .{ + .insert_value = .{ + .expression = optional_undefined, + .index = 0, + .new_value = v, + }, + }); + + try builder.appendInstruction(unit, context, insert_value_to_optional); + + const final_insert = try unit.instructions.append(context.allocator, .{ + .insert_value = .{ + .expression = .{ + .value = .{ + .runtime = insert_value_to_optional, + }, + .type = optional_type_index, + }, + .index = 1, + // This tells the optional is valid (aka not null) + .new_value = .{ + .value = .{ + .@"comptime" = .{ + .bool = true, + }, + }, + .type = .bool, + }, + }, + }); + + try builder.appendInstruction(unit, context, final_insert); + + return .{ + .value = .{ + .runtime = final_insert, + }, + .type = expected_type_index, + }; + }, } }, + .optional => unreachable, } } else { var scope_it: ?*Debug.Scope = builder.current_scope; @@ -1861,12 +2258,56 @@ pub const Builder = struct { else => {}, } const right = try builder.resolveRuntimeValue(unit, context, Type.Expect{ .type = left.type }, node.right, .right); + const value_to_store = switch (node.id) { + .assign => right, + else => blk: { + const left_load = try unit.instructions.append(context.allocator, .{ + .load = .{ + .value = left, + }, + }); + + try builder.appendInstruction(unit, context, left_load); + + const instruction = switch (unit.types.get(left.type).*) { + .integer => |integer| { + const instruction = try unit.instructions.append(context.allocator, .{ + .integer_binary_operation = .{ + .left = .{ + .value = .{ + .runtime = left_load, + }, + .type = left.type, + }, + .right = right, + .signedness = integer.signedness, + .id = switch (node.id) { + .add_assign => .add, + else => |t| @panic(@tagName(t)), + }, + }, + }); + try builder.appendInstruction(unit, context, instruction); + + break :blk V{ + .value = .{ + .runtime = instruction, + }, + .type = left.type, + }; + }, + else => |t| @panic(@tagName(t)), + }; + _ = instruction; // autofix + unreachable; + }, + }; const store = try unit.instructions.append(context.allocator, .{ .store = .{ .destination = left, - .source = right, + .source = value_to_store, }, - }); + }); try builder.appendInstruction(unit, context, store); return .{ @@ -1889,9 +2330,9 @@ pub const Builder = struct { return entry_basic_block; } - fn resolveType(builder: *Builder, unit: *Unit, context: *const Context, node_index: Node.Index) !Type.Index { + fn resolveIntegerType(builder: *Builder, unit: *Unit, context: *const Context, node_index: Node.Index) anyerror!Type.Index { + _ = builder; // autofix const node = unit.getNode(node_index); - const result: Type.Index = switch (node.id) { .signed_integer_type, .unsigned_integer_type, => b: { const token_bytes = unit.getExpectedTokenBytes(node.token, switch (node.id) { @@ -1911,6 +2352,19 @@ pub const Builder = struct { }); break :b type_index; }, + else => unreachable, + }; + + return result; + } + + fn resolveType(builder: *Builder, unit: *Unit, context: *const Context, node_index: Node.Index) anyerror!Type.Index { + const node = unit.getNode(node_index); + + const result: Type.Index = switch (node.id) { + .signed_integer_type, .unsigned_integer_type, => b: { + break :b try builder.resolveIntegerType(unit, context, node_index); + }, .pointer_type => b: { const attribute_node_list = unit.getNodeList(node.left); var mutability = Mutability.@"var"; @@ -1963,6 +2417,53 @@ pub const Builder = struct { }); break :b pointer_type; }, + .slice_type => b: { + const attribute_node_list = unit.getNodeList(node.left); + var mutability = Mutability.@"var"; + var element_type_index = Type.Index.null; + var termination = Type.Termination.none; + + for (attribute_node_list) |element_node_index| { + const element_node = unit.getNode(element_node_index); + switch (element_node.id) { + .function_prototype, + .identifier, + .unsigned_integer_type, + .signed_integer_type, + .optional_type, + .array_type, + .usize_type, + .pointer_type, + => { + if (element_type_index != .null) { + unreachable; + } + + element_type_index = try builder.resolveType(unit, context, element_node_index); + }, + .const_expression => mutability = .@"const", + .zero_terminated => { + assert(termination == .none); + termination = .zero; + }, + .null_terminated => { + assert(termination == .none); + termination = .null; + }, + else => |t| @panic(@tagName(t)), + } + } + + assert(element_type_index != .null); + + const slice_type = try unit.getSliceType(context, .{ + .mutability = mutability, + .type = element_type_index, + .termination = termination, + .nullable = false, + }); + break :b slice_type; + }, .optional_type => blk: { const element_type_index = try builder.resolveType(unit, context, node.left); const element_type = unit.types.get(element_type_index); @@ -1973,12 +2474,22 @@ pub const Builder = struct { nullable_pointer.nullable = true; break :b try unit.getPointerType(context, nullable_pointer); }, - else => unreachable, + else => b: { + const optional_type = try unit.getOptionalType(context, element_type_index); + break :b optional_type; + }, }; break :blk r; }, .keyword_noreturn => .noreturn, .usize_type => .usize, + .void_type => .void, + .identifier, .field_access => { + const resolved_type_value = try builder.resolveComptimeValue(unit, context, Type.Expect{ .type = .type }, .{}, node_index); + return resolved_type_value.type; + }, + .bool_type => .bool, + .ssize_type => .ssize, else => |t| @panic(@tagName(t)), }; @@ -2100,6 +2611,7 @@ pub const Builder = struct { }, }, .backing_type = backing_type, + .optional = false, .type = .null, }); const struct_type = unit.structs.get(struct_index); @@ -2107,6 +2619,7 @@ pub const Builder = struct { const type_index = try unit.types.append(context.allocator, .{ .@"struct" = struct_index, }); + struct_type.type = type_index; // Save file type @@ -2116,6 +2629,12 @@ pub const Builder = struct { const file = @fieldParentPtr(Debug.File, "scope", global_scope); file.type = type_index; }, + .file_container => {}, + // .file_container => { + // const global_scope = @fieldParentPtr(Debug.Scope.Global, "scope", builder.current_scope); + // const file = @fieldParentPtr(Debug.File, "scope", global_scope); + // file.type = type_index; + // }, else => |t| @panic(@tagName(t)), } @@ -2291,37 +2810,39 @@ pub const Builder = struct { } if (count.fields > 0) { + const ty = unit.types.get(type_index); switch (container_type) { .@"enum" => { - const ty = unit.types.get(type_index); const enum_type = unit.enums.get(ty.@"enum"); const field_count = field_nodes.items.len; try enum_type.fields.ensureTotalCapacity(context.allocator, field_count); if (enum_type.backing_type == .null) { const bit_count = @bitSizeOf(@TypeOf(field_nodes.items.len)) - @clz(field_nodes.items.len); - const real_bit_count: u16 = if (bit_count <= 8) 8 else if (bit_count <= 16) 16 else if (bit_count <= 32) 32 else if (bit_count <= 64) 64 else unreachable; enum_type.backing_type = try unit.getIntegerType(context, .{ - .bit_count = real_bit_count, + .bit_count = bit_count, .signedness = .unsigned, }); } }, - else => |t| @panic(@tagName(t)), + .@"struct" => { + const struct_type = unit.structs.get(ty.@"struct"); + const field_count = field_nodes.items.len; + try struct_type.fields.ensureTotalCapacity(context.allocator, field_count); + }, } for (field_nodes.items, 0..) |field_node_index, index| { const field_node = unit.getNode(field_node_index); + const identifier = unit.getExpectedTokenBytes(field_node.token, .identifier); + const hash = try unit.processIdentifier(context, identifier); + switch (container_type) { .@"enum" => { assert(field_node.id == .@"enum_field"); - const ty = unit.types.get(type_index); const enum_type = unit.enums.get(ty.@"enum"); - const identifier = unit.getExpectedTokenBytes(field_node.token, .identifier); - const hash = try unit.processIdentifier(context, identifier); - const enum_value: usize = switch (field_node.left) { .null => index, else => b: { @@ -2330,9 +2851,11 @@ pub const Builder = struct { }, .{}, //Debug.Declaration.Global.Attributes.initEmpty(), field_node.left); - break :b enum_value.integer; + assert(enum_value.comptime_int.signedness == .unsigned); + break :b enum_value.comptime_int.value; }, }; + const enum_field_index = try unit.enum_fields.append(context.allocator, .{ .name = hash, .value = enum_value, @@ -2340,7 +2863,24 @@ pub const Builder = struct { }); enum_type.fields.appendAssumeCapacity(enum_field_index); }, - else => |t| @panic(@tagName(t)), + .@"struct" => { + assert(field_node.id == .@"container_field"); + const struct_type = unit.structs.get(ty.@"struct"); + const field_name = unit.getExpectedTokenBytes(field_node.token, .identifier); + const field_name_hash = try unit.processIdentifier(context, field_name); + const field_type = try builder.resolveType(unit, context, field_node.left); + const field_default_value: ?V.Comptime = switch (field_node.right) { + .null => null, + else => |default_value_node_index| try builder.resolveComptimeValue(unit, context, Type.Expect{ .type = field_type }, .{}, default_value_node_index), + }; + + const struct_field = try unit.struct_fields.append(context.allocator, .{ + .name = field_name_hash, + .type = field_type, + .default_value = field_default_value, + }); + struct_type.fields.appendAssumeCapacity(struct_field); + }, } } } @@ -2373,16 +2913,15 @@ pub const Builder = struct { fn resolveRuntimeValue(builder: *Builder, unit: *Unit, context: *const Context, type_expect: Type.Expect, node_index: Node.Index, side: Side) anyerror!V{ const node = unit.getNode(node_index); - switch (node.id) { - .identifier => { + + const v : V = switch (node.id) { + .identifier => block: { const identifier = unit.getExpectedTokenBytes(node.token, .identifier); const result = try builder.resolveIdentifier(unit, context, type_expect, identifier, side); - return result; + break :block result; }, - .intrinsic => { - return try builder.resolveIntrinsic(unit, context, type_expect, node_index); - }, - .pointer_dereference => { + .intrinsic => try builder.resolveIntrinsic(unit, context, type_expect, node_index), + .pointer_dereference => block: { // TODO: const pointer_type_expect = switch (type_expect) { .none => unreachable,//type_expect, @@ -2399,6 +2938,7 @@ pub const Builder = struct { }; break :b result; }, + .optional => unreachable, }; // TODO: is this right? .right @@ -2414,19 +2954,70 @@ pub const Builder = struct { const load_type = switch (type_expect) { .none => unreachable, .type => |type_index| type_index, + .optional => unreachable, }; - return .{ + break :block .{ .value = .{ .runtime = load, }, .type = load_type, }; }, - .add, .sub, .mul, .div, .mod, .bit_and, .bit_or, .bit_xor, .shift_left, .shift_right => { + .compare_equal, .compare_not_equal, .compare_greater, .compare_greater_equal, .compare_less, .compare_less_equal, => |cmp_node_id| block: { + const left_node_index = node.left; + const right_node_index = node.right; + const left_expect_type = Type.Expect.none; + const left_value = try builder.resolveRuntimeValue(unit, context, left_expect_type, left_node_index, .right); + const left_type = left_value.type; + const right_expect_type = Type.Expect{ .type = left_type }; + const right_value = try builder.resolveRuntimeValue(unit, context, right_expect_type, right_node_index, .right); + + switch (unit.types.get(left_type).*) { + .integer => |integer| { + const compare = try unit.instructions.append(context.allocator, .{ + .integer_compare = .{ + .left = left_value, + .right = right_value, + .type = left_type, + .id = switch (cmp_node_id) { + .compare_equal => .equal, + .compare_not_equal => .not_equal, + else => switch (integer.signedness) { + .unsigned => switch (cmp_node_id) { + .compare_less => .unsigned_less, + .compare_less_equal => .unsigned_less_equal, + .compare_greater => .unsigned_greater, + .compare_greater_equal => .unsigned_greater_equal, + else => unreachable, + }, + .signed => switch (cmp_node_id) { + .compare_less => .signed_less, + .compare_less_equal => .signed_less_equal, + .compare_greater => .signed_greater, + .compare_greater_equal => .signed_greater_equal, + else => unreachable, + }, + } + }, + }, + }); + try builder.appendInstruction(unit, context, compare); + + break :block .{ + .value = .{ + .runtime = compare, + }, + .type = .bool, + }; + }, + else => |t| @panic(@tagName(t)), + } + }, + .add, .sub, .mul, .div, .mod, .bit_and, .bit_or, .bit_xor, .shift_left, .shift_right => block: { const left_node_index = node.left; const right_node_index = node.left; - const binary_operation_id: BinaryOperationId = switch (node.id) { + const binary_operation_id: ArithmeticLogicIntegerInstruction = switch (node.id) { .add => .add, .sub => .sub, .mul => .mul, @@ -2437,25 +3028,10 @@ pub const Builder = struct { .bit_or => .bit_or, .shift_left => .shift_left, .shift_right => .shift_right, - .compare_equal => .compare_equal, - .compare_not_equal => .compare_not_equal, - .compare_greater => .compare_greater, - .compare_greater_equal => .compare_greater_equal, - .compare_less => .compare_less, - .compare_less_equal => .compare_less_equal, else => |t| @panic(@tagName(t)), }; - const left_expect_type: Type.Expect = switch (binary_operation_id) { - .compare_equal, - .compare_not_equal, - .compare_less, - .compare_less_equal, - .compare_greater, - .compare_greater_equal, - => Type.Expect.none, - else => type_expect, - }; + const left_expect_type = type_expect; const left_value = try builder.resolveRuntimeValue(unit, context, left_expect_type, left_node_index, .right); const left_type = left_value.type; @@ -2476,22 +3052,8 @@ pub const Builder = struct { .shift_left, .shift_right, => type_expect, - // .shift_left, - // .shift_right, - // => ExpectType{ - // .type_index = Type.u8, - // }, - .compare_equal, - .compare_not_equal, - .compare_less, - .compare_greater, - .compare_greater_equal, - .compare_less_equal, - => Type.Expect{ - .type = left_type, - }, - }, - // else => |t| @panic(@tagName(t)), + }, + .optional => unreachable, }; const right_value = try builder.resolveRuntimeValue(unit, context, right_expect_type, right_node_index, .right); @@ -2509,7 +3071,7 @@ pub const Builder = struct { else => |t| @panic(@tagName(t)), }, .type => |type_index| type_index, - // else => |t| @panic(@tagName(t)), + .optional => unreachable, }; const instruction = switch (unit.types.get(left_type).*) { @@ -2525,7 +3087,6 @@ pub const Builder = struct { .bit_xor => .bit_xor, .shift_left => .shift_left, .shift_right => .shift_right, - else => |t| @panic(@tagName(t)), }; const i = try unit.instructions.append(context.allocator, .{ @@ -2535,7 +3096,7 @@ pub const Builder = struct { .id = id, .signedness = integer.signedness, }, - }); + }); break :b i; }, else => |t| @panic(@tagName(t)), @@ -2543,51 +3104,51 @@ pub const Builder = struct { try builder.appendInstruction(unit, context, instruction); - return .{ + break :block .{ .value = .{ .runtime = instruction, }, .type = type_index, }; }, - .call => { - return try builder.resolveCall(unit, context, node_index); - }, - .field_access => { - const result = try builder.resolveFieldAccess(unit, context, type_expect, node_index); - return result; - }, + .call => try builder.resolveCall(unit, context, node_index), + .field_access => try builder.resolveFieldAccess(unit, context, type_expect, node_index), .number_literal => switch (std.zig.parseNumberLiteral(unit.getExpectedTokenBytes(node.token, .number_literal))) { - .int => |integer| { - const type_index = switch (type_expect) { - .type => |type_index| b: { - const ty = unit.types.get(type_index); - break :b switch (ty.*) { - .integer => type_index, - else => |t| @panic(@tagName(t)), - }; + .int => |integer| switch (type_expect) { + .type => |type_index| switch (unit.types.get(type_index).*) { + .integer => V{ + .value = .{ + .@"comptime" = .{ + .constant_int = .{ + .value = integer, + }, + }, + }, + .type = type_index, }, - .none => Type.Index.comptime_int, - //else => |t| @panic(@tagName(t)), - }; - - return .{ + else => |t| @panic(@tagName(t)), + }, + .none => V{ .value = .{ .@"comptime" = .{ - .integer = integer, + .comptime_int = .{ + .value = integer, + .signedness = .unsigned, + }, }, }, - .type = type_index, - }; + .type = .comptime_int, + }, + .optional => unreachable, }, else => |t| @panic(@tagName(t)), }, - .assign, .add_assign => { + .assign, .add_assign => block: { assert(type_expect == .none or type_expect.type == .void); const result = try builder.resolveAssignment(unit, context, node_index); - return result; + break :block result; }, - .block => { + .block => block: { assert(type_expect == .none or type_expect.type == .void); const block = try builder.resolveBlock(unit, context, node_index); const block_i = try unit.instructions.append(context.allocator, .{ @@ -2598,15 +3159,390 @@ pub const Builder = struct { // try builder.appendInstruction(unit, context, block_i); // } - return .{ + break :block .{ .value = .{ .runtime = block_i, }, .type = .void, }; }, + .container_literal => block: { + assert(node.left != .null); + assert(node.right != .null); + + const container_type_index = try builder.resolveType(unit, context, node.left); + const container_type = unit.types.get(container_type_index); + + const initialization_nodes = unit.getNodeList(node.right); + + switch (container_type.*) { + .@"struct" => |struct_index| { + const struct_type = unit.structs.get( struct_index); + const fields = struct_type.fields.items; + var list = try ArrayList(V).initCapacity(context.allocator, fields.len); + var is_comptime = true; + + for (fields) |field_index| { + const field = unit.struct_fields.get(field_index); + + for (initialization_nodes) |initialization_node_index| { + const initialization_node = unit.getNode(initialization_node_index); + assert(initialization_node.id == .container_field_initialization); + assert(initialization_node.left != .null); + assert(initialization_node.right == .null); + const field_name = unit.getExpectedTokenBytes(Token.addInt(initialization_node.token, 1), .identifier); + const field_name_hash = try unit.processIdentifier(context, field_name); + if (field_name_hash == field.name) { + const expected_type = field.type; + const field_initialization = try builder.resolveRuntimeValue(unit, context, Type.Expect{ .type = expected_type }, initialization_node.left, .right); + is_comptime = is_comptime and field_initialization.value == .@"comptime"; + list.appendAssumeCapacity(field_initialization); + break; + } + unreachable; + } else if (field.default_value) |default_value| { + _ = default_value; // autofix + unreachable; + } else { + @panic("Missing field"); + } + } + + if (is_comptime) { + var comptime_list = try ArrayList(V.Comptime).initCapacity(context.allocator, fields.len); + for (list.items) |item| { + comptime_list.appendAssumeCapacity(item.value.@"comptime"); + } + + break :block .{ + .value = .{ + .@"comptime" = .{ + .constant_struct = try unit.constant_structs.append(context.allocator, .{ + .fields = comptime_list.items, + .type = container_type_index, + }), + }, + }, + .type = container_type_index, + }; + } else { + var struct_initialization = V{ + .value = .{ + .@"comptime" = .undefined, + }, + .type = container_type_index, + }; + + for (list.items, 0..) |field, index| { + const struct_initialization_instruction = try unit.instructions.append(context.allocator, .{ + .insert_value = .{ + .expression = struct_initialization, + .index = @intCast(index), + .new_value = field, + }, + }); + + try builder.appendInstruction(unit, context, struct_initialization_instruction); + + struct_initialization.value = .{ + .runtime = struct_initialization_instruction, + }; + + } + + break :block struct_initialization; + } + }, + else => |t| @panic(@tagName(t)), + } + }, + .enum_literal => block: { + switch (type_expect) { + .type => |type_index| { + const expected_type = unit.types.get(type_index); + switch (expected_type.*) { + .@"enum" => |enum_index| { + const enum_type = unit.enums.get(enum_index); + const identifier = unit.getExpectedTokenBytes(Token.addInt( node.token, 1), .identifier); + const hash = try unit.processIdentifier(context, identifier); + for (enum_type.fields.items) |field_index| { + const field = unit.enum_fields.get(field_index); + if (field.name == hash) { + break :block V{ + .value = .{ + .@"comptime" = .{ + .enum_value = field_index, + }, + }, + .type = type_index, + }; + } + } else { + unreachable; + } + }, + else => |t| @panic(@tagName(t)), + } + }, + else => |t| @panic(@tagName(t)), + } + }, + .null_literal => switch (type_expect) { + .type => |type_index| switch (unit.types.get(type_index).*) { + .@"struct" => |struct_index| { + const struct_type = unit.structs.get(struct_index); + if (struct_type.optional) { + const optional_undefined = V{ + .value = .{ + .@"comptime" = .undefined, + }, + .type = type_index, + }; + + const final_insert = try unit.instructions.append(context.allocator, .{ + .insert_value = .{ + .expression = optional_undefined, + .index = 1, + // This tells the optional is valid (aka not null) + .new_value = .{ + .value = .{ + .@"comptime" = .{ + .bool = true, + }, + }, + .type = .bool, + }, + }, + }); + + try builder.appendInstruction(unit, context, final_insert); + + return .{ + .value = .{ + .runtime = final_insert, + }, + .type = type_index, + }; + } else { + unreachable; + } + }, + // .optional => .{ + // .value = .{ + // .@"comptime" = .optional_null_literal, + // }, + // .type = type_index, + // }, + 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 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); + const range_end: V = switch (range_node.right) { + .null => switch (unit.types.get(expression_to_slice.type).*) { + .slice => b: { + const extract_value = try unit.instructions.append(context.allocator, .{ + .extract_value = .{ + .expression = expression_to_slice, + .index = 1, + }, + }); + try builder.appendInstruction(unit, context, extract_value); + + break :b .{ + .value = .{ + .runtime = extract_value, + }, + .type = .usize, + }; + }, + else => |t| @panic(@tagName(t)), + }, + else => unreachable, + }; + + switch (unit.types.get(expression_to_slice.type).*) { + .slice => |slice| { + const extract_value = try unit.instructions.append(context.allocator, .{ + .extract_value = .{ + .expression = expression_to_slice, + .index = 0, + }, + }); + try builder.appendInstruction(unit, context, extract_value); + + const pointer_type = try unit.getPointerType(context, .{ + .type = slice.type, + .termination = slice.termination, + .nullable = slice.nullable, + .mutability = slice.mutability, + .many = true, + }); + const pointer_gep = try unit.instructions.append(context.allocator, .{ + .get_element_pointer = .{ + .pointer = extract_value, + .base_type = slice.type, + .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 = expression_to_slice.type, + }, + .index = 0, + .new_value = .{ + .value = .{ + .runtime = pointer_gep, + }, + .type = pointer_type, + }, + }, + }); + 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{ + .value = .{ + .runtime = slice_builder, + }, + .type = expression_to_slice.type, + }, + .index = 1, + .new_value = .{ + .value = .{ + .runtime = range_compute, + }, + .type = .usize, + }, + }, + }); + + try builder.appendInstruction(unit, context, final_slice); + + break :blk .{ + .value = .{ + .runtime = final_slice, + }, + .type = expression_to_slice.type, + }; + }, + else => |t| @panic(@tagName(t)), + } + }, + .keyword_false, .keyword_true => .{ + .value = .{ + .@"comptime" = .{ + .bool = node.id == .keyword_true, + }, + }, + .type = .bool, + }, + .string_literal => switch (type_expect) { + .type => |type_index| switch (unit.types.get(type_index).*) { + .slice => |slice| blk: { + assert(slice.type == .u8); + assert(!slice.nullable); + assert(slice.mutability == .@"const"); + + const string_global = try builder.processStringLiteral(unit, context, node.token); + + const pointer_type = try unit.getPointerType(context, .{ + .type = slice.type, + .termination = slice.termination, + .nullable = slice.nullable, + .mutability = slice.mutability, + .many = true, + }); + const instruction = try unit.instructions.append(context.allocator, .{ + .global = string_global, + }); + // try builder.appendInstruction(instruction); + + const global_string_pointer = .{ + .value = .{ + .runtime = instruction, + }, + .type = pointer_type, + }; + + const slice_builder = try unit.instructions.append(context.allocator, .{ + .insert_value = .{ + .expression = V{ + .value = .{ + .@"comptime" = .undefined, + }, + .type = type_index, + }, + .index = 0, + .new_value = global_string_pointer, + }, + }); + try builder.appendInstruction(unit, context, slice_builder); + + const len = unit.types.get(string_global.declaration.type).array.count; + + const final_slice = try unit.instructions.append(context.allocator, .{ + .insert_value = .{ + .expression = V{ + .value = .{ + .runtime = slice_builder, + }, + .type = type_index, + }, + .index = 1, + .new_value = .{ + .value = .{ + .@"comptime" = .{ + .constant_int = .{ + .value = len, + }, + }, + }, + .type = .usize, + }, + }, + }); + + try builder.appendInstruction(unit, context, final_slice); + + break :blk .{ + .value = .{ + .runtime = final_slice, + }, + .type = type_index, + }; + }, + else => |t| @panic(@tagName(t)), + }, + else => |t| @panic(@tagName(t)), + }, + .if_else => try builder.resolveIfElse(unit, context, type_expect, node_index), else => |t| @panic(@tagName(t)), - } + }; + + return v; } fn resolveCall(builder: *Builder, unit: *Unit, context: *const Context, node_index: Node.Index) !V{ @@ -2615,13 +3551,128 @@ pub const Builder = struct { assert(node.left != .null); assert(node.right != .null); const left_node = unit.getNode(node.left); - const is_field_access = switch (left_node.id) { - .field_access => true, - else => false, + + var argument_list = ArrayList(V){}; + const callable = switch (left_node.id) { + .field_access => b: { + const field_access_left = try builder.resolveRuntimeValue(unit, context, Type.Expect.none, left_node.left, .right); + const right_identifier_node = unit.getNode(left_node.right); + assert(right_identifier_node.id == .identifier); + const right_identifier = unit.getExpectedTokenBytes(right_identifier_node.token, .identifier); + const right_identifier_hash = try unit.processIdentifier(context, right_identifier); + + switch (field_access_left.value) { + .@"comptime" => |ct| switch (ct) { + .type => |type_index| switch (unit.types.get(type_index).*) { + .@"struct", .@"enum" => { + const container_type = unit.types.get(type_index); + const container_scope = container_type.getScope(unit); + const look_in_parent_scopes = false; + if (container_scope.lookupDeclaration(right_identifier_hash, look_in_parent_scopes)) |lookup| { + const global_decl_ref = try builder.referenceGlobalDeclaration(unit, context, lookup.scope, lookup.declaration); + break :b global_decl_ref; + } else { + unreachable; + } + }, + else => |t| @panic(@tagName(t)), + }, + .constant_struct => |constant_struct_index| { + _ = constant_struct_index; // autofix + // const constant_struct = unit.constant_structs.get(constant_struct_index); + const struct_type = unit.structs.get(unit.types.get(field_access_left.type).@"struct" ); + for (struct_type.fields.items) |field_index| { + const field = unit.struct_fields.get(field_index); + if (field.name == right_identifier_hash) { + unreachable; + } + } else { + const look_in_parent_scopes = false; + if (struct_type.scope.scope.lookupDeclaration(right_identifier_hash, look_in_parent_scopes)) |lookup| { + const global_decl_ref = try builder.referenceGlobalDeclaration(unit, context, lookup.scope, lookup.declaration); + switch (global_decl_ref.value) { + .function_reference => |function_declaration| { + const function_prototype_type = unit.types.get( function_declaration.declaration.type); + const function_prototype = unit.function_prototypes.get(function_prototype_type.function); + + if (function_prototype.argument_types.len == 0) { + unreachable; + } + + const first_argument_type_index = function_prototype.argument_types[0]; + if (first_argument_type_index == field_access_left.type) { + try argument_list.append(context.allocator, field_access_left); + break :b global_decl_ref; + } else { + unreachable; + } + }, + else => |t| @panic(@tagName(t)), + } + unreachable; + // break :b global_decl_ref; + } else { + unreachable; + } + } + }, + else => |t| @panic(@tagName(t)), + }, + .runtime => |instruction_index| { + switch (unit.types.get(field_access_left.type).*) { + .@"struct" => |struct_index| { + const struct_type = unit.structs.get(struct_index); + for (struct_type.fields.items) |field_index| { + const field = unit.struct_fields.get(field_index); + if (field.name == right_identifier_hash) { + unreachable; + } + } else { + const look_in_parent_scopes = false; + if (struct_type.scope.scope.lookupDeclaration(right_identifier_hash, look_in_parent_scopes)) |lookup| { + const global_decl_ref = try builder.referenceGlobalDeclaration(unit, context, lookup.scope, lookup.declaration); + switch (global_decl_ref.value) { + .function_reference => |function_declaration| { + const function_prototype_type = unit.types.get( function_declaration.declaration.type); + const function_prototype = unit.function_prototypes.get(function_prototype_type.function); + + if (function_prototype.argument_types.len == 0) { + unreachable; + } + + const first_argument_type_index = function_prototype.argument_types[0]; + if (first_argument_type_index == field_access_left.type) { + try argument_list.append(context.allocator, field_access_left); + break :b global_decl_ref; + } else { + unreachable; + } + }, + else => |t| @panic(@tagName(t)), + } + unreachable; + // break :b global_decl_ref; + } else { + unreachable; + } + } + }, + else => |t| @panic(@tagName(t)), + } + _ = instruction_index; // autofix + }, + else => |t| @panic(@tagName(t)), + } + }, + .identifier => blk: { + const identifier = unit.getExpectedTokenBytes(left_node.token, .identifier); + const result = try builder.resolveIdentifier(unit, context, Type.Expect.none, identifier, .left); + break :blk result; + }, + else => |t| @panic(@tagName(t)), }; - _ = is_field_access; // autofix - const left = try builder.resolveRuntimeValue(unit, context, Type.Expect.none, node.left, .left); - switch (left.value) { + + switch (callable.value) { .function_reference => |function_declaration| { const function_definition_index = function_declaration.getFunctionDefinitionIndex(); const function = unit.function_definitions.get(function_definition_index); @@ -2629,14 +3680,16 @@ pub const Builder = struct { const function_prototype = unit.function_prototypes.get(function_type.function); const argument_nodes = unit.getNodeList(node.right); const argument_declaration_count = function.scope.scope.declarations.count(); - // - if (argument_nodes.len != argument_declaration_count) { + + // Argument list holds already the value of the member value + if (argument_nodes.len + argument_list.items.len != argument_declaration_count) { @panic("Argument count mismatch"); } - var list = try ArrayList(V).initCapacity(context.allocator, argument_declaration_count); + try argument_list.ensureTotalCapacity(context.allocator, argument_declaration_count); - for (argument_nodes, function.scope.scope.declarations.values()) |arg_ni, argument_declaration| { + const argument_offset = argument_list.items.len; + for (argument_nodes, function.scope.scope.declarations.values()[argument_offset..]) |arg_ni, argument_declaration| { const argument_node = unit.getNode(arg_ni); const arg_type_expect = Type.Expect{ .type = argument_declaration.type, @@ -2646,7 +3699,7 @@ pub const Builder = struct { else => arg_ni, }; const argument_value = try builder.resolveRuntimeValue(unit, context, arg_type_expect, argument_node_index, .right); - list.appendAssumeCapacity(argument_value); + argument_list.appendAssumeCapacity(argument_value); } const instruction = try unit.instructions.append(context.allocator, .{ @@ -2655,7 +3708,7 @@ pub const Builder = struct { .function_definition = function_declaration, }, .function_type = function.type, - .arguments = list.items, + .arguments = argument_list.items, }, }); try builder.appendInstruction(unit, context, instruction); @@ -2667,15 +3720,77 @@ pub const Builder = struct { .type = function_prototype.return_type, }; }, - // .@"comptime" => |ct| switch (ct) { - // .function_definition => |function_definition_index| { - // }, - // else => |t| @panic(@tagName(t)), - // }, .runtime => unreachable, else => unreachable, } + } + fn emitLocalVariableDeclaration(builder: *Builder, unit: *Unit, context: *const Context, token: Token.Index, mutability: Mutability, declaration_type: Type.Index, initialization: V, emit: bool) !void { + assert(builder.current_scope.local); + const identifier = unit.getExpectedTokenBytes(token, .identifier); + logln(.compilation, .identifier, "Analyzing local declaration {s}", .{identifier}); + const identifier_hash = try unit.processIdentifier(context, identifier); + const token_debug_info = builder.getTokenDebugInfo(unit, token); + + const look_in_parent_scopes = true; + if (builder.current_scope.lookupDeclaration(identifier_hash, look_in_parent_scopes)) |lookup| { + _ = lookup; // autofix + std.debug.panic("Identifier '{s}' already declarared on scope", .{identifier}); + } + + const declaration_index = try unit.local_declarations.append(context.allocator, .{ + .declaration = .{ + .scope = builder.current_scope, + .name = identifier_hash, + .type = declaration_type, + .mutability = mutability, + .line = token_debug_info.line, + .column = token_debug_info.column, + .kind = .local, + }, + .init_value = initialization, + }); + + const local_declaration = unit.local_declarations.get(declaration_index); + assert(builder.current_scope.kind == .block); + try builder.current_scope.declarations.putNoClobber(context.allocator, identifier_hash, &local_declaration.declaration); + + if (emit) { + const stack = try unit.instructions.append(context.allocator, .{ + .stack_slot = .{ + .type = declaration_type, + }, + }); + + try builder.appendInstruction(unit, context, stack); + + assert(builder.current_scope.kind == .block); + const local_scope = @fieldParentPtr(Debug.Scope.Local, "scope", builder.current_scope); + try local_scope.local_declaration_map.putNoClobber(context.allocator, local_declaration, stack); + + const debug_declare_local = try unit.instructions.append(context.allocator, .{ + .debug_declare_local_variable = .{ + .variable = local_declaration, + .stack = stack, + }, + }); + + try builder.appendInstruction(unit, context, debug_declare_local); + + const store = try unit.instructions.append(context.allocator, .{ + .store = .{ + .destination = .{ + .value = .{ + .runtime = stack, + }, + .type = declaration_type, + }, + .source = initialization, + }, + }); + + try builder.appendInstruction(unit, context, store); + } } fn resolveBlock(builder: *Builder, unit: *Unit, context: *const Context, node_index: Node.Index) anyerror!Debug.Block.Index { @@ -2692,8 +3807,8 @@ pub const Builder = struct { .local = builder.current_scope.local, .file = builder.current_file, }, - }, - }); + }, + }); const block = unit.blocks.get(block_index); if (builder.current_basic_block != .null) { @@ -2730,15 +3845,6 @@ pub const Builder = struct { // All variables here are local assert(builder.current_scope.local); const expected_identifier_token_index = Token.addInt(statement_node.token, 1); - const identifier = unit.getExpectedTokenBytes(expected_identifier_token_index, .identifier); - logln(.compilation, .identifier, "Analyzing local declaration {s}", .{identifier}); - const identifier_hash = try unit.processIdentifier(context, identifier); - - const look_in_parent_scopes = true; - if (builder.current_scope.lookupDeclaration(identifier_hash, look_in_parent_scopes)) |lookup| { - _ = lookup; // autofix - std.debug.panic("Identifier '{s}' already declarared on scope", .{identifier}); - } const mutability: Mutability = switch (statement_node.id) { .constant_symbol_declaration => .@"const", @@ -2757,7 +3863,7 @@ pub const Builder = struct { const type_node_index = metadata_node.left; assert(metadata_node.right == .null); const type_expect = Type.Expect{ - .type = try builder.resolveType(unit, context, type_node_index), + .type = try builder.resolveType(unit, context, type_node_index), }; break :b type_expect; }, @@ -2770,72 +3876,72 @@ pub const Builder = struct { const declaration_type = switch (type_expect) { .none => initialization.type, .type => |type_index| type_index, + .optional => unreachable, }; - - const declaration_index = try unit.local_declarations.append(context.allocator, .{ - .declaration = .{ - .scope = builder.current_scope, - .name = identifier_hash, - .type = declaration_type, - .mutability = mutability, - .line = token_debug_info.line, - .column = token_debug_info.column, - .kind = .local, - }, - .init_value = initialization, - }); - const local_declaration = unit.local_declarations.get(declaration_index); - assert(builder.current_scope.kind == .block); - try builder.current_scope.declarations.putNoClobber(context.allocator, identifier_hash, &local_declaration.declaration); - - if (emit) { - const stack = try unit.instructions.append(context.allocator, .{ - .stack_slot = .{ - .type = declaration_type, - }, - }); - - try builder.appendInstruction(unit, context, stack); - - try block.scope.local_declaration_map.putNoClobber(context.allocator, local_declaration, stack); - - const debug_declare_local = try unit.instructions.append(context.allocator, .{ - .debug_declare_local_variable = .{ - .variable = local_declaration, - .stack = stack, - }, - }); - - try builder.appendInstruction(unit, context, debug_declare_local); - - const store = try unit.instructions.append(context.allocator, .{ - .store = .{ - .destination = .{ - .value = .{ - .runtime = stack, - }, - .type = declaration_type, - }, - .source = initialization, - }, - }); - - try builder.appendInstruction(unit, context, store); - } + try builder.emitLocalVariableDeclaration(unit, context, expected_identifier_token_index, mutability, declaration_type, initialization, emit); }, .@"return" => { assert(statement_node.left != .null); assert(statement_node.right == .null); const return_value_node_index = statement_node.left; + // if (@intFromEnum(return_value_node_index) == 1355) { + // @breakpoint(); + // } + const return_type = unit.getReturnType(builder.current_function); const return_value = try builder.resolveRuntimeValue(unit, context, Type.Expect{ - .type = unit.getReturnType(builder.current_function), + .type = return_type, }, return_value_node_index, .right); - const ret = try unit.instructions.append(context.allocator, .{ - .ret = return_value, - }); - try builder.appendInstruction(unit, context, ret); + // if (return_value.value == .runtime and @intFromEnum(return_value.value.runtime) == 162) { + // @breakpoint(); + // } + + if (builder.return_block != .null) { + if (builder.return_phi != .null) { + const phi = &unit.instructions.get(builder.return_phi).phi; + try phi.values.append(context.allocator, return_value); + try phi.basic_blocks.append(context.allocator, builder.current_basic_block); + } + + assert(builder.current_basic_block != builder.return_block); + + try builder.jump(unit, context, builder.return_block); + } else if (builder.exit_blocks.items.len > 0) { + builder.return_phi = try unit.instructions.append(context.allocator, .{ + .phi = .{ + .type = return_type, + }, + }); + + builder.return_block = try builder.newBasicBlock(unit, context); + const current_basic_block = builder.current_basic_block; + builder.current_basic_block = builder.return_block; + + try builder.appendInstruction(unit, context, builder.return_phi); + + const phi = &unit.instructions.get(builder.return_phi).phi; + try phi.values.append(context.allocator, return_value); + try phi.basic_blocks.append(context.allocator, current_basic_block); + + const ret = try unit.instructions.append(context.allocator, .{ + .ret = .{ + .value = .{ + .runtime = builder.return_phi, + }, + .type = return_type, + }, + }); + try builder.appendInstruction(unit, context, ret); + + builder.current_basic_block = current_basic_block; + try builder.jump(unit, context, builder.return_block); + } else { + const ret = try unit.instructions.append(context.allocator, .{ + .ret = return_value, + }); + try builder.appendInstruction(unit, context, ret); + } }, .call => { const result = try builder.resolveCall(unit, context, statement_node_index); @@ -2875,6 +3981,98 @@ pub const Builder = struct { const instruction = try unit.instructions.append(context.allocator, .@"unreachable"); try builder.appendInstruction(unit, context, instruction); }, + .@"while" => { + assert(statement_node.left != .null); + assert(statement_node.right != .null); + + const loop_header_block = try builder.newBasicBlock(unit, context); + try builder.jump(unit, context, loop_header_block); + builder.current_basic_block = loop_header_block; + + const condition = try builder.resolveRuntimeValue(unit, context, Type.Expect{ .type = .bool }, statement_node.left, .right); + switch (condition.value) { + .runtime => |condition_instruction| { + const body_block = try builder.newBasicBlock(unit, context); + const exit_block = try builder.newBasicBlock(unit, context); + try builder.branch(unit, context, condition_instruction, body_block, exit_block); + + builder.current_basic_block = body_block; + + const body_value = try builder.resolveRuntimeValue(unit, context, Type.Expect{ .type = .void }, statement_node.right, .right); + _ = body_value; // autofix + // _ = body_value; // autofix + try builder.jump(unit, context, loop_header_block); + + builder.current_basic_block = exit_block; + }, + .@"comptime" => { + unreachable; + }, + else => unreachable, + } + }, + .if_else_payload => { + assert(statement_node.left != .null); + assert(statement_node.right != .null); + const payload_node = unit.getNode(statement_node.right); + assert(payload_node.id == .identifier); + + const if_else_node = unit.getNode(statement_node.left); + assert(if_else_node.id == .if_else); + assert(if_else_node.left != .null); + assert(if_else_node.right != .null); + + const if_node = unit.getNode(if_else_node.left); + assert(if_node.id == .@"if"); + assert(if_node.left != .null); + assert(if_node.right != .null); + + const optional_expression = try builder.resolveRuntimeValue(unit, context, Type.Expect.none, if_node.left, .right); + + const taken_expression_node_index = if_node.right; + const not_taken_expression_node_index = if_else_node.right; + + switch (optional_expression.value) { + .runtime => { + switch (unit.types.get(optional_expression.type).*) { + .@"struct" => |struct_index| { + const struct_type = unit.structs.get(struct_index); + if (struct_type.optional) { + const condition = try unit.instructions.append(context.allocator, .{ + .extract_value = .{ + .expression = optional_expression, + .index = 1, + }, + }); + try builder.appendInstruction(unit, context, condition); + + try builder.resolveBranch(unit, context, Type.Expect{ .type = .void }, condition, taken_expression_node_index, not_taken_expression_node_index, payload_node.token); + } else { + unreachable; + } + }, + // .optional => |optional_element_type_index| { + // }, + else => |t| @panic(@tagName(t)), + } + }, + else => |t| @panic(@tagName(t)), + } + }, + .@"if" => { + assert(statement_node.left != .null); + assert(statement_node.right != .null); + const condition = try builder.resolveRuntimeValue(unit, context, Type.Expect{ .type = .bool }, statement_node.left, .right); + const taken_expression_node_index = statement_node.right; + const not_taken_expression_node_index = .null; + switch (condition.value) { + .@"comptime" => unreachable, + .runtime => |condition_instruction| { + try builder.resolveBranch(unit, context, Type.Expect{ .type = .void }, condition_instruction, taken_expression_node_index, not_taken_expression_node_index, .null); + }, + else => unreachable, + } + }, else => |t| @panic(@tagName(t)), } } @@ -2882,6 +4080,87 @@ pub const Builder = struct { return block_index; } + fn resolveBranch(builder: *Builder, unit: *Unit, context: *const Context, type_expect: Type.Expect, condition: Instruction.Index, taken_node_index: Node.Index, not_taken_node_index: Node.Index, optional_payload_token: Token.Index) !void { + const taken_block = try builder.newBasicBlock(unit, context); + const exit_block = try builder.newBasicBlock(unit, context); + const not_taken_block = if (not_taken_node_index != .null) try builder.newBasicBlock(unit, context) else exit_block; + try builder.exit_blocks.append(context.allocator, exit_block); + try builder.branch(unit, context, condition, taken_block, not_taken_block); + + builder.current_basic_block = taken_block; + + if (optional_payload_token != .null) { + const conditional_instruction = unit.instructions.get(condition); + const optional_expression = conditional_instruction.extract_value.expression; + // TODO: avoid local symbol name collisions + const unwrap = try unit.instructions.append(context.allocator, .{ + .extract_value = .{ + .expression = optional_expression, + .index = 0, + }, + }); + try builder.appendInstruction(unit, context, unwrap); + const emit = true; + const optional_type_index = optional_expression.type; + const optional_type = unit.types.get(optional_type_index); + const optional_struct = unit.structs.get(optional_type.@"struct"); + const optional_payload = unit.struct_fields.get( optional_struct.fields.items[0]); + try builder.emitLocalVariableDeclaration(unit, context, optional_payload_token, .@"const", optional_payload.type, .{ + .value = .{ + .runtime = unwrap, + }, + .type = optional_payload.type, + }, emit); + } + + _ = try builder.resolveRuntimeValue(unit, context, type_expect, taken_node_index, .right); + if (!unit.basic_blocks.get(builder.current_basic_block).terminated) { + try builder.jump(unit, context, exit_block); + } + + if (not_taken_node_index != .null) { + builder.current_basic_block = not_taken_block; + _ = try builder.resolveRuntimeValue(unit, context, type_expect, not_taken_node_index, .right); + if (!unit.basic_blocks.get(builder.current_basic_block).terminated) { + try builder.jump(unit, context, exit_block); + } + } + + + builder.current_basic_block = exit_block; + } + + fn branch(builder: *Builder, unit: *Unit, context: *const Context, condition: Instruction.Index, taken_block: BasicBlock.Index, non_taken_block: BasicBlock.Index) !void { + const br = try unit.instructions.append(context.allocator, .{ + .branch = .{ + .condition = condition, + .from = builder.current_basic_block, + .taken = taken_block, + .not_taken = non_taken_block, + }, + }); + + try builder.appendInstruction(unit, context, br); + + unit.basic_blocks.get(builder.current_basic_block).terminated = true; + unit.basic_blocks.get(taken_block).predecessor = builder.current_basic_block; + unit.basic_blocks.get(non_taken_block).predecessor = builder.current_basic_block; + } + + fn jump(builder: *Builder, unit: *Unit, context: *const Context, new_basic_block: BasicBlock.Index) !void { + const instruction = try unit.instructions.append(context.allocator, .{ + .jump = .{ + .from = builder.current_basic_block, + .to = new_basic_block, + }, + }); + + try builder.appendInstruction(unit, context, instruction); + + unit.basic_blocks.get(builder.current_basic_block).terminated = true; + unit.basic_blocks.get(new_basic_block).predecessor = builder.current_basic_block; + } + fn resolveSwitch(builder: *Builder, unit: *Unit, context: *const Context, type_expect: Type.Expect, node_index: Node.Index) !V { const node = unit.getNode(node_index); assert(node.id == .@"switch"); @@ -2925,7 +4204,7 @@ pub const Builder = struct { const left_node_index = node.left; const left = try builder.resolveRuntimeValue(unit, context, Type.Expect.none, left_node_index, .right); - const result = switch (left.value) { + const result: V = switch (left.value) { .@"comptime" => |ct| switch (ct) { .type => |type_index| b: { const left_type = unit.types.get(type_index); @@ -2949,7 +4228,7 @@ pub const Builder = struct { .@"comptime" = .{ .enum_value = field_index, }, - }, + }, .type = type_index, }; }, @@ -2958,9 +4237,97 @@ pub const Builder = struct { break :b result; }, + .constant_struct => |constant_struct_index| b: { + const constant_struct = unit.constant_structs.get(constant_struct_index); + const type_index = constant_struct.type; + const left_type = unit.types.get(type_index); + const scope = left_type.getScope(unit); + const look_in_parent_scopes = false; + const result = if (scope.lookupDeclaration(identifier_hash, look_in_parent_scopes)) |lookup| blk: { + const global_decl_ref = try builder.referenceGlobalDeclaration(unit, context, lookup.scope, lookup.declaration); + break :blk global_decl_ref; + } else unreachable; + + break :b result; + }, else => |t| @panic(@tagName(t)), }, - .runtime => unreachable, + .runtime => |instruction_index| b: { + _ = instruction_index; + const left_type = unit.types.get(left.type); + switch (left_type.*) { + .@"struct" => |struct_index| { + const struct_type = unit.structs.get(struct_index); + const fields = struct_type.fields.items; + + for (fields, 0..) |field_index, i| { + const field = unit.struct_fields.get(field_index); + if (field.name == identifier_hash) { + const extract_value = try unit.instructions.append(context.allocator, .{ + .extract_value = .{ + .expression = left, + .index = @intCast(i), + }, + }); + + try builder.appendInstruction(unit, context, extract_value); + break :b V{ + .value = .{ + .runtime = extract_value, + }, + .type = field.type, + }; + } + } else { + const scope = left_type.getScope(unit); + const look_in_parent_scopes = false; + if (scope.lookupDeclaration(identifier_hash, look_in_parent_scopes)) |lookup| { + const global_decl_ref = try builder.referenceGlobalDeclaration(unit, context, lookup.scope, lookup.declaration); + break :b global_decl_ref; + } else { + unreachable; + } + } + }, + .@"enum" => { + unreachable; + }, + .slice => |slice| { + const slice_field : enum{ + ptr, + len, + } = if (equal(u8, "ptr", identifier)) .ptr else if (equal(u8, "len", identifier)) .len else unreachable; + const field_type = switch (slice_field) { + .ptr => try unit.getPointerType(context, .{ + .type = slice.type, + .nullable = slice.nullable, + .termination = slice.termination, + .mutability = slice.mutability, + .many = true, + }), + .len => Type.Index.usize, + }; + const field_index = @intFromEnum(slice_field); + + const extract_value = try unit.instructions.append(context.allocator, .{ + .extract_value = .{ + .expression = left, + .index = field_index, + }, + }); + + try builder.appendInstruction(unit, context, extract_value); + + break :b .{ + .value = .{ + .runtime = extract_value, + }, + .type = field_type, + }; + }, + else => |t| @panic(@tagName(t)), + } + }, else => |t| @panic(@tagName(t)), }; @@ -2973,6 +4340,7 @@ pub const Builder = struct { else => |t| @panic(@tagName(t)), } }, + .optional => unreachable, } } @@ -2986,22 +4354,23 @@ pub const Builder = struct { const condition_node_index = if_node.left; const taken_expression_node_index = if_node.right; const not_taken_expression_node_index = node.right; - const not_taken_expression_node = unit.getNode(not_taken_expression_node_index); - _ = not_taken_expression_node; // autofix assert(if_node.id == .@"if"); - const result: V = if (builder.resolveComptimeValue(unit, context, Type.Expect{ - .type = .bool, - }, .{}, condition_node_index)) |comptime_value| switch (comptime_value) { - .bool => |boolean| switch (boolean) { - true => try builder.resolveRuntimeValue(unit, context, type_expect, taken_expression_node_index, .right), - false => try builder.resolveRuntimeValue(unit, context, type_expect, not_taken_expression_node_index, .right), - }, - else => |t| @panic(@tagName(t)), - } else |err| { - std.debug.print("Foo err: {}", .{err}); - try builder.insertDebugCheckPoint(unit, context, if_node.token); - unreachable; + try builder.insertDebugCheckPoint(unit, context, if_node.token); + + const condition = try builder.resolveRuntimeValue(unit, context, Type.Expect{ .type = .bool }, condition_node_index, .right); + const result: V = switch (condition.value) { + .@"comptime" => |ct| switch (ct.bool) { + true => try builder.resolveRuntimeValue(unit, context, type_expect, taken_expression_node_index, .right), + false => try builder.resolveRuntimeValue(unit, context, type_expect, not_taken_expression_node_index, .right), + }, + .runtime => |condition_instruction| { + try builder.resolveBranch(unit, context, type_expect, condition_instruction, taken_expression_node_index, not_taken_expression_node_index, .null); + // TODO WARN SAFETY: + return undefined; + }, + else => unreachable, }; + return result; } }; @@ -3030,6 +4399,7 @@ pub const Unit = struct { // values: Value.List = .{}, types: Type.List = .{}, structs: Struct.List = .{}, + struct_fields: Struct.Field.List = .{}, enums: Enum.List = .{}, enum_fields: Enum.Field.List = .{}, function_definitions: Function.Definition.List = .{}, @@ -3043,6 +4413,7 @@ pub const Unit = struct { inline_assembly: InlineAssembly.List = .{}, instructions: Instruction.List = .{}, basic_blocks: BasicBlock.List = .{}, + constant_structs: V.Comptime.ConstantStruct.List = .{}, // global_variables: GlobalVariable.List = .{}, // global_variable_map: AutoHashMap(Declaration.Index, GlobalVariable.Index) = .{}, token_buffer: Token.Buffer = .{}, @@ -3050,10 +4421,18 @@ pub const Unit = struct { file_token_offsets: AutoArrayHashMap(Token.Range, Debug.File.Index) = .{}, file_map: StringArrayHashMap(Debug.File.Index) = .{}, identifiers: StringKeyMap([]const u8) = .{}, + string_literal_values: AutoHashMap(u32, [:0]const u8) = .{}, + string_literal_globals: AutoHashMap(u32, *Debug.Declaration.Global) = .{}, + + optionals: AutoHashMap(Type.Index, Type.Index) = .{}, pointers: AutoHashMap(Type.Pointer, Type.Index) = .{}, + slices: AutoHashMap(Type.Slice, Type.Index) = .{}, + arrays: AutoHashMap(Type.Array, Type.Index) = .{}, + integers: AutoHashMap(Type.Integer, Type.Index) = .{}, code_to_emit: ArrayList(*Debug.Declaration.Global) = .{}, data_to_emit: ArrayList(*Debug.Declaration.Global) = .{}, + type_declarations: AutoHashMap(Type.Index, *Debug.Declaration.Global) = .{}, // function_declaration_map: AutoHashMap(Function.Definition.Index, Declaration.Index) = .{}, // type_declaration_map: AutoHashMap(Type.Index, Declaration.Index) = .{}, // TODO @@ -3071,6 +4450,86 @@ pub const Unit = struct { main_package: *Package = undefined, descriptor: Descriptor, + fn dumpFunctionDefinition(unit: *Unit, function_definition_index: Function.Definition.Index) void { + const function_definition = unit.function_definitions.get(function_definition_index); + + for (function_definition.basic_blocks.items) |basic_block_index| { + const basic_block = unit.basic_blocks.get(basic_block_index); + logln(.compilation, .ir, "[#{}]:", .{BasicBlock.unwrap(basic_block_index)}); + + for (basic_block.instructions.items) |instruction_index| { + const instruction = unit.instructions.get(instruction_index); + log(.compilation, .ir, " %{}: {s} ", .{Instruction.unwrap(instruction_index), @tagName(instruction.*)}); + + switch (instruction.*) { + .load => |load| { + switch (load.value.value) { + .@"comptime" => unreachable, + .runtime => |ii| { + log(.compilation, .ir, "%{}", .{@intFromEnum(ii)}); + }, + else => unreachable, + } + }, + .push_scope => |push_scope| { + log(.compilation, .ir, "0x{x} -> 0x{x}", .{@as(u24, @truncate(@intFromPtr(push_scope.old))), @as(u24, @truncate(@intFromPtr(push_scope.new)))}); + }, + .pop_scope => |pop_scope| { + log(.compilation, .ir, "0x{x} <- 0x{x}", .{@as(u24, @truncate(@intFromPtr(pop_scope.new))), @as(u24, @truncate(@intFromPtr(pop_scope.old)))}); + }, + .debug_checkpoint => |checkpoint| { + log(.compilation, .ir, "{}, {}", .{checkpoint.line, checkpoint.column}); + }, + .argument_declaration => |arg|{ + log(.compilation, .ir, "\"{s}\"", .{unit.getIdentifier(arg.declaration.name)}); + }, + .cast => |cast| { + log(.compilation, .ir, "{s}", .{@tagName(cast.id)}); + }, + .jump => |jump| { + log(.compilation, .ir, "[#{}]", .{BasicBlock.unwrap(jump.to)}); + }, + .branch => |branch| { + log(.compilation, .ir, "bool %{}, [#{}, #{}]", .{Instruction.unwrap(branch.condition), BasicBlock.unwrap(branch.taken), BasicBlock.unwrap(branch.not_taken)}); + }, + .phi => |phi| { + for (phi.values.items, phi.basic_blocks.items) |value, bb| { + log(.compilation, .ir, "(%{}, #{}), ", .{switch (value.value) { + .@"comptime" => 0xffff_ffff, + .runtime => |ii| @intFromEnum(ii), + else => unreachable, + }, @intFromEnum(bb)}); + } + }, + .integer_compare => |compare| { + log(.compilation, .ir, "{s} ", .{@tagName(compare.id)}); + switch (compare.left.value) { + .@"comptime" => { + log(.compilation, .ir, "$comptime, ", .{}); + }, + .runtime => |ii| { + log(.compilation, .ir, "%{}, ", .{@intFromEnum(ii)}); + }, + else => unreachable, + } + + switch (compare.right.value) { + .@"comptime" => { + log(.compilation, .ir, "$comptime", .{}); + }, + .runtime => |ii| { + log(.compilation, .ir, "%{}", .{@intFromEnum(ii)}); + }, + else => unreachable, + } + }, + else => {} + } + logln(.compilation, .ir, "", .{}); + } + } + } + fn getReturnType(unit: *Unit, function_index: Function.Definition.Index) Type.Index{ const function = unit.function_definitions.get(function_index); const function_type = unit.types.get(function.type); @@ -3206,11 +4665,49 @@ pub const Unit = struct { return bytes; } - fn tokenStringLiteral(unit: *Unit, token_index: Token.Index) []const u8 { - const bytes = unit.getExpectedTokenBytes(token_index, .string_literal); - // Eat double quotes - const string_literal_bytes = bytes[1..][0 .. bytes.len - 2]; - return string_literal_bytes; + fn getOptionalType(unit: *Unit, context: *const Context, element_type: Type.Index) !Type.Index{ + if (unit.optionals.get(element_type)) |optional| { + return optional; + } else { + const optional_struct_index = try unit.structs.append(context.allocator, .{ + // TODO: this is going to bite my ass + .scope = .{ + .scope = .{ + .file = @enumFromInt(0), + .line = 0, + .column = 0, + // can this trick the compiler? + .kind = .block, + .local = true, + .level = 0, + }, + }, + .backing_type = .null, + .optional = true, + .type = .null, + }); + const optional_struct = unit.structs.get(optional_struct_index); + try optional_struct.fields.ensureTotalCapacity(context.allocator, 2); + const types = [_]Type.Index{element_type, .bool}; + const names = [_][]const u8{"payload", "is_valid"}; + for (types, names) |t, name| { + const field = try unit.struct_fields.append(context.allocator, .{ + .name = try unit.processIdentifier(context, name), + .type = t, + .default_value = null, + }); + + optional_struct.fields.appendAssumeCapacity(field); + } + + const optional_type_index = try unit.types.append(context.allocator, .{ + .@"struct" = optional_struct_index, + }); + + try unit.optionals.putNoClobber(context.allocator, element_type, optional_type_index); + + return optional_type_index; + } } fn getPointerType(unit: *Unit, context: *const Context, pointer: Type.Pointer) !Type.Index { @@ -3226,9 +4723,33 @@ pub const Unit = struct { } } + fn getSliceType(unit: *Unit, context: *const Context, slice: Type.Slice) !Type.Index{ + if (unit.slices.get(slice)) |existing_type_index| { + return existing_type_index; + } else { + const type_index = try unit.types.append(context.allocator, .{ + .slice = slice, + }); + try unit.slices.putNoClobber(context.allocator, slice, type_index); + + return type_index; + } + } + + fn getArrayType(unit: *Unit, context: *const Context, array: Type.Array) !Type.Index { + if (unit.arrays.get(array)) |array_type| { + return array_type; + } else { + const array_type = try unit.types.append(context.allocator, .{ + .array = array, + }); + try unit.arrays.putNoClobber(context.allocator, array, array_type); + + return array_type; + } + } + fn getIntegerType(unit: *Unit, context: *const Context, integer: Type.Integer) !Type.Index { - _ = unit; // autofix - _ = context; // autofix const existing_type_index: Type.Index = switch (integer.bit_count) { 8 => switch (integer.signedness) { .unsigned => .u8, @@ -3246,7 +4767,17 @@ pub const Unit = struct { .unsigned => .u64, .signed => .s64, }, - else => unreachable, + else => { + if (unit.integers.get(integer)) |type_index| { + return type_index; + } else { + const type_index = try unit.types.append(context.allocator, .{ + .integer = integer, + }); + try unit.integers.putNoClobber(context.allocator, integer, type_index); + return type_index; + } + }, }; return existing_type_index; @@ -3257,6 +4788,35 @@ pub const Unit = struct { return lookup_result.key; } + fn fixupStringLiteral(unit: *Unit, context: *const Context, token_index: Token.Index) ![:0]const u8 { + const bytes = unit.getExpectedTokenBytes(token_index, .string_literal); + // Eat double quotes + const string_literal_bytes = bytes[1..][0 .. bytes.len - 2]; + var fixed_string = try ArrayList(u8).initCapacity(context.allocator, string_literal_bytes.len + 1); + var i: usize = 0; + + while (i < string_literal_bytes.len) : (i += 1) { + const ch = string_literal_bytes[i]; + switch (ch) { + '\\' => { + i += 1; + const next_ch = string_literal_bytes[i]; + switch (next_ch) { + 'n' => fixed_string.appendAssumeCapacity('\n'), + else => unreachable, + } + }, + else => fixed_string.appendAssumeCapacity(ch), + } + } + + fixed_string.appendAssumeCapacity(0); + + const string = fixed_string.items[0 .. fixed_string.items.len - 1 :0]; + + return string; + } + pub fn getIdentifier(unit: *Unit, hash: u32) []const u8 { return unit.identifiers.getValue(hash).?; } @@ -3276,6 +4836,13 @@ pub const Unit = struct { } try builder.analyzePackage(unit, context, main_package); + + for (unit.code_to_emit.items) |function_declaration| { + const function_definition_index = function_declaration.initial_value.function_definition; + logln(.compilation, .ir, "Function #{} {s}", .{Function.Definition.unwrap(function_definition_index), unit.getIdentifier(function_declaration.declaration.name) }); + + unit.dumpFunctionDefinition(function_definition_index); + } } pub fn generateAbstractSyntaxTreeForFile(unit: *Unit, context: *const Context, file_index: Debug.File.Index) !void { @@ -3549,7 +5116,6 @@ const MemberType = enum { comptime_block, }; -// TODO: switch to packed struct when speed is important pub const Token = struct { line: u32, offset: u32, diff --git a/bootstrap/backend/llvm.cpp b/bootstrap/backend/llvm.cpp index 63f9bf0..730a4b7 100644 --- a/bootstrap/backend/llvm.cpp +++ b/bootstrap/backend/llvm.cpp @@ -288,11 +288,16 @@ extern "C" BasicBlock* NativityLLVMCreateBasicBlock(LLVMContext& context, const return basic_block; } -extern "C" PHINode* NativityLLVMCreatePhiNode(Type* type, unsigned reserved_value_count, const char* name_ptr, size_t name_len, BasicBlock* basic_block) +extern "C" PHINode* NativityLLVMBuilderCreatePhi(IRBuilder<> builder, Type* type, unsigned reserved_value_count, const char* name_ptr, size_t name_len) { auto name = StringRef(name_ptr, name_len); - auto* phi_node = PHINode::Create(type, reserved_value_count, name, basic_block); - return phi_node; + auto* phi = builder.CreatePHI(type, reserved_value_count, name); + return phi; +} + +extern "C" void NativityLLVMPhiAddIncoming(PHINode& node, Value* value, BasicBlock* basic_block) +{ + node.addIncoming(value, basic_block); } extern "C" void NativityLLVMBasicBlockRemoveFromParent(BasicBlock* basic_block) @@ -317,6 +322,12 @@ extern "C" Type* NativityLLVMValueGetType(Value* value) return type; } +extern "C" PoisonValue* NativityLLVMGetPoisonValue(Type* type) +{ + auto* poison_value = PoisonValue::get(type); + return poison_value; +} + extern "C" void NativityLLVMFunctionGetArguments(Function& function, Argument** argument_ptr, size_t* argument_count) { auto actual_argument_count = function.arg_size(); @@ -588,21 +599,36 @@ extern "C" ConstantInt* NativityLLVMContextGetConstantInt(LLVMContext& context, return constant_int; } -extern "C" Constant* NativityLLVMContextGetConstString(LLVMContext& context, const char* string_ptr, size_t string_len, bool null_terminate) +extern "C" Constant* NativityLLVMContextGetConstantString(LLVMContext& context, const char* string_ptr, size_t string_len, bool null_terminate) { auto string = StringRef(string_ptr, string_len); auto* constant = ConstantDataArray::getString(context, string, null_terminate); return constant; } -extern "C" Constant* NativityLLVMContextGetConstArray(ArrayType* array_type, Constant** value_ptr, size_t value_count) +extern "C" Constant* NativityLLVMGetConstantArray(ArrayType* array_type, Constant** value_ptr, size_t value_count) { auto values = ArrayRef(value_ptr, value_count); auto* constant_array = ConstantArray::get(array_type, values); return constant_array; } -extern "C" Constant* NativityLLVMContextCreateGlobalStringPointer(IRBuilder<>& builder, const char* string_ptr, size_t string_len, const char* name_ptr, size_t name_len, unsigned address_space, Module* module) +extern "C" Constant* NativityLLVMGetConstantStruct(StructType* struct_type, Constant** constant_ptr, size_t constant_len) +{ + auto values = ArrayRef(constant_ptr, constant_len); + auto* constant_struct = ConstantStruct::get(struct_type, values); + return constant_struct; +} + +extern "C" GlobalVariable* NativityLLVMBuilderCreateGlobalString(IRBuilder<>& builder, const char* string_ptr, size_t string_len, const char* name_ptr, size_t name_len, unsigned address_space, Module* module) +{ + auto string = StringRef(string_ptr, string_len); + auto name = StringRef(name_ptr, name_len); + auto* string_global_variable = builder.CreateGlobalString(string, name, address_space, module); + return string_global_variable; +} + +extern "C" Constant* NativityLLVMBuilderCreateGlobalStringPointer(IRBuilder<>& builder, const char* string_ptr, size_t string_len, const char* name_ptr, size_t name_len, unsigned address_space, Module* module) { auto string = StringRef(string_ptr, string_len); auto name = StringRef(name_ptr, name_len); @@ -770,13 +796,6 @@ extern "C" void NativityLLVMCallSetCallingConvention(CallBase& call_instruction, call_instruction.setCallingConv(calling_convention); } -extern "C" Constant* NativityLLVMGetStruct(StructType* struct_type, Constant** constants_ptr, size_t constant_count) -{ - auto constants = ArrayRef(constants_ptr, constant_count); - auto* named_struct = ConstantStruct::get(struct_type, constants); - return named_struct; -} - namespace lld { namespace coff { bool link(llvm::ArrayRef args, llvm::raw_ostream &stdoutOS, diff --git a/bootstrap/backend/llvm.zig b/bootstrap/backend/llvm.zig index a7d03c0..a87c26b 100644 --- a/bootstrap/backend/llvm.zig +++ b/bootstrap/backend/llvm.zig @@ -35,6 +35,7 @@ pub const LLVM = struct { function_definition_map: AutoArrayHashMap(*Compilation.Debug.Declaration.Global, *LLVM.Value.Function) = .{}, llvm_instruction_map: AutoHashMap(Compilation.Instruction.Index, *LLVM.Value) = .{}, llvm_value_map: AutoArrayHashMap(Compilation.V, *LLVM.Value) = .{}, + llvm_block_map: AutoHashMap(Compilation.BasicBlock.Index, *LLVM.Value.BasicBlock) = .{}, global_variable_map: AutoArrayHashMap(*Compilation.Debug.Declaration.Global, *LLVM.Value.Constant.GlobalVariable) = .{}, scope_map: AutoHashMap(*Compilation.Debug.Scope, *LLVM.DebugInfo.Scope) = .{}, pointer_type: ?*LLVM.Type.Pointer = null, @@ -122,7 +123,9 @@ pub const LLVM = struct { const createSRem = bindings.NativityLLVMBuilderCreateSRem; const createExtractValue = bindings.NativityLLVMBuilderCreateExtractValue; const createInsertValue = bindings.NativityLLVMBuilderCreateInsertValue; - const createGlobalStringPointer = bindings.NativityLLVMContextCreateGlobalStringPointer; + const createGlobalString = bindings.NativityLLVMBuilderCreateGlobalString; + const createGlobalStringPointer = bindings.NativityLLVMBuilderCreateGlobalStringPointer; + const createPhi = bindings.NativityLLVMBuilderCreatePhi; const getInsertBlock = bindings.NativityLLVMBuilderGetInsertBlock; const isCurrentBlockTerminated = bindings.NativityLLVMBuilderIsCurrentBlockTerminated; @@ -480,8 +483,7 @@ pub const LLVM = struct { }; pub const Struct = opaque { - const instantiate = bindings.NativityLLVMGetStruct; - const instantiateConstant = bindings.NativityLLVMConstantStruct; + const getConstant = bindings.NativityLLVMGetConstantStruct; fn toType(integer: *@This()) *Type { return @ptrCast(integer); } @@ -497,7 +499,8 @@ pub const LLVM = struct { array, }; - const getUndefined = bindings.NativityLLVMGetUndefined; + // const getUndefined = bindings.NativityLLVMGetUndefined; + const getPoison = bindings.NativityLLVMGetPoisonValue; }; pub const Value = opaque { @@ -507,7 +510,7 @@ pub const LLVM = struct { const toFunction = bindings.NativityLLVMValueToFunction; const toAlloca = bindings.NativityLLVMValueToAlloca; - const IntrinsicID = enum(u32) { + pub const IntrinsicID = enum(u32) { none = 0, _, }; @@ -813,7 +816,8 @@ pub const LLVM = struct { fn toValue(this: *@This()) *Value { return @ptrCast(this); } - const Kind = enum(c_uint) { + + pub const Kind = enum(c_uint) { eq = 32, // equal ne = 33, // not equal ugt = 34, // unsigned greater than @@ -834,6 +838,8 @@ pub const LLVM = struct { }; pub const PhiNode = opaque { + pub const addIncoming = bindings.NativityLLVMPhiAddIncoming; + fn toValue(this: *@This()) *Value { return @ptrCast(this); } @@ -912,6 +918,15 @@ pub const LLVM = struct { } }; + pub const Poison = opaque{ + fn toConstant(this: *@This()) *Constant { + return @ptrCast(this); + } + fn toValue(this: *@This()) *Value { + return @ptrCast(this); + } + }; + fn toValue(this: *@This()) *Value { return @ptrCast(this); } @@ -952,7 +967,8 @@ pub const LLVM = struct { for (sema_function_prototype.argument_types) |argument_type_index| { switch (unit.types.get(argument_type_index).*) { - .integer, .pointer, .@"enum" => try parameter_types.append(context.allocator, try llvm.getType(unit, context, argument_type_index)), + // TODO: ABI + .integer, .pointer, .@"enum", .@"struct", .slice => try parameter_types.append(context.allocator, try llvm.getType(unit, context, argument_type_index)), // .slice => |slice| { // const pointer_type = try llvm.getType(llvm.sema.map.pointers.get(.{ // .many = true, @@ -1011,67 +1027,59 @@ pub const LLVM = struct { }, .@"enum" => |enum_index| blk: { const enum_type = unit.enums.get(enum_index); - // switch (enum_type.backing_type.invalid) { - // true => { - const field_count = enum_type.fields.items.len; - const bit_count = @bitSizeOf(@TypeOf(field_count)) - @clz(field_count); - const real_bit_count: u32 = if (bit_count <= 8) 8 else if (bit_count <= 16) 16 else if (bit_count <= 32) 32 else if (bit_count <= 64) 64 else unreachable; - const llvm_integer_type = llvm.context.getIntegerType(real_bit_count) orelse return Type.Error.integer; - break :blk llvm_integer_type.toType(); - // }, - // false => break :blk try llvm.getType(enum_type.backing_type), - // } + const field_count = enum_type.fields.items.len; + const bit_count = @bitSizeOf(@TypeOf(field_count)) - @clz(field_count); + // const real_bit_count: u32 = if (bit_count <= 8) 8 else if (bit_count <= 16) 16 else if (bit_count <= 32) 32 else if (bit_count <= 64) 64 else unreachable; + const llvm_integer_type = llvm.context.getIntegerType(bit_count) orelse return Type.Error.integer; + break :blk llvm_integer_type.toType(); }, - // .slice => |slice| blk: { - // const sema_slice_pointer = Compilation.Type.Pointer{ - // .element_type = slice.element_type, - // .many = true, - // .@"const" = slice.@"const", - // .termination = slice.termination, - // }; - // const sema_pointer_type = llvm.sema.map.pointers.get(sema_slice_pointer).?; - // const llvm_pointer_type = try llvm.getType(sema_pointer_type); - // const llvm_usize_type = try llvm.getType(Compilation.Type.usize); - // const slice_types = [_]*Type{ llvm_pointer_type, llvm_usize_type }; - // const name = [_]u8{}; - // const is_packed = false; - // const struct_type = llvm.context.createStructType(&slice_types, slice_types.len, &name, name.len, is_packed) orelse return Type.Error.@"struct"; - // break :blk struct_type.toType(); - // }, - // .@"struct" => |struct_type_index| blk: { - // const sema_struct_type = llvm.sema.types.structs.get(struct_type_index); - // switch (sema_struct_type.backing_type.invalid) { - // true => { - // var field_type_list = try ArrayList(*LLVM.Type).initCapacity(context.allocator, sema_struct_type.fields.items.len); - // for (sema_struct_type.fields.items) |sema_field_index| { - // const sema_field = llvm.sema.types.container_fields.get(sema_field_index); - // const llvm_type = try llvm.getType(sema_field.type); - // field_type_list.appendAssumeCapacity(llvm_type); - // } - // - // const struct_name: []const u8 = if (llvm.sema.map.types.get(type_index)) |declaration_index| b: { - // const declaration = llvm.sema.values.declarations.get(declaration_index); - // const name = llvm.sema.getName(declaration.name).?; - // break :b name; - // } else "anonymous_struct"; - // const is_packed = true; - // const struct_type = llvm.context.createStructType(field_type_list.items.ptr, field_type_list.items.len, struct_name.ptr, struct_name.len, is_packed) orelse return Type.Error.@"struct"; - // - // break :blk struct_type.toType(); - // }, - // else => |b| @panic(if (b) "true" else "false"), - // } - // }, - // .optional => |optional| blk: { - // switch (llvm.sema.types.array.get(optional.element_type).*) { + .slice => |slice| blk: { + const sema_slice_pointer = Compilation.Type.Pointer{ + .type = slice.type, + .many = true, + .mutability = slice.mutability, + .termination = slice.termination, + .nullable = slice.nullable, + }; + const sema_pointer_type = unit.pointers.get(sema_slice_pointer).?; + const llvm_pointer_type = try llvm.getType(unit, context, sema_pointer_type); + const llvm_usize_type = try llvm.getType(unit, context, .usize); + const slice_types = [_]*Type{ llvm_pointer_type, llvm_usize_type }; + const name = [_]u8{}; + const is_packed = false; + const struct_type = llvm.context.createStructType(&slice_types, slice_types.len, &name, name.len, is_packed) orelse return Type.Error.@"struct"; + break :blk struct_type.toType(); + }, + .@"struct" => |struct_type_index| blk: { + const sema_struct_type = unit.structs.get(struct_type_index); + switch (sema_struct_type.backing_type) { + else => @panic(@tagName(.null)), + .null => { + var field_type_list = try ArrayList(*LLVM.Type).initCapacity(context.allocator, sema_struct_type.fields.items.len); + for (sema_struct_type.fields.items) |sema_field_index| { + const sema_field = unit.struct_fields.get(sema_field_index); + const llvm_type = try llvm.getType(unit, context, sema_field.type); + field_type_list.appendAssumeCapacity(llvm_type); + } + + // TODO: + const name = try llvm.renderTypeName(unit, context, type_index); + const is_packed = false; + const struct_type = llvm.context.createStructType(field_type_list.items.ptr, field_type_list.items.len, name.ptr, name.len, is_packed) orelse return Type.Error.@"struct"; + + break :blk struct_type.toType(); + }, + } + }, + // .optional => |optional_element_type| blk: { + // switch (unit.types.get(optional_element_type).*) { // .pointer => |pointer| { // _ = pointer; - // - // unreachable; + // @panic("super unreachable"); // }, // else => { - // const element_type = try llvm.getType(optional.element_type); - // const selector_type = try llvm.getType(Compilation.Type.boolean); + // const element_type = try llvm.getType(unit, context, optional_element_type); + // const selector_type = try llvm.getType(unit, context, .bool); // const optional_types = [2]*LLVM.Type{ element_type, selector_type }; // const name = "optional_type"; // const is_packed = false; @@ -1080,11 +1088,12 @@ pub const LLVM = struct { // }, // } // }, - // .array => |array| blk: { - // const element_type = try llvm.getType(array.element_type); - // const array_type = LLVM.Type.Array.get(element_type, array.element_count) orelse return Type.Error.array; - // break :blk array_type.toType(); - // }, + .array => |array| blk: { + if (true) unreachable; + const element_type = try llvm.getType(unit, context, array.type); + const array_type = LLVM.Type.Array.get(element_type, array.element_count) orelse return Type.Error.array; + break :blk array_type.toType(); + }, else => |t| @panic(@tagName(t)), }; @@ -1821,169 +1830,6 @@ pub const LLVM = struct { llvm.builder.setCurrentDebugLocation(llvm.context, sema_statement.line + 1, sema_statement.column + 1, llvm.scope, llvm.function); switch (sema_statement_value.*) { - .declaration => |sema_declaration_index| { - _ = try llvm.emitDeclaration(sema_declaration_index, null); - }, - .@"return" => |return_index| { - const return_expression = llvm.sema.values.returns.get(return_index); - const sema_value_return_type = llvm.sema.values.array.get(return_expression.value).getType(llvm.sema); - const sema_function = llvm.sema.types.function_definitions.get(llvm.sema_function); - const sema_function_prototype = llvm.sema.types.function_prototypes.get(llvm.sema.types.array.get(sema_function.prototype).function); - const sema_return_type = sema_function_prototype.return_type; - assert(sema_value_return_type.eq(sema_return_type)); - const return_value = try llvm.emitValue(return_expression.value, context); - - // if (!llvm.inside_branch) { - const function_return_type = llvm.function.getReturnType() orelse unreachable; - _ = function_return_type; - const ret = llvm.builder.createRet(return_value) orelse return LLVM.Value.Instruction.Error.ret; - _ = ret; - // } else { - // if (llvm.return_phi_node) |return_phi_node| { - // _ = return_phi_node; - // - // unreachable; - // } else { - // const phi_node_count_hint = 2; - // const insert_basic_block = null; - // _ = insert_basic_block; - // const phi_node = LLVM.newPhiNode(return_type, phi_node_count_hint, "phi", "phi".len, null) orelse unreachable; - // const go_to_exit = llvm.builder.createBranch(llvm.exit_block) orelse unreachable; - // _ = go_to_exit; - // llvm.return_phi_node = phi_node; - // } - // } - }, - .intrinsic => |intrinsic_index| { - _ = try llvm.emitIntrinsic(intrinsic_index, context); - }, - .@"unreachable" => { - const unreachable_instruction = llvm.builder.createUnreachable() orelse return LLVM.Value.Instruction.Error.@"unreachable"; - _ = unreachable_instruction; - }, - .assign => |assignment_index| { - const assignment = llvm.sema.values.assignments.get(assignment_index); - const right = try llvm.emitValue(assignment.source, context); - const pointer = try llvm.emitLValue(assignment.destination, context); - const value: *LLVM.Value = if (assignment.operation) |operation| switch (operation) { - .add, .sub, .mul, .div => blk: { - const is_volatile = false; - const name = "compound_assign"; - const destination_type = try llvm.getType(llvm.sema.values.array.get(assignment.destination).getType(llvm.sema)); - const load = llvm.builder.createLoad(destination_type, pointer, is_volatile, name, name.len) orelse return LLVM.Value.Instruction.Error.load; - const right_value = llvm.sema.values.array.get(assignment.source); - const right_type = llvm.sema.types.array.get(right_value.getType(llvm.sema)); - - break :blk switch (right_type.*) { - .integer => |integer_type| switch (operation) { - .add, .sub, .mul => try llvm.arithmeticIntegerBinaryOperation(load.toValue(), right, operation, integer_type, "compound_assign"), - .div => b: { - const is_exact = false; - const result = switch (integer_type.signedness) { - .unsigned => llvm.builder.createUDiv(load.toValue(), right, "udiv", "udiv".len, is_exact) orelse return LLVM.Value.Instruction.Error.udiv, - .signed => llvm.builder.createSDiv(load.toValue(), right, "sdiv", "sdiv".len, is_exact) orelse return LLVM.Value.Instruction.Error.sdiv, - }; - - break :b result; - }, - else => |t| @panic(@tagName(t)), - }, - else => |t| @panic(@tagName(t)), - }; - }, - else => |t| @panic(@tagName(t)), - } else right; - - const is_volatile = false; - const store = llvm.builder.createStore(value, pointer, is_volatile) orelse return LLVM.Value.Instruction.Error.store; - _ = store; - }, - .call => |call_index| { - assert(context == .local); - _ = try llvm.emitCall(call_index, context); - }, - .assembly_block => |assembly_block_index| { - const assembly_block = llvm.sema.values.assembly_blocks.get(assembly_block_index); - - var assembly_statements = ArrayList(u8){}; - var constraints = ArrayList(u8){}; - var operand_values = ArrayList(*LLVM.Value){}; - var operand_types = ArrayList(*LLVM.Type){}; - - switch (llvm.sema.descriptor.target.cpu.arch) { - .x86_64 => { - for (assembly_block.instructions) |instruction_index| { - const instruction = llvm.sema.values.assembly_instructions.get(instruction_index); - const instruction_id: Compilation.Assembly.x86_64.Instruction = @enumFromInt(instruction.id); - - try assembly_statements.appendSlice(context.allocator, switch (instruction_id) { - .xor => "xorl", - .mov => "movq", - .@"and" => "andq", - .call => "callq", - }); - try assembly_statements.append(context.allocator, ' '); - - if (instruction.operands.len > 0) { - var reverse_operand_iterator = std.mem.reverseIterator(instruction.operands); - - while (reverse_operand_iterator.next()) |operand| { - switch (operand) { - .register => |register_value| { - const register: Compilation.Assembly.x86_64.Register = @enumFromInt(register_value); - try assembly_statements.append(context.allocator, '%'); - try assembly_statements.appendSlice(context.allocator, @tagName(register)); - }, - .number_literal => |literal| { - try assembly_statements.writer(context.allocator).print("$$0x{x}", .{literal}); - }, - .value_index => |value_index| { - switch (llvm.sema.values.array.get(value_index).*) { - .function_definition => { - const value = try llvm.emitValue(value_index, context); - try assembly_statements.writer(context.allocator).print("${{{}:P}}", .{operand_values.items.len}); - try operand_values.append(context.allocator, value); - try constraints.append(context.allocator, 'X'); - const value_type = value.getType(); - try operand_types.append(context.allocator, value_type); - }, - else => |t| @panic(@tagName(t)), - } - // try assembly_statements.writer(context.allocator).print("%{}", .{operand_values.items.len}); - }, - } - - try assembly_statements.appendSlice(context.allocator, ", "); - } - - _ = assembly_statements.pop(); - _ = assembly_statements.pop(); - } - - try assembly_statements.appendSlice(context.allocator, "\n\t"); - } - - try constraints.appendSlice(context.allocator, ",~{dirflag},~{fpsr},~{flags}"); - }, - else => |t| @panic(@tagName(t)), - } - - const is_var_args = false; - const function_type = LLVM.Context.getFunctionType(try llvm.getType(Compilation.Type.void), operand_types.items.ptr, operand_types.items.len, is_var_args) orelse unreachable; - const has_side_effects = true; - const is_align_stack = true; - const dialect = LLVM.Value.InlineAssembly.Dialect.@"at&t"; - const can_throw = false; - - const inline_assembly = LLVM.Value.InlineAssembly.get(function_type, assembly_statements.items.ptr, assembly_statements.items.len, constraints.items.ptr, constraints.items.len, has_side_effects, is_align_stack, dialect, can_throw) orelse return LLVM.Value.Error.inline_assembly; - const call = llvm.builder.createCall(function_type, inline_assembly.toValue(), operand_values.items.ptr, operand_values.items.len, "", "".len, null) orelse return LLVM.Value.Instruction.Error.call; - _ = call; - }, - .block => |block_index| { - assert(context == .local); - const emit_arguments = false; - try llvm.emitBlock(block_index, context, emit_arguments); - }, .branch => |branch_index| { const branch = llvm.sema.values.branches.get(branch_index); const branch_type = llvm.sema.values.array.get(branch.expression).getType(llvm.sema); @@ -2225,7 +2071,7 @@ pub const LLVM = struct { .unsigned => "llvm.umin", .signed => "llvm.smin", }; - const intrinsic_id = lookupIntrinsic(intrinsic_name.ptr, intrinsic_name.len); + const intrinsic_id = LLVM.lookupIntrinsic(intrinsic_name.ptr, intrinsic_name.len); assert(intrinsic_id != .none); const left_type = llvm.sema.values.array.get(sema_values.left).getType(llvm.sema); @@ -2403,70 +2249,84 @@ pub const LLVM = struct { if (llvm.type_name_map.get(sema_type_index)) |typename| { return typename; } else { - const sema_type = unit.types.get(sema_type_index); - const result: []const u8 = switch (sema_type.*) { - .integer => |integer| b: { - const signedness_char: u8 = switch (integer.signedness) { - .signed => 's', - .unsigned => 'u', - }; - const name = try std.fmt.allocPrint(context.allocator, "{c}{}", .{ signedness_char, integer.bit_count }); - break :b name; - }, - .bool => "bool", - .pointer => |pointer| b: { - var name = ArrayList(u8){}; - try name.appendSlice(context.allocator, "&"); - if (pointer.mutability == .@"const") { - try name.appendSlice(context.allocator, "const"); - } - try name.appendSlice(context.allocator, " "); - const element_type_name = try llvm.renderTypeName(unit, context, pointer.type); - try name.appendSlice(context.allocator, element_type_name); - break :b name.items; - }, - .@"struct" => b: { - // TODO: - break :b "anon1"; - }, - // .@"enum", - // .@"struct", - // => b: { - // if (unit.type_declaration_map.get(sema_type_index)) |type_declaration_index| { - // const declaration = unit.declarations.get(type_declaration_index); - // const name = unit.getIdentifier(declaration.name); - // break :b name; - // } else { - // unreachable; - // } - // }, - // .optional => |optional| b: { - // var name = ArrayList(u8){}; - // const element_type_name = try llvm.renderTypeName(optional.element_type); - // try name.writer(context.allocator).print("?{s}", .{element_type_name}); - // break :b name.items; - // }, - // .array => |array| b: { - // var name = ArrayList(u8){}; - // const element_type_name = try llvm.renderTypeName(array.element_type); - // try name.writer(context.allocator).print("[{}]{s}", .{ array.element_count, element_type_name }); - // break :b name.items; - // }, - // .slice => |slice| b: { - // var name = ArrayList(u8){}; - // try name.appendSlice(context.allocator, "[] "); - // if (slice.@"const") { - // try name.appendSlice(context.allocator, "const "); - // } - // const element_type_name = try llvm.renderTypeName(slice.element_type); - // try name.appendSlice(context.allocator, element_type_name); - // break :b name.items; - // }, - else => |t| @panic(@tagName(t)), - }; + if (unit.type_declarations.get(sema_type_index)) |global_declaration| { + return unit.getIdentifier(global_declaration.declaration.name); + } else { + const sema_type = unit.types.get(sema_type_index); + const result: []const u8 = switch (sema_type.*) { + .integer => |integer| b: { + const signedness_char: u8 = switch (integer.signedness) { + .signed => 's', + .unsigned => 'u', + }; + const name = try std.fmt.allocPrint(context.allocator, "{c}{}", .{ signedness_char, integer.bit_count }); + break :b name; + }, + .bool => "bool", + .pointer => |pointer| b: { + var name = ArrayList(u8){}; + try name.appendSlice(context.allocator, "&"); + if (pointer.mutability == .@"const") { + try name.appendSlice(context.allocator, "const"); + } + try name.appendSlice(context.allocator, " "); + const element_type_name = try llvm.renderTypeName(unit, context, pointer.type); + try name.appendSlice(context.allocator, element_type_name); + break :b name.items; + }, + .@"struct" => |struct_index| b: { + const struct_type = unit.structs.get(struct_index); + if (struct_type.optional) { + var name = ArrayList(u8){}; + try name.append(context.allocator, '?'); + + const element_type_name = try llvm.renderTypeName(unit, context, unit.struct_fields.get( struct_type.fields.items[0]).type); + try name.appendSlice(context.allocator, element_type_name); - try llvm.type_name_map.putNoClobber(context.allocator, sema_type_index, result); - return result; + break :b name.items; + } else { + unreachable; + } + }, + // .@"enum", + // .@"struct", + // => b: { + // if (unit.type_declaration_map.get(sema_type_index)) |type_declaration_index| { + // const declaration = unit.declarations.get(type_declaration_index); + // const name = unit.getIdentifier(declaration.name); + // break :b name; + // } else { + // unreachable; + // } + // }, + // .optional => |optional| b: { + // var name = ArrayList(u8){}; + // const element_type_name = try llvm.renderTypeName(optional.element_type); + // try name.writer(context.allocator).print("?{s}", .{element_type_name}); + // break :b name.items; + // }, + // .array => |array| b: { + // var name = ArrayList(u8){}; + // const element_type_name = try llvm.renderTypeName(array.element_type); + // try name.writer(context.allocator).print("[{}]{s}", .{ array.element_count, element_type_name }); + // break :b name.items; + // }, + .slice => |slice| b: { + var name = ArrayList(u8){}; + try name.appendSlice(context.allocator, "[] "); + if (slice.mutability == .@"const") { + try name.appendSlice(context.allocator, "const "); + } + const element_type_name = try llvm.renderTypeName(unit, context, slice.type); + try name.appendSlice(context.allocator, element_type_name); + break :b name.items; + }, + else => |t| @panic(@tagName(t)), + }; + + try llvm.type_name_map.putNoClobber(context.allocator, sema_type_index, result); + return result; + } } } @@ -2600,14 +2460,11 @@ pub const LLVM = struct { .@"struct" => |struct_index| b: { const sema_struct_type = unit.structs.get(struct_index); - assert(sema_struct_type.fields.items.len == 0); - // var field_types = try ArrayList(*LLVM.DebugInfo.Type).initCapacity(context.allocator, sema_struct_type.fields.items.len); + var field_types = try ArrayList(*LLVM.DebugInfo.Type).initCapacity(context.allocator, sema_struct_type.fields.items.len); for (sema_struct_type.fields.items) |struct_field_index| { - _ = struct_field_index; // autofix - unreachable; - // const struct_field = unit. .get(struct_field_index); - // const field_type = try llvm.getDebugType(struct_field.type); - // field_types.appendAssumeCapacity(field_type); + const struct_field = unit.struct_fields.get(struct_field_index); + const field_type = try llvm.getDebugType(unit, context, struct_field.type); + field_types.appendAssumeCapacity(field_type); } const fields = &.{}; @@ -2619,28 +2476,54 @@ pub const LLVM = struct { const struct_type = llvm.createDebugStructType(.{ .scope = null, .name = name, .file = file, .line = line, .bitsize = 0, .alignment = 0, .field_types = fields }); break :b struct_type.toType(); }, - // .@"enum" => |enum_index| b: { - // const enum_type = llvm.sema.types.enums.get(enum_index); - // var enumerators = try ArrayList(*LLVM.DebugInfo.Type.Enumerator).initCapacity(context.allocator, enum_type.fields.items.len); - // for (enum_type.fields.items) |enum_field_index| { - // const enum_field = llvm.sema.types.enum_fields.get(enum_field_index); - // const enum_field_name = llvm.sema.getName(enum_field.name).?; - // - // const is_unsigned = true; - // const enumerator = llvm.debug_info_builder.createEnumerator(enum_field_name.ptr, enum_field_name.len, enum_field.value, is_unsigned) orelse unreachable; - // enumerators.appendAssumeCapacity(enumerator); - // } - // - // const sema_declaration_index = llvm.sema.map.types.get(sema_type_index) orelse unreachable; - // const sema_declaration = llvm.sema.values.declarations.get(sema_declaration_index); - // const file = try llvm.getDebugInfoFile(llvm.sema.values.scopes.get(sema_declaration.scope).file); - // const bit_size = llvm.sema.types.array.get(enum_type.backing_type).getBitSize(llvm.sema); - // const backing_type = try llvm.getDebugType(enum_type.backing_type); - // const alignment = 0; - // const line = sema_declaration.line + 1; - // const enumeration_type = llvm.debug_info_builder.createEnumerationType(llvm.scope, name.ptr, name.len, file, line, bit_size, alignment, enumerators.items.ptr, enumerators.items.len, backing_type) orelse unreachable; - // break :b enumeration_type.toType(); - // }, + .@"enum" => |enum_index| b: { + const enum_type = unit.enums.get(enum_index); + var enumerators = try ArrayList(*LLVM.DebugInfo.Type.Enumerator).initCapacity(context.allocator, enum_type.fields.items.len); + for (enum_type.fields.items) |enum_field_index| { + const enum_field = unit.enum_fields.get(enum_field_index); + const enum_field_name = unit.getIdentifier(enum_field.name); + + const is_unsigned = true; + const enumerator = llvm.debug_info_builder.createEnumerator(enum_field_name.ptr, enum_field_name.len, enum_field.value, is_unsigned) orelse unreachable; + enumerators.appendAssumeCapacity(enumerator); + } + + const type_declaration = unit.type_declarations.get(sema_type_index).?; + const file = try llvm.getDebugInfoFile(unit, context, type_declaration.declaration.scope.file); + const bit_size = unit.types.get(enum_type.backing_type).integer.bit_count; + const backing_type = try llvm.getDebugType(unit, context, enum_type.backing_type); + const alignment = 0; + const line = type_declaration.declaration.line + 1; + const enumeration_type = llvm.debug_info_builder.createEnumerationType(llvm.scope, name.ptr, name.len, file, line, bit_size, alignment, enumerators.items.ptr, enumerators.items.len, backing_type) orelse unreachable; + break :b enumeration_type.toType(); + }, + .slice => |slice| b: { + const pointer_type = try llvm.getDebugType(unit, context, unit.pointers.get(.{ + .type = slice.type, + .many = true, + .mutability = slice.mutability, + .termination = slice.termination, + .nullable = slice.nullable, + }).?); + const len_type = try llvm.getDebugType(unit, context, .usize); + const scope = null; + const file = null; + const line = 1; + // const forward_declared_type = llvm.debug_info_builder.createReplaceableCompositeType(tag_count, name.ptr, name.len, scope, file, line) orelse unreachable; + // tag_count += 1; + + const field_types = [2]*LLVM.DebugInfo.Type{ pointer_type, len_type }; + const struct_type = llvm.createDebugStructType(.{ + .scope = scope, + .name = name, + .file = file, + .line = line, + .bitsize = 2 * @bitSizeOf(usize), + .alignment = @alignOf(usize), + .field_types = &field_types, + }); + break :b struct_type.toType(); + }, // .optional => |optional| { // const element_type = try llvm.getDebugType(optional.element_type); // const bool_type = try llvm.getDebugType(Compilation.Type.boolean); @@ -2656,32 +2539,6 @@ pub const LLVM = struct { // }); // return struct_type.toType(); // }, - // .slice => |slice| b: { - // const pointer_type = try llvm.getDebugType(llvm.sema.map.pointers.get(.{ - // .element_type = slice.element_type, - // .many = true, - // .@"const" = slice.@"const", - // .termination = slice.termination, - // }).?); - // const len_type = try llvm.getDebugType(Compilation.Type.usize); - // const scope = null; - // const file = null; - // const line = 1; - // // const forward_declared_type = llvm.debug_info_builder.createReplaceableCompositeType(tag_count, name.ptr, name.len, scope, file, line) orelse unreachable; - // // tag_count += 1; - // - // const field_types = [2]*LLVM.DebugInfo.Type{ pointer_type, len_type }; - // const struct_type = llvm.createDebugStructType(.{ - // .scope = scope, - // .name = name, - // .file = file, - // .line = line, - // .bitsize = 2 * @bitSizeOf(usize), - // .alignment = @alignOf(usize), - // .field_types = &field_types, - // }); - // break :b struct_type.toType(); - // }, // .array => |array| b: { // const byte_size = array.element_count * llvm.sema.types.array.get(array.element_type).getSize(); // const bit_size = byte_size * 8; @@ -2724,11 +2581,10 @@ pub const LLVM = struct { } fn emitRightValue(llvm: *LLVM, unit: *Compilation.Unit, context: *const Compilation.Context, v: Compilation.V) !*LLVM.Value { - _ = context; // autofix switch (v.value) { .@"comptime" => |ct| { switch (ct) { - .integer => |integer| { + .constant_int => |integer| { const integer_type = unit.types.get(v.type); switch (integer_type.*) { .integer => |integer_t| { @@ -2736,7 +2592,21 @@ pub const LLVM = struct { .signed => true, .unsigned => false, }; - const constant_int = llvm.context.getConstantInt(integer_t.bit_count, integer, signed) orelse unreachable; + const constant_int = llvm.context.getConstantInt(integer_t.bit_count, integer.value, signed) orelse unreachable; + return constant_int.toValue(); + }, + else => |t| @panic(@tagName(t)), + } + }, + .comptime_int => |integer| { + const integer_type = unit.types.get(v.type); + switch (integer_type.*) { + .integer => |integer_t| { + const signed = switch (integer_t.signedness) { + .signed => true, + .unsigned => false, + }; + const constant_int = llvm.context.getConstantInt(integer_t.bit_count, integer.value, signed) orelse unreachable; return constant_int.toValue(); }, else => |t| @panic(@tagName(t)), @@ -2753,6 +2623,33 @@ pub const LLVM = struct { const constant_int = llvm.context.getConstantInt(backing_integer_type.bit_count, enum_field.value, signed) orelse unreachable; return constant_int.toValue(); }, + .constant_struct => |constant_struct_index| { + const constant_struct = unit.constant_structs.get(constant_struct_index); + const field_values = try ArrayList(*LLVM.Value.Constant).initCapacity(context.allocator, constant_struct.fields.len); + for (constant_struct.fields) |field_value| { + _ = field_value; // autofix + unreachable; + // const value = try llvm.emitRightValue(unit, context, field_value); + // const constant = value.toConstant() orelse unreachable; + // field_values.appendAssumeCapacity(constant); + } + + const llvm_type = try llvm.getType(unit, context, constant_struct.type); + const struct_type = llvm_type.toStruct() orelse unreachable; + const const_struct = struct_type.getConstant(field_values.items.ptr, field_values.items.len) orelse unreachable; + return const_struct.toValue(); + }, + .undefined => { + const undefined_type = try llvm.getType(unit, context, v.type); + const poison = undefined_type.getPoison() orelse unreachable; + return poison.toValue(); + }, + .bool => |boolean| { + const bit_count = 1; + const signed = false; + const constant_bool = llvm.context.getConstantInt(bit_count, @intFromBool(boolean), signed) orelse unreachable; + return constant_bool.toValue(); + }, else => |t| @panic(@tagName(t)), } }, @@ -2762,6 +2659,10 @@ pub const LLVM = struct { } else { const instruction = unit.instructions.get(instruction_index); switch (instruction.*) { + .global => |global| { + const global_variable = llvm.global_variable_map.get(global).?; + return global_variable.toValue(); + }, else => |t| @panic(@tagName(t)), } @@ -2772,13 +2673,6 @@ pub const LLVM = struct { } } - fn emitBasicBlock(llvm: *LLVM, unit: *Compilation.Unit, context: *const Compilation.Context, basic_block_index: Compilation.BasicBlock.Index) !void { - _ = llvm; // autofix - _ = unit; // autofix - _ = context; // autofix - _ = basic_block_index; // autofix - } - fn getScope(llvm: *LLVM, unit: *Compilation.Unit, context: *const Compilation.Context, sema_scope: *Compilation.Debug.Scope) !*LLVM.DebugInfo.Scope { switch (sema_scope.kind) { .function, .block, .compilation_unit, => { @@ -2801,8 +2695,21 @@ pub const LLVM = struct { } unreachable; } + + fn createBasicBlock(llvm: *LLVM, context: *const Compilation.Context, basic_block_index: Compilation.BasicBlock.Index, name: []const u8) !*BasicBlockList.Node { + const basic_block = llvm.context.createBasicBlock(name.ptr, name.len, llvm.function, null) orelse return Error.basic_block; + const basic_block_node = try context.allocator.create(BasicBlockList.Node); + basic_block_node.* = .{ + .data = basic_block_index, + }; + try llvm.llvm_block_map.putNoClobber(context.allocator, basic_block_index, basic_block); + + return basic_block_node; + } }; +const BasicBlockList = std.DoublyLinkedList(Compilation.BasicBlock.Index); + var tag_count: c_uint = 0; const Error = error{ @@ -2822,6 +2729,7 @@ pub const Format = enum(c_uint) { coff = 2, }; + pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !void { const llvm_context = LLVM.Context.create() orelse return Error.context; const module = LLVM.Module.create(@ptrCast(unit.descriptor.name.ptr), unit.descriptor.name.len, llvm_context) orelse return Error.module; @@ -2860,53 +2768,69 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo // First, cache all the global variables for (unit.data_to_emit.items) |global_declaration| { - const global_type = try llvm.getType(unit, context, global_declaration.declaration.type); - const linkage: LLVM.Linkage = switch (global_declaration.attributes.contains(.@"export")) { - true => .@"extern", - false => .internal, - }; const name = unit.getIdentifier(global_declaration.declaration.name); - const constant = switch (global_declaration.declaration.mutability) { - .@"var" => false, - .@"const" => true, - }; - const initializer: ?*LLVM.Value.Constant = null; - const thread_local_mode = LLVM.ThreadLocalMode.not_thread_local; - const externally_initialized = false; - const global_variable = llvm.module.addGlobalVariable(global_type, constant, linkage, initializer, name.ptr, name.len, null, thread_local_mode, address_space, externally_initialized) orelse return LLVM.Value.Error.constant_int; - try llvm.global_variable_map.putNoClobber(context.allocator, global_declaration, global_variable); + switch (global_declaration.initial_value) { + .string_literal => |hash| { + const string_literal = unit.string_literal_values.get(hash).?; + const global_variable = llvm.builder.createGlobalString(string_literal.ptr, string_literal.len, name.ptr, name.len, address_space, llvm.module) orelse unreachable; + try llvm.global_variable_map.putNoClobber(context.allocator, global_declaration, global_variable); + }, + else => { + const global_type = try llvm.getType(unit, context, global_declaration.declaration.type); + const linkage: LLVM.Linkage = switch (global_declaration.attributes.contains(.@"export")) { + true => .@"extern", + false => .internal, + }; + const constant = switch (global_declaration.declaration.mutability) { + .@"var" => false, + .@"const" => true, + }; + const initializer: ?*LLVM.Value.Constant = null; + const thread_local_mode = LLVM.ThreadLocalMode.not_thread_local; + const externally_initialized = false; + const global_variable = llvm.module.addGlobalVariable(global_type, constant, linkage, initializer, name.ptr, name.len, null, thread_local_mode, address_space, externally_initialized) orelse return LLVM.Value.Error.constant_int; + try llvm.global_variable_map.putNoClobber(context.allocator, global_declaration, global_variable); + }, + } if (unit.descriptor.generate_debug_information) { - const scope = try llvm.getScope(unit, context, global_declaration.declaration.scope); - const file = try llvm.getDebugInfoFile(unit, context, global_declaration.declaration.scope.file); - const debug_type = try llvm.getDebugType(unit, context, global_declaration.declaration.type); - const is_local_to_unit = !global_declaration.attributes.contains(.@"export"); - const is_defined = true; - const expression = null; - const declaration = null; - const template_parameters = null; - const alignment = 0; - const debug_global_variable = llvm.debug_info_builder.createGlobalVariableExpression(scope, name.ptr, name.len, name.ptr, name.len, file, global_declaration.declaration.line, debug_type, is_local_to_unit, is_defined, expression, declaration, template_parameters, alignment) orelse unreachable; - _ = debug_global_variable; // autofix + // Don't emit debug information for strings + if (@intFromEnum(global_declaration.declaration.scope.kind) < @intFromEnum(Compilation.Debug.Scope.Kind.function)) { + const scope = try llvm.getScope(unit, context, global_declaration.declaration.scope); + const file = try llvm.getDebugInfoFile(unit, context, global_declaration.declaration.scope.file); + const debug_type = try llvm.getDebugType(unit, context, global_declaration.declaration.type); + const is_local_to_unit = !global_declaration.attributes.contains(.@"export"); + const is_defined = true; + const expression = null; + const declaration = null; + const template_parameters = null; + const alignment = 0; + const debug_global_variable = llvm.debug_info_builder.createGlobalVariableExpression(scope, name.ptr, name.len, name.ptr, name.len, file, global_declaration.declaration.line, debug_type, is_local_to_unit, is_defined, expression, declaration, template_parameters, alignment) orelse unreachable; + _ = debug_global_variable; // autofix + } } } for (llvm.global_variable_map.keys(), llvm.global_variable_map.values()) |global_declaration, global_variable| { const constant_initializer = switch (global_declaration.initial_value) { - .integer => |integer| b: { + .comptime_int => |ct_int| blk: { const integer_type = unit.types.get( global_declaration.declaration.type).integer; const signed = switch (integer_type.signedness) { .signed => true, .unsigned => false, }; - const constant_int = llvm.context.getConstantInt(integer_type.bit_count, integer, signed) orelse unreachable; - break :b constant_int.toConstant(); + assert(ct_int.signedness == .unsigned); + assert(!signed); + const constant_int = llvm.context.getConstantInt(integer_type.bit_count, ct_int.value, signed) orelse unreachable; + break :blk constant_int.toConstant(); }, .undefined => b: { const global_type = try llvm.getType(unit, context, global_declaration.declaration.type); - const undef = global_type.getUndefined() orelse unreachable; - break :b undef.toConstant(); + const poison = global_type.getPoison() orelse unreachable; + break :b poison.toConstant(); }, + // String literal global variables are already initialized + .string_literal => continue, else =>|t| @panic(@tagName(t)), }; @@ -2957,7 +2881,7 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo 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); @@ -3010,8 +2934,7 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo .object_c_direct = false, }; const subprogram_declaration = null; - const name = unit.getIdentifier(function_declaration.declaration.name); - const subprogram = llvm.debug_info_builder.createFunction(debug_file.toScope(), name.ptr, name.len, name.ptr, name.len, debug_file, function_declaration.declaration.line + 1, subroutine_type, scope_line, subroutine_type_flags, subprogram_flags, subprogram_declaration) orelse unreachable; + 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); llvm.file = subprogram.getFile() orelse unreachable; llvm.subprogram = subprogram; @@ -3023,327 +2946,391 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo llvm.arg_index = 0; llvm.alloca_map.clearRetainingCapacity(); - const block_name = ""; - const basic_block = llvm.context.createBasicBlock(block_name, block_name.len, llvm.function, null) orelse return Error.basic_block; - llvm.builder.setInsertPoint(basic_block); - const sema_basic_block = unit.basic_blocks.get(function_definition.basic_blocks.items[0]); + var block_command_list = BasicBlockList{}; - for (sema_basic_block.instructions.items) |instruction_index| { - const sema_instruction = unit.instructions.get(instruction_index); + const entry_block_node = try llvm.createBasicBlock(context, function_definition.basic_blocks.items[0], "fn_entry"); + block_command_list.append(entry_block_node); - switch (sema_instruction.*) { - .push_scope => |push_scope| { - const old_scope = try llvm.getScope(unit, context, push_scope.old); - assert(@intFromEnum(push_scope.old.kind) >= @intFromEnum(Compilation.Debug.Scope.Kind.function)); + while (block_command_list.len != 0) { + const block_node = block_command_list.first orelse unreachable; + const basic_block_index = block_node.data; + const sema_basic_block = unit.basic_blocks.get(basic_block_index); + const basic_block = llvm.llvm_block_map.get(basic_block_index).?; + llvm.builder.setInsertPoint(basic_block); - const lexical_block = llvm.debug_info_builder.createLexicalBlock(old_scope, llvm.file, push_scope.new.line + 1, push_scope.new.column + 1) orelse unreachable; - try llvm.scope_map.putNoClobber(context.allocator, push_scope.new, lexical_block.toScope()); - llvm.scope = lexical_block.toScope(); - }, - .pop_scope => |pop_scope| { - const old = try llvm.getScope(unit, context, pop_scope.old); - assert(llvm.scope == old); - const new = try llvm.getScope(unit, context, pop_scope.new); - if (pop_scope.new.kind == .function) { - assert(new.toSubprogram() orelse unreachable == llvm.subprogram); - } - llvm.scope = new; - var scope = pop_scope.old; - while (scope.kind != .function) { - scope = scope.parent.?; - } - const subprogram_scope = try llvm.getScope(unit, context, scope); - assert(llvm.subprogram == subprogram_scope.toSubprogram() orelse unreachable); - }, - .debug_checkpoint => |debug_checkpoint| { - const scope = try llvm.getScope(unit, context, debug_checkpoint.scope); - assert(scope == llvm.scope); - llvm.builder.setCurrentDebugLocation(llvm.context, debug_checkpoint.line + 1, debug_checkpoint.column + 1, scope, llvm.function); - }, - .inline_assembly => |inline_assembly_index| { - const assembly_block = unit.inline_assembly.get(inline_assembly_index); + for (sema_basic_block.instructions.items) |instruction_index| { + const sema_instruction = unit.instructions.get(instruction_index); - var assembly_statements = ArrayList(u8){}; - var constraints = ArrayList(u8){}; - var operand_values = ArrayList(*LLVM.Value){}; - var operand_types = ArrayList(*LLVM.Type){}; + switch (sema_instruction.*) { + .push_scope => |push_scope| { + const old_scope = try llvm.getScope(unit, context, push_scope.old); + assert(@intFromEnum(push_scope.old.kind) >= @intFromEnum(Compilation.Debug.Scope.Kind.function)); - switch (unit.descriptor.target.cpu.arch) { - .x86_64 => { - for (assembly_block.instructions) |assembly_instruction_index| { - const instruction = unit.assembly_instructions.get(assembly_instruction_index); - const instruction_id: Compilation.InlineAssembly.x86_64.Instruction = @enumFromInt(instruction.id); + const lexical_block = llvm.debug_info_builder.createLexicalBlock(old_scope, llvm.file, push_scope.new.line + 1, push_scope.new.column + 1) orelse unreachable; + try llvm.scope_map.putNoClobber(context.allocator, push_scope.new, lexical_block.toScope()); + llvm.scope = lexical_block.toScope(); + }, + .pop_scope => |pop_scope| { + // const old = try llvm.getScope(unit, context, pop_scope.old); + // assert(llvm.scope == old); + const new = try llvm.getScope(unit, context, pop_scope.new); + if (pop_scope.new.kind == .function) { + assert(new.toSubprogram() orelse unreachable == llvm.subprogram); + } + llvm.scope = new; + var scope = pop_scope.old; + while (scope.kind != .function) { + scope = scope.parent.?; + } + const subprogram_scope = try llvm.getScope(unit, context, scope); + assert(llvm.subprogram == subprogram_scope.toSubprogram() orelse unreachable); + }, + .debug_checkpoint => |debug_checkpoint| { + const scope = try llvm.getScope(unit, context, debug_checkpoint.scope); + // assert(scope == llvm.scope); + llvm.builder.setCurrentDebugLocation(llvm.context, debug_checkpoint.line + 1, debug_checkpoint.column + 1, scope, llvm.function); + }, + .inline_assembly => |inline_assembly_index| { + const assembly_block = unit.inline_assembly.get(inline_assembly_index); - try assembly_statements.appendSlice(context.allocator, switch (instruction_id) { - .xor => "xorl", - .mov => "movq", - .@"and" => "andq", - .call => "callq", - }); - try assembly_statements.append(context.allocator, ' '); + var assembly_statements = ArrayList(u8){}; + var constraints = ArrayList(u8){}; + var operand_values = ArrayList(*LLVM.Value){}; + var operand_types = ArrayList(*LLVM.Type){}; - if (instruction.operands.len > 0) { - var reverse_operand_iterator = std.mem.reverseIterator(instruction.operands); + switch (unit.descriptor.target.cpu.arch) { + .x86_64 => { + for (assembly_block.instructions) |assembly_instruction_index| { + const instruction = unit.assembly_instructions.get(assembly_instruction_index); + const instruction_id: Compilation.InlineAssembly.x86_64.Instruction = @enumFromInt(instruction.id); - while (reverse_operand_iterator.next()) |operand| { - switch (operand) { - .register => |register_value| { - const register: Compilation.InlineAssembly.x86_64.Register = @enumFromInt(register_value); - try assembly_statements.append(context.allocator, '%'); - try assembly_statements.appendSlice(context.allocator, @tagName(register)); - }, - .number_literal => |literal| { - try assembly_statements.writer(context.allocator).print("$$0x{x}", .{literal}); - }, - .value => |sema_value| { - if (llvm.llvm_value_map.get(sema_value)) |v| { - _ = v; // autofix - unreachable; - } else { - const value = try llvm.emitLeftValue(unit, context, sema_value); - try assembly_statements.writer(context.allocator).print("${{{}:P}}", .{operand_values.items.len}); - try operand_values.append(context.allocator, value); - const value_type = value.getType(); - try operand_types.append(context.allocator, value_type); - try constraints.append(context.allocator, 'X'); - } - }, + try assembly_statements.appendSlice(context.allocator, switch (instruction_id) { + .xor => "xorl", + .mov => "movq", + .@"and" => "andq", + .call => "callq", + }); + try assembly_statements.append(context.allocator, ' '); + + if (instruction.operands.len > 0) { + var reverse_operand_iterator = std.mem.reverseIterator(instruction.operands); + + while (reverse_operand_iterator.next()) |operand| { + switch (operand) { + .register => |register_value| { + const register: Compilation.InlineAssembly.x86_64.Register = @enumFromInt(register_value); + try assembly_statements.append(context.allocator, '%'); + try assembly_statements.appendSlice(context.allocator, @tagName(register)); + }, + .number_literal => |literal| { + try assembly_statements.writer(context.allocator).print("$$0x{x}", .{literal}); + }, + .value => |sema_value| { + if (llvm.llvm_value_map.get(sema_value)) |v| { + _ = v; // autofix + unreachable; + } else { + const value = try llvm.emitLeftValue(unit, context, sema_value); + try assembly_statements.writer(context.allocator).print("${{{}:P}}", .{operand_values.items.len}); + try operand_values.append(context.allocator, value); + const value_type = value.getType(); + try operand_types.append(context.allocator, value_type); + try constraints.append(context.allocator, 'X'); + } + }, + } + + try assembly_statements.appendSlice(context.allocator, ", "); } - try assembly_statements.appendSlice(context.allocator, ", "); + _ = assembly_statements.pop(); + _ = assembly_statements.pop(); } - _ = assembly_statements.pop(); - _ = assembly_statements.pop(); + try assembly_statements.appendSlice(context.allocator, "\n\t"); } - try assembly_statements.appendSlice(context.allocator, "\n\t"); - } + try constraints.appendSlice(context.allocator, ",~{dirflag},~{fpsr},~{flags}"); + }, + else => |t| @panic(@tagName(t)), + } - try constraints.appendSlice(context.allocator, ",~{dirflag},~{fpsr},~{flags}"); - }, - else => |t| @panic(@tagName(t)), - } + const is_var_args = false; + const function_type = LLVM.Context.getFunctionType(try llvm.getType(unit, context, Compilation.Type.Index.void), operand_types.items.ptr, operand_types.items.len, is_var_args) orelse unreachable; + const has_side_effects = true; + const is_align_stack = true; + const dialect = LLVM.Value.InlineAssembly.Dialect.@"at&t"; + const can_throw = false; - const is_var_args = false; - const function_type = LLVM.Context.getFunctionType(try llvm.getType(unit, context, Compilation.Type.Index.void), operand_types.items.ptr, operand_types.items.len, is_var_args) orelse unreachable; - const has_side_effects = true; - const is_align_stack = true; - const dialect = LLVM.Value.InlineAssembly.Dialect.@"at&t"; - const can_throw = false; + const inline_assembly = LLVM.Value.InlineAssembly.get(function_type, assembly_statements.items.ptr, assembly_statements.items.len, constraints.items.ptr, constraints.items.len, has_side_effects, is_align_stack, dialect, can_throw) orelse return LLVM.Value.Error.inline_assembly; + const call = llvm.builder.createCall(function_type, inline_assembly.toValue(), operand_values.items.ptr, operand_values.items.len, "", "".len, null) orelse return LLVM.Value.Instruction.Error.call; + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, call.toValue()); + }, + .stack_slot => |stack_slot| { + switch (unit.types.get(stack_slot.type).*) { + .void, .noreturn, .type => unreachable, + .comptime_int => unreachable, + .bool => unreachable, + .@"struct" => {}, + .@"enum" => {}, + .function => unreachable, + .integer => {}, + .pointer => {}, + .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; + try llvm.alloca_map.putNoClobber(context.allocator, instruction_index, declaration_alloca.toValue()); + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, declaration_alloca.toValue()); + }, + .store => |store| { + const right = try llvm.emitRightValue(unit, context, store.source); - const inline_assembly = LLVM.Value.InlineAssembly.get(function_type, assembly_statements.items.ptr, assembly_statements.items.len, constraints.items.ptr, constraints.items.len, has_side_effects, is_align_stack, dialect, can_throw) orelse return LLVM.Value.Error.inline_assembly; - const call = llvm.builder.createCall(function_type, inline_assembly.toValue(), operand_values.items.ptr, operand_values.items.len, "", "".len, null) orelse return LLVM.Value.Instruction.Error.call; - try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, call.toValue()); - }, - .stack_slot => |stack_slot| { - switch (unit.types.get(stack_slot.type).*) { - .void, .noreturn, .type => unreachable, - .comptime_int => unreachable, - .bool => unreachable, - .@"struct" => {}, - .@"enum" => {}, - .function => unreachable, - .integer => {}, - .pointer => {}, - } - 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; - try llvm.alloca_map.putNoClobber(context.allocator, instruction_index, declaration_alloca.toValue()); - try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, declaration_alloca.toValue()); - }, - .store => |store| { - const right = try llvm.emitRightValue(unit, context, store.source); + const is_volatile = false; + const left = try llvm.emitLeftValue(unit, context, store.destination); + const store_instruction = llvm.builder.createStore(right, left, is_volatile) orelse return LLVM.Value.Instruction.Error.store; + _ = store_instruction; // autofix + }, + .cast => |cast|{ + const value = try llvm.emitRightValue(unit, context, cast.value); + const dest_type = try llvm.getType(unit, context, cast.type); + switch (cast.id) { + .int_to_pointer => { + const cast_type = LLVM.Value.Instruction.Cast.Type.int_to_pointer; + const cast_name = @tagName(cast_type); + const cast_instruction = llvm.builder.createCast(cast_type, value, value.getType(), cast_name.ptr, cast_name.len) orelse return LLVM.Value.Instruction.Error.cast; + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, cast_instruction); + }, + .pointer_var_to_const, .enum_to_int => { + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, value); + }, + .sign_extend => { + const sign_extend = llvm.builder.createCast(.sign_extend, value, dest_type, "sign_extend", "sign_extend".len) orelse return LLVM.Value.Instruction.Error.cast; + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, sign_extend); + }, + .zero_extend => { + const zero_extend = llvm.builder.createCast(.zero_extend, value, dest_type, "zero_extend", "zero_extend".len) orelse return LLVM.Value.Instruction.Error.cast; + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, zero_extend); + }, + .bitcast => { + const bitcast = llvm.builder.createCast(.bitcast, value, dest_type, "bitcast", "bitcast".len) orelse return LLVM.Value.Instruction.Error.cast; + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, bitcast); + }, + .pointer_to_int => { + const pointer_to_int = llvm.builder.createCast(.pointer_to_int, value, dest_type, "pointer_to_int", "pointer_to_int".len) orelse return LLVM.Value.Instruction.Error.cast; + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, pointer_to_int); + }, + } + }, + .load => |load| { + const value = if (llvm.llvm_value_map.get(load.value)) |v| v else blk: { + const value = switch (load.value.value) { + .runtime => |instr_index| llvm.llvm_instruction_map.get(instr_index).?, + else => |t| @panic(@tagName(t)), + }; + try llvm.llvm_value_map.putNoClobber(context.allocator, load.value, value); - const is_volatile = false; - const left = try llvm.emitLeftValue(unit, context, store.destination); - const store_instruction = llvm.builder.createStore(right, left, is_volatile) orelse return LLVM.Value.Instruction.Error.store; - _ = store_instruction; // autofix - }, - .cast => |cast|{ - const value = try llvm.emitRightValue(unit, context, cast.value); - const dest_type = try llvm.getType(unit, context, cast.type); - switch (cast.id) { - .int_to_pointer => { - const cast_type = LLVM.Value.Instruction.Cast.Type.int_to_pointer; - const cast_name = @tagName(cast_type); - const cast_instruction = llvm.builder.createCast(cast_type, value, value.getType(), cast_name.ptr, cast_name.len) orelse return LLVM.Value.Instruction.Error.cast; - try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, cast_instruction); - }, - .enum_to_int => { - try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, value); - }, - .sign_extend => { - const sign_extend = llvm.builder.createCast(.sign_extend, value, dest_type, "sign_extend", "sign_extend".len) orelse return LLVM.Value.Instruction.Error.cast; - try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, sign_extend); - }, - .pointer_var_to_const => { - try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, value); - }, - .bitcast => { - const bitcast = llvm.builder.createCast(.bitcast, value, dest_type, "bitcast", "bitcast".len) orelse return LLVM.Value.Instruction.Error.cast; - try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, bitcast); - }, - else => |t| @panic(@tagName(t)), - } - }, - .load => |load| { - const value = if (llvm.llvm_value_map.get(load.value)) |v| v else blk: { - const value = switch (load.value.value) { - .runtime => |instr_index| llvm.llvm_instruction_map.get(instr_index).?, + break :blk value; + }; + + 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()); + }, + .integer_binary_operation => |binary_operation| { + const left = try llvm.emitRightValue(unit, context, binary_operation.left); + const right = try llvm.emitRightValue(unit, context, binary_operation.right); + const no_signed_wrapping = binary_operation.signedness == .signed; + const no_unsigned_wrapping = binary_operation.signedness == .unsigned; + const name = @tagName(binary_operation.id); + const is_exact = false; + const instruction = switch (binary_operation.id) { + .add => llvm.builder.createAdd(left, right, name.ptr, name.len, no_unsigned_wrapping, no_signed_wrapping) orelse return LLVM.Value.Instruction.Error.add, + .mul => llvm.builder.createMultiply(left, right, name.ptr, name.len, no_unsigned_wrapping, no_signed_wrapping) orelse return LLVM.Value.Instruction.Error.multiply, + .sub => llvm.builder.createSub(left, right, name.ptr, name.len, no_unsigned_wrapping, no_signed_wrapping) orelse return LLVM.Value.Instruction.Error.add, + .div => switch (binary_operation.signedness) { + .unsigned => llvm.builder.createUDiv(left, right, name.ptr, name.len, is_exact) orelse unreachable, + .signed => llvm.builder.createSDiv(left, right, name.ptr, name.len, is_exact) orelse unreachable, + }, + .mod => switch (binary_operation.signedness) { + .unsigned => llvm.builder.createURem(left, right, name.ptr, name.len) orelse unreachable, + .signed => llvm.builder.createSRem(left, right, name.ptr, name.len) orelse unreachable, + }, + .bit_and => llvm.builder.createAnd(left, right, name.ptr, name.len) orelse unreachable, + .bit_or => llvm.builder.createOr(left, right, name.ptr, name.len) orelse unreachable, + .bit_xor => llvm.builder.createXor(left, right, name.ptr, name.len) orelse unreachable, + .shift_left => llvm.builder.createShiftLeft(left, right, name.ptr, name.len, no_unsigned_wrapping, no_signed_wrapping) orelse unreachable, + .shift_right => switch (binary_operation.signedness) { + .unsigned => llvm.builder.createLogicalShiftRight(left, right, name.ptr, name.len, is_exact) orelse unreachable, + .signed => llvm.builder.createArithmeticShiftRight(left, right, name.ptr, name.len, is_exact) orelse unreachable, + }, + //else => |t| @panic(@tagName(t)), + }; + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, instruction); + }, + .call => |call| { + var argument_buffer: [32]*LLVM.Value = undefined; + const argument_count = call.arguments.len; + const arguments = argument_buffer[0..argument_count]; + + switch (call.callable) { + .function_definition => |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); + assert(call_function_definition.type == call.function_type); + + for (call.arguments, arguments) |argument_value, *argument| { + argument.* = try llvm.emitRightValue(unit, context, argument_value); + } + + const llvm_calling_convention = callee.getCallingConvention(); + const name = ""; + const call_type = try llvm.getType(unit, context, call.function_type); + const function_type = call_type.toFunction() orelse unreachable; + const call_instruction = llvm.builder.createCall(function_type, callee.toValue(), arguments.ptr, arguments.len, name.ptr, name.len, null) orelse return LLVM.Value.Instruction.Error.call; + call_instruction.setCallingConvention(llvm_calling_convention); + + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, call_instruction.toValue()); + }, + } + }, + .ret => |return_value| { + const value = try llvm.emitRightValue(unit, context, return_value); + // const value = llvm.llvm_value_map.get(return_value).?; + const ret = llvm.builder.createRet(value) orelse return LLVM.Value.Instruction.Error.ret; + _ = ret; // autofix + // _ = ret; // autofix + }, + .syscall => |syscall| { + var syscall_argument_buffer: [7]*LLVM.Value = undefined; + var syscall_argument_type_buffer: [7]*LLVM.Type = undefined; + const sema_syscall_arguments = syscall.arguments; + const syscall_argument_count: usize = sema_syscall_arguments.len; + const syscall_arguments = syscall_argument_buffer[0..syscall_argument_count]; + const syscall_argument_types = syscall_argument_type_buffer[0..syscall_argument_count]; + + for (sema_syscall_arguments, syscall_arguments, syscall_argument_types) |sema_syscall_argument_value_index, *syscall_argument, *syscall_argument_type| { + syscall_argument.* = try llvm.emitRightValue(unit, context, sema_syscall_argument_value_index); + syscall_argument_type.* = syscall_argument.*.getType(); + } + + const return_type = try llvm.getType(unit, context, Compilation.Type.Index.usize); + const is_var_args = false; + const function_type = LLVM.Context.getFunctionType(return_type, syscall_argument_types.ptr, syscall_argument_types.len, is_var_args) orelse unreachable; + var constraints = ArrayList(u8){}; + + const inline_asm = switch (unit.descriptor.target.cpu.arch) { + .x86_64 => blk: { + try constraints.appendSlice(context.allocator, "={rax}"); + + const syscall_registers = [7][]const u8{ "rax", "rdi", "rsi", "rdx", "r10", "r8", "r9" }; + for (syscall_registers[0..syscall_argument_count]) |syscall_register| { + try constraints.append(context.allocator, ','); + try constraints.append(context.allocator, '{'); + try constraints.appendSlice(context.allocator, syscall_register); + try constraints.append(context.allocator, '}'); + } + + try constraints.appendSlice(context.allocator, ",~{rcx},~{r11},~{memory}"); + + const assembly = "syscall"; + const has_side_effects = true; + const is_align_stack = true; + const can_throw = false; + const inline_assembly = LLVM.Value.InlineAssembly.get(function_type, assembly, assembly.len, constraints.items.ptr, constraints.items.len, has_side_effects, is_align_stack, LLVM.Value.InlineAssembly.Dialect.@"at&t", can_throw) orelse return LLVM.Value.Error.inline_assembly; + break :blk inline_assembly; + }, else => |t| @panic(@tagName(t)), }; - try llvm.llvm_value_map.putNoClobber(context.allocator, load.value, value); - break :blk value; - }; + const call_to_asm = llvm.builder.createCall(function_type, inline_asm.toValue(), syscall_arguments.ptr, syscall_arguments.len, "syscall", "syscall".len, null) orelse return LLVM.Value.Instruction.Error.call; + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, call_to_asm.toValue()); + }, + .@"unreachable" => { + _ = llvm.builder.createUnreachable() orelse return LLVM.Value.Instruction.Error.@"unreachable"; + }, + .argument_declaration => |argument_declaration| { + var argument_buffer: [16]*LLVM.Value.Argument = undefined; + var argument_count: usize = argument_buffer.len; + llvm.function.getArguments(&argument_buffer, &argument_count); + const arguments = argument_buffer[0..argument_count]; + const argument_index = llvm.arg_index; + llvm.arg_index += 1; + const argument = arguments[argument_index]; + const name = unit.getIdentifier(argument_declaration.declaration.name); + argument.toValue().setName(name.ptr, name.len); + const argument_type_index = argument_declaration.declaration.type; + switch (unit.types.get(argument_type_index).*) { + .void, .noreturn, .type => unreachable, + .comptime_int => unreachable, + .bool => unreachable, + .@"struct" => {}, + .@"enum" => {}, + .function => unreachable, + .integer => {}, + .pointer => {}, + .slice => {}, + .array => {}, + } + const argument_type = argument.toValue().getType(); + const alloca_array_size: ?*LLVM.Value = null; + const argument_value = argument.toValue(); + const declaration_alloca = llvm.builder.createAlloca(argument_type, address_space, alloca_array_size, "", "".len) orelse return LLVM.Value.Instruction.Error.alloca; - 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()); - }, - .integer_binary_operation => |binary_operation| { - const left = try llvm.emitRightValue(unit, context, binary_operation.left); - const right = try llvm.emitRightValue(unit, context, binary_operation.right); - const no_signed_wrapping = binary_operation.signedness == .signed; - const no_unsigned_wrapping = binary_operation.signedness == .unsigned; - const name = @tagName(binary_operation.id); - const is_exact = false; - const instruction = switch (binary_operation.id) { - .add => llvm.builder.createAdd(left, right, name.ptr, name.len, no_unsigned_wrapping, no_signed_wrapping) orelse return LLVM.Value.Instruction.Error.add, - .mul => llvm.builder.createMultiply(left, right, name.ptr, name.len, no_unsigned_wrapping, no_signed_wrapping) orelse return LLVM.Value.Instruction.Error.multiply, - .sub => llvm.builder.createSub(left, right, name.ptr, name.len, no_unsigned_wrapping, no_signed_wrapping) orelse return LLVM.Value.Instruction.Error.add, - .div => switch (binary_operation.signedness) { - .unsigned => llvm.builder.createUDiv(left, right, name.ptr, name.len, is_exact) orelse unreachable, - .signed => llvm.builder.createSDiv(left, right, name.ptr, name.len, is_exact) orelse unreachable, - }, - .mod => switch (binary_operation.signedness) { - .unsigned => llvm.builder.createURem(left, right, name.ptr, name.len) orelse unreachable, - .signed => llvm.builder.createSRem(left, right, name.ptr, name.len) orelse unreachable, - }, - .bit_and => llvm.builder.createAnd(left, right, name.ptr, name.len) orelse unreachable, - .bit_or => llvm.builder.createOr(left, right, name.ptr, name.len) orelse unreachable, - .bit_xor => llvm.builder.createXor(left, right, name.ptr, name.len) orelse unreachable, - .shift_left => llvm.builder.createShiftLeft(left, right, name.ptr, name.len, no_unsigned_wrapping, no_signed_wrapping) orelse unreachable, - .shift_right => switch (binary_operation.signedness) { - .unsigned => llvm.builder.createLogicalShiftRight(left, right, name.ptr, name.len, is_exact) orelse unreachable, - .signed => llvm.builder.createArithmeticShiftRight(left, right, name.ptr, name.len, is_exact) orelse unreachable, - }, - //else => |t| @panic(@tagName(t)), - }; - try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, instruction); - }, - .call => |call| { - var argument_buffer: [32]*LLVM.Value = undefined; - const argument_count = call.arguments.len; - const arguments = argument_buffer[0..argument_count]; + if (unit.descriptor.generate_debug_information) { + const debug_declaration_type = try llvm.getDebugType(unit, context, argument_declaration.declaration.type); + const always_preserve = true; + const 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 declaration_name = unit.getIdentifier(argument_declaration.declaration.name); + const line = argument_declaration.declaration.line; + const column = argument_declaration.declaration.column; + const debug_parameter_variable = llvm.debug_info_builder.createParameterVariable(llvm.scope, declaration_name.ptr, declaration_name.len, argument_index + 1, llvm.file, line, debug_declaration_type, always_preserve, flags) orelse unreachable; - switch (call.callable) { - .function_definition => |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); - assert(call_function_definition.type == call.function_type); - - for (call.arguments, arguments) |argument_value, *argument| { - argument.* = try llvm.emitRightValue(unit, context, argument_value); - } - - const llvm_calling_convention = callee.getCallingConvention(); - const name = ""; - const call_type = try llvm.getType(unit, context, call.function_type); - const function_type = call_type.toFunction() orelse unreachable; - const call_instruction = llvm.builder.createCall(function_type, callee.toValue(), arguments.ptr, arguments.len, name.ptr, name.len, null) orelse return LLVM.Value.Instruction.Error.call; - call_instruction.setCallingConvention(llvm_calling_convention); + const insert_declare = llvm.debug_info_builder.insertDeclare(declaration_alloca.toValue(), debug_parameter_variable, llvm.context, line, column, (llvm.function.getSubprogram() orelse unreachable).toLocalScope().toScope(), llvm.builder.getInsertBlock() orelse unreachable); + _ = insert_declare; + } - try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, call_instruction.toValue()); - }, - } - }, - .ret => |return_value| { - const value = try llvm.emitRightValue(unit, context, return_value); - // const value = llvm.llvm_value_map.get(return_value).?; - const ret = llvm.builder.createRet(value) orelse return LLVM.Value.Instruction.Error.ret; - _ = ret; // autofix - // _ = ret; // autofix - }, - .syscall => |syscall| { - var syscall_argument_buffer: [7]*LLVM.Value = undefined; - var syscall_argument_type_buffer: [7]*LLVM.Type = undefined; - const sema_syscall_arguments = syscall.arguments; - const syscall_argument_count: usize = sema_syscall_arguments.len; - const syscall_arguments = syscall_argument_buffer[0..syscall_argument_count]; - const syscall_argument_types = syscall_argument_type_buffer[0..syscall_argument_count]; - - for (sema_syscall_arguments, syscall_arguments, syscall_argument_types) |sema_syscall_argument_value_index, *syscall_argument, *syscall_argument_type| { - syscall_argument.* = try llvm.emitRightValue(unit, context, sema_syscall_argument_value_index); - syscall_argument_type.* = syscall_argument.*.getType(); - } - - const return_type = try llvm.getType(unit, context, Compilation.Type.Index.usize); - const is_var_args = false; - const function_type = LLVM.Context.getFunctionType(return_type, syscall_argument_types.ptr, syscall_argument_types.len, is_var_args) orelse unreachable; - var constraints = ArrayList(u8){}; - - const inline_asm = switch (unit.descriptor.target.cpu.arch) { - .x86_64 => blk: { - try constraints.appendSlice(context.allocator, "={rax}"); - - const syscall_registers = [7][]const u8{ "rax", "rdi", "rsi", "rdx", "r10", "r8", "r9" }; - for (syscall_registers[0..syscall_argument_count]) |syscall_register| { - try constraints.appendSlice(context.allocator, ",{"); - try constraints.appendSlice(context.allocator, syscall_register); - try constraints.append(context.allocator, '}'); - } - - try constraints.appendSlice(context.allocator, ",~{rcx},~{r11},~{memory}"); - - const assembly = "syscall"; - const has_side_effects = true; - const is_align_stack = true; - const can_throw = false; - const inline_assembly = LLVM.Value.InlineAssembly.get(function_type, assembly, assembly.len, constraints.items.ptr, constraints.items.len, has_side_effects, is_align_stack, LLVM.Value.InlineAssembly.Dialect.@"at&t", can_throw) orelse return LLVM.Value.Error.inline_assembly; - break :blk inline_assembly; - }, - else => |t| @panic(@tagName(t)), - }; - - const call_to_asm = llvm.builder.createCall(function_type, inline_asm.toValue(), syscall_arguments.ptr, syscall_arguments.len, "syscall", "syscall".len, null) orelse return LLVM.Value.Instruction.Error.call; - try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, call_to_asm.toValue()); - }, - .@"unreachable" => { - _ = llvm.builder.createUnreachable() orelse return LLVM.Value.Instruction.Error.@"unreachable"; - }, - .argument_declaration => |argument_declaration| { - var argument_buffer: [16]*LLVM.Value.Argument = undefined; - var argument_count: usize = argument_buffer.len; - llvm.function.getArguments(&argument_buffer, &argument_count); - const arguments = argument_buffer[0..argument_count]; - const argument_index = llvm.arg_index; - llvm.arg_index += 1; - const argument = arguments[argument_index]; - const name = unit.getIdentifier(argument_declaration.declaration.name); - argument.toValue().setName(name.ptr, name.len); - const argument_type_index = argument_declaration.declaration.type; - switch (unit.types.get(argument_type_index).*) { - .void, .noreturn, .type => unreachable, - .comptime_int => unreachable, - .bool => unreachable, - .@"struct" => {}, - .@"enum" => {}, - .function => unreachable, - .integer => {}, - .pointer => {}, - } - const argument_type = argument.toValue().getType(); - const alloca_array_size: ?*LLVM.Value = null; - const argument_value = argument.toValue(); - const declaration_alloca = llvm.builder.createAlloca(argument_type, address_space, alloca_array_size, "", "".len) orelse return LLVM.Value.Instruction.Error.alloca; - - if (unit.descriptor.generate_debug_information) { - const debug_declaration_type = try llvm.getDebugType(unit, context, argument_declaration.declaration.type); + const is_volatile = false; + const store = llvm.builder.createStore(argument_value, declaration_alloca.toValue(), is_volatile) orelse return LLVM.Value.Instruction.Error.store; + _ = store; // autofix + try llvm.argument_allocas.putNoClobber(context.allocator, instruction_index, declaration_alloca.toValue()); + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, declaration_alloca.toValue()); + }, + .debug_declare_local_variable => |declare_local_variable| { + const local_variable = declare_local_variable.variable; + const debug_declaration_type = try llvm.getDebugType(unit, context, local_variable.declaration.type); const always_preserve = true; const flags = LLVM.DebugInfo.Node.Flags{ .visibility = .none, @@ -3374,68 +3361,122 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo .little_endian = false, .all_calls_described = false, }; - const declaration_name = unit.getIdentifier(argument_declaration.declaration.name); - const line = argument_declaration.declaration.line; - const column = argument_declaration.declaration.column; - const debug_parameter_variable = llvm.debug_info_builder.createParameterVariable(llvm.scope, declaration_name.ptr, declaration_name.len, argument_index, llvm.file, line, debug_declaration_type, always_preserve, flags) orelse unreachable; - const insert_declare = llvm.debug_info_builder.insertDeclare(declaration_alloca.toValue(), debug_parameter_variable, llvm.context, line, column, (llvm.function.getSubprogram() orelse unreachable).toLocalScope().toScope(), llvm.builder.getInsertBlock() orelse unreachable); + const alignment = 0; + const declaration_name = unit.getIdentifier(local_variable.declaration.name); + const line = local_variable.declaration.line; + const column = local_variable.declaration.column; + const debug_local_variable = llvm.debug_info_builder.createAutoVariable(llvm.scope, declaration_name.ptr, declaration_name.len, llvm.file, line, debug_declaration_type, always_preserve, flags, alignment) orelse unreachable; + + const local = llvm.alloca_map.get(declare_local_variable.stack).?; + + const insert_declare = llvm.debug_info_builder.insertDeclare(local, debug_local_variable, llvm.context, line, column, (llvm.function.getSubprogram() orelse unreachable).toLocalScope().toScope(), llvm.builder.getInsertBlock() orelse unreachable); _ = insert_declare; - } + }, + .insert_value => |insert_value| { + const aggregate = try llvm.emitRightValue(unit, context, insert_value.expression); + const value = try llvm.emitRightValue(unit, context, insert_value.new_value); + const indices = [1]c_uint{insert_value.index}; + const instruction = llvm.builder.createInsertValue(aggregate, value, &indices, indices.len, "", "".len) orelse unreachable; + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, instruction); + }, + .extract_value => |extract_value| { + const aggregate = try llvm.emitRightValue(unit, context, extract_value.expression); + const indices = [1]c_uint{extract_value.index}; + const instruction = llvm.builder.createExtractValue(aggregate, &indices, indices.len, "", "".len) orelse unreachable; + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, instruction); + }, + .integer_compare => |integer_compare| { + const left = try llvm.emitRightValue(unit, context, integer_compare.left); + const right = try llvm.emitRightValue(unit, context, integer_compare.right); + const comparison_id: LLVM.Value.Instruction.ICmp.Kind = switch (integer_compare.id) { + .equal => .eq, + .not_equal => .ne, + .unsigned_less => .ult, + .unsigned_less_equal => .ule, + .unsigned_greater => .ugt, + .unsigned_greater_equal => .uge, + .signed_less => .slt, + .signed_less_equal => .sle, + .signed_greater => .sgt, + .signed_greater_equal => .sge, + }; + const icmp = llvm.builder.createICmp(comparison_id, left, right, "", "".len) orelse unreachable; + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, icmp); + }, + .jump => |jump| { + const target_block = if (llvm.llvm_block_map.get(jump.to)) |target_block| target_block else blk: { + const jump_target_block_node = try llvm.createBasicBlock(context, jump.to, "jmp_target"); + block_command_list.append(jump_target_block_node); - const is_volatile = false; - const store = llvm.builder.createStore(argument_value, declaration_alloca.toValue(), is_volatile) orelse return LLVM.Value.Instruction.Error.store; - _ = store; // autofix - try llvm.argument_allocas.putNoClobber(context.allocator, instruction_index, declaration_alloca.toValue()); - try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, declaration_alloca.toValue()); - }, - .debug_declare_local_variable => |declare_local_variable| { - const local_variable = declare_local_variable.variable; - const debug_declaration_type = try llvm.getDebugType(unit, context, local_variable.declaration.type); - const always_preserve = true; - const 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, - }; + // TODO: make this efficient + break :blk llvm.llvm_block_map.get(jump_target_block_node.data).?; + }; - const alignment = 0; - const declaration_name = unit.getIdentifier(local_variable.declaration.name); - const line = local_variable.declaration.line; - const column = local_variable.declaration.column; - const debug_local_variable = llvm.debug_info_builder.createAutoVariable(llvm.scope, declaration_name.ptr, declaration_name.len, llvm.file, line, debug_declaration_type, always_preserve, flags, alignment) orelse unreachable; + const br = llvm.builder.createBranch(target_block) orelse unreachable; + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, br.toValue()); + }, + .branch => |branch| { + const taken_node = try llvm.createBasicBlock(context, branch.taken, "taken_block"); + const not_taken_node = try llvm.createBasicBlock(context, branch.not_taken, "not_taken_block"); + block_command_list.insertAfter(block_node, taken_node); + block_command_list.insertAfter(taken_node, not_taken_node); - const local = llvm.alloca_map.get(declare_local_variable.stack).?; + // TODO: make this fast + const taken_block = llvm.llvm_block_map.get(taken_node.data).?; + const not_taken_block = llvm.llvm_block_map.get(not_taken_node.data).?; - const insert_declare = llvm.debug_info_builder.insertDeclare(local, debug_local_variable, llvm.context, line, column, (llvm.function.getSubprogram() orelse unreachable).toLocalScope().toScope(), llvm.builder.getInsertBlock() orelse unreachable); - _ = insert_declare; - }, - else => |t| @panic(@tagName(t)), + const condition = llvm.llvm_instruction_map.get(branch.condition).?; + const branch_weights = null; + const unpredictable = null; + const br = llvm.builder.createConditionalBranch(condition, taken_block, not_taken_block, branch_weights, unpredictable) orelse unreachable; + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, br.toValue()); + }, + .phi => |phi| { + const phi_type = try llvm.getType(unit, context, phi.type); + const reserved_value_count: c_uint = @intCast(phi.values.items.len); + const phi_name = "phi"; + const phi_node = llvm.builder.createPhi(phi_type, reserved_value_count, phi_name, phi_name.len) orelse unreachable; + + for (phi.values.items, phi.basic_blocks.items) |sema_value, sema_block| { + const value = llvm.llvm_value_map.get(sema_value) orelse try llvm.emitRightValue(unit, context, sema_value); + const value_basic_block = llvm.llvm_block_map.get(sema_block).?; + phi_node.addIncoming(value, value_basic_block); + } + + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, phi_node.toValue()); + }, + .umin => |umin| { + const name = "llvm.umin"; + const intrinsic_id = LLVM.lookupIntrinsic(name.ptr, name.len); + assert(intrinsic_id != .none); + + const intrinsic_return_type = try llvm.getType(unit, context, umin.type); + const types = [_]*LLVM.Type{intrinsic_return_type}; + const intrinsic_function = llvm.module.getIntrinsicDeclaration(intrinsic_id, &types, types.len) orelse return LLVM.Value.Error.intrinsic; + const intrinsic_function_type = llvm.context.getIntrinsicType(intrinsic_id, &types, types.len) orelse return LLVM.Type.Error.intrinsic; + + const left = try llvm.emitRightValue(unit, context, umin.left); + const right = try llvm.emitRightValue(unit, context, umin.right); + const arguments = [_]*LLVM.Value{ left, right }; + + const call = llvm.builder.createCall(intrinsic_function_type, intrinsic_function.toValue(), &arguments, arguments.len, "min".ptr, "min".len, null) orelse return LLVM.Value.Instruction.Error.call; + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, call.toValue()); + }, + .get_element_pointer => |gep| { + const index = try llvm.emitRightValue(unit, context, gep.index); + const pointer = llvm.llvm_instruction_map.get(gep.pointer).?; + const t = try llvm.getType(unit, context, gep.base_type); + const indices = [1]*LLVM.Value{index}; + const in_bounds = true; + const get_element_pointer = llvm.builder.createGEP(t, pointer, &indices, indices.len, "gep", "gep".len, in_bounds) orelse unreachable; + try llvm.llvm_instruction_map.putNoClobber(context.allocator, instruction_index, get_element_pointer); + }, + else => |t| @panic(@tagName(t)), + } } + + _ = block_command_list.popFirst(); } if (!builder.isCurrentBlockTerminated()) { @@ -3445,24 +3486,34 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo } else if (return_type == Compilation.Type.Index.void) { _ = builder.createRet(null) orelse unreachable; } else { - unreachable; + var message_len: usize = 0; + const function_str = llvm.function.toString(&message_len); + const function_dump = function_str[0..message_len]; + std.debug.panic("Function block with no termination:\n{s}\n", .{function_dump}); } } - // const verify_function = true; - // if (verify_function) { - // var message_ptr: [*]const u8 = undefined; - // var message_len: usize = 0; - // const result = llvm.function.verify(&message_ptr, &message_len); - // - // if (!result) { - // var function_len: usize = 0; - // const function_ptr = llvm.function.toString(&function_len); - // const function_ir = function_ptr[0..function_len]; - // const error_message = message_ptr[0..message_len]; - // std.debug.panic("\n{s}. LLVM verification for the function above failed:\n{s}\n", .{ function_ir, error_message }); - // } - // } + const verify_function = true; + if (verify_function) { + var message_ptr: [*]const u8 = undefined; + var message_len: usize = 0; + const result = llvm.function.verify(&message_ptr, &message_len); + + if (!result) { + var function_len: usize = 0; + const function_ptr = llvm.function.toString(&function_len); + const function_ir = function_ptr[0..function_len]; + const error_message = message_ptr[0..message_len]; + std.debug.print("PANIC: Failed to verify function:\n{s}\n\n{s}\n", .{error_message, function_ir}); + + var module_len: usize = 0; + const module_ptr = llvm.module.toString(&module_len); + const module_dump = module_ptr[0..module_len]; + + std.debug.print("\nLLVM verification for function inside module failed:\nFull module: {s}\n```\n{s}\n```\n{s}\n", .{ module_dump, function_ir, error_message }); + @panic("LLVM function verification failed"); + } + } } llvm.debug_info_builder.finalize(); diff --git a/bootstrap/backend/llvm_bindings.zig b/bootstrap/backend/llvm_bindings.zig index f02fc7c..babb369 100644 --- a/bootstrap/backend/llvm_bindings.zig +++ b/bootstrap/backend/llvm_bindings.zig @@ -49,8 +49,9 @@ pub extern fn NativityLLVMFunctionGetReturnType(function: *LLVM.Value.Function) pub extern fn NativityLLVMBuilderCreateAlloca(builder: *LLVM.Builder, type: *LLVM.Type, address_space: c_uint, array_size: ?*LLVM.Value, name_ptr: [*]const u8, name_len: usize) ?*LLVM.Value.Instruction.Alloca; pub extern fn NativityLLVMBuilderCreateStore(builder: *LLVM.Builder, value: *LLVM.Value, pointer: *LLVM.Value, is_volatile: bool) ?*LLVM.Value.Instruction.Store; pub extern fn NativityLLVMContextGetConstantInt(context: *LLVM.Context, bit_count: c_uint, value: u64, is_signed: bool) ?*LLVM.Value.Constant.Int; -pub extern fn NativityLLVMContextGetConstString(context: *LLVM.Context, name_ptr: [*]const u8, name_len: usize, null_terminate: bool) ?*LLVM.Value.Constant; -pub extern fn NativityLLVMContextGetConstArray(array_type: *LLVM.Type.Array, value_ptr: [*]const *LLVM.Value.Constant, value_count: usize) ?*LLVM.Value.Constant; +pub extern fn NativityLLVMContextGetConstantString(context: *LLVM.Context, name_ptr: [*]const u8, name_len: usize, null_terminate: bool) ?*LLVM.Value.Constant; +pub extern fn NativityLLVMGetConstantArray(array_type: *LLVM.Type.Array, value_ptr: [*]const *LLVM.Value.Constant, value_count: usize) ?*LLVM.Value.Constant; +pub extern fn NativityLLVMGetConstantStruct(struct_type: *LLVM.Type.Struct, constant_ptr: [*]const *LLVM.Value.Constant, constant_len: usize) ?*LLVM.Value.Constant; pub extern fn NativityLLVMBuilderCreateICmp(builder: *LLVM.Builder, integer_comparison: LLVM.Value.Instruction.ICmp.Kind, left: *LLVM.Value, right: *LLVM.Value, name_ptr: [*]const u8, name_len: usize) ?*LLVM.Value; pub extern fn NativityLLVMBuilderCreateLoad(builder: *LLVM.Builder, type: *LLVM.Type, value: *LLVM.Value, is_volatile: bool, name_ptr: [*]const u8, name_len: usize) ?*LLVM.Value.Instruction.Load; pub extern fn NativityLLVMBuilderCreateRet(builder: *LLVM.Builder, value: ?*LLVM.Value) ?*LLVM.Value.Instruction.Ret; @@ -90,6 +91,7 @@ pub extern fn NativityLLVMFunctionToString(function: *LLVM.Value.Function, messa pub extern fn NativityLLVMBuilderIsCurrentBlockTerminated(builder: *LLVM.Builder) bool; pub extern fn NativityLLVMGetUndefined(type: *LLVM.Type) ?*LLVM.Value.Constant.Undefined; +pub extern fn NativityLLVMGetPoisonValue(type: *LLVM.Type) ?*LLVM.Value.Constant.Poison; pub extern fn NativityLLVMFunctionSetCallingConvention(function: *LLVM.Value.Function, calling_convention: LLVM.Value.Function.CallingConvention) void; pub extern fn NativityLLVMFunctionGetCallingConvention(function: *LLVM.Value.Function) LLVM.Value.Function.CallingConvention; pub extern fn NativityLLVMFunctionSetSubprogram(function: *LLVM.Value.Function, subprogram: *LLVM.DebugInfo.Subprogram) void; @@ -114,9 +116,11 @@ pub extern fn NativityLLVMModuleGetIntrinsicDeclaration(module: *LLVM.Module, in pub extern fn NativityLLVMContextGetIntrinsicType(context: *LLVM.Context, intrinsic_id: LLVM.Value.IntrinsicID, parameter_type_ptr: [*]const *LLVM.Type, parameter_type_count: usize) ?*LLVM.Type.Function; pub extern fn NativityLLVMBuilderCreateExtractValue(builder: *LLVM.Builder, aggregate: *LLVM.Value, indices_ptr: [*]const c_uint, indices_len: usize, name_ptr: [*]const u8, name_len: usize) ?*LLVM.Value; pub extern fn NativityLLVMBuilderCreateInsertValue(builder: *LLVM.Builder, aggregate: *LLVM.Value, value: *LLVM.Value, indices_ptr: [*]const c_uint, indices_len: usize, name_ptr: [*]const u8, name_len: usize) ?*LLVM.Value; -pub extern fn NativityLLVMContextCreateGlobalStringPointer(builder: *LLVM.Builder, string_ptr: [*]const u8, string_len: usize, name_ptr: [*]const u8, name_len: usize, address_space: c_uint, module: *LLVM.Module) ?*LLVM.Value.Constant; +pub extern fn NativityLLVMBuilderCreateGlobalString(builder: *LLVM.Builder, string_ptr: [*]const u8, string_len: usize, name_ptr: [*]const u8, name_len: usize, address_space: c_uint, module: *LLVM.Module) ?*LLVM.Value.Constant.GlobalVariable; +pub extern fn NativityLLVMBuilderCreateGlobalStringPointer(builder: *LLVM.Builder, string_ptr: [*]const u8, string_len: usize, name_ptr: [*]const u8, name_len: usize, address_space: c_uint, module: *LLVM.Module) ?*LLVM.Value.Constant; pub extern fn NativityLLVMCompareTypes(a: *LLVM.Type, b: *LLVM.Type) bool; -pub extern fn NativityLLVMCreatePhiNode(type: *LLVM.Type, reserved_value_count: c_uint, name_ptr: [*]const u8, name_len: usize, basic_block: ?*LLVM.Value.BasicBlock) ?*LLVM.Value.Instruction.PhiNode; +pub extern fn NativityLLVMBuilderCreatePhi(builder: *LLVM.Builder, type: *LLVM.Type, reserved_value_count: c_uint, name_ptr: [*]const u8, name_len: usize) ?*LLVM.Value.Instruction.PhiNode; +pub extern fn NativityLLVMPhiAddIncoming(phi: *LLVM.Value.Instruction.PhiNode, value: *LLVM.Value, basic_block: *LLVM.Value.BasicBlock) void; pub extern fn NativityLLVMAllocatGetAllocatedType(alloca: *LLVM.Value.Instruction.Alloca) *LLVM.Type; pub extern fn NativityLLVMValueToAlloca(value: *LLVM.Value) ?*LLVM.Value.Instruction.Alloca; diff --git a/bootstrap/data_structures.zig b/bootstrap/data_structures.zig index b2f6e1e..e762e9e 100644 --- a/bootstrap/data_structures.zig +++ b/bootstrap/data_structures.zig @@ -171,6 +171,11 @@ pub fn enumFromString(comptime E: type, string: []const u8) ?E { } else null; } +pub fn hash(string: []const u8) u32 { + const string_key: u32 = @truncate(std.hash.Wyhash.hash(0, string)); + return string_key; +} + pub fn StringKeyMap(comptime Value: type) type { return struct { list: std.MultiArrayList(Data) = .{}, diff --git a/bootstrap/frontend/parser.zig b/bootstrap/frontend/parser.zig index e522ba3..87b6e01 100644 --- a/bootstrap/frontend/parser.zig +++ b/bootstrap/frontend/parser.zig @@ -1583,7 +1583,7 @@ const Analyzer = struct { else => unreachable, }; - const type_node = if (analyzer.peekToken() == .operator_left_parenthesis) b: { + const type_node = if (analyzer.hasTokens() and analyzer.peekToken() == .operator_left_parenthesis) b: { analyzer.consumeToken(); const result = try analyzer.typeExpression(); _ = try analyzer.expectToken(.operator_right_parenthesis); diff --git a/build/test_runner.zig b/build/test_runner.zig index 30d8d3f..c9dde74 100644 --- a/build/test_runner.zig +++ b/build/test_runner.zig @@ -29,41 +29,78 @@ pub fn main() !void { standalone_test_dir.close(); + const total_compilation_count = standalone_test_names.items.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; for (standalone_test_names.items) |standalone_test_name| { - defer ran_test_count += 1; 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 process_run = try std.ChildProcess.run(.{ + const compile_run = try std.ChildProcess.run(.{ .allocator = allocator, // TODO: delete -main_source_file? .argv = &.{"zig-out/bin/nat", "exe", "-main_source_file", source_file_path}, }); - const result: TestError!bool = switch (process_run.term) { + 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 success = result catch b: { - failed_test_count += 1; + const compilation_success = compilation_result catch b: { + failed_compilation_count += 1; break :b false; }; - std.debug.print("[{s}]\n", .{if (success) "OK" else "FAIL"}); - if (process_run.stdout.len > 0) { - std.debug.print("STDOUT:\n\n{s}\n\n", .{process_run.stdout}); + std.debug.print("[COMPILATION {s}] ", .{if (compilation_success) "OK" else "FAILED"}); + if (compile_run.stdout.len > 0) { + std.debug.print("STDOUT:\n\n{s}\n\n", .{compile_run.stdout}); } - if (process_run.stderr.len > 0) { - std.debug.print("STDERR:\n\n{s}\n\n", .{process_run.stderr}); + 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/", standalone_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) "OK" else "FAILED"}); + 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", .{}); } } - std.debug.print("\nTOTAL: {}. RAN: {}. FAILED: {}\n", .{total_test_count, ran_test_count, failed_test_count}); - if (failed_test_count > 0) { + std.debug.print("\nTOTAL COMPILATIONS: {}. FAILED: {}\n", .{total_compilation_count, failed_compilation_count}); + std.debug.print("\nTOTAL 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; } } diff --git a/lib/std/os.nat b/lib/std/os.nat index 44ff06f..b7021d3 100644 --- a/lib/std/os.nat +++ b/lib/std/os.nat @@ -30,7 +30,7 @@ const FileDescriptor = struct{ switch (current) { .linux => { const len: usize = #min(max_file_operation_byte_count, bytes.len); - if (linux.unwrapSyscall(syscall_result = #syscall(#cast(linux.Syscall.read), #cast(file_descriptor.handle), #cast(bytes.ptr), len))) |byte_count| { + if (linux.unwrap_syscall(syscall_result = #syscall(#cast(linux.Syscall.read), #cast(file_descriptor.handle), #cast(bytes.ptr), len))) |byte_count| { return byte_count; } else { return null; @@ -48,7 +48,7 @@ const FileDescriptor = struct{ .linux => { const len: usize = #min(max_file_operation_byte_count, bytes.len); const raw_result = #syscall(#cast(linux.Syscall.write), #cast(file_descriptor.handle), #cast(bytes.ptr), len); - if (linux.unwrapSyscall(syscall_result = raw_result)) |byte_count| { + if (linux.unwrap_syscall(syscall_result = raw_result)) |byte_count| { return byte_count; } else { return null; @@ -93,7 +93,7 @@ const allocate_virtual_memory = fn(address: ?[&]u8, length: usize, general_prote switch (current) { .linux => { - if (linux.unwrapSyscall(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 = -1, offset = 0))) |result_address| { const pointer: [&]u8 = #cast(result_address); return pointer; } else { @@ -108,7 +108,7 @@ const allocate_virtual_memory = fn(address: ?[&]u8, length: usize, general_prote const free_virtual_memory = fn(bytes_ptr: [&]const u8, bytes_len: usize) bool { switch (current) { .linux => { - if (linux.unwrapSyscall(syscall_result = linux.munmap(bytes_ptr, bytes_len))) |result| { + if (linux.unwrap_syscall(syscall_result = linux.munmap(bytes_ptr, bytes_len))) |result| { return result == 0; } else { return false; @@ -123,7 +123,7 @@ const readlink = fn(file_path: [&:0]const u8, buffer: []u8) ?[]u8 { .linux => { const raw_result = linux.readlink(file_path, bytes_ptr = buffer.ptr, bytes_len = buffer.len); - if (linux.unwrapSyscall(syscall_result = raw_result)) |byte_count| { + if (linux.unwrap_syscall(syscall_result = raw_result)) |byte_count| { const bytes = buffer[0..byte_count]; return bytes; } else { @@ -159,7 +159,7 @@ const Process = struct{ const duplicate_process = fn () ?Process.Id { switch (current) { .linux => { - if (linux.unwrapSyscall(syscall_result = linux.fork())) |fork_result| { + if (linux.unwrap_syscall(syscall_result = linux.fork())) |fork_result| { return #cast(fork_result); } else { return null; @@ -181,7 +181,7 @@ const execute = fn(path: [&:0]const u8, argv: [&:null]const ?[&:0]const u8, env: const event_file_descriptor = fn(initial_value: u32, flags: u32) ?s32 { switch (current) { .linux => { - if (linux.unwrapSyscall(syscall_result = linux.event_file_descriptor(count = initial_value, flags))) |raw_result| { + if (linux.unwrap_syscall(syscall_result = linux.event_file_descriptor(count = initial_value, flags))) |raw_result| { const result: s32 = #cast(raw_result); return result; } else { @@ -195,7 +195,7 @@ const event_file_descriptor = fn(initial_value: u32, flags: u32) ?s32 { const dup2 = fn(old_file_descriptor: system.FileDescriptor, new_file_descriptor: system.FileDescriptor) bool { switch (current) { .linux => { - if (linux.unwrapSyscall(syscall_result = linux.dup2(old = old_file_descriptor, new = new_file_descriptor))) |_| { + if (linux.unwrap_syscall(syscall_result = linux.dup2(old = old_file_descriptor, new = new_file_descriptor))) |_| { return true; } else { return false; @@ -208,7 +208,7 @@ const dup2 = fn(old_file_descriptor: system.FileDescriptor, new_file_descriptor: const open = fn(path: [&:0]const u8, flags: u32, permissions: u32) ?FileDescriptor{ switch (current) { .linux => { - if (linux.unwrapSyscall(syscall_result = linux.open(path, flags, permissions))) |raw_result| { + if (linux.unwrap_syscall(syscall_result = linux.open(path, flags, permissions))) |raw_result| { const file_descriptor = FileDescriptor{ .handle = #cast(raw_result), }; @@ -225,7 +225,7 @@ const open = fn(path: [&:0]const u8, flags: u32, permissions: u32) ?FileDescript const close = fn(file_descriptor: s32) bool { switch (current) { .linux => { - if (linux.unwrapSyscall(syscall_result = linux.close(file_descriptor))) |_| { + if (linux.unwrap_syscall(syscall_result = linux.close(file_descriptor))) |_| { return true; } else { return false; @@ -239,7 +239,7 @@ const pipe2 = fn(flags: u32) ?[2]system.FileDescriptor{ switch (current) { .linux => { var pipe: [2]s32 = undefined; - if (linux.unwrapSyscall(syscall_result = linux.pipe2(pipe_pointer = pipe.&, flags))) |_| { + if (linux.unwrap_syscall(syscall_result = linux.pipe2(pipe_pointer = pipe.&, flags))) |_| { return pipe; } else { return null; @@ -268,7 +268,7 @@ const PollFileDescriptor = system.PollFileDescriptor; const poll = fn(file_descriptors: []PollFileDescriptor, timeout: s32) ?usize { switch (current) { .linux => { - if (linux.unwrapSyscall(syscall_result = linux.poll(file_descriptors = file_descriptors.ptr, file_descriptor_count = file_descriptors.len, timeout = timeout))) |result| { + if (linux.unwrap_syscall(syscall_result = linux.poll(file_descriptors = file_descriptors.ptr, file_descriptor_count = file_descriptors.len, timeout = timeout))) |result| { return result; } else { return null; @@ -339,7 +339,7 @@ const waitpid = fn(pid: Process.Id, flags: u32) ?u32 { 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 (linux.unwrapSyscall(syscall_result = raw_syscall_result)) |_| { + if (linux.unwrap_syscall(syscall_result = raw_syscall_result)) |_| { return status; } else { return null; @@ -355,7 +355,7 @@ const waitpid = fn(pid: Process.Id, flags: u32) ?u32 { const memfd_create = fn(name: [&:0]const u8, flags: u32) ?FileDescriptor{ switch (current) { .linux => { - if (linux.unwrapSyscall(syscall_result = linux.memfd_create(path, flags))) |raw_result| { + if (linux.unwrap_syscall(syscall_result = linux.memfd_create(path, flags))) |raw_result| { const file_descriptor = FileDescriptor{ .handle = #cast(raw_result), }; diff --git a/lib/std/os/linux.nat b/lib/std/os/linux.nat index 1d3d1d4..7bc0045 100644 --- a/lib/std/os/linux.nat +++ b/lib/std/os/linux.nat @@ -490,7 +490,7 @@ const memfd_create = fn(name: [&:0]const u8, flags: u32) usize { return result; } -const unwrapSyscall = fn(syscall_result: usize) ?usize { +const unwrap_syscall = fn(syscall_result: usize) ?usize { const signed_syscall_result: ssize = #cast(syscall_result); if (signed_syscall_result >= 0) { return syscall_result; diff --git a/lib/std/std.nat b/lib/std/std.nat index 84edca3..195f262 100644 --- a/lib/std/std.nat +++ b/lib/std/std.nat @@ -18,7 +18,7 @@ const print = fn(bytes: []const u8) void { const file_writer = FileWriter{ .descriptor = file_descriptor, }; - _ = file_writer.writeAll(bytes); + _ = file_writer.write_all(bytes); } const format_usize = fn(n: usize, buffer: &[65]u8) []u8 { @@ -46,7 +46,7 @@ const print_usize = fn(n: usize) void { const file_writer = FileWriter{ .descriptor = file_descriptor, }; - _ = file_writer.writeAll(bytes); + _ = file_writer.write_all(bytes); } const print_u8 = fn(n: u8) void { @@ -145,7 +145,7 @@ const FileWriter = struct{ return file_writer.descriptor.write(bytes); } - const writeAll = fn(file_writer: FileWriter, bytes: []const u8) bool { + const write_all = fn(file_writer: FileWriter, bytes: []const u8) bool { var bytes_written: usize = 0; while (bytes_written < bytes.len) { diff --git a/test/standalone/else_if/main.nat b/test/standalone/else_if/main.nat new file mode 100644 index 0000000..07ad19e --- /dev/null +++ b/test/standalone/else_if/main.nat @@ -0,0 +1,21 @@ +const main = fn () s32 { + const a = foo(5); + if (a != 123) { + return 1; + } + const b = foo(5); + if (b != 123) { + return 1; + } + return a - b; +} + +const foo = fn (arg: s32) s32 { + if (arg < 0) { + return 12312; + } else if (arg > 0) { + return 123; + } else { + return 0; + } +} diff --git a/todo_test/standalone/hello_world/main.nat b/test/standalone/hello_world/main.nat similarity index 100% rename from todo_test/standalone/hello_world/main.nat rename to test/standalone/hello_world/main.nat diff --git a/test/standalone/if/main.nat b/test/standalone/if/main.nat new file mode 100644 index 0000000..0042318 --- /dev/null +++ b/test/standalone/if/main.nat @@ -0,0 +1,19 @@ +const main = fn () s32 { + const a = foo(5); + if (a != 6) { + return 1; + } + const b = foo(5); + if (b != 6) { + return 1; + } + return a - b; +} + +const foo = fn (arg: s32) s32 { + if (arg > 1) { + return 6; + } else { + return 0; + } +} diff --git a/test/standalone/if_not_else/main.nat b/test/standalone/if_not_else/main.nat new file mode 100644 index 0000000..d538365 --- /dev/null +++ b/test/standalone/if_not_else/main.nat @@ -0,0 +1,19 @@ +const main = fn() s32 { + const a = foo(5); + if (a != 12412) { + return 1; + } + const b = foo(5); + if (b != 12412) { + return 1; + } + return a - b; +} + +const foo = fn(arg: s32) s32 { + if (arg > 1241) { + return 125151; + } + + return 12412; +} diff --git a/test/standalone/loop_return/main.nat b/test/standalone/loop_return/main.nat new file mode 100644 index 0000000..96066a4 --- /dev/null +++ b/test/standalone/loop_return/main.nat @@ -0,0 +1,24 @@ +const main = fn () s32 { + const a = foo(5142); + if (a != 2501) { + return 1; + } + const b = foo(5142); + if (b != 2501) { + return 1; + } + return #cast(a - b); +} + +const foo = fn (arg: u32) u32 { + var i: u32 = 0; + while (i < arg) { + if (i > 2500) { + return i; + } + + i += 1; + } + + return 321; +} diff --git a/test/standalone/loop_return_only_else/main.nat b/test/standalone/loop_return_only_else/main.nat new file mode 100644 index 0000000..eb42d29 --- /dev/null +++ b/test/standalone/loop_return_only_else/main.nat @@ -0,0 +1,19 @@ +const main = fn () s32 { + const a = foo(5142); + const b = foo(5142); + return #cast(a - b); +} + +const foo = fn (arg: u32) u32 { + var i: u32 = 0; + while (i < arg) { + if (i < 2500) { + i += 1; + } else { + return i - 100; + } + + } + + return 321; +} diff --git a/test/standalone/nested_if/main.nat b/test/standalone/nested_if/main.nat new file mode 100644 index 0000000..98b6427 --- /dev/null +++ b/test/standalone/nested_if/main.nat @@ -0,0 +1,17 @@ +const main = fn () s32 { + const a = foo(5); + const b = foo(5); + return a - b; +} + +const foo = fn (arg: s32) s32 { + if (arg > 1) { + if (arg < 5) { + return 6; + } else { + return 5; + } + } else { + return 0; + } +}