diff --git a/src/LLVM.zig b/src/LLVM.zig index d9a4b51..efda199 100644 --- a/src/LLVM.zig +++ b/src/LLVM.zig @@ -1245,6 +1245,14 @@ pub const DI = struct { pub fn create_pointer_type(builder: *DI.Builder, element_type: *DI.Type, bit_size: u64, align_in_bits: u32, address_space: c_uint, name: []const u8) *DI.Type.Derived { return api.LLVMDIBuilderCreatePointerType(builder, element_type, bit_size, align_in_bits, address_space, name.ptr, name.len); } + + pub fn create_enumerator(builder: *DI.Builder, name: []const u8, value: i64, is_signed: bool) *DI.Enumerator { + return api.LLVMDIBuilderCreateEnumerator(builder, name.ptr, name.len, value, @intFromBool(is_signed)); + } + + pub fn create_enumeration_type(builder: *DI.Builder, scope: *DI.Scope, name: []const u8, file: *DI.File, line: c_uint, bit_size: u64, align_in_bits: u32, enumeration_types: []const *DI.Enumerator, backing_type: *DI.Type) *DI.Type.Composite { + return api.LLVMDIBuilderCreateEnumerationType(builder, scope, name.ptr, name.len, file, line, bit_size, align_in_bits, enumeration_types.ptr, @intCast(enumeration_types.len), backing_type); + } }; pub const create_debug_location = api.LLVMDIBuilderCreateDebugLocation; @@ -1293,6 +1301,8 @@ pub const DI = struct { }; }; + pub const Enumerator = opaque {}; + pub const Flags = packed struct(u32) { visibility: Visibility = .none, forward_declaration: bool = false, diff --git a/src/compiler.bbb b/src/compiler.bbb index d192d43..a4fe5cc 100644 --- a/src/compiler.bbb +++ b/src/compiler.bbb @@ -1,3 +1,12 @@ +Linux_PROT = bits u32 +{ + read: u1, + write: u1, + exec: u1, + sem: u1, + _: u28, +} + OSProtectionFlags = bits { read: u1, @@ -13,6 +22,10 @@ OSMapFlags = bits populate: u1, } +os_reserve = fn (base: u64, protection: OSProtectionFlags, map: OSMapFlags) &u8 +{ +} + Arena = struct { reserved_size: u64, diff --git a/src/converter.zig b/src/converter.zig index f700f13..a31401e 100644 --- a/src/converter.zig +++ b/src/converter.zig @@ -68,6 +68,7 @@ const GlobalKind = enum { @"fn", @"struct", bits, + @"enum", }; const FunctionKeyword = enum { @@ -1112,7 +1113,15 @@ pub const FloatType = struct { kind: Kind, }; -pub const Enumerator = struct {}; +pub const Enumerator = struct { + fields: []const Enumerator.Field, + backing_type: *Type, + + pub const Field = struct { + name: []const u8, + value: u64, + }; +}; pub const PointerType = struct { type: *Type, @@ -1205,7 +1214,7 @@ pub const Type = struct { pub fn get_evaluation_kind(ty: *const Type) EvaluationKind { return switch (ty.bb) { .structure, .array => .aggregate, - .integer, .bits, .pointer => .scalar, + .integer, .bits, .pointer, .enumerator => .scalar, else => @trap(), }; } @@ -1263,9 +1272,9 @@ pub const Type = struct { .bits => |bits| bits.backing_type.get_byte_alignment(), .function => 1, .void, .forward_declaration, .noreturn => unreachable, - .array => |*array| array.element_type.get_byte_alignment(), + .array => |array| array.element_type.get_byte_alignment(), .pointer => 8, - .enumerator => @trap(), + .enumerator => |enumerator| enumerator.backing_type.get_byte_alignment(), .float => @trap(), .vector => @trap(), }; @@ -1547,7 +1556,7 @@ const Converter = struct { return value; } - fn parse_integer(noalias converter: *Converter, noalias module: *Module, expected_type: *Type, sign: bool) *Value { + fn parse_integer_value(converter: *Converter, sign: bool) u64 { const start = converter.offset; const integer_start_ch = converter.content[start]; assert(!is_space(integer_start_ch)); @@ -1589,6 +1598,12 @@ const Converter = struct { else => unreachable, }; + return absolute_value; + } + + fn parse_integer(noalias converter: *Converter, noalias module: *Module, expected_type: *Type, sign: bool) *Value { + const absolute_value = converter.parse_integer_value(sign); + const value: u64 = switch (sign) { true => @bitCast(-@as(i64, @intCast(absolute_value))), false => absolute_value, @@ -2565,6 +2580,7 @@ const Converter = struct { cast, cast_to, extend, + int_from_enum, trap, truncate, va_start, @@ -2661,6 +2677,29 @@ const Converter = struct { return value; }, + .int_from_enum => { + const source_value = converter.parse_value(module, null, .value); + converter.skip_space(); + converter.expect_character(right_parenthesis); + if (source_value.type.bb != .enumerator) { + converter.report_error(); + } + const original_target_type = source_value.type.bb.enumerator.backing_type; + const target_type = expected_type orelse original_target_type; + + if (target_type.bb != .integer) { + converter.report_error(); + } + + if (target_type.get_bit_size() < original_target_type.get_bit_size()) { + converter.report_error(); + } + + const value = module.values.add(); + value.* = source_value.*; + value.type = target_type; + return value; + }, .trap => { converter.expect_character(right_parenthesis); @@ -3184,6 +3223,38 @@ const Converter = struct { converter.skip_space(); break :blk .not_zero; }, + '.' => { + const expected_ty = expected_type orelse converter.report_error(); + if (expected_ty.bb != .enumerator) { + converter.report_error(); + } + converter.offset += 1; + + converter.skip_space(); + const field_name = converter.parse_identifier(); + const field_value = for (expected_ty.bb.enumerator.fields) |*field| { + if (lib.string.equal(field.name, field_name)) { + break field.value; + } + } else { + converter.report_error(); + }; + const value = module.values.add(); + value.* = .{ + .bb = .{ + .constant_integer = .{ + .value = field_value, + .signed = false, + }, + }, + .llvm = expected_ty.llvm.handle.to_integer().get_constant(field_value, @intFromBool(false)).to_value(), + .type = expected_ty, + .lvalue = false, + .dereference_to_assign = false, + }; + + return value; + }, else => os.abort(), }; @@ -4459,797 +4530,888 @@ pub noinline fn convert(arena: *Arena, options: ConvertOptions) void { const global_string = converter.parse_identifier(); converter.skip_space(); - if (string_to_enum(GlobalKind, global_string)) |global_kind| { - switch (global_kind) { - .@"fn" => { - var calling_convention = CallingConvention.c; - const function_attributes = Function.Attributes{}; - var is_var_args = false; + if (string_to_enum(GlobalKind, global_string)) |global_kind| switch (global_kind) { + .@"fn" => { + var calling_convention = CallingConvention.c; + const function_attributes = Function.Attributes{}; + var is_var_args = false; - if (converter.consume_character_if_match(left_bracket)) { - while (converter.offset < converter.content.len) { - const function_identifier = converter.parse_identifier(); + if (converter.consume_character_if_match(left_bracket)) { + while (converter.offset < converter.content.len) { + const function_identifier = converter.parse_identifier(); - const function_keyword = string_to_enum(FunctionKeyword, function_identifier) orelse converter.report_error(); + const function_keyword = string_to_enum(FunctionKeyword, function_identifier) orelse converter.report_error(); - converter.skip_space(); - - switch (function_keyword) { - .cc => { - converter.expect_character(left_parenthesis); - - converter.skip_space(); - - const calling_convention_string = converter.parse_identifier(); - - calling_convention = string_to_enum(CallingConvention, calling_convention_string) orelse converter.report_error(); - - converter.skip_space(); - - converter.expect_character(right_parenthesis); - }, - else => converter.report_error(), - } - - converter.skip_space(); - - switch (converter.content[converter.offset]) { - right_bracket => break, - else => converter.report_error(), - } - } - - converter.expect_character(right_bracket); - } - - converter.skip_space(); - - converter.expect_character(left_parenthesis); - - var argument_buffer: [max_argument_count]struct { - name: []const u8, - type: *Type, - line: u32, - column: u32, - } = undefined; - var semantic_argument_count: u32 = 0; - - while (converter.offset < converter.content.len and converter.content[converter.offset] != right_parenthesis) : (semantic_argument_count += 1) { converter.skip_space(); - const argument_line = converter.get_line(); - const argument_column = converter.get_column(); + switch (function_keyword) { + .cc => { + converter.expect_character(left_parenthesis); - if (converter.consume_character_if_match('.')) { - if (converter.consume_character_if_match('.')) { - converter.expect_character('.'); converter.skip_space(); - if (converter.content[converter.offset] == ')') { - if (calling_convention != .c) { - converter.report_error(); - } - is_var_args = true; - break; - } else { - @trap(); - } - } else { - @trap(); - } - } + const calling_convention_string = converter.parse_identifier(); - const argument_name = converter.parse_identifier(); + calling_convention = string_to_enum(CallingConvention, calling_convention_string) orelse converter.report_error(); - converter.skip_space(); + converter.skip_space(); - converter.expect_character(':'); - - converter.skip_space(); - - const argument_type = converter.parse_type(module); - - converter.skip_space(); - _ = converter.consume_character_if_match(','); - - argument_buffer[semantic_argument_count] = .{ - .name = argument_name, - .type = argument_type, - .line = argument_line, - .column = argument_column, - }; - } - - converter.expect_character(right_parenthesis); - - converter.skip_space(); - - const semantic_return_type = converter.parse_type(module); - const linkage_name = global_name; - - const semantic_arguments = argument_buffer[0..semantic_argument_count]; - const argument_type_abis = module.arena.allocate(Abi.Information, semantic_arguments.len); - - var return_type_abi: Abi.Information = undefined; - - const resolved_calling_convention = calling_convention.resolve(module.target); - const is_reg_call = resolved_calling_convention == .system_v and false; // TODO: regcall calling_convention - - const function_type = switch (resolved_calling_convention) { - .system_v => ft: { - var available_registers: Abi.RegisterCount = switch (resolved_calling_convention) { - .system_v => .{ - .system_v = .{ - .gpr = if (is_reg_call) 11 else 6, - .sse = if (is_reg_call) 16 else 8, - }, - }, - .win64 => @trap(), - }; - var abi_return_type: *Type = undefined; - var abi_argument_type_count: u16 = 0; - var llvm_abi_argument_type_buffer: [max_argument_count]*llvm.Type = undefined; - var abi_argument_type_buffer: [max_argument_count]*Type = undefined; - - return_type_abi = Abi.SystemV.classify_return_type(module, semantic_return_type); - const return_abi_kind = return_type_abi.flags.kind; - abi_return_type = switch (return_abi_kind) { - .direct, .extend => return_type_abi.coerce_to_type.?, - .ignore, .indirect => module.void_type, - else => |t| @panic(@tagName(t)), - }; - - if (return_type_abi.flags.kind == .indirect) { - assert(!return_type_abi.flags.sret_after_this); - available_registers.system_v.gpr -= 1; - const indirect_type = module.get_pointer_type(.{ .type = return_type_abi.semantic_type }); - abi_argument_type_buffer[abi_argument_type_count] = indirect_type; - llvm_abi_argument_type_buffer[abi_argument_type_count] = indirect_type.llvm.handle; - abi_argument_type_count += 1; - } - - const required_arguments = semantic_argument_count; - - for (argument_type_abis, semantic_arguments, 0..) |*argument_type_abi, semantic_argument, semantic_argument_index| { - const semantic_argument_type = semantic_argument.type; - const is_named_argument = semantic_argument_index < required_arguments; - assert(is_named_argument); - - argument_type_abi.* = Abi.SystemV.classify_argument(module, &available_registers, &llvm_abi_argument_type_buffer, &abi_argument_type_buffer, .{ - .type = semantic_argument_type, - .abi_start = abi_argument_type_count, - .is_named_argument = is_named_argument, - }); - - abi_argument_type_count += argument_type_abi.abi_count; - } - - const abi_argument_types = module.arena.allocate(*Type, abi_argument_type_count); - @memcpy(abi_argument_types, abi_argument_type_buffer[0..abi_argument_types.len]); - - const llvm_abi_argument_types = llvm_abi_argument_type_buffer[0..abi_argument_type_count]; - const llvm_function_type = llvm.Type.Function.get(abi_return_type.llvm.handle, llvm_abi_argument_types, is_var_args); - - const subroutine_type_flags = llvm.DI.Flags{}; - const subroutine_type = if (module.llvm.di_builder) |di_builder| blk: { - var debug_argument_type_buffer: [max_argument_count + 1]*llvm.DI.Type = undefined; - const semantic_debug_argument_types = debug_argument_type_buffer[0 .. argument_type_abis.len + 1 + @intFromBool(is_var_args)]; - semantic_debug_argument_types[0] = return_type_abi.semantic_type.llvm.debug; - - for (argument_type_abis, semantic_debug_argument_types[1..][0..argument_type_abis.len]) |argument_abi, *debug_argument_type| { - debug_argument_type.* = argument_abi.semantic_type.llvm.debug; - } - - if (is_var_args) { - semantic_debug_argument_types[argument_type_abis.len + 1] = module.void_type.llvm.debug; - } - - const subroutine_type = di_builder.create_subroutine_type(module.llvm.file, semantic_debug_argument_types, subroutine_type_flags); - break :blk subroutine_type; - } else undefined; - - const result = module.types.add(.{ - .bb = .{ - .function = .{ - .return_type_abi = return_type_abi, - .calling_convention = calling_convention, - .is_var_args = is_var_args, - .argument_type_abis = argument_type_abis, - .abi_return_type = abi_return_type, - .abi_argument_types = abi_argument_types, - .available_registers = available_registers, - }, - }, - .llvm = .{ - .handle = llvm_function_type.to_type(), - .debug = subroutine_type.to_type(), - }, - .name = null, - }); - break :ft result; - }, - .win64 => { - @trap(); - }, - }; - - const llvm_handle = module.llvm.handle.create_function(.{ - .name = global_name, - .linkage = switch (is_export or is_extern) { - true => .ExternalLinkage, - false => .InternalLinkage, - }, - .type = function_type.llvm.handle.to_function(), - }); - - llvm_handle.set_calling_convention(calling_convention.to_llvm()); - const has_semicolon = converter.consume_character_if_match(';'); - - const function_scope: *llvm.DI.Scope = if (module.llvm.di_builder) |di_builder| blk: { - const scope_line: u32 = @intCast(converter.line_offset + 1); - const local_to_unit = !is_export and !is_extern; - const flags = llvm.DI.Flags{}; - const is_definition = !is_extern; - const subprogram = di_builder.create_function(module.llvm.global_scope, global_name, linkage_name, module.llvm.file, global_line, function_type.llvm.debug.to_subroutine(), local_to_unit, is_definition, scope_line, flags, options.build_mode.is_optimized()); - llvm_handle.set_subprogram(subprogram); - - break :blk @ptrCast(subprogram); - } else undefined; - - const value = module.values.add(); - value.* = .{ - .llvm = llvm_handle.to_value(), - .type = module.get_pointer_type(.{ .type = function_type }), - .bb = switch (has_semicolon) { - true => .external_function, - false => .{ - .function = .{ - .current_scope = function_scope, - .attributes = function_attributes, - .return_pointer = undefined, - .return_alloca = undefined, - .exit_block = null, - .return_block = undefined, - }, - }, - }, - .lvalue = true, - .dereference_to_assign = false, - }; - - const global = module.globals.add(); - global.* = .{ - .value = value, - .name = global_name, - }; - - const attribute_list = module.build_attribute_list(.{ - .abi_return_type = function_type.bb.function.abi_return_type, - .abi_argument_types = function_type.bb.function.abi_argument_types, - .argument_type_abis = function_type.bb.function.argument_type_abis, - .return_type_abi = function_type.bb.function.return_type_abi, - .attributes = function_attributes, - .call_site = false, - }); - - llvm_handle.set_attributes(attribute_list); - - if (!has_semicolon) { - const entry_block = module.llvm.context.create_basic_block("entry", llvm_handle); - value.bb.function.return_block = module.llvm.context.create_basic_block("ret_block", null); - - module.llvm.builder.position_at_end(entry_block); - module.llvm.builder.set_current_debug_location(null); - - // function prologue - - var llvm_abi_argument_buffer: [argument_buffer.len]*llvm.Argument = undefined; - llvm_handle.get_arguments(&llvm_abi_argument_buffer); - const llvm_abi_arguments = llvm_abi_argument_buffer[0..function_type.bb.function.abi_argument_types.len]; - - module.current_function = global; - defer module.current_function = null; - - switch (return_type_abi.flags.kind) { - .ignore => {}, - .indirect => { - const indirect_argument_index = @intFromBool(return_type_abi.flags.sret_after_this); - if (return_type_abi.flags.sret_after_this) { - @trap(); - } - value.bb.function.return_alloca = llvm_abi_arguments[indirect_argument_index].to_value(); - if (!return_type_abi.flags.indirect_by_value) { - @trap(); - } - }, - .in_alloca => { - @trap(); - }, - else => { - const alloca = module.create_alloca(.{ .type = return_type_abi.semantic_type, .name = "retval" }); - value.bb.function.return_alloca = alloca; + converter.expect_character(right_parenthesis); }, + else => converter.report_error(), } - const argument_variables = global.value.bb.function.arguments.add_many(semantic_argument_count); - for (semantic_arguments, argument_type_abis, argument_variables, 0..) |semantic_argument, argument_abi, *argument_variable, argument_index| { - const abi_arguments = llvm_abi_arguments[argument_abi.abi_start..][0..argument_abi.abi_count]; - assert(argument_abi.flags.kind == .ignore or argument_abi.abi_count != 0); - const argument_abi_kind = argument_abi.flags.kind; - const semantic_argument_storage = switch (argument_abi_kind) { - .direct, .extend => blk: { - const first_argument = abi_arguments[0]; - const coerce_to_type = argument_abi.get_coerce_to_type(); - if (coerce_to_type.bb != .structure and coerce_to_type.is_abi_equal(argument_abi.semantic_type) and argument_abi.attributes.direct.offset == 0) { - assert(argument_abi.abi_count == 1); - const is_promoted = false; - var v = first_argument.to_value(); - v = switch (coerce_to_type.llvm.handle == v.get_type()) { - true => v, - false => @trap(), - }; - if (is_promoted) { - @trap(); - } - - switch (argument_abi.semantic_type.is_arbitrary_bit_integer()) { - true => { - const bit_count = argument_abi.semantic_type.get_bit_size(); - const abi_bit_count: u32 = @intCast(@max(8, lib.next_power_of_two(bit_count))); - const is_signed = argument_abi.semantic_type.is_signed(); - const destination_type = module.align_integer_type(argument_abi.semantic_type); - const alloca = module.create_alloca(.{ .type = destination_type, .name = semantic_argument.name }); - const result = switch (bit_count < abi_bit_count) { - true => switch (is_signed) { - true => module.llvm.builder.create_sign_extend(first_argument.to_value(), destination_type.llvm.handle), - false => module.llvm.builder.create_zero_extend(first_argument.to_value(), destination_type.llvm.handle), - }, - false => @trap(), - }; - _ = module.create_store(.{ .source_value = result, .destination_value = alloca, .source_type = destination_type, .destination_type = destination_type }); - break :blk alloca; - }, - false => { // TODO: ExtVectorBoolType - const alloca = module.create_alloca(.{ .type = argument_abi.semantic_type, .name = semantic_argument.name }); - _ = module.create_store(.{ .source_value = first_argument.to_value(), .destination_value = alloca, .source_type = argument_abi.semantic_type, .destination_type = argument_abi.semantic_type }); - break :blk alloca; - }, - } - } else { - const is_fixed_vector_type = false; - if (is_fixed_vector_type) { - @trap(); - } - - if (coerce_to_type.bb == .structure and coerce_to_type.bb.structure.fields.len > 1 and argument_abi.flags.kind == .direct and !argument_abi.flags.can_be_flattened) { - const contains_homogeneous_scalable_vector_types = false; - if (contains_homogeneous_scalable_vector_types) { - @trap(); - } - } - - const alloca = module.create_alloca(.{ .type = argument_abi.semantic_type }); - const pointer = switch (argument_abi.attributes.direct.offset > 0) { - true => @trap(), - false => alloca, - }; - const pointer_type = switch (argument_abi.attributes.direct.offset > 0) { - true => @trap(), - false => argument_abi.semantic_type, - }; - - if (coerce_to_type.bb == .structure and coerce_to_type.bb.structure.fields.len > 1 and argument_abi.flags.kind == .direct and argument_abi.flags.can_be_flattened) { - const struct_size = coerce_to_type.get_byte_size(); - const pointer_element_size = pointer_type.get_byte_size(); // TODO: fix - const is_scalable = false; - - switch (is_scalable) { - true => @trap(), - false => { - const source_size = struct_size; - const destination_size = pointer_element_size; - const address_alignment = argument_abi.semantic_type.get_byte_alignment(); - const address = switch (source_size <= destination_size) { - true => alloca, - false => module.create_alloca(.{ .type = coerce_to_type, .alignment = address_alignment, .name = "coerce" }), - }; - assert(coerce_to_type.bb.structure.fields.len == argument_abi.abi_count); - for (coerce_to_type.bb.structure.fields, abi_arguments, 0..) |field, abi_argument, field_index| { - const gep = module.llvm.builder.create_struct_gep(coerce_to_type.llvm.handle.to_struct(), address, @intCast(field_index)); - // TODO: check if alignment is right - _ = module.create_store(.{ .source_value = abi_argument.to_value(), .destination_value = gep, .source_type = field.type, .destination_type = field.type }); - } - - if (source_size > destination_size) { - _ = module.llvm.builder.create_memcpy(pointer, pointer_type.get_byte_alignment(), address, address_alignment, module.integer_type(64, false).llvm.handle.to_integer().get_constant(destination_size, @intFromBool(false)).to_value()); - } - }, - } - } else { - assert(argument_abi.abi_count == 1); - const abi_argument_type = function_type.bb.function.abi_argument_types[argument_abi.abi_start]; - const destination_size = pointer_type.get_byte_size() - argument_abi.attributes.direct.offset; - const is_volatile = false; - module.create_coerced_store(abi_arguments[0].to_value(), abi_argument_type, pointer, pointer_type, destination_size, is_volatile); - } - - switch (argument_abi.semantic_type.get_evaluation_kind()) { - .scalar => @trap(), - else => { - // TODO - }, - } - - break :blk alloca; - } - }, - .indirect, .indirect_aliased => blk: { - assert(argument_abi.abi_count == 1); - switch (argument_abi.semantic_type.get_evaluation_kind()) { - .scalar => @trap(), - else => { - if (argument_abi.flags.indirect_realign or argument_abi.flags.kind == .indirect_aliased) { - @trap(); - } - - const use_indirect_debug_address = !argument_abi.flags.indirect_by_value; - if (use_indirect_debug_address) { - @trap(); - } - - const llvm_argument = abi_arguments[0]; - break :blk llvm_argument.to_value(); - }, - } - }, - else => @trap(), - }; - - const argument_value = module.values.add(); - argument_value.* = .{ - .llvm = semantic_argument_storage, - .type = module.get_pointer_type(.{ .type = semantic_argument.type }), - .bb = .argument, - .lvalue = true, - .dereference_to_assign = false, - }; - argument_variable.* = .{ - .value = argument_value, - .name = semantic_argument.name, - }; - - if (module.llvm.di_builder) |di_builder| { - const always_preserve = true; - const flags = llvm.DI.Flags{}; - const parameter_variable = di_builder.create_parameter_variable(function_scope, semantic_argument.name, @intCast(argument_index + 1), module.llvm.file, semantic_argument.line, semantic_argument.type.llvm.debug, always_preserve, flags); - const inlined_at: ?*llvm.DI.Metadata = null; // TODO - const debug_location = llvm.DI.create_debug_location(module.llvm.context, semantic_argument.line, semantic_argument.column, function_scope, inlined_at); - _ = di_builder.insert_declare_record_at_end(semantic_argument_storage, parameter_variable, di_builder.null_expression(), debug_location, module.current_basic_block()); - } - } - - converter.parse_block(module); - - // Handle jump to the return block - const return_block = value.bb.function.return_block; - - if (module.llvm.builder.get_insert_block()) |current_basic_block| { - assert(current_basic_block.get_terminator() == null); - - if (current_basic_block.is_empty() or current_basic_block.to_value().use_empty()) { - return_block.to_value().replace_all_uses_with(current_basic_block.to_value()); - return_block.delete(); - } else { - module.emit_block(return_block); - } - } else { - var is_reachable = false; - - if (return_block.to_value().has_one_use()) { - if (llvm.Value.to_branch(return_block.user_begin())) |branch| { - is_reachable = !branch.is_conditional() and branch.get_successor(0) == return_block; - - if (is_reachable) { - module.llvm.builder.position_at_end(branch.to_instruction().get_parent()); - branch.to_instruction().erase_from_parent(); - return_block.delete(); - } - } - } - - if (!is_reachable) { - module.emit_block(return_block); - } - } - - // End function debug info - if (module.llvm.di_builder) |di_builder| { - if (llvm_handle.get_subprogram()) |subprogram| { - di_builder.finalize_subprogram(subprogram); - } - } - - if (return_type_abi.semantic_type == module.noreturn_type or value.bb.function.attributes.naked) { - @trap(); - } else if (return_type_abi.semantic_type == module.void_type) { - module.llvm.builder.create_ret_void(); - } else { - const abi_kind = return_type_abi.flags.kind; - const return_value: ?*llvm.Value = switch (abi_kind) { - .direct, .extend => blk: { - const coerce_to_type = return_type_abi.get_coerce_to_type(); - const return_alloca = value.bb.function.return_alloca; - - if (return_type_abi.semantic_type.is_abi_equal(coerce_to_type) and return_type_abi.attributes.direct.offset == 0) { - if (module.llvm.builder.find_return_value_dominating_store(return_alloca, return_type_abi.semantic_type.llvm.handle)) |store| { - const store_instruction = store.to_instruction(); - const return_value = store_instruction.to_value().get_operand(0); - const alloca = store_instruction.to_value().get_operand(1); - assert(alloca == return_alloca); - store_instruction.erase_from_parent(); - assert(alloca.use_empty()); - alloca.to_instruction().erase_from_parent(); - break :blk return_value; - } else { - const load_value = module.create_load(.{ .type = return_type_abi.semantic_type, .value = return_alloca }); - break :blk load_value; - } - } else { - const source = switch (return_type_abi.attributes.direct.offset == 0) { - true => return_alloca, - false => @trap(), - }; - - const source_type = return_type_abi.semantic_type; - const destination_type = coerce_to_type; - const result = module.create_coerced_load(source, source_type, destination_type); - break :blk result; - } - }, - .indirect => switch (return_type_abi.semantic_type.get_evaluation_kind()) { - .complex => @trap(), - .aggregate => null, - .scalar => @trap(), - }, - else => @trap(), - }; - - if (return_value) |rv| { - module.llvm.builder.create_ret(rv); - } else { - module.llvm.builder.create_ret_void(); - } - } - } - - if (!has_semicolon and lib.optimization_mode == .Debug) { - const verify_result = llvm_handle.verify(); - if (!verify_result.success) { - lib.print_string(module.llvm.handle.to_string()); - lib.print_string("============================\n"); - lib.print_string(llvm_handle.to_string()); - lib.print_string("============================\n"); - lib.print_string(verify_result.error_message orelse unreachable); - lib.print_string("\n============================\n"); - os.abort(); - } - } - }, - .@"struct" => { - converter.skip_space(); - - converter.expect_character(left_brace); - - if (module.types.find(global_name) != null) { - @trap(); - } - - const struct_type = module.types.add(.{ - .name = global_name, - .bb = .forward_declaration, - .llvm = .{ - .handle = undefined, - .debug = if (module.llvm.di_builder) |di_builder| blk: { - const r = di_builder.create_replaceable_composite_type(module.debug_tag, global_name, module.llvm.global_scope, module.llvm.file, global_line); - module.debug_tag += 1; - break :blk r.to_type(); - } else undefined, - }, - }); - - var field_buffer: [256]Field = undefined; - var llvm_field_type_buffer: [field_buffer.len]*llvm.Type = undefined; - var llvm_debug_member_type_buffer: [field_buffer.len]*llvm.DI.Type.Derived = undefined; - var field_count: usize = 0; - var byte_offset: u64 = 0; - var byte_alignment: u32 = 1; - var bit_alignment: u32 = 1; - - while (true) { - converter.skip_space(); - - if (converter.consume_character_if_match(right_brace)) { - break; - } - - const field_line = converter.get_line(); - const field_name = converter.parse_identifier(); - - converter.skip_space(); - - converter.expect_character(':'); - - converter.skip_space(); - - const field_type = converter.parse_type(module); - - const field_byte_alignment = field_type.get_byte_alignment(); - const field_bit_alignment = field_type.get_bit_alignment(); - const field_bit_size = field_type.get_bit_size(); - const field_byte_size = field_type.get_byte_size(); - - const field_byte_offset = lib.align_forward_u64(byte_offset, field_byte_alignment); - const field_bit_offset = field_byte_offset * 8; - - field_buffer[field_count] = .{ - .byte_offset = field_byte_offset, - .bit_offset = field_bit_offset, - .type = field_type, - .name = field_name, - }; - - llvm_field_type_buffer[field_count] = field_type.llvm.handle; - - if (module.llvm.di_builder) |di_builder| { - const member_type = di_builder.create_member_type(module.llvm.global_scope, field_name, module.llvm.file, field_line, field_bit_size, @intCast(field_bit_alignment), field_bit_offset, .{}, field_type.llvm.debug); - llvm_debug_member_type_buffer[field_count] = member_type; - } - - byte_alignment = @max(byte_alignment, field_byte_alignment); - bit_alignment = @max(bit_alignment, field_bit_alignment); - byte_offset = field_byte_offset + field_byte_size; - - field_count += 1; - converter.skip_space(); switch (converter.content[converter.offset]) { - ',' => converter.offset += 1, - else => {}, + right_bracket => break, + else => converter.report_error(), } } + converter.expect_character(right_bracket); + } + + converter.skip_space(); + + converter.expect_character(left_parenthesis); + + var argument_buffer: [max_argument_count]struct { + name: []const u8, + type: *Type, + line: u32, + column: u32, + } = undefined; + var semantic_argument_count: u32 = 0; + + while (converter.offset < converter.content.len and converter.content[converter.offset] != right_parenthesis) : (semantic_argument_count += 1) { converter.skip_space(); - _ = converter.consume_character_if_match(';'); + const argument_line = converter.get_line(); + const argument_column = converter.get_column(); - const byte_size = byte_offset; - const bit_size = byte_size * 8; + if (converter.consume_character_if_match('.')) { + if (converter.consume_character_if_match('.')) { + converter.expect_character('.'); + converter.skip_space(); - const fields = module.arena.allocate(Field, field_count); - @memcpy(fields, field_buffer[0..field_count]); - - const element_types = llvm_field_type_buffer[0..field_count]; - struct_type.llvm.handle = module.llvm.context.get_struct_type(element_types).to_type(); - - if (module.llvm.di_builder) |di_builder| { - const member_types = llvm_debug_member_type_buffer[0..field_count]; - const debug_struct_type = di_builder.create_struct_type(module.llvm.global_scope, global_name, module.llvm.file, global_line, bit_size, @intCast(bit_alignment), .{}, member_types); - const forward_declared: *llvm.DI.Type.Composite = @ptrCast(struct_type.llvm.debug); - forward_declared.replace_all_uses_with(debug_struct_type); - struct_type.llvm.debug = debug_struct_type.to_type(); + if (converter.content[converter.offset] == ')') { + if (calling_convention != .c) { + converter.report_error(); + } + is_var_args = true; + break; + } else { + @trap(); + } + } else { + @trap(); + } } - struct_type.bb = .{ - .structure = .{ - .bit_size = byte_size * 8, - .byte_size = byte_size, - .bit_alignment = bit_alignment, - .byte_alignment = byte_alignment, - .fields = fields, - }, - }; - }, - .bits => { - const allow_implicit_type = converter.content[converter.offset] == left_brace; - const maybe_backing_type: ?*Type = switch (allow_implicit_type) { - true => null, - false => converter.parse_type(module), - }; + const argument_name = converter.parse_identifier(); converter.skip_space(); - converter.expect_character(left_brace); + converter.expect_character(':'); - var field_buffer: [128]Field = undefined; - var field_line_buffer: [128]u32 = undefined; - var field_count: usize = 0; + converter.skip_space(); - var field_bit_offset: u64 = 0; + const argument_type = converter.parse_type(module); - while (true) : (field_count += 1) { - converter.skip_space(); + converter.skip_space(); + _ = converter.consume_character_if_match(','); - if (converter.consume_character_if_match(right_brace)) { - break; - } + argument_buffer[semantic_argument_count] = .{ + .name = argument_name, + .type = argument_type, + .line = argument_line, + .column = argument_column, + }; + } - const field_line = converter.get_line(); - field_line_buffer[field_count] = field_line; + converter.expect_character(right_parenthesis); - const field_name = converter.parse_identifier(); + converter.skip_space(); - converter.skip_space(); + const semantic_return_type = converter.parse_type(module); + const linkage_name = global_name; - converter.expect_character(':'); + const semantic_arguments = argument_buffer[0..semantic_argument_count]; + const argument_type_abis = module.arena.allocate(Abi.Information, semantic_arguments.len); - converter.skip_space(); + var return_type_abi: Abi.Information = undefined; - const field_type = converter.parse_type(module); + const resolved_calling_convention = calling_convention.resolve(module.target); + const is_reg_call = resolved_calling_convention == .system_v and false; // TODO: regcall calling_convention - field_buffer[field_count] = .{ - .name = field_name, - .type = field_type, - .bit_offset = field_bit_offset, - .byte_offset = 0, + const function_type = switch (resolved_calling_convention) { + .system_v => ft: { + var available_registers: Abi.RegisterCount = switch (resolved_calling_convention) { + .system_v => .{ + .system_v = .{ + .gpr = if (is_reg_call) 11 else 6, + .sse = if (is_reg_call) 16 else 8, + }, + }, + .win64 => @trap(), + }; + var abi_return_type: *Type = undefined; + var abi_argument_type_count: u16 = 0; + var llvm_abi_argument_type_buffer: [max_argument_count]*llvm.Type = undefined; + var abi_argument_type_buffer: [max_argument_count]*Type = undefined; + + return_type_abi = Abi.SystemV.classify_return_type(module, semantic_return_type); + const return_abi_kind = return_type_abi.flags.kind; + abi_return_type = switch (return_abi_kind) { + .direct, .extend => return_type_abi.coerce_to_type.?, + .ignore, .indirect => module.void_type, + else => |t| @panic(@tagName(t)), }; - const field_bit_size = field_type.get_bit_size(); + if (return_type_abi.flags.kind == .indirect) { + assert(!return_type_abi.flags.sret_after_this); + available_registers.system_v.gpr -= 1; + const indirect_type = module.get_pointer_type(.{ .type = return_type_abi.semantic_type }); + abi_argument_type_buffer[abi_argument_type_count] = indirect_type; + llvm_abi_argument_type_buffer[abi_argument_type_count] = indirect_type.llvm.handle; + abi_argument_type_count += 1; + } - // if (module.llvm.di_builder) |di_builder| { - // llvm_debug_field_buffer[field_count] = member_type; - // } + const required_arguments = semantic_argument_count; - field_bit_offset += field_bit_size; + for (argument_type_abis, semantic_arguments, 0..) |*argument_type_abi, semantic_argument, semantic_argument_index| { + const semantic_argument_type = semantic_argument.type; + const is_named_argument = semantic_argument_index < required_arguments; + assert(is_named_argument); - converter.skip_space(); + argument_type_abi.* = Abi.SystemV.classify_argument(module, &available_registers, &llvm_abi_argument_type_buffer, &abi_argument_type_buffer, .{ + .type = semantic_argument_type, + .abi_start = abi_argument_type_count, + .is_named_argument = is_named_argument, + }); - _ = converter.consume_character_if_match(','); + abi_argument_type_count += argument_type_abi.abi_count; + } + + const abi_argument_types = module.arena.allocate(*Type, abi_argument_type_count); + @memcpy(abi_argument_types, abi_argument_type_buffer[0..abi_argument_types.len]); + + const llvm_abi_argument_types = llvm_abi_argument_type_buffer[0..abi_argument_type_count]; + const llvm_function_type = llvm.Type.Function.get(abi_return_type.llvm.handle, llvm_abi_argument_types, is_var_args); + + const subroutine_type_flags = llvm.DI.Flags{}; + const subroutine_type = if (module.llvm.di_builder) |di_builder| blk: { + var debug_argument_type_buffer: [max_argument_count + 1]*llvm.DI.Type = undefined; + const semantic_debug_argument_types = debug_argument_type_buffer[0 .. argument_type_abis.len + 1 + @intFromBool(is_var_args)]; + semantic_debug_argument_types[0] = return_type_abi.semantic_type.llvm.debug; + + for (argument_type_abis, semantic_debug_argument_types[1..][0..argument_type_abis.len]) |argument_abi, *debug_argument_type| { + debug_argument_type.* = argument_abi.semantic_type.llvm.debug; + } + + if (is_var_args) { + semantic_debug_argument_types[argument_type_abis.len + 1] = module.void_type.llvm.debug; + } + + const subroutine_type = di_builder.create_subroutine_type(module.llvm.file, semantic_debug_argument_types, subroutine_type_flags); + break :blk subroutine_type; + } else undefined; + + const result = module.types.add(.{ + .bb = .{ + .function = .{ + .return_type_abi = return_type_abi, + .calling_convention = calling_convention, + .is_var_args = is_var_args, + .argument_type_abis = argument_type_abis, + .abi_return_type = abi_return_type, + .abi_argument_types = abi_argument_types, + .available_registers = available_registers, + }, + }, + .llvm = .{ + .handle = llvm_function_type.to_type(), + .debug = subroutine_type.to_type(), + }, + .name = null, + }); + break :ft result; + }, + .win64 => { + @trap(); + }, + }; + + const llvm_handle = module.llvm.handle.create_function(.{ + .name = global_name, + .linkage = switch (is_export or is_extern) { + true => .ExternalLinkage, + false => .InternalLinkage, + }, + .type = function_type.llvm.handle.to_function(), + }); + + llvm_handle.set_calling_convention(calling_convention.to_llvm()); + const has_semicolon = converter.consume_character_if_match(';'); + + const function_scope: *llvm.DI.Scope = if (module.llvm.di_builder) |di_builder| blk: { + const scope_line: u32 = @intCast(converter.line_offset + 1); + const local_to_unit = !is_export and !is_extern; + const flags = llvm.DI.Flags{}; + const is_definition = !is_extern; + const subprogram = di_builder.create_function(module.llvm.global_scope, global_name, linkage_name, module.llvm.file, global_line, function_type.llvm.debug.to_subroutine(), local_to_unit, is_definition, scope_line, flags, options.build_mode.is_optimized()); + llvm_handle.set_subprogram(subprogram); + + break :blk @ptrCast(subprogram); + } else undefined; + + const value = module.values.add(); + value.* = .{ + .llvm = llvm_handle.to_value(), + .type = module.get_pointer_type(.{ .type = function_type }), + .bb = switch (has_semicolon) { + true => .external_function, + false => .{ + .function = .{ + .current_scope = function_scope, + .attributes = function_attributes, + .return_pointer = undefined, + .return_alloca = undefined, + .exit_block = null, + .return_block = undefined, + }, + }, + }, + .lvalue = true, + .dereference_to_assign = false, + }; + + const global = module.globals.add(); + global.* = .{ + .value = value, + .name = global_name, + }; + + const attribute_list = module.build_attribute_list(.{ + .abi_return_type = function_type.bb.function.abi_return_type, + .abi_argument_types = function_type.bb.function.abi_argument_types, + .argument_type_abis = function_type.bb.function.argument_type_abis, + .return_type_abi = function_type.bb.function.return_type_abi, + .attributes = function_attributes, + .call_site = false, + }); + + llvm_handle.set_attributes(attribute_list); + + if (!has_semicolon) { + const entry_block = module.llvm.context.create_basic_block("entry", llvm_handle); + value.bb.function.return_block = module.llvm.context.create_basic_block("ret_block", null); + + module.llvm.builder.position_at_end(entry_block); + module.llvm.builder.set_current_debug_location(null); + + // function prologue + + var llvm_abi_argument_buffer: [argument_buffer.len]*llvm.Argument = undefined; + llvm_handle.get_arguments(&llvm_abi_argument_buffer); + const llvm_abi_arguments = llvm_abi_argument_buffer[0..function_type.bb.function.abi_argument_types.len]; + + module.current_function = global; + defer module.current_function = null; + + switch (return_type_abi.flags.kind) { + .ignore => {}, + .indirect => { + const indirect_argument_index = @intFromBool(return_type_abi.flags.sret_after_this); + if (return_type_abi.flags.sret_after_this) { + @trap(); + } + value.bb.function.return_alloca = llvm_abi_arguments[indirect_argument_index].to_value(); + if (!return_type_abi.flags.indirect_by_value) { + @trap(); + } + }, + .in_alloca => { + @trap(); + }, + else => { + const alloca = module.create_alloca(.{ .type = return_type_abi.semantic_type, .name = "retval" }); + value.bb.function.return_alloca = alloca; + }, } - _ = converter.consume_character_if_match(';'); + const argument_variables = global.value.bb.function.arguments.add_many(semantic_argument_count); + for (semantic_arguments, argument_type_abis, argument_variables, 0..) |semantic_argument, argument_abi, *argument_variable, argument_index| { + const abi_arguments = llvm_abi_arguments[argument_abi.abi_start..][0..argument_abi.abi_count]; + assert(argument_abi.flags.kind == .ignore or argument_abi.abi_count != 0); + const argument_abi_kind = argument_abi.flags.kind; + const semantic_argument_storage = switch (argument_abi_kind) { + .direct, .extend => blk: { + const first_argument = abi_arguments[0]; + const coerce_to_type = argument_abi.get_coerce_to_type(); + if (coerce_to_type.bb != .structure and coerce_to_type.is_abi_equal(argument_abi.semantic_type) and argument_abi.attributes.direct.offset == 0) { + assert(argument_abi.abi_count == 1); + const is_promoted = false; + var v = first_argument.to_value(); + v = switch (coerce_to_type.llvm.handle == v.get_type()) { + true => v, + false => @trap(), + }; + if (is_promoted) { + @trap(); + } - const fields = module.arena.allocate(Field, field_count); - @memcpy(fields, field_buffer[0..field_count]); + switch (argument_abi.semantic_type.is_arbitrary_bit_integer()) { + true => { + const bit_count = argument_abi.semantic_type.get_bit_size(); + const abi_bit_count: u32 = @intCast(@max(8, lib.next_power_of_two(bit_count))); + const is_signed = argument_abi.semantic_type.is_signed(); + const destination_type = module.align_integer_type(argument_abi.semantic_type); + const alloca = module.create_alloca(.{ .type = destination_type, .name = semantic_argument.name }); + const result = switch (bit_count < abi_bit_count) { + true => switch (is_signed) { + true => module.llvm.builder.create_sign_extend(first_argument.to_value(), destination_type.llvm.handle), + false => module.llvm.builder.create_zero_extend(first_argument.to_value(), destination_type.llvm.handle), + }, + false => @trap(), + }; + _ = module.create_store(.{ .source_value = result, .destination_value = alloca, .source_type = destination_type, .destination_type = destination_type }); + break :blk alloca; + }, + false => { // TODO: ExtVectorBoolType + const alloca = module.create_alloca(.{ .type = argument_abi.semantic_type, .name = semantic_argument.name }); + _ = module.create_store(.{ .source_value = first_argument.to_value(), .destination_value = alloca, .source_type = argument_abi.semantic_type, .destination_type = argument_abi.semantic_type }); + break :blk alloca; + }, + } + } else { + const is_fixed_vector_type = false; + if (is_fixed_vector_type) { + @trap(); + } - const field_lines = field_line_buffer[0..field_count]; + if (coerce_to_type.bb == .structure and coerce_to_type.bb.structure.fields.len > 1 and argument_abi.flags.kind == .direct and !argument_abi.flags.can_be_flattened) { + const contains_homogeneous_scalable_vector_types = false; + if (contains_homogeneous_scalable_vector_types) { + @trap(); + } + } - const backing_type = if (maybe_backing_type) |bt| bt else module.integer_type(@intCast(@max(8, lib.next_power_of_two(field_bit_offset))), false); - if (backing_type.bb != .integer) { - converter.report_error(); - } + const alloca = module.create_alloca(.{ .type = argument_abi.semantic_type }); + const pointer = switch (argument_abi.attributes.direct.offset > 0) { + true => @trap(), + false => alloca, + }; + const pointer_type = switch (argument_abi.attributes.direct.offset > 0) { + true => @trap(), + false => argument_abi.semantic_type, + }; - if (backing_type.get_bit_size() > 64) { - converter.report_error(); - } + if (coerce_to_type.bb == .structure and coerce_to_type.bb.structure.fields.len > 1 and argument_abi.flags.kind == .direct and argument_abi.flags.can_be_flattened) { + const struct_size = coerce_to_type.get_byte_size(); + const pointer_element_size = pointer_type.get_byte_size(); // TODO: fix + const is_scalable = false; - const bit_size = backing_type.get_bit_size(); - const bit_alignment = backing_type.get_bit_alignment(); + switch (is_scalable) { + true => @trap(), + false => { + const source_size = struct_size; + const destination_size = pointer_element_size; + const address_alignment = argument_abi.semantic_type.get_byte_alignment(); + const address = switch (source_size <= destination_size) { + true => alloca, + false => module.create_alloca(.{ .type = coerce_to_type, .alignment = address_alignment, .name = "coerce" }), + }; + assert(coerce_to_type.bb.structure.fields.len == argument_abi.abi_count); + for (coerce_to_type.bb.structure.fields, abi_arguments, 0..) |field, abi_argument, field_index| { + const gep = module.llvm.builder.create_struct_gep(coerce_to_type.llvm.handle.to_struct(), address, @intCast(field_index)); + // TODO: check if alignment is right + _ = module.create_store(.{ .source_value = abi_argument.to_value(), .destination_value = gep, .source_type = field.type, .destination_type = field.type }); + } - var llvm_debug_field_buffer: [128]*llvm.DI.Type.Derived = undefined; - const debug_member_types = llvm_debug_field_buffer[0..field_count]; + if (source_size > destination_size) { + _ = module.llvm.builder.create_memcpy(pointer, pointer_type.get_byte_alignment(), address, address_alignment, module.integer_type(64, false).llvm.handle.to_integer().get_constant(destination_size, @intFromBool(false)).to_value()); + } + }, + } + } else { + assert(argument_abi.abi_count == 1); + const abi_argument_type = function_type.bb.function.abi_argument_types[argument_abi.abi_start]; + const destination_size = pointer_type.get_byte_size() - argument_abi.attributes.direct.offset; + const is_volatile = false; + module.create_coerced_store(abi_arguments[0].to_value(), abi_argument_type, pointer, pointer_type, destination_size, is_volatile); + } - if (module.llvm.di_builder) |di_builder| { - for (fields, debug_member_types, field_lines) |field, *debug_member_type, field_line| { - debug_member_type.* = di_builder.create_bit_field_member_type(module.llvm.global_scope, field.name, module.llvm.file, field_line, field.type.get_bit_size(), field_bit_offset, 0, .{}, backing_type.llvm.debug); + switch (argument_abi.semantic_type.get_evaluation_kind()) { + .scalar => @trap(), + else => { + // TODO + }, + } + + break :blk alloca; + } + }, + .indirect, .indirect_aliased => blk: { + assert(argument_abi.abi_count == 1); + switch (argument_abi.semantic_type.get_evaluation_kind()) { + .scalar => @trap(), + else => { + if (argument_abi.flags.indirect_realign or argument_abi.flags.kind == .indirect_aliased) { + @trap(); + } + + const use_indirect_debug_address = !argument_abi.flags.indirect_by_value; + if (use_indirect_debug_address) { + @trap(); + } + + const llvm_argument = abi_arguments[0]; + break :blk llvm_argument.to_value(); + }, + } + }, + else => @trap(), + }; + + const argument_value = module.values.add(); + argument_value.* = .{ + .llvm = semantic_argument_storage, + .type = module.get_pointer_type(.{ .type = semantic_argument.type }), + .bb = .argument, + .lvalue = true, + .dereference_to_assign = false, + }; + argument_variable.* = .{ + .value = argument_value, + .name = semantic_argument.name, + }; + + if (module.llvm.di_builder) |di_builder| { + const always_preserve = true; + const flags = llvm.DI.Flags{}; + const parameter_variable = di_builder.create_parameter_variable(function_scope, semantic_argument.name, @intCast(argument_index + 1), module.llvm.file, semantic_argument.line, semantic_argument.type.llvm.debug, always_preserve, flags); + const inlined_at: ?*llvm.DI.Metadata = null; // TODO + const debug_location = llvm.DI.create_debug_location(module.llvm.context, semantic_argument.line, semantic_argument.column, function_scope, inlined_at); + _ = di_builder.insert_declare_record_at_end(semantic_argument_storage, parameter_variable, di_builder.null_expression(), debug_location, module.current_basic_block()); } } - _ = module.types.add(.{ - .name = global_name, - .llvm = .{ - .handle = backing_type.llvm.handle, - .debug = if (module.llvm.di_builder) |di_builder| di_builder.create_struct_type(module.llvm.global_scope, global_name, module.llvm.file, global_line, bit_size, @intCast(bit_alignment), .{}, debug_member_types).to_type() else undefined, - }, - .bb = .{ - .bits = .{ - .fields = fields, - .backing_type = backing_type, + converter.parse_block(module); + + // Handle jump to the return block + const return_block = value.bb.function.return_block; + + if (module.llvm.builder.get_insert_block()) |current_basic_block| { + assert(current_basic_block.get_terminator() == null); + + if (current_basic_block.is_empty() or current_basic_block.to_value().use_empty()) { + return_block.to_value().replace_all_uses_with(current_basic_block.to_value()); + return_block.delete(); + } else { + module.emit_block(return_block); + } + } else { + var is_reachable = false; + + if (return_block.to_value().has_one_use()) { + if (llvm.Value.to_branch(return_block.user_begin())) |branch| { + is_reachable = !branch.is_conditional() and branch.get_successor(0) == return_block; + + if (is_reachable) { + module.llvm.builder.position_at_end(branch.to_instruction().get_parent()); + branch.to_instruction().erase_from_parent(); + return_block.delete(); + } + } + } + + if (!is_reachable) { + module.emit_block(return_block); + } + } + + // End function debug info + if (module.llvm.di_builder) |di_builder| { + if (llvm_handle.get_subprogram()) |subprogram| { + di_builder.finalize_subprogram(subprogram); + } + } + + if (return_type_abi.semantic_type == module.noreturn_type or value.bb.function.attributes.naked) { + @trap(); + } else if (return_type_abi.semantic_type == module.void_type) { + module.llvm.builder.create_ret_void(); + } else { + const abi_kind = return_type_abi.flags.kind; + const return_value: ?*llvm.Value = switch (abi_kind) { + .direct, .extend => blk: { + const coerce_to_type = return_type_abi.get_coerce_to_type(); + const return_alloca = value.bb.function.return_alloca; + + if (return_type_abi.semantic_type.is_abi_equal(coerce_to_type) and return_type_abi.attributes.direct.offset == 0) { + if (module.llvm.builder.find_return_value_dominating_store(return_alloca, return_type_abi.semantic_type.llvm.handle)) |store| { + const store_instruction = store.to_instruction(); + const return_value = store_instruction.to_value().get_operand(0); + const alloca = store_instruction.to_value().get_operand(1); + assert(alloca == return_alloca); + store_instruction.erase_from_parent(); + assert(alloca.use_empty()); + alloca.to_instruction().erase_from_parent(); + break :blk return_value; + } else { + const load_value = module.create_load(.{ .type = return_type_abi.semantic_type, .value = return_alloca }); + break :blk load_value; + } + } else { + const source = switch (return_type_abi.attributes.direct.offset == 0) { + true => return_alloca, + false => @trap(), + }; + + const source_type = return_type_abi.semantic_type; + const destination_type = coerce_to_type; + const result = module.create_coerced_load(source, source_type, destination_type); + break :blk result; + } }, + .indirect => switch (return_type_abi.semantic_type.get_evaluation_kind()) { + .complex => @trap(), + .aggregate => null, + .scalar => @trap(), + }, + else => @trap(), + }; + + if (return_value) |rv| { + module.llvm.builder.create_ret(rv); + } else { + module.llvm.builder.create_ret_void(); + } + } + } + + if (!has_semicolon and lib.optimization_mode == .Debug) { + const verify_result = llvm_handle.verify(); + if (!verify_result.success) { + lib.print_string(module.llvm.handle.to_string()); + lib.print_string("============================\n"); + lib.print_string(llvm_handle.to_string()); + lib.print_string("============================\n"); + lib.print_string(verify_result.error_message orelse unreachable); + lib.print_string("\n============================\n"); + os.abort(); + } + } + }, + .@"struct" => { + converter.skip_space(); + + converter.expect_character(left_brace); + + if (module.types.find(global_name) != null) { + @trap(); + } + + const struct_type = module.types.add(.{ + .name = global_name, + .bb = .forward_declaration, + .llvm = .{ + .handle = undefined, + .debug = if (module.llvm.di_builder) |di_builder| blk: { + const r = di_builder.create_replaceable_composite_type(module.debug_tag, global_name, module.llvm.global_scope, module.llvm.file, global_line); + module.debug_tag += 1; + break :blk r.to_type(); + } else undefined, + }, + }); + + var field_buffer: [256]Field = undefined; + var llvm_field_type_buffer: [field_buffer.len]*llvm.Type = undefined; + var llvm_debug_member_type_buffer: [field_buffer.len]*llvm.DI.Type.Derived = undefined; + var field_count: usize = 0; + var byte_offset: u64 = 0; + var byte_alignment: u32 = 1; + var bit_alignment: u32 = 1; + + while (true) { + converter.skip_space(); + + if (converter.consume_character_if_match(right_brace)) { + break; + } + + const field_line = converter.get_line(); + const field_name = converter.parse_identifier(); + + converter.skip_space(); + + converter.expect_character(':'); + + converter.skip_space(); + + const field_type = converter.parse_type(module); + + const field_byte_alignment = field_type.get_byte_alignment(); + const field_bit_alignment = field_type.get_bit_alignment(); + const field_bit_size = field_type.get_bit_size(); + const field_byte_size = field_type.get_byte_size(); + + const field_byte_offset = lib.align_forward_u64(byte_offset, field_byte_alignment); + const field_bit_offset = field_byte_offset * 8; + + field_buffer[field_count] = .{ + .byte_offset = field_byte_offset, + .bit_offset = field_bit_offset, + .type = field_type, + .name = field_name, + }; + + llvm_field_type_buffer[field_count] = field_type.llvm.handle; + + if (module.llvm.di_builder) |di_builder| { + const member_type = di_builder.create_member_type(module.llvm.global_scope, field_name, module.llvm.file, field_line, field_bit_size, @intCast(field_bit_alignment), field_bit_offset, .{}, field_type.llvm.debug); + llvm_debug_member_type_buffer[field_count] = member_type; + } + + byte_alignment = @max(byte_alignment, field_byte_alignment); + bit_alignment = @max(bit_alignment, field_bit_alignment); + byte_offset = field_byte_offset + field_byte_size; + + field_count += 1; + + converter.skip_space(); + + switch (converter.content[converter.offset]) { + ',' => converter.offset += 1, + else => {}, + } + } + + converter.skip_space(); + + _ = converter.consume_character_if_match(';'); + + const byte_size = byte_offset; + const bit_size = byte_size * 8; + + const fields = module.arena.allocate(Field, field_count); + @memcpy(fields, field_buffer[0..field_count]); + + const element_types = llvm_field_type_buffer[0..field_count]; + struct_type.llvm.handle = module.llvm.context.get_struct_type(element_types).to_type(); + + if (module.llvm.di_builder) |di_builder| { + const member_types = llvm_debug_member_type_buffer[0..field_count]; + const debug_struct_type = di_builder.create_struct_type(module.llvm.global_scope, global_name, module.llvm.file, global_line, bit_size, @intCast(bit_alignment), .{}, member_types); + const forward_declared: *llvm.DI.Type.Composite = @ptrCast(struct_type.llvm.debug); + forward_declared.replace_all_uses_with(debug_struct_type); + struct_type.llvm.debug = debug_struct_type.to_type(); + } + + struct_type.bb = .{ + .structure = .{ + .bit_size = byte_size * 8, + .byte_size = byte_size, + .bit_alignment = bit_alignment, + .byte_alignment = byte_alignment, + .fields = fields, + }, + }; + }, + .bits => { + const is_implicit_type = converter.content[converter.offset] == left_brace; + const maybe_backing_type: ?*Type = switch (is_implicit_type) { + true => null, + false => converter.parse_type(module), + }; + + converter.skip_space(); + + converter.expect_character(left_brace); + + var field_buffer: [128]Field = undefined; + var field_line_buffer: [128]u32 = undefined; + var field_count: usize = 0; + + var field_bit_offset: u64 = 0; + + while (true) : (field_count += 1) { + converter.skip_space(); + + if (converter.consume_character_if_match(right_brace)) { + break; + } + + const field_line = converter.get_line(); + field_line_buffer[field_count] = field_line; + + const field_name = converter.parse_identifier(); + + converter.skip_space(); + + converter.expect_character(':'); + + converter.skip_space(); + + const field_type = converter.parse_type(module); + + field_buffer[field_count] = .{ + .name = field_name, + .type = field_type, + .bit_offset = field_bit_offset, + .byte_offset = 0, + }; + + const field_bit_size = field_type.get_bit_size(); + + // if (module.llvm.di_builder) |di_builder| { + // llvm_debug_field_buffer[field_count] = member_type; + // } + + field_bit_offset += field_bit_size; + + converter.skip_space(); + + _ = converter.consume_character_if_match(','); + } + + _ = converter.consume_character_if_match(';'); + + const fields = module.arena.allocate(Field, field_count); + @memcpy(fields, field_buffer[0..field_count]); + + const field_lines = field_line_buffer[0..field_count]; + + const backing_type = if (maybe_backing_type) |bt| bt else module.integer_type(@intCast(@max(8, lib.next_power_of_two(field_bit_offset))), false); + if (backing_type.bb != .integer) { + converter.report_error(); + } + + if (backing_type.get_bit_size() > 64) { + converter.report_error(); + } + + const bit_size = backing_type.get_bit_size(); + const bit_alignment = backing_type.get_bit_alignment(); + + var llvm_debug_field_buffer: [128]*llvm.DI.Type.Derived = undefined; + const debug_member_types = llvm_debug_field_buffer[0..field_count]; + + if (module.llvm.di_builder) |di_builder| { + for (fields, debug_member_types, field_lines) |field, *debug_member_type, field_line| { + debug_member_type.* = di_builder.create_bit_field_member_type(module.llvm.global_scope, field.name, module.llvm.file, field_line, field.type.get_bit_size(), field_bit_offset, 0, .{}, backing_type.llvm.debug); + } + } + + _ = module.types.add(.{ + .name = global_name, + .llvm = .{ + .handle = backing_type.llvm.handle, + .debug = if (module.llvm.di_builder) |di_builder| di_builder.create_struct_type(module.llvm.global_scope, global_name, module.llvm.file, global_line, bit_size, @intCast(bit_alignment), .{}, debug_member_types).to_type() else undefined, + }, + .bb = .{ + .bits = .{ + .fields = fields, + .backing_type = backing_type, }, - }); - }, - } + }, + }); + }, + .@"enum" => { + const is_implicit_type = converter.content[converter.offset] == left_brace; + const maybe_backing_type: ?*Type = switch (is_implicit_type) { + true => null, + false => converter.parse_type(module), + }; + + converter.skip_space(); + + converter.expect_character(left_brace); + + var highest_value: u64 = 0; + var lowest_value = ~@as(u64, 0); + + var field_buffer: [64]Enumerator.Field = undefined; + var field_count: u64 = 0; + + while (true) : (field_count += 1) { + converter.skip_space(); + + if (converter.consume_character_if_match(right_brace)) { + break; + } + + const field_index = field_count; + const field_name = converter.parse_identifier(); + converter.skip_space(); + + const field_value = if (converter.consume_character_if_match('=')) blk: { + converter.skip_space(); + const field_value = converter.parse_integer_value(false); + break :blk field_value; + } else { + @trap(); + }; + + field_buffer[field_index] = .{ + .name = field_name, + .value = field_value, + }; + + highest_value = @max(highest_value, field_value); + lowest_value = @min(lowest_value, field_value); + + converter.skip_space(); + converter.expect_character(','); + } + + converter.skip_space(); + + _ = converter.consume_character_if_match(';'); + + const backing_type = maybe_backing_type orelse blk: { + const bits_needed = 64 - @clz(highest_value); + const int_type = module.integer_type(bits_needed, false); + break :blk int_type; + }; + + if (maybe_backing_type) |bt| { + const bits_needed = 64 - @clz(highest_value); + if (bits_needed > bt.get_bit_size()) { + converter.report_error(); + } + } + + const fields = arena.allocate(Enumerator.Field, field_count); + @memcpy(fields, field_buffer[0..field_count]); + + const debug_type = if (module.llvm.di_builder) |di_builder| blk: { + var enumerator_buffer: [64]*llvm.DI.Enumerator = undefined; + const enumerators = enumerator_buffer[0..field_count]; + for (enumerators, fields) |*enumerator_pointer, *field| { + enumerator_pointer.* = di_builder.create_enumerator(field.name, @bitCast(field.value), false); + } + const alignment = 0; // TODO + const enumeration_type = di_builder.create_enumeration_type(module.llvm.global_scope, global_name, module.llvm.file, global_line, backing_type.get_bit_size(), alignment, enumerators, backing_type.llvm.debug); + break :blk enumeration_type.to_type(); + } else undefined; + + _ = module.types.add(.{ + .bb = .{ + .enumerator = .{ + .backing_type = backing_type, + .fields = fields, + }, + }, + .llvm = .{ + .handle = backing_type.llvm.handle, + .debug = debug_type, + }, + .name = global_name, + }); + }, } else { converter.report_error(); } diff --git a/src/converter_test.zig b/src/converter_test.zig index 430d23d..e674bd3 100644 --- a/src/converter_test.zig +++ b/src/converter_test.zig @@ -376,3 +376,7 @@ test "byte_size" { test "bits_no_backing_type" { try invsrc(@src()); } + +test "basic_enum" { + try invsrc(@src()); +} diff --git a/src/llvm_api.zig b/src/llvm_api.zig index 13c87b6..41b0841 100644 --- a/src/llvm_api.zig +++ b/src/llvm_api.zig @@ -216,6 +216,8 @@ pub extern fn LLVMDIBuilderCreateStructType(builder: *llvm.DI.Builder, scope: *l pub extern fn LLVMDIBuilderCreateMemberType(builder: *llvm.DI.Builder, scope: *llvm.DI.Scope, name_pointer: [*]const u8, name_length: usize, file: *llvm.DI.File, line: c_uint, bit_size: u64, align_in_bits: u32, bit_offset: u64, flags: llvm.DI.Flags, member_type: *llvm.DI.Type) *llvm.DI.Type.Derived; pub extern fn LLVMDIBuilderCreateBitFieldMemberType(builder: *llvm.DI.Builder, scope: *llvm.DI.Scope, name_pointer: [*]const u8, name_length: usize, file: *llvm.DI.File, line: c_uint, bit_size: u64, bit_offset: u64, bit_storage_offset: u64, flags: llvm.DI.Flags, member_type: *llvm.DI.Type) *llvm.DI.Type.Derived; pub extern fn LLVMDIBuilderCreatePointerType(builder: *llvm.DI.Builder, element_type: *llvm.DI.Type, bit_size: u64, align_in_bits: u32, address_space: c_uint, name_pointer: [*]const u8, name_length: usize) *llvm.DI.Type.Derived; +pub extern fn LLVMDIBuilderCreateEnumerator(builder: *llvm.DI.Builder, name_pointer: [*]const u8, name_length: usize, value: i64, is_unsigned: Bool) *llvm.DI.Enumerator; +pub extern fn LLVMDIBuilderCreateEnumerationType(builder: *llvm.DI.Builder, scope: *llvm.DI.Scope, name_pointer: [*]const u8, name_length: usize, file: *llvm.DI.File, line: c_uint, bit_size: u64, align_in_bits: u32, enumerator_pointer: [*]const *llvm.DI.Enumerator, enumerator_count: c_uint, backing_type: *llvm.DI.Type) *llvm.DI.Type.Composite; pub extern fn LLVMMetadataReplaceAllUsesWith(forward: *llvm.DI.Type.Composite, complete: *llvm.DI.Type.Composite) void; diff --git a/src/main.zig b/src/main.zig index d1b9635..d8b79d0 100644 --- a/src/main.zig +++ b/src/main.zig @@ -45,7 +45,7 @@ pub fn main(argc: c_int, argv: [*:null]const ?[*:0]const u8) callconv(.C) c_int .build_mode = .debug_none, .content = file_content, .path = file_path, - .has_debug_info = false, + .has_debug_info = true, .target = converter.Target.get_native(), }); return 0; diff --git a/tests/basic_enum.bbb b/tests/basic_enum.bbb new file mode 100644 index 0000000..a83edab --- /dev/null +++ b/tests/basic_enum.bbb @@ -0,0 +1,18 @@ +E = enum +{ + zero = 0, + one = 1, + two = 2, + three = 3, +} + +[export] main = fn [cc(c)] () s32 +{ + >a: E = .three; + >b: E = .two; + >c: E = .one; + >a_int: s32 = #extend(#int_from_enum(a)); + >b_int: s32 = #extend(#int_from_enum(b)); + >c_int: s32 = #extend(#int_from_enum(c)); + return a_int - (b_int + c_int); +}