const lib = @import("lib.zig"); const assert = lib.assert; const os = lib.os; const Arena = lib.Arena; const llvm = @import("LLVM.zig"); test { _ = @import("converter_test.zig"); } const left_bracket = '['; const right_bracket = ']'; const left_brace = '{'; const right_brace = '}'; const left_parenthesis = '('; const right_parenthesis = ')'; const max_argument_count = 64; fn array_type_name(arena: *Arena, array_type: ArrayType) [:0]const u8 { var buffer: [256]u8 = undefined; var i: usize = 0; buffer[i] = left_bracket; i += 1; i += lib.string_format.integer_decimal(buffer[i..], array_type.element_count.?); buffer[i] = right_bracket; i += 1; const element_name = array_type.element_type.name.?; @memcpy(buffer[i..][0..element_name.len], element_name); i += element_name.len; return arena.duplicate_string(buffer[0..i]); } fn array_type_llvm(noalias module: *Module, array: ArrayType) Type.LLVM { const element_count = array.element_count.?; return .{ .handle = array.element_type.llvm.handle.get_array_type(element_count).to_type(), .debug = if (module.llvm.di_builder) |di_builder| di_builder.create_array_type(element_count, @intCast(array.element_type.get_bit_alignment()), array.element_type.llvm.debug, &.{}).to_type() else undefined, }; } fn is_identifier_start_ch(ch: u8) bool { return (ch >= 'a' and ch <= 'z') or (ch >= 'A' and ch <= 'Z') or ch == '_'; } fn is_decimal_ch(ch: u8) bool { return ch >= '0' and ch <= '9'; } fn is_identifier_ch(ch: u8) bool { return is_identifier_start_ch(ch) or is_decimal_ch(ch); } fn string_to_enum(comptime E: type, string: []const u8) ?E { inline for (@typeInfo(E).@"enum".fields) |e| { if (lib.string.equal(e.name, string)) { return @field(E, e.name); } } else return null; } const GlobalKeyword = enum { @"export", @"extern", }; const GlobalKind = enum { @"fn", @"struct", bits, @"enum", }; const FunctionKeyword = enum { cc, foo, }; const CallingConvention = enum { c, pub fn to_llvm(calling_convention: CallingConvention) llvm.CallingConvention { return switch (calling_convention) { .c => .c, }; } pub fn resolve(calling_convention: CallingConvention, target: Target) ResolvedCallingConvention { return switch (calling_convention) { .c => switch (target.cpu) { .x86_64 => switch (target.os) { .linux => .system_v, }, }, }; } }; pub const ResolvedCallingConvention = enum { system_v, win64, }; const Module = struct { arena: *Arena, llvm: LLVM, target: Target, globals: Variable.Array = .{}, types: Type.Array = .{}, values: Value.Array = .{}, current_function: ?*Variable = null, debug_tag: c_uint = 0, void_type: *Type = undefined, noreturn_type: *Type = undefined, va_list_type: ?*Type = null, void_value: *Value = undefined, unreachable_value: *Value = undefined, anonymous_pair_type_buffer: [64]u32 = undefined, pointer_type_buffer: [128]u32 = undefined, pointer_type_count: u32 = 0, anonymous_pair_type_count: u32 = 0, arena_restore_position: u64, fn get_zero_value(module: *Module, ty: *Type) *Value { const value = module.values.add(); value.* = switch (ty.bb) { .bits => |bits| .{ .llvm = bits.backing_type.llvm.handle.to_integer().get_constant(0, @intFromBool(false)).to_value(), .lvalue = false, .dereference_to_assign = false, .type = ty, .bb = .bits_initialization, }, .structure => str: { const constant_struct = ty.llvm.handle.get_zero(); const llvm_value = switch (module.current_function == null) { true => constant_struct.to_value(), false => blk: { const global_variable = module.llvm.handle.create_global_variable(.{ .linkage = .InternalLinkage, .name = module.arena.join_string(&.{ "__const.", module.current_function.?.name, if (ty.name) |n| n else "" }), .initial_value = constant_struct, .type = ty.llvm.handle, }); break :blk global_variable.to_value(); }, }; break :str .{ .llvm = llvm_value, .type = ty, .bb = .{ .struct_initialization = .{ .is_constant = true, }, }, .lvalue = true, .dereference_to_assign = false, }; }, .integer => .{ .llvm = ty.llvm.handle.to_integer().get_constant(0, @intFromBool(false)).to_value(), .lvalue = false, .dereference_to_assign = false, .type = ty, .bb = .{ .constant_integer = .{ .value = 0, .signed = false, }, }, }, else => @trap(), }; return value; } pub fn emit_block(module: *Module, block: *llvm.BasicBlock) void { const maybe_current_block = module.llvm.builder.get_insert_block(); var emit_branch = false; if (maybe_current_block) |current_block| { emit_branch = current_block.get_terminator() == null; } if (emit_branch) { _ = module.llvm.builder.create_branch(block); } if (maybe_current_block != null and maybe_current_block.?.get_parent() != null) { module.llvm.builder.insert_basic_block_after_insert_block(block); } else { module.current_function.?.value.llvm.to_function().append_basic_block(block); } module.llvm.builder.position_at_end(block); } pub fn dump(module: *Module) void { lib.print_string(module.llvm.handle.to_string()); } pub fn coerce_int_or_pointer_to_int_or_pointer(module: *Module, source: *llvm.Value, source_ty: *Type, destination_ty: *Type) *llvm.Value { const source_type = source_ty; var destination_type = destination_ty; switch (source_type == destination_type) { true => return source, false => { if (source_type.bb == .pointer and destination_type.bb == .pointer) { @trap(); } else { if (source_type.bb == .pointer) { @trap(); } if (destination_type.bb == .pointer) { destination_type = module.integer_type(64, false); } if (source_type != destination_type) { @trap(); } // This is the original destination type if (destination_ty.bb == .pointer) { @trap(); } @trap(); } }, } } pub fn create_coerced_load(module: *Module, source: *llvm.Value, source_ty: *Type, destination_type: *Type) *llvm.Value { var source_pointer = source; var source_type = source_ty; const result = switch (source_type.is_abi_equal(destination_type)) { true => module.create_load(.{ .type = destination_type, .value = source_pointer, }), false => res: { const destination_size = destination_type.get_byte_size(); if (source_type.bb == .structure) { const src = module.enter_struct_pointer_for_coerced_access(source_pointer, source_type, destination_size); source_pointer = src.value; source_type = src.type; } if (source_type.is_integer_backing() and destination_type.is_integer_backing()) { const load = module.create_load(.{ .type = destination_type, .value = source_pointer, }); const result = module.coerce_int_or_pointer_to_int_or_pointer(load, source_type, destination_type); return result; } else { const source_size = source_type.get_byte_size(); const is_source_type_scalable = false; const is_destination_type_scalable = false; if (!is_source_type_scalable and !is_destination_type_scalable and source_size >= destination_size) { const load = module.create_load(.{ .type = destination_type, .value = source, .alignment = source_type.get_byte_alignment() }); break :res load; } else { const is_destination_scalable_vector_type = false; if (is_destination_scalable_vector_type) { @trap(); } // Coercion through memory const original_destination_alignment = destination_type.get_byte_alignment(); const source_alignment = source_type.get_byte_alignment(); const destination_alignment = @max(original_destination_alignment, source_alignment); const destination_alloca = module.create_alloca(.{ .type = destination_type, .name = "coerce", .alignment = destination_alignment }); _ = module.llvm.builder.create_memcpy(destination_alloca, destination_alignment, source, source_alignment, module.integer_type(64, false).llvm.handle.to_integer().get_constant(source_size, @intFromBool(false)).to_value()); const load = module.create_load(.{ .type = destination_type, .value = destination_alloca, .alignment = destination_alignment }); return load; } } }, }; return result; } pub fn create_coerced_store(module: *Module, source_value: *llvm.Value, source_type: *Type, destination: *llvm.Value, destination_ty: *Type, destination_size: u64, destination_volatile: bool) void { _ = destination_volatile; var destination_type = destination_ty; var destination_pointer = destination; const source_size = source_type.get_byte_size(); if (!source_type.is_abi_equal(destination_type)) { const r = module.enter_struct_pointer_for_coerced_access(destination_pointer, destination_type, source_size); destination_pointer = r.value; destination_type = r.type; } const is_scalable = false; // TODO if (is_scalable or source_size <= destination_size) { const destination_alignment = destination_type.get_byte_alignment(); if (source_type.bb == .integer and destination_type.bb == .pointer and source_size == lib.align_forward_u64(destination_size, destination_alignment)) { @trap(); } else if (source_type.bb == .structure) { for (source_type.bb.structure.fields, 0..) |field, field_index| { // TODO: volatile const gep = module.llvm.builder.create_struct_gep(source_type.llvm.handle.to_struct(), destination_pointer, @intCast(field_index)); const field_value = module.llvm.builder.create_extract_value(source_value, @intCast(field_index)); _ = module.create_store(.{ .source_value = field_value, .source_type = field.type, .destination_value = gep, .destination_type = field.type, .alignment = destination_alignment, }); } } else { _ = module.create_store(.{ .source_value = source_value, .source_type = source_type, .destination_value = destination_pointer, .destination_type = destination_type, .alignment = destination_alignment, }); } // TODO: is this valid for pointers too? } else if (source_type.is_integer_backing()) { @trap(); } else { // Coercion through memory const original_destination_alignment = destination_type.get_byte_alignment(); const source_alloca_alignment = @max(original_destination_alignment, source_type.get_byte_alignment()); const source_alloca = module.create_alloca(.{ .type = source_type, .alignment = source_alloca_alignment, .name = "coerce" }); _ = module.create_store(.{ .source_value = source_value, .destination_value = source_alloca, .source_type = source_type, .destination_type = source_type, .alignment = source_alloca_alignment, }); _ = module.llvm.builder.create_memcpy(destination_pointer, original_destination_alignment, source_alloca, source_alloca_alignment, module.integer_type(64, false).llvm.handle.to_integer().get_constant(destination_size, @intFromBool(false)).to_value()); } } pub fn enter_struct_pointer_for_coerced_access(module: *Module, source_value: *llvm.Value, source_ty: *Type, destination_size: u64) struct { value: *llvm.Value, type: *Type, } { _ = module; var source_pointer = source_value; var source_type = source_ty; assert(source_type.bb == .structure and source_type.bb.structure.fields.len > 0); const first_field_type = source_type.bb.structure.fields[0].type; const first_field_size = first_field_type.get_byte_size(); const source_size = source_type.get_byte_size(); source_pointer = switch (first_field_size < destination_size and first_field_size < source_size) { true => source_pointer, false => @trap(), // TODO: make sure `source_type` is also updated here }; return .{ .value = source_pointer, .type = source_type }; } const AttributeBuildOptions = struct { return_type_abi: Abi.Information, abi_argument_types: []const *Type, argument_type_abis: []const Abi.Information, abi_return_type: *Type, attributes: Function.Attributes, call_site: bool, }; pub fn build_attribute_list(module: *Module, options: AttributeBuildOptions) *llvm.Attribute.List { const return_attributes = llvm.Attribute.Argument{ .semantic_type = options.return_type_abi.semantic_type.llvm.handle, .abi_type = options.abi_return_type.llvm.handle, .dereferenceable_bytes = 0, .alignment = 0, .flags = .{ .no_alias = false, .non_null = false, .no_undef = false, .sign_extend = options.return_type_abi.flags.kind == .extend and options.return_type_abi.flags.sign_extension, .zero_extend = options.return_type_abi.flags.kind == .extend and !options.return_type_abi.flags.sign_extension, .in_reg = false, .no_fp_class = .{}, .struct_return = false, .writable = false, .dead_on_unwind = false, .in_alloca = false, .dereferenceable = false, .dereferenceable_or_null = false, .nest = false, .by_value = false, .by_reference = false, .no_capture = false, }, }; var argument_attribute_buffer: [128]llvm.Attribute.Argument = undefined; const argument_attributes = argument_attribute_buffer[0..options.abi_argument_types.len]; if (options.return_type_abi.flags.kind == .indirect) { const abi_index = @intFromBool(options.return_type_abi.flags.sret_after_this); const argument_attribute = &argument_attributes[abi_index]; argument_attribute.* = .{ .semantic_type = options.return_type_abi.semantic_type.llvm.handle, .abi_type = options.abi_argument_types[abi_index].llvm.handle, .dereferenceable_bytes = 0, .alignment = options.return_type_abi.semantic_type.get_byte_alignment(), .flags = .{ .no_alias = true, .non_null = false, .no_undef = false, .sign_extend = false, .zero_extend = false, .in_reg = options.return_type_abi.flags.in_reg, .no_fp_class = .{}, .struct_return = true, .writable = true, .dead_on_unwind = true, .in_alloca = false, .dereferenceable = false, .dereferenceable_or_null = false, .nest = false, .by_value = false, .by_reference = false, .no_capture = false, }, }; } for (options.argument_type_abis) |argument_type_abi| { for (argument_type_abi.abi_start..argument_type_abi.abi_start + argument_type_abi.abi_count) |abi_index| { const argument_attribute = &argument_attributes[abi_index]; argument_attribute.* = .{ .semantic_type = argument_type_abi.semantic_type.llvm.handle, .abi_type = options.abi_argument_types[abi_index].llvm.handle, .dereferenceable_bytes = 0, .alignment = if (argument_type_abi.flags.kind == .indirect) 8 else 0, .flags = .{ .no_alias = false, .non_null = false, .no_undef = false, .sign_extend = argument_type_abi.flags.kind == .extend and argument_type_abi.flags.sign_extension, .zero_extend = argument_type_abi.flags.kind == .extend and !argument_type_abi.flags.sign_extension, .in_reg = argument_type_abi.flags.in_reg, .no_fp_class = .{}, .struct_return = false, .writable = false, .dead_on_unwind = false, .in_alloca = false, .dereferenceable = false, .dereferenceable_or_null = false, .nest = false, .by_value = argument_type_abi.flags.indirect_by_value, .by_reference = false, .no_capture = false, }, }; } } return llvm.Attribute.List.build(module.llvm.context, llvm.Attribute.Function{ .prefer_vector_width = llvm.String{}, .stack_protector_buffer_size = llvm.String{}, .definition_probe_stack = llvm.String{}, .definition_stack_probe_size = llvm.String{}, .flags0 = .{ .noreturn = options.return_type_abi.semantic_type == module.noreturn_type, .cmse_ns_call = false, .returns_twice = false, .cold = false, .hot = false, .no_duplicate = false, .convergent = false, .no_merge = false, .will_return = false, .no_caller_saved_registers = false, .no_cf_check = false, .no_callback = false, .alloc_size = false, // TODO .uniform_work_group_size = false, .nounwind = true, .aarch64_pstate_sm_body = false, .aarch64_pstate_sm_enabled = false, .aarch64_pstate_sm_compatible = false, .aarch64_preserves_za = false, .aarch64_in_za = false, .aarch64_out_za = false, .aarch64_inout_za = false, .aarch64_preserves_zt0 = false, .aarch64_in_zt0 = false, .aarch64_out_zt0 = false, .aarch64_inout_zt0 = false, .optimize_for_size = false, .min_size = false, .no_red_zone = false, .indirect_tls_seg_refs = false, .no_implicit_floats = false, .sample_profile_suffix_elision_policy = false, .memory_none = false, .memory_readonly = false, .memory_inaccessible_or_arg_memory_only = false, .memory_arg_memory_only = false, .strict_fp = false, .no_inline = options.attributes.inline_behavior == .no_inline, .always_inline = options.attributes.inline_behavior == .always_inline, .guard_no_cf = false, // TODO: branch protection function attributes // TODO: cpu features // CALL-SITE ATTRIBUTES .call_no_builtins = false, // DEFINITION-SITE ATTRIBUTES .definition_frame_pointer_kind = .none, .definition_less_precise_fpmad = false, .definition_null_pointer_is_valid = false, .definition_no_trapping_fp_math = false, .definition_no_infs_fp_math = false, .definition_no_nans_fp_math = false, .definition_approx_func_fp_math = false, .definition_unsafe_fp_math = false, .definition_use_soft_float = false, .definition_no_signed_zeroes_fp_math = false, .definition_stack_realignment = false, .definition_backchain = false, .definition_split_stack = false, .definition_speculative_load_hardening = false, .definition_zero_call_used_registers = .all, // TODO: denormal builtins .definition_non_lazy_bind = false, .definition_cmse_nonsecure_entry = false, .definition_unwind_table_kind = .none, }, .flags1 = .{ .definition_disable_tail_calls = false, .definition_stack_protect_strong = false, .definition_stack_protect = false, .definition_stack_protect_req = false, .definition_aarch64_new_za = false, .definition_aarch64_new_zt0 = false, .definition_optimize_none = false, .definition_naked = !options.call_site and options.attributes.naked, .definition_inline_hint = !options.call_site and options.attributes.inline_behavior == .inline_hint, }, }, return_attributes, argument_attributes, options.call_site); } pub fn get_va_list_type(module: *Module) *Type { if (module.va_list_type) |va_list_type| { @branchHint(.likely); return va_list_type; } else { @branchHint(.unlikely); const unsigned_int = module.integer_type(32, false); const void_pointer = module.get_pointer_type(.{ .type = module.integer_type(8, false), }); const llvm_parameter_types = [_]*llvm.Type{ unsigned_int.llvm.handle, unsigned_int.llvm.handle, void_pointer.llvm.handle, void_pointer.llvm.handle, }; const line = 1; const bit_alignment = 0; // TODO const flags = llvm.DI.Flags{}; const llvm_member_types = [_]*llvm.DI.Type.Derived{ if (module.llvm.di_builder) |di_builder| di_builder.create_member_type(module.llvm.global_scope, "gp_offset", module.llvm.file, line, 32, bit_alignment, 0, .{}, unsigned_int.llvm.debug) else undefined, if (module.llvm.di_builder) |di_builder| di_builder.create_member_type(module.llvm.global_scope, "fp_offset", module.llvm.file, line, 32, bit_alignment, 32, .{}, unsigned_int.llvm.debug) else undefined, if (module.llvm.di_builder) |di_builder| di_builder.create_member_type(module.llvm.global_scope, "overflow_arg_area", module.llvm.file, line, 64, bit_alignment, 64, .{}, void_pointer.llvm.debug) else undefined, if (module.llvm.di_builder) |di_builder| di_builder.create_member_type(module.llvm.global_scope, "reg_save_area", module.llvm.file, line, 64, bit_alignment, 128, .{}, void_pointer.llvm.debug) else undefined, }; const llvm_handle = module.llvm.context.get_struct_type(&llvm_parameter_types); const bit_size = 24 * 8; const va_list_name = "va_list"; const llvm_debug = if (module.llvm.di_builder) |di_builder| di_builder.create_struct_type(module.llvm.global_scope, va_list_name, module.llvm.file, line, bit_size, bit_alignment, flags, &llvm_member_types) else undefined; const field_buffer = [_]Field{ .{ .name = "gp_offset", .type = unsigned_int, .bit_offset = 0, .byte_offset = 0 }, .{ .name = "fp_offset", .type = unsigned_int, .bit_offset = 32, .byte_offset = 4 }, .{ .name = "overflow_arg_area", .type = void_pointer, .bit_offset = 64, .byte_offset = 8 }, .{ .name = "reg_save_area", .type = void_pointer, .bit_offset = 128, .byte_offset = 16 }, }; const fields = module.arena.allocate(Field, 4); @memcpy(fields, &field_buffer); const result = module.types.add(.{ .llvm = .{ .handle = llvm_handle.to_type(), .debug = llvm_debug.to_type(), }, .name = va_list_name, .bb = .{ .structure = .{ .bit_alignment = 64, .byte_alignment = 16, .byte_size = 24, .bit_size = 24 * 8, .fields = fields, }, }, }); const array = ArrayType{ .element_count = 1, .element_type = result, }; const ty = module.types.add(.{ .name = array_type_name(module.arena, array), .llvm = array_type_llvm(module, array), .bb = .{ .array = array, }, }); module.va_list_type = ty; return ty; } } const AllocaOptions = struct { type: *Type, name: []const u8 = "", alignment: ?c_uint = null, }; pub fn create_alloca(module: *Module, options: AllocaOptions) *llvm.Value { const abi_type = switch (options.type.is_arbitrary_bit_integer()) { true => module.align_integer_type(options.type), false => options.type, }; const alignment: c_uint = if (options.alignment) |a| a else @intCast(abi_type.get_byte_alignment()); const v = module.llvm.builder.create_alloca(abi_type.llvm.handle, options.name); v.set_alignment(alignment); return v; } const IntCast = struct { source_type: *Type, destination_type: *Type, value: *llvm.Value, }; pub fn raw_int_cast(module: *Module, options: IntCast) *llvm.Value { assert(options.source_type != options.destination_type); const source_size = options.source_type.get_bit_size(); const destination_size = options.destination_type.get_bit_size(); const result = switch (source_size < destination_size) { true => switch (options.source_type.is_signed()) { true => module.llvm.builder.create_sign_extend(options.value, options.destination_type.llvm.handle), false => module.llvm.builder.create_zero_extend(options.value, options.destination_type.llvm.handle), }, false => module.llvm.builder.create_truncate(options.value, options.destination_type.llvm.handle), }; return result; } const LoadOptions = struct { type: *Type, value: *llvm.Value, alignment: ?c_uint = null, }; pub fn create_load(module: *Module, options: LoadOptions) *llvm.Value { switch (options.type.bb) { .void, .noreturn, .forward_declaration => unreachable, .array => unreachable, .function => unreachable, .vector => @trap(), .bits, .float, .integer, .pointer, .enumerator, .structure => { const storage_type = switch (options.type.is_arbitrary_bit_integer()) { true => module.align_integer_type(options.type), false => options.type, }; const alignment: c_uint = if (options.alignment) |a| a else @intCast(storage_type.get_byte_alignment()); const v = module.llvm.builder.create_load(storage_type.llvm.handle, options.value); v.set_alignment(alignment); return switch (storage_type == options.type) { true => v, false => module.raw_int_cast(.{ .source_type = storage_type, .destination_type = options.type, .value = v }), }; }, } } const StoreOptions = struct { source_value: *llvm.Value, destination_value: *llvm.Value, source_type: *Type, destination_type: *Type, alignment: ?c_uint = null, }; pub fn create_store(module: *Module, options: StoreOptions) *llvm.Value { const raw_store_type = switch (options.source_type.is_arbitrary_bit_integer()) { true => module.align_integer_type(options.source_type), false => options.source_type, }; const source_value = switch (raw_store_type == options.source_type) { true => options.source_value, false => module.raw_int_cast(.{ .source_type = options.source_type, .destination_type = raw_store_type, .value = options.source_value }), }; const alignment = if (options.alignment) |a| a else options.destination_type.get_byte_alignment(); const v = module.llvm.builder.create_store(source_value, options.destination_value); v.set_alignment(alignment); return v; } pub fn current_basic_block(module: *Module) *llvm.BasicBlock { return module.llvm.builder.get_insert_block() orelse unreachable; } const LLVM = struct { context: *llvm.Context, handle: *llvm.Module, builder: *llvm.Builder, di_builder: ?*llvm.DI.Builder, global_scope: *llvm.DI.Scope, file: *llvm.DI.File, pointer_type: *llvm.Type, intrinsic_table: IntrinsicTable, const IntrinsicTable = struct { trap: llvm.Intrinsic.Id, va_start: llvm.Intrinsic.Id, va_end: llvm.Intrinsic.Id, va_copy: llvm.Intrinsic.Id, }; }; pub fn get_anonymous_struct_pair(module: *Module, pair: [2]*Type) *Type { for (module.anonymous_pair_type_buffer[0..module.anonymous_pair_type_count]) |anonymous_type_index| { const anonymous_type = &module.types.get()[anonymous_type_index]; const fields = anonymous_type.bb.structure.fields; if (fields.len == 2 and pair[0] == fields[0].type and pair[1] == fields[1].type) { return anonymous_type; } } else { const llvm_pair_members = &.{ pair[0].llvm.handle, pair[1].llvm.handle }; const llvm_pair = module.llvm.context.get_struct_type(llvm_pair_members); const byte_alignment = @max(pair[0].get_byte_alignment(), pair[1].get_byte_alignment()); const byte_size = lib.align_forward_u64(pair[0].get_byte_size() + pair[1].get_byte_size(), byte_alignment); const fields = module.arena.allocate(Field, 2); fields[0] = .{ .bit_offset = 0, .byte_offset = 0, .type = pair[0], .name = "", }; fields[1] = .{ .bit_offset = pair[0].get_bit_size(), // TODO .byte_offset = pair[0].get_byte_size(), // TODO .type = pair[1], .name = "", }; const pair_type = module.types.add(.{ .name = "", .bb = .{ .structure = .{ .bit_alignment = byte_alignment * 8, .byte_alignment = byte_alignment, .byte_size = byte_size, .bit_size = byte_size * 8, .fields = fields, }, }, .llvm = .{ .handle = llvm_pair.to_type(), .debug = undefined, }, }); module.anonymous_pair_type_buffer[module.anonymous_pair_type_count] = @intCast(pair_type - module.types.get().ptr); module.anonymous_pair_type_count += 1; return pair_type; } } pub fn get_type(module: *Module, index: usize) *Type { assert(index < module.types.count); const result = &module.types.buffer[index]; return result; } pub fn integer_type(module: *Module, bit_count: u32, sign: bool) *Type { switch (bit_count) { 1...64 => { const index = @as(usize, @intFromBool(sign)) * 64 + bit_count; const result = module.get_type(index); assert(result.bb == .integer); assert(result.bb.integer.bit_count == bit_count); assert(result.bb.integer.signed == sign); return result; }, 128 => @trap(), else => @trap(), } } pub fn align_integer_type(module: *Module, ty: *Type) *Type { assert(ty.bb == .integer); const bit_count = ty.get_bit_size(); const abi_bit_count: u32 = @intCast(@max(8, lib.next_power_of_two(bit_count))); if (bit_count != abi_bit_count) { const is_signed = ty.is_signed(); return module.integer_type(abi_bit_count, is_signed); } else { return ty; } } pub fn load_arbitrary_integer_type(module: *Module, destination_type: *Type, value: *Value) *Value { _ = module; assert(value.type.bb == .pointer); const appointee_type = value.type.bb.pointer.type; assert(appointee_type != destination_type); assert(destination_type.bb == .integer); assert(appointee_type.bb == .integer); assert(!appointee_type.is_arbitrary_bit_integer()); assert(destination_type.is_arbitrary_bit_integer()); // const bit_count = appointee_type.get_bit_size(); // const abi_bit_count: u32 = @intCast(@max(8, lib.next_power_of_two(bit_count))); // const is_signed = appointee_type.is_signed(); _ = integer_type; @trap(); } pub fn store_arbitrary_integer_type(module: *Module) void { _ = module; @trap(); } pub fn initialize(arena: *Arena, options: ConvertOptions) *Module { const arena_restore_position = arena.position; const context = llvm.Context.create(); const handle = context.create_module(options.name); var maybe_di_builder: ?*llvm.DI.Builder = null; var global_scope: *llvm.DI.Scope = undefined; var file: *llvm.DI.File = undefined; if (options.has_debug_info) { const di_builder = handle.create_di_builder(); maybe_di_builder = di_builder; var directory: []const u8 = undefined; var file_name: []const u8 = undefined; if (lib.string.last_character(options.path, '/')) |index| { directory = options.path[0..index]; file_name = options.path[index + 1 ..]; } else { os.abort(); } file = di_builder.create_file(file_name, directory); const compile_unit = di_builder.create_compile_unit(file, options.build_mode.is_optimized()); global_scope = compile_unit.to_scope(); } const module = arena.allocate_one(Module); const default_address_space = 0; module.* = .{ .arena = arena, .target = options.target, .llvm = .{ .global_scope = global_scope, .file = file, .handle = handle, .context = context, .builder = context.create_builder(), .di_builder = maybe_di_builder, .pointer_type = context.get_pointer_type(default_address_space).to_type(), .intrinsic_table = .{ .trap = llvm.lookup_intrinsic_id("llvm.trap"), .va_start = llvm.lookup_intrinsic_id("llvm.va_start"), .va_end = llvm.lookup_intrinsic_id("llvm.va_end"), .va_copy = llvm.lookup_intrinsic_id("llvm.va_copy"), }, }, .arena_restore_position = arena_restore_position, }; var llvm_integer_types: [64]*llvm.Type = undefined; for (1..64 + 1) |bit_count| { llvm_integer_types[bit_count - 1] = context.get_integer_type(@intCast(bit_count)).to_type(); } const llvm_i128 = context.get_integer_type(128).to_type(); module.void_type = module.types.add(.{ .name = "void", .llvm = .{ .handle = context.get_void_type(), .debug = if (maybe_di_builder) |di_builder| di_builder.create_basic_type("void", 0, .void, .{}) else undefined, }, .bb = .void, }); for ([2]bool{ false, true }) |sign| { for (1..64 + 1) |bit_count| { const name_buffer = [3]u8{ if (sign) 's' else 'u', @intCast(if (bit_count < 10) bit_count % 10 + '0' else bit_count / 10 + '0'), if (bit_count > 9) @intCast(bit_count % 10 + '0') else 0 }; const name_length = @as(usize, 2) + @intFromBool(bit_count > 9); const name = arena.duplicate_string(name_buffer[0..name_length]); _ = module.types.add(.{ .name = name, .bb = .{ .integer = .{ .bit_count = @intCast(bit_count), .signed = sign, }, }, .llvm = .{ .handle = llvm_integer_types[bit_count - 1], .debug = if (maybe_di_builder) |di_builder| blk: { const dwarf_type: llvm.Dwarf.Type = if (bit_count == 8 and !sign) .unsigned_char else if (sign) .signed else .unsigned; break :blk di_builder.create_basic_type(name, bit_count, dwarf_type, .{}); } else undefined, }, }); } } for ([2]bool{ false, true }) |sign| { const name = if (sign) "s128" else "u128"; _ = module.types.add(.{ .name = name, .bb = .{ .integer = .{ .bit_count = 128, .signed = sign, }, }, .llvm = .{ .handle = llvm_i128, .debug = if (maybe_di_builder) |di_builder| blk: { const dwarf_type: llvm.Dwarf.Type = if (sign) .signed else .unsigned; break :blk di_builder.create_basic_type(name, 128, dwarf_type, .{}); } else undefined, }, }); } module.noreturn_type = module.types.add(.{ .name = "noreturn", .llvm = .{ .handle = context.get_void_type(), .debug = if (maybe_di_builder) |di_builder| di_builder.create_basic_type("noreturn", 0, .void, .{ .no_return = true }) else undefined, }, .bb = .noreturn, }); module.void_value = module.values.add(); module.void_value.* = .{ .llvm = undefined, .bb = .infer_or_ignore, .type = module.void_type, .lvalue = false, .dereference_to_assign = false, }; module.unreachable_value = module.values.add(); module.unreachable_value.* = .{ .llvm = undefined, .bb = .@"unreachable", .type = module.noreturn_type, .lvalue = false, .dereference_to_assign = false, }; return module; } pub fn deinitialize(module: *Module) void { const arena = module.arena; const position = module.arena_restore_position; defer arena.restore(position); } const Pointer = struct { type: *Type, alignment: ?u32 = null, }; pub fn get_pointer_type(module: *Module, pointer: Pointer) *Type { const p = PointerType{ .type = pointer.type, .alignment = if (pointer.alignment) |a| a else pointer.type.get_byte_alignment(), }; const all_types = module.types.get(); const pointer_type = for (module.pointer_type_buffer[0..module.pointer_type_count]) |pointer_type_index| { const ty = &all_types[pointer_type_index]; const pointer_type = &all_types[pointer_type_index].bb.pointer; if (pointer_type.type == p.type and pointer_type.alignment == p.alignment) { break ty; } } else blk: { const pointer_name = if (p.type.name) |name| module.arena.join_string(&.{ "&", name }) else "unknownptr"; const pointer_type = module.types.add(.{ .name = pointer_name, .llvm = .{ .handle = module.llvm.pointer_type, .debug = if (module.llvm.di_builder) |di_builder| di_builder.create_pointer_type(p.type.llvm.debug, 64, 64, 0, pointer_name).to_type() else undefined, }, .bb = .{ .pointer = p, }, }); const index = pointer_type - module.types.get().ptr; module.pointer_type_buffer[module.pointer_type_count] = @intCast(index); module.pointer_type_count += 1; break :blk pointer_type; }; return pointer_type; } }; const AttributeContainerType = enum { call, function, }; fn llvm_add_function_attribute(value: *llvm.Value, attribute: *llvm.Attribute, container_type: AttributeContainerType) void { switch (container_type) { .call => { const call = value.is_call_instruction() orelse unreachable; call.add_attribute(.function, attribute); }, .function => { const function = value.to_function(); function.add_attribute(.function, attribute); }, } } fn llvm_add_argument_attribute(value: *llvm.Value, attribute: *llvm.Attribute, index: c_uint, container_type: AttributeContainerType) void { switch (container_type) { .call => { const call = value.is_call_instruction() orelse unreachable; call.add_attribute(@enumFromInt(index), attribute); }, .function => { const function = value.to_function(); function.add_attribute(@enumFromInt(index), attribute); }, } } pub const Function = struct { return_alloca: *llvm.Value, exit_block: ?*llvm.BasicBlock, return_block: *llvm.BasicBlock, current_scope: *llvm.DI.Scope, return_pointer: *Value, attributes: Attributes, locals: Variable.Array = .{}, arguments: Variable.Array = .{}, const Attributes = struct { inline_behavior: enum { default, always_inline, no_inline, inline_hint, } = .default, naked: bool = false, }; }; pub const ConstantInteger = struct { value: u64, signed: bool, }; pub const Value = struct { bb: union(enum) { function: Function, local, global, argument, instruction, struct_initialization: struct { is_constant: bool, }, bits_initialization, infer_or_ignore, constant_integer: ConstantInteger, constant_array, external_function, @"unreachable", }, type: *Type, llvm: *llvm.Value, lvalue: bool, dereference_to_assign: bool, const Array = struct { buffer: [1024]Value = undefined, count: usize = 0, pub fn add(values: *Array) *Value { const result = &values.buffer[values.count]; values.count += 1; return result; } }; pub fn is_constant(value: *Value) bool { return switch (value.bb) { .constant_integer, .constant_array => true, .struct_initialization => |si| si.is_constant, else => @trap(), }; } }; const Field = struct { name: []const u8, type: *Type, bit_offset: usize, byte_offset: usize, }; const FunctionType = struct { return_type_abi: Abi.Information, argument_type_abis: []const Abi.Information, abi_return_type: *Type, abi_argument_types: []const *Type, calling_convention: CallingConvention, available_registers: Abi.RegisterCount, is_var_args: bool, fn get_abi_argument_types(function_type: *const FunctionType) []const *Type { return function_type.abi_argument_types[0..function_type.abi_argument_count]; } }; const StructType = struct { fields: []const Field, bit_size: u64, byte_size: u64, bit_alignment: u32, byte_alignment: u32, }; const Bits = struct { fields: []const Field, backing_type: *Type, implicit_backing_type: bool, }; pub const ArrayType = struct { element_count: ?usize, element_type: *Type, }; pub const IntegerType = struct { bit_count: u32, signed: bool, }; pub const FloatType = struct { const Kind = enum { half, bfloat, float, double, fp128, }; kind: Kind, }; pub const Enumerator = struct { fields: []const Enumerator.Field, backing_type: *Type, implicit_backing_type: bool, pub const Field = struct { name: []const u8, value: u64, }; }; pub const PointerType = struct { type: *Type, alignment: u32, }; pub const Type = struct { bb: BB, llvm: LLVM, name: ?[]const u8, pub const EvaluationKind = enum { scalar, complex, aggregate, }; pub const BB = union(enum) { void, noreturn, forward_declaration, integer: IntegerType, float: FloatType, structure: StructType, bits: Bits, function: FunctionType, array: ArrayType, pointer: PointerType, enumerator: Enumerator, vector, }; pub fn is_aggregate_type_for_abi(ty: *Type) bool { const ev_kind = ty.get_evaluation_kind(); const is_member_function_pointer_type = false; // TODO return ev_kind != .scalar or is_member_function_pointer_type; } pub fn is_integer_backing(ty: *Type) bool { return switch (ty.bb) { .enumerator, .integer, .bits, .pointer => true, else => false, }; } pub fn is_abi_equal(ty: *const Type, other: *const Type) bool { return ty == other or ty.llvm.handle == other.llvm.handle; } pub fn is_signed(ty: *const Type) bool { return switch (ty.bb) { .integer => |integer| integer.signed, .bits => |bits| bits.backing_type.is_signed(), else => @trap(), }; } pub fn is_integral_or_enumeration_type(ty: *Type) bool { return switch (ty.bb) { .integer => true, .bits => true, .structure => false, // .integer => |integer| switch (integer.bit_count) { // 1, 8, 16, 32, 64, 128 => true, // else => false, // }, else => @trap(), }; } pub fn is_arbitrary_bit_integer(ty: *Type) bool { return switch (ty.bb) { .integer => |integer| switch (integer.bit_count) { 8, 16, 32, 64, 128 => false, else => true, }, .bits => |bits| bits.backing_type.is_arbitrary_bit_integer(), else => false, }; } pub fn is_promotable_integer_type_for_abi(ty: *Type) bool { return switch (ty.bb) { .integer => |integer| integer.bit_count < 32, .bits => |bits| bits.backing_type.is_promotable_integer_type_for_abi(), else => @trap(), }; } pub fn get_evaluation_kind(ty: *const Type) EvaluationKind { return switch (ty.bb) { .structure, .array => .aggregate, .integer, .bits, .pointer, .enumerator => .scalar, else => @trap(), }; } pub fn get_byte_allocation_size(ty: *const Type) u64 { return lib.align_forward_u64(ty.get_byte_size(), ty.get_byte_alignment()); } pub fn get_bit_size(ty: *const Type) u64 { return switch (ty.bb) { .integer => |integer| integer.bit_count, .structure => |struct_type| struct_type.bit_size, .bits => |bits| bits.backing_type.get_bit_size(), .void, .forward_declaration, .function, .noreturn => unreachable, .array => |*array| array.element_type.get_bit_size() * array.element_count.?, .pointer => 64, .enumerator => |enumerator| enumerator.backing_type.get_bit_size(), .float => @trap(), .vector => @trap(), }; } pub fn get_byte_size(ty: *const Type) u64 { return switch (ty.bb) { .integer => |integer| @divExact(@max(8, lib.next_power_of_two(integer.bit_count)), 8), .structure => |struct_type| struct_type.byte_size, .bits => |bits| bits.backing_type.get_byte_size(), .void, .forward_declaration, .function, .noreturn => unreachable, .array => |*array| array.element_type.get_byte_size() * array.element_count.?, .pointer => 8, .enumerator => @trap(), .float => @trap(), .vector => @trap(), }; } pub fn get_bit_alignment(ty: *const Type) u32 { return switch (ty.bb) { .integer => |integer| integer.bit_count, .structure => |struct_type| struct_type.bit_alignment, .bits => |bits| bits.backing_type.get_bit_alignment(), .void, .forward_declaration, .function, .noreturn => unreachable, .array => |*array| array.element_type.get_bit_alignment(), .pointer => 64, .enumerator => @trap(), .float => @trap(), .vector => @trap(), }; } pub fn get_byte_alignment(ty: *const Type) u32 { return switch (ty.bb) { .integer => |integer| @as(u32, @intCast(@divExact(@max(8, lib.next_power_of_two(integer.bit_count)), 8))), .structure => |struct_type| struct_type.byte_alignment, .bits => |bits| bits.backing_type.get_byte_alignment(), .function => 1, .void, .forward_declaration, .noreturn => unreachable, .array => |array| array.element_type.get_byte_alignment(), .pointer => 8, .enumerator => |enumerator| enumerator.backing_type.get_byte_alignment(), .float => @trap(), .vector => @trap(), }; } const Array = struct { buffer: [1024]Type = undefined, count: usize = 0, const buffer_size = 1024; pub fn get(types: *Array) []Type { return types.buffer[0..types.count]; } pub fn find(types: *Array, name: []const u8) ?*Type { for (types.get()) |*ty| { if (ty.name) |type_name| { if (lib.string.equal(type_name, name)) { return ty; } } } else { return null; } } fn add(types: *Array, ty: Type) *Type { const result = &types.buffer[types.count]; types.count += 1; result.* = ty; return result; } }; pub const LLVM = struct { handle: *llvm.Type, debug: *llvm.DI.Type, }; }; pub const Variable = struct { value: *Value, name: []const u8, const Array = struct { buffer: [1024]Variable = undefined, count: u32 = 0, pub fn get(variables: *Array) []Variable { return variables.buffer[0..variables.count]; } pub fn add(variables: *Array) *Variable { const result = &variables.buffer[variables.count]; variables.count += 1; return result; } pub fn add_many(variables: *Array, count: u32) []Variable { const result = variables.buffer[variables.count .. variables.count + count]; variables.count += count; return result; } pub fn find(variables: *Array, name: []const u8) ?*Variable { for (variables.get()) |*variable| { if (lib.string.equal(variable.name, name)) { return variable; } } else { return null; } } }; }; const Converter = struct { content: []const u8, offset: usize, line_offset: usize, line_character_offset: usize, fn get_line(converter: *const Converter) u32 { return @intCast(converter.line_offset + 1); } fn get_column(converter: *const Converter) u32 { return @intCast(converter.offset - converter.line_character_offset + 1); } fn report_error(noalias converter: *Converter) noreturn { @branchHint(.cold); _ = converter; lib.os.abort(); } fn skip_space(noalias converter: *Converter) void { while (true) { const offset = converter.offset; while (converter.offset < converter.content.len and is_space(converter.content[converter.offset])) { converter.line_offset += @intFromBool(converter.content[converter.offset] == '\n'); converter.line_character_offset = if (converter.content[converter.offset] == '\n') converter.offset else converter.line_character_offset; converter.offset += 1; } if (converter.offset + 1 < converter.content.len) { const i = converter.offset; const is_comment = converter.content[i] == '/' and converter.content[i + 1] == '/'; if (is_comment) { while (converter.offset < converter.content.len and converter.content[converter.offset] != '\n') { converter.offset += 1; } if (converter.offset < converter.content.len) { converter.line_offset += 1; converter.line_character_offset = converter.offset; converter.offset += 1; } } } if (converter.offset - offset == 0) { break; } } } pub fn parse_type(noalias converter: *Converter, noalias module: *Module) *Type { switch (converter.content[converter.offset]) { 'a'...'z', 'A'...'Z', '_' => { const identifier = converter.parse_identifier(); var integer_type = identifier.len > 1 and identifier[0] == 's' or identifier[0] == 'u'; if (integer_type) { for (identifier[1..]) |ch| { integer_type = integer_type and is_decimal_ch(ch); } } if (integer_type) { const signedness = switch (identifier[0]) { 's' => true, 'u' => false, else => unreachable, }; const bit_count: u32 = @intCast(lib.parse.integer_decimal(identifier[1..])); const ty = module.integer_type(bit_count, signedness); return ty; } else if (lib.string.equal(identifier, "noreturn")) { return module.noreturn_type; } else { const ty = module.types.find(identifier) orelse @trap(); return ty; } }, left_bracket => { converter.offset += 1; converter.skip_space(); const length_expression = converter.parse_value(module, module.integer_type(64, false), .value); converter.skip_space(); converter.expect_character(right_bracket); const element_type = converter.parse_type(module); if (length_expression.bb == .infer_or_ignore) { const ty = module.types.add(.{ .name = undefined, .llvm = undefined, .bb = .{ .array = .{ .element_count = null, .element_type = element_type, }, }, }); return ty; } else { const element_count = length_expression.bb.constant_integer.value; const array = ArrayType{ .element_count = element_count, .element_type = element_type, }; const ty = module.types.add(.{ .name = array_type_name(module.arena, array), .llvm = array_type_llvm(module, array), .bb = .{ .array = array, }, }); return ty; } }, '&' => { converter.offset += 1; converter.skip_space(); const element_type = converter.parse_type(module); return module.get_pointer_type(.{ .type = element_type, }); }, '#' => return converter.parse_type_intrinsic(module), else => @trap(), } } pub fn parse_identifier(noalias converter: *Converter) []const u8 { const start = converter.offset; if (is_identifier_start_ch(converter.content[start])) { converter.offset += 1; while (converter.offset < converter.content.len) { if (is_identifier_ch(converter.content[converter.offset])) { converter.offset += 1; } else { break; } } } if (converter.offset - start == 0) { converter.report_error(); } return converter.content[start..converter.offset]; } fn consume_character_if_match(noalias converter: *Converter, expected_ch: u8) bool { var is_ch = false; if (converter.offset < converter.content.len) { const ch = converter.content[converter.offset]; is_ch = expected_ch == ch; converter.offset += @intFromBool(is_ch); } return is_ch; } fn expect_or_consume(noalias converter: *Converter, expected_ch: u8, is_required: bool) bool { if (is_required) { converter.expect_character(expected_ch); return true; } else { return converter.consume_character_if_match(expected_ch); } } fn parse_decimal(noalias converter: *Converter) u64 { var value: u64 = 0; while (true) { const ch = converter.content[converter.offset]; if (!is_decimal_ch(ch)) { break; } converter.offset += 1; value = lib.parse.accumulate_decimal(value, ch); } return value; } fn parse_hexadecimal(noalias converter: *Converter) u64 { var value: u64 = 0; while (true) { const ch = converter.content[converter.offset]; if (!lib.is_hex_digit(ch)) { break; } converter.offset += 1; value = lib.parse.accumulate_hexadecimal(value, ch); } return 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)); assert(is_decimal_ch(integer_start_ch)); const absolute_value: u64 = switch (integer_start_ch) { '0' => blk: { converter.offset += 1; const next_ch = converter.content[converter.offset]; break :blk switch (sign) { false => switch (next_ch) { 'x' => b: { converter.offset += 1; break :b converter.parse_hexadecimal(); }, 'o' => { // TODO: parse octal converter.report_error(); }, 'b' => { // TODO: parse binary converter.report_error(); }, '0'...'9' => { converter.report_error(); }, // Zero literal else => 0, }, true => switch (next_ch) { 'x', 'o', 'b', '0' => converter.report_error(), '1'...'9' => converter.parse_decimal(), else => unreachable, }, }; }, '1'...'9' => converter.parse_decimal(), 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, }; const integer_type = expected_type.llvm.handle.to_integer(); const llvm_integer_value = integer_type.get_constant(value, @intFromBool(expected_type.bb.integer.signed)); const integer_value = module.values.add(); integer_value.* = .{ .llvm = llvm_integer_value.to_value(), .type = expected_type, .bb = .{ .constant_integer = .{ .value = absolute_value, .signed = sign, }, }, .lvalue = false, .dereference_to_assign = false, }; return integer_value; } fn expect_character(noalias converter: *Converter, expected_ch: u8) void { if (!converter.consume_character_if_match(expected_ch)) { converter.report_error(); } } fn parse_call(noalias converter: *Converter, noalias module: *Module, may_be_callable: *Value) *Value { const child_type = may_be_callable.type.bb.pointer.type; const pointer_type = switch (child_type.bb) { .function => may_be_callable.type, .pointer => |p| switch (p.type.bb) { .function => child_type, else => @trap(), }, else => @trap(), }; const raw_function_type = pointer_type.bb.pointer.type; const llvm_callable = switch (child_type == raw_function_type) { true => may_be_callable.llvm, else => module.create_load(.{ .type = pointer_type, .value = may_be_callable.llvm }), }; const function_type = &raw_function_type.bb.function; const calling_convention = function_type.calling_convention; const llvm_calling_convention = calling_convention.to_llvm(); var llvm_abi_argument_value_buffer: [max_argument_count]*llvm.Value = undefined; var llvm_abi_argument_type_buffer: [max_argument_count]*llvm.Type = undefined; var abi_argument_type_buffer: [max_argument_count]*Type = undefined; var argument_type_abi_buffer: [max_argument_count]Abi.Information = undefined; var abi_argument_count: u16 = 0; var semantic_argument_count: u32 = 0; const function_semantic_argument_count = function_type.argument_type_abis.len; // TODO const uses_in_alloca = false; if (uses_in_alloca) { @trap(); } const llvm_indirect_return_value: *llvm.Value = switch (function_type.return_type_abi.flags.kind) { .indirect, .in_alloca, .coerce_and_expand => blk: { // TODO: handle edge cases: // - virtual function pointer thunk // - return alloca already exists const temporal_alloca = module.create_alloca(.{ .type = function_type.return_type_abi.semantic_type, .name = "tmp" }); const has_sret = function_type.return_type_abi.flags.kind == .indirect; if (has_sret) { llvm_abi_argument_value_buffer[abi_argument_count] = temporal_alloca; abi_argument_type_buffer[abi_argument_count] = module.void_type; llvm_abi_argument_type_buffer[abi_argument_count] = module.void_type.llvm.handle; abi_argument_count += 1; break :blk temporal_alloca; } else if (function_type.return_type_abi.flags.kind == .in_alloca) { @trap(); } else { @trap(); } }, else => undefined, }; var available_registers = function_type.available_registers; while (true) : (semantic_argument_count += 1) { converter.skip_space(); if (converter.consume_character_if_match(right_parenthesis)) { break; } const semantic_argument_index = semantic_argument_count; const is_named_argument = semantic_argument_index < function_semantic_argument_count; if (is_named_argument or function_type.is_var_args) { const expected_semantic_argument_type: ?*Type = if (is_named_argument) function_type.argument_type_abis[semantic_argument_index].semantic_type else null; const semantic_argument_value = converter.parse_value(module, expected_semantic_argument_type, .value); _ = converter.consume_character_if_match(','); const semantic_argument_type = switch (is_named_argument) { true => function_type.argument_type_abis[semantic_argument_index].semantic_type, false => if (semantic_argument_value.lvalue and semantic_argument_value.dereference_to_assign) blk: { const t = semantic_argument_value.type; assert(t.bb == .pointer); assert(t.bb.pointer.type.bb == .structure); break :blk t.bb.pointer.type; } else semantic_argument_value.type, }; const argument_abi = if (is_named_argument) function_type.argument_type_abis[semantic_argument_index] else Abi.SystemV.classify_argument(module, &available_registers, &llvm_abi_argument_type_buffer, &abi_argument_type_buffer, .{ .type = semantic_argument_type, .abi_start = abi_argument_count, .is_named_argument = true, }); if (is_named_argument) { for (llvm_abi_argument_type_buffer[argument_abi.abi_start..][0..argument_abi.abi_count], abi_argument_type_buffer[argument_abi.abi_start..][0..argument_abi.abi_count], function_type.abi_argument_types[argument_abi.abi_start..][0..argument_abi.abi_count]) |*llvm_t, *t, abi_argument_type| { llvm_t.* = abi_argument_type.llvm.handle; t.* = abi_argument_type; } } argument_type_abi_buffer[semantic_argument_index] = argument_abi; if (argument_abi.padding.type) |padding_type| { _ = padding_type; @trap(); } assert(abi_argument_count == argument_abi.abi_start); const argument_abi_kind = argument_abi.flags.kind; switch (argument_abi_kind) { .direct, .extend => { const coerce_to_type = argument_abi.get_coerce_to_type(); if (coerce_to_type.bb != .structure and semantic_argument_type.is_abi_equal(coerce_to_type) and argument_abi.attributes.direct.offset == 0) { var v = switch (argument_abi.semantic_type.get_evaluation_kind()) { .aggregate => @trap(), else => semantic_argument_value, }; _ = &v; if (!coerce_to_type.is_abi_equal(v.type)) { switch (v.type) { else => @trap(), } } // TODO: bitcast // if (argument_abi.abi_start < function_type.argument_type_abis.len and v.type.llvm.handle != abi_arguments // TODO: fill types llvm_abi_argument_value_buffer[abi_argument_count] = v.llvm; abi_argument_count += 1; } else { if (coerce_to_type.bb == .structure and argument_abi.flags.kind == .direct and !argument_abi.flags.can_be_flattened) { @trap(); } const evaluation_kind = semantic_argument_type.get_evaluation_kind(); var src = switch (evaluation_kind) { .aggregate => semantic_argument_value, .scalar => { @trap(); }, .complex => @trap(), }; src = switch (argument_abi.attributes.direct.offset > 0) { true => @trap(), false => src, }; if (coerce_to_type.bb == .structure and argument_abi.flags.kind == .direct and argument_abi.flags.can_be_flattened) { const source_type_size_is_scalable = false; // TODO if (source_type_size_is_scalable) { @trap(); } else { const destination_size = coerce_to_type.get_byte_size(); const source_size = argument_abi.semantic_type.get_byte_size(); const alignment = argument_abi.semantic_type.get_byte_alignment(); const source = switch (source_size < destination_size) { true => blk: { const temporal_alloca = module.create_alloca(.{ .type = coerce_to_type, .name = "coerce", .alignment = alignment }); const destination = temporal_alloca; const source = semantic_argument_value.llvm; _ = module.llvm.builder.create_memcpy(destination, alignment, source, alignment, module.integer_type(64, false).llvm.handle.to_integer().get_constant(semantic_argument_type.get_byte_size(), @intFromBool(false)).to_value()); break :blk temporal_alloca; }, false => src.llvm, }; // TODO: assert(argument_abi.attributes.direct.offset == 0); for (coerce_to_type.bb.structure.fields, 0..) |field, field_index| { const gep = module.llvm.builder.create_struct_gep(coerce_to_type.llvm.handle.to_struct(), source, @intCast(field_index)); const maybe_undef = false; if (maybe_undef) { @trap(); } const load = module.create_load(.{ .value = gep, .type = field.type, .alignment = alignment }); llvm_abi_argument_value_buffer[abi_argument_count] = load; abi_argument_count += 1; } } } else { assert(argument_abi.abi_count == 1); assert(src.type.bb == .pointer); const source_type = src.type.bb.pointer.type; assert(source_type == argument_abi.semantic_type); const destination_type = argument_abi.get_coerce_to_type(); const load = module.create_coerced_load(src.llvm, source_type, destination_type); const is_cmse_ns_call = false; if (is_cmse_ns_call) { @trap(); } const maybe_undef = false; if (maybe_undef) { @trap(); } llvm_abi_argument_value_buffer[abi_argument_count] = load; abi_argument_count += 1; } } }, .indirect, .indirect_aliased => indirect: { if (semantic_argument_type.get_evaluation_kind() == .aggregate) { const same_address_space = true; assert(argument_abi.abi_start >= function_type.abi_argument_types.len or same_address_space); const indirect_alignment = argument_abi.attributes.indirect.alignment; const address_alignment = semantic_argument_type.get_byte_alignment(); const get_or_enforce_known_alignment = indirect_alignment; // llvm::getOrEnforceKnownAlignment(Addr.emitRawPointer(*this), // Align.getAsAlign(), // *TD) < Align.getAsAlign()) { // TODO const need_copy = switch (address_alignment < indirect_alignment and get_or_enforce_known_alignment < indirect_alignment) { true => @trap(), false => b: { const is_lvalue = !(semantic_argument_value.type.bb == .pointer and semantic_argument_type == semantic_argument_value.type.bb.pointer.type); if (is_lvalue) { var need_copy = false; const is_by_val_or_by_ref = argument_abi.flags.kind == .indirect_aliased or argument_abi.flags.indirect_by_value; const lv_alignment = semantic_argument_value.type.get_byte_alignment(); const arg_type_alignment = argument_abi.semantic_type.get_byte_alignment(); if (!is_by_val_or_by_ref or lv_alignment < arg_type_alignment) { need_copy = true; } break :b need_copy; } else { break :b false; } }, }; if (!need_copy) { llvm_abi_argument_value_buffer[abi_argument_count] = semantic_argument_value.llvm; abi_argument_count += 1; break :indirect; } } @trap(); }, .ignore => unreachable, else => @trap(), } assert(abi_argument_count == argument_abi.abi_start + argument_abi.abi_count); } else { converter.report_error(); } } if (function_type.is_var_args) { assert(abi_argument_count >= function_type.abi_argument_types.len); } else { // TODO assert(abi_argument_count == function_type.abi_argument_types.len); } const llvm_abi_argument_values = llvm_abi_argument_value_buffer[0..abi_argument_count]; const llvm_call = module.llvm.builder.create_call(raw_function_type.llvm.handle.to_function(), llvm_callable, llvm_abi_argument_values); const attribute_list = module.build_attribute_list(.{ .return_type_abi = function_type.return_type_abi, .abi_return_type = function_type.abi_return_type, .abi_argument_types = abi_argument_type_buffer[0..abi_argument_count], .argument_type_abis = argument_type_abi_buffer[0..semantic_argument_count], .attributes = .{}, .call_site = true, }); const call_base = llvm_call.to_instruction().to_call_base(); call_base.set_calling_convention(llvm_calling_convention); call_base.set_attributes(attribute_list); const return_type_abi = &function_type.return_type_abi; const return_abi_kind = return_type_abi.flags.kind; switch (return_abi_kind) { .ignore => { assert(return_type_abi.semantic_type == module.noreturn_type or return_type_abi.semantic_type == module.void_type); return module.void_value; }, .direct, .extend => { const coerce_to_type = return_type_abi.get_coerce_to_type(); if (return_type_abi.semantic_type.is_abi_equal(coerce_to_type) and return_type_abi.attributes.direct.offset == 0) { const coerce_to_type_kind = coerce_to_type.get_evaluation_kind(); switch (coerce_to_type_kind) { .aggregate => {}, .complex => @trap(), .scalar => { const value = module.values.add(); value.* = .{ .llvm = llvm_call, .bb = .instruction, .type = return_type_abi.semantic_type, .lvalue = false, .dereference_to_assign = false, }; return value; }, } } // TODO: if const fixed_vector_type = false; if (fixed_vector_type) { @trap(); } const coerce_alloca = module.create_alloca(.{ .type = return_type_abi.semantic_type, .name = "coerce" }); var destination_pointer = switch (return_type_abi.attributes.direct.offset == 0) { true => coerce_alloca, false => @trap(), }; _ = &destination_pointer; if (return_type_abi.semantic_type.bb.structure.fields.len > 0) { // CreateCoercedStore( // CI, StorePtr, // llvm::TypeSize::getFixed(DestSize - RetAI.getDirectOffset()), // DestIsVolatile); const source_value = llvm_call; const source_type = function_type.abi_return_type; // const source_size = source_type.get_byte_size(); var destination_type = return_type_abi.semantic_type; const destination_size = destination_type.get_byte_size(); // const destination_alignment = destination_type.get_byte_alignment(); const left_destination_size = destination_size - return_type_abi.attributes.direct.offset; const is_destination_volatile = false; // TODO module.create_coerced_store(source_value, source_type, destination_pointer, destination_type, left_destination_size, is_destination_volatile); } else { @trap(); } const value = module.values.add(); value.* = .{ .llvm = destination_pointer, .bb = .instruction, .type = module.get_pointer_type(.{ .type = return_type_abi.semantic_type }), .lvalue = true, .dereference_to_assign = true, }; return value; }, .indirect => { const value = module.values.add(); value.* = .{ .llvm = llvm_indirect_return_value, .bb = .instruction, .type = module.get_pointer_type(.{ .type = return_type_abi.semantic_type }), .lvalue = true, .dereference_to_assign = true, }; return value; }, else => @trap(), } } fn parse_block(noalias converter: *Converter, noalias module: *Module) void { converter.skip_space(); const current_function_global = module.current_function orelse unreachable; const current_function = ¤t_function_global.value.bb.function; const current_function_type = ¤t_function_global.value.type.bb.pointer.type.bb.function; const block_line = converter.get_line(); const block_column = converter.get_column(); const current_scope = current_function.current_scope; defer current_function.current_scope = current_scope; if (module.llvm.di_builder) |di_builder| { const lexical_block = di_builder.create_lexical_block(current_scope, module.llvm.file, block_line, block_column); current_function.current_scope = lexical_block.to_scope(); } converter.expect_character(left_brace); const local_offset = current_function.locals.count; defer current_function.locals.count = local_offset; while (true) { converter.skip_space(); if (converter.offset == converter.content.len) { break; } if (converter.content[converter.offset] == right_brace) { break; } var require_semicolon = true; const line = converter.get_line(); const column = converter.get_column(); var statement_debug_location: *llvm.DI.Location = undefined; if (module.llvm.di_builder) |_| { const inlined_at: ?*llvm.DI.Metadata = null; // TODO statement_debug_location = llvm.DI.create_debug_location(module.llvm.context, line, column, current_function.current_scope, inlined_at); module.llvm.builder.set_current_debug_location(statement_debug_location); } const statement_start_ch = converter.content[converter.offset]; if (statement_start_ch == '>') { converter.offset += 1; converter.skip_space(); const local_name = converter.parse_identifier(); converter.skip_space(); const has_type = converter.consume_character_if_match(':'); converter.skip_space(); const local_type_stated: ?*Type = switch (has_type) { true => converter.parse_type(module), false => null, }; converter.skip_space(); converter.expect_character('='); const value = converter.parse_value(module, local_type_stated, .value); const local_storage = module.values.add(); const is_inferred = local_type_stated == null; const is_inferred_pointer = is_inferred and value.dereference_to_assign; const local_type = switch (is_inferred_pointer) { true => value.type.bb.pointer.type, false => if (local_type_stated) |t| t else value.type, }; const local_pointer_type = switch (value.dereference_to_assign) { true => b: { assert(value.type != local_type); assert(value.type.bb == .pointer); break :b value.type; }, false => b: { assert(value.type == local_type); const pointer_type = module.get_pointer_type(.{ .type = local_type, }); break :b pointer_type; }, }; const local_alignment = local_pointer_type.bb.pointer.alignment; const llvm_alloca = module.create_alloca(.{ .type = local_type, .name = local_name, .alignment = local_alignment }); local_storage.* = .{ .llvm = llvm_alloca, .type = local_pointer_type, .bb = .local, .lvalue = true, .dereference_to_assign = false, }; if (module.llvm.di_builder) |di_builder| { module.llvm.builder.set_current_debug_location(statement_debug_location); const debug_type = local_type.llvm.debug; const always_preserve = true; // TODO: const alignment = 0; const flags = llvm.DI.Flags{}; const local_variable = di_builder.create_auto_variable(current_function.current_scope, local_name, module.llvm.file, line, debug_type, always_preserve, flags, alignment); const inlined_at: ?*llvm.DI.Metadata = null; // TODO const debug_location = llvm.DI.create_debug_location(module.llvm.context, line, column, current_function.current_scope, inlined_at); _ = di_builder.insert_declare_record_at_end(local_storage.llvm, local_variable, di_builder.null_expression(), debug_location, module.current_basic_block()); module.llvm.builder.set_current_debug_location(statement_debug_location); } const alignment: u32 = @intCast(local_type.get_byte_alignment()); const destination = local_storage.llvm; const source = value.llvm; switch (local_type.get_evaluation_kind()) { .aggregate => { _ = module.llvm.builder.create_memcpy(destination, alignment, source, alignment, module.integer_type(64, false).llvm.handle.to_integer().get_constant(local_type.get_byte_size(), @intFromBool(false)).to_value()); }, else => { _ = module.create_store(.{ .source_value = source, .destination_value = destination, .source_type = local_type, .destination_type = local_type }); }, } const local = current_function.locals.add(); local.* = .{ .name = local_name, .value = local_storage, }; } else if (statement_start_ch == '#') { const intrinsic = converter.parse_value_intrinsic(module, null); switch (intrinsic.type.bb) { .void, .noreturn => {}, else => @trap(), } } else if (is_identifier_start_ch(statement_start_ch)) { const statement_start_identifier = converter.parse_identifier(); if (string_to_enum(StatementStartKeyword, statement_start_identifier)) |statement_start_keyword| { switch (statement_start_keyword) { .@"return" => { converter.skip_space(); const abi_return_type = current_function_type.abi_return_type; _ = abi_return_type; const return_type_abi = ¤t_function_type.return_type_abi; const returns_nothing = converter.consume_character_if_match(';'); if (returns_nothing) { @trap(); } else { // TODO: take ABI into account const return_value = converter.parse_value(module, return_type_abi.semantic_type, .value); if (module.llvm.di_builder) |_| { module.llvm.builder.set_current_debug_location(statement_debug_location); } // Clang equivalent: CodeGenFunction::EmitReturnStmt switch (return_type_abi.semantic_type.get_evaluation_kind()) { .scalar => { switch (return_type_abi.flags.kind) { .indirect => { @trap(); }, else => { const return_alloca = current_function.return_alloca; _ = module.create_store(.{ .source_value = return_value.llvm, .destination_value = return_alloca, .source_type = return_type_abi.semantic_type, .destination_type = current_function_type.abi_return_type, }); }, } }, .aggregate => { // TODO: handcoded code, might be wrong const return_alloca = current_function.return_alloca; const abi_alignment = current_function_type.return_type_abi.semantic_type.get_byte_alignment(); const abi_size = current_function_type.return_type_abi.semantic_type.get_byte_size(); switch (return_type_abi.flags.kind) { .indirect => { _ = module.llvm.builder.create_memcpy(return_alloca, abi_alignment, return_value.llvm, abi_alignment, module.integer_type(64, false).llvm.handle.to_integer().get_constant(abi_size, @intFromBool(false)).to_value()); }, else => { switch (current_function_type.abi_return_type.get_evaluation_kind()) { .aggregate => { assert(abi_alignment == return_type_abi.semantic_type.get_byte_alignment()); assert(abi_size == return_type_abi.semantic_type.get_byte_size()); _ = module.llvm.builder.create_memcpy(return_alloca, abi_alignment, return_value.llvm, abi_alignment, module.integer_type(64, false).llvm.handle.to_integer().get_constant(abi_size, @intFromBool(false)).to_value()); }, .scalar => { const destination_type = current_function_type.return_type_abi.semantic_type; const source_type = current_function_type.return_type_abi.semantic_type; assert(return_value.type == source_type); const rv = switch (return_value.type.bb) { .pointer => return_value.llvm, // TODO: this feels hacky else => switch (return_value.lvalue) { true => module.create_load(.{ .type = return_value.type, .value = return_value.llvm }), false => return_value.llvm, }, }; _ = module.create_store(.{ .source_value = rv, .source_type = source_type, .destination_value = return_alloca, .destination_type = destination_type }); }, .complex => @trap(), } }, } }, .complex => @trap(), } } _ = module.llvm.builder.create_branch(current_function.return_block); _ = module.llvm.builder.clear_insertion_position(); }, .@"if" => { const taken_block = module.llvm.context.create_basic_block("if.true", current_function_global.value.llvm.to_function()); const not_taken_block = module.llvm.context.create_basic_block("if.false", current_function_global.value.llvm.to_function()); const exit_block = module.llvm.context.create_basic_block("if.end", null); converter.skip_space(); converter.expect_character(left_parenthesis); converter.skip_space(); const condition = converter.parse_value(module, null, .value); converter.skip_space(); converter.expect_character(right_parenthesis); _ = module.llvm.builder.create_conditional_branch(condition.llvm, taken_block, not_taken_block); module.llvm.builder.position_at_end(taken_block); const previous_exit_block = current_function.exit_block; defer current_function.exit_block = previous_exit_block; current_function.exit_block = exit_block; converter.parse_block(module); const if_final_block = module.llvm.builder.get_insert_block(); converter.skip_space(); var is_else = false; if (is_identifier_start_ch(converter.content[converter.offset])) { const identifier = converter.parse_identifier(); is_else = lib.string.equal(identifier, "else"); if (!is_else) { converter.offset -= identifier.len; } } var is_second_block_terminated = false; module.llvm.builder.position_at_end(not_taken_block); if (is_else) { current_function.exit_block = exit_block; converter.parse_block(module); is_second_block_terminated = module.llvm.builder.get_insert_block() == null; } else { if (if_final_block) |final_block| { const current_insert_block = module.llvm.builder.get_insert_block(); defer if (current_insert_block) |block| { module.llvm.builder.position_at_end(block); }; module.llvm.builder.position_at_end(final_block); _ = module.llvm.builder.create_branch(not_taken_block); module.llvm.builder.clear_insertion_position(); } assert(exit_block.to_value().use_empty()); not_taken_block.to_value().set_name("if.end"); assert(exit_block.get_parent() == null); exit_block.delete(); } if (!(if_final_block == null and is_second_block_terminated)) { if (if_final_block != null) { // @trap(); } if (!is_second_block_terminated) { // if (is_else) { // @trap(); // } else {} } } else { assert(exit_block.get_parent() == null); // TODO: // if call `exit_block.erase_from_paren()`, it crashes, investigate exit_block.delete(); } require_semicolon = false; }, } } else { converter.offset -= statement_start_identifier.len; const v = converter.parse_value(module, null, .maybe_pointer); converter.skip_space(); switch (converter.content[converter.offset]) { '=' => { // const left = v; converter.expect_character('='); converter.skip_space(); const left = v; if (left.type.bb != .pointer) { converter.report_error(); } const store_alignment = left.type.bb.pointer.alignment; const store_type = left.type.bb.pointer.type; const right = converter.parse_value(module, store_type, .value); switch (store_type.get_evaluation_kind()) { .aggregate => { @trap(); }, else => _ = module.create_store(.{ .source_value = right.llvm, .destination_value = left.llvm, .source_type = store_type, .destination_type = store_type, .alignment = store_alignment }), } }, ';' => { const is_noreturn = v.type.bb == .noreturn; const is_valid = v.type.bb == .void or is_noreturn; if (!is_valid) { converter.report_error(); } if (is_noreturn) { _ = module.llvm.builder.create_unreachable(); } }, else => @trap(), } } } else { converter.report_error(); } converter.skip_space(); if (require_semicolon) { converter.expect_character(';'); } } converter.expect_character(right_brace); } const ExpressionState = enum { none, add, sub, mul, udiv, sdiv, urem, srem, shl, ashr, lshr, @"and", @"or", xor, icmp_eq, icmp_ne, pub fn to_int_predicate(expression_state: ExpressionState) llvm.IntPredicate { return switch (expression_state) { .icmp_ne => .ne, .icmp_eq => .eq, else => @trap(), }; } }; const ValueKind = enum { pointer, value, maybe_pointer, }; fn parse_value(noalias converter: *Converter, noalias module: *Module, maybe_expected_type: ?*Type, value_kind: ValueKind) *Value { converter.skip_space(); var value_state = ExpressionState.none; var previous_value: ?*Value = null; var iterations: usize = 0; var iterative_expected_type = maybe_expected_type; const value: *Value = while (true) : (iterations += 1) { if (iterations == 1) { iterative_expected_type = previous_value.?.type; } const current_value = switch (converter.consume_character_if_match(left_parenthesis)) { true => blk: { const r = converter.parse_value(module, iterative_expected_type, value_kind); converter.skip_space(); converter.expect_character(right_parenthesis); break :blk r; }, false => converter.parse_single_value(module, iterative_expected_type, value_kind), }; converter.skip_space(); const left = switch (value_state) { .none => undefined, else => previous_value.?.llvm, }; const right = current_value.llvm; const next_ty = if (previous_value) |pv| pv.type else current_value.type; const llvm_value = switch (value_state) { .none => current_value.llvm, .sub => module.llvm.builder.create_sub(left, right), .add => module.llvm.builder.create_add(left, right), .mul => module.llvm.builder.create_mul(left, right), .sdiv => module.llvm.builder.create_sdiv(left, right), .udiv => module.llvm.builder.create_udiv(left, right), .srem => module.llvm.builder.create_srem(left, right), .urem => module.llvm.builder.create_urem(left, right), .shl => module.llvm.builder.create_shl(left, right), .ashr => module.llvm.builder.create_ashr(left, right), .lshr => module.llvm.builder.create_lshr(left, right), .@"and" => module.llvm.builder.create_and(left, right), .@"or" => module.llvm.builder.create_or(left, right), .xor => module.llvm.builder.create_xor(left, right), .icmp_ne, .icmp_eq => |icmp| module.llvm.builder.create_compare(icmp.to_int_predicate(), left, right), }; switch (value_state) { .none => previous_value = current_value, else => { previous_value = module.values.add(); previous_value.?.* = .{ .llvm = llvm_value, .type = switch (value_state) { .none => unreachable, .icmp_eq, .icmp_ne => module.integer_type(1, false), .sub, .add, .mul, .sdiv, .udiv, .srem, .urem, .shl, .ashr, .lshr, .@"and", .@"or", .xor, => next_ty, }, .bb = .instruction, .lvalue = false, .dereference_to_assign = false, }; }, } const ch = converter.content[converter.offset]; value_state = switch (ch) { ',', ';', right_parenthesis, right_bracket, right_brace => break previous_value.?, '=' => switch (converter.content[converter.offset + 1]) { '=' => blk: { converter.offset += 2; break :blk .icmp_eq; }, else => break previous_value.?, }, '-' => blk: { converter.offset += 1; break :blk .sub; }, '+' => blk: { converter.offset += 1; break :blk .add; }, '*' => blk: { converter.offset += 1; break :blk .mul; }, '/' => blk: { converter.offset += 1; const ty = iterative_expected_type orelse unreachable; break :blk switch (ty.bb) { .integer => |int| switch (int.signed) { true => .sdiv, false => .udiv, }, else => unreachable, }; }, '%' => blk: { converter.offset += 1; const ty = iterative_expected_type orelse unreachable; break :blk switch (ty.bb) { .integer => |int| switch (int.signed) { true => .srem, false => .urem, }, else => unreachable, }; }, '<' => blk: { converter.offset += 1; break :blk switch (converter.content[converter.offset]) { '<' => b: { converter.offset += 1; break :b .shl; }, else => os.abort(), }; }, '>' => blk: { converter.offset += 1; break :blk switch (converter.content[converter.offset]) { '>' => b: { converter.offset += 1; const ty = iterative_expected_type orelse unreachable; break :b switch (ty.bb) { .integer => |int| switch (int.signed) { true => .ashr, false => .lshr, }, else => unreachable, }; }, else => os.abort(), }; }, '&' => blk: { converter.offset += 1; break :blk .@"and"; }, '|' => blk: { converter.offset += 1; break :blk .@"or"; }, '^' => blk: { converter.offset += 1; break :blk .xor; }, '!' => blk: { converter.offset += 1; break :blk switch (converter.content[converter.offset]) { '=' => b: { converter.offset += 1; break :b .icmp_ne; }, else => os.abort(), }; }, else => os.abort(), }; converter.skip_space(); }; return value; } const Prefix = enum { none, negative, not_zero, }; const ValueIntrinsic = enum { byte_size, cast, cast_to, extend, integer_max, int_from_enum, int_from_pointer, select, trap, truncate, va_start, va_end, va_copy, va_arg, }; fn parse_value_intrinsic(noalias converter: *Converter, noalias module: *Module, expected_type: ?*Type) *Value { converter.expect_character('#'); converter.skip_space(); const intrinsic_name = converter.parse_identifier(); const intrinsic_keyword = string_to_enum(ValueIntrinsic, intrinsic_name) orelse converter.report_error(); converter.skip_space(); converter.expect_character(left_parenthesis); converter.skip_space(); switch (intrinsic_keyword) { .byte_size => { const ty = converter.parse_type(module); converter.skip_space(); converter.expect_character(')'); const byte_size = ty.get_byte_size(); const destination_type = expected_type orelse converter.report_error(); if (destination_type.bb != .integer) { converter.report_error(); } const value = module.values.add(); value.* = .{ .llvm = destination_type.llvm.handle.to_integer().get_constant(byte_size, @intFromBool(false)).to_value(), .bb = .{ .constant_integer = .{ .value = byte_size, .signed = false, }, }, .type = destination_type, .lvalue = false, .dereference_to_assign = false, }; return value; }, .cast => { @trap(); }, .cast_to => { const destination_type = converter.parse_type(module); converter.skip_space(); converter.expect_character(','); const source_value = converter.parse_value(module, null, .value); converter.skip_space(); converter.expect_character(')'); if (source_value.type.bb == .pointer and destination_type.bb == .integer) { const value = module.values.add(); value.* = .{ .llvm = module.llvm.builder.create_ptr_to_int(source_value.llvm, destination_type.llvm.handle), .type = destination_type, .bb = .instruction, .lvalue = false, .dereference_to_assign = false, }; return value; } else { @trap(); } }, .extend => { const source_value = converter.parse_value(module, null, .value); converter.skip_space(); converter.expect_character(right_parenthesis); const source_type = source_value.type; const destination_type = expected_type orelse converter.report_error(); if (source_type.get_bit_size() > destination_type.get_bit_size()) { converter.report_error(); } else if (source_type.get_bit_size() == destination_type.get_bit_size() and source_type.is_signed() == destination_type.is_signed()) { converter.report_error(); } const extension_instruction = switch (source_type.bb.integer.signed) { true => module.llvm.builder.create_sign_extend(source_value.llvm, destination_type.llvm.handle), false => module.llvm.builder.create_zero_extend(source_value.llvm, destination_type.llvm.handle), }; const value = module.values.add(); value.* = .{ .llvm = extension_instruction, .type = destination_type, .bb = .instruction, .lvalue = false, .dereference_to_assign = false, }; return value; }, .integer_max => { converter.skip_space(); const ty = converter.parse_type(module); converter.expect_character(right_parenthesis); if (ty.bb != .integer) { converter.report_error(); } const bit_count = ty.bb.integer.bit_count; const max_value = if (bit_count == 64) ~@as(u64, 0) else (@as(u64, 1) << @intCast(bit_count - @intFromBool(ty.bb.integer.signed))) - 1; const expected_ty = expected_type orelse ty; if (ty.get_bit_size() > expected_ty.get_bit_size()) { converter.report_error(); } const constant_integer = expected_ty.llvm.handle.to_integer().get_constant(max_value, @intFromBool(false)); const value = module.values.add(); value.* = .{ .llvm = constant_integer.to_value(), .type = expected_ty, .bb = .{ .constant_integer = .{ .value = max_value, .signed = false, }, }, .lvalue = false, .dereference_to_assign = false, }; 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; }, .int_from_pointer => { const source_value = converter.parse_value(module, null, .value); converter.skip_space(); converter.expect_character(right_parenthesis); if (source_value.type.bb != .pointer) { converter.report_error(); } const original_target_type = module.integer_type(64, false); 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.* = .{ .llvm = module.llvm.builder.create_ptr_to_int(source_value.llvm, target_type.llvm.handle), .type = target_type, .lvalue = false, .dereference_to_assign = false, .bb = .instruction, }; return value; }, .select => { const condition_value = converter.parse_value(module, null, .value); if (condition_value.type.bb != .integer) { converter.report_error(); } if (condition_value.type.bb.integer.bit_count != 1) { converter.report_error(); } converter.skip_space(); converter.expect_character(','); converter.skip_space(); const true_value = converter.parse_value(module, expected_type, .value); converter.skip_space(); converter.expect_character(','); converter.skip_space(); const expected_ty = expected_type orelse true_value.type; const false_value = converter.parse_value(module, expected_ty, .value); converter.skip_space(); converter.expect_character(right_parenthesis); if (true_value.type != expected_ty) { converter.report_error(); } if (false_value.type != expected_ty) { converter.report_error(); } const value = module.values.add(); value.* = .{ .llvm = module.llvm.builder.create_select(condition_value.llvm, true_value.llvm, false_value.llvm), .bb = .instruction, .type = expected_ty, .lvalue = false, .dereference_to_assign = false, }; return value; }, .trap => { converter.expect_character(right_parenthesis); // TODO: lookup in advance const intrinsic_id = module.llvm.intrinsic_table.trap; const argument_types: []const *llvm.Type = &.{}; const argument_values: []const *llvm.Value = &.{}; const intrinsic_function = module.llvm.handle.get_intrinsic_declaration(intrinsic_id, argument_types); const intrinsic_function_type = module.llvm.context.get_intrinsic_type(intrinsic_id, argument_types); const llvm_call = module.llvm.builder.create_call(intrinsic_function_type, intrinsic_function, argument_values); _ = module.llvm.builder.create_unreachable(); module.llvm.builder.clear_insertion_position(); const value = module.values.add(); value.* = .{ .llvm = llvm_call, .type = module.noreturn_type, .bb = .instruction, .lvalue = false, .dereference_to_assign = false, }; return value; }, .truncate => { const source_value = converter.parse_value(module, null, .value); converter.skip_space(); converter.expect_character(right_parenthesis); const destination_type = expected_type orelse converter.report_error(); const truncate = module.llvm.builder.create_truncate(source_value.llvm, destination_type.llvm.handle); const value = module.values.add(); value.* = .{ .llvm = truncate, .type = destination_type, .bb = .instruction, .lvalue = false, .dereference_to_assign = false, }; return value; }, .va_start => { converter.expect_character(right_parenthesis); const va_list_type = module.get_va_list_type(); const alloca = module.create_alloca(.{ .type = va_list_type }); const intrinsic_id = module.llvm.intrinsic_table.va_start; const argument_types: []const *llvm.Type = &.{module.llvm.pointer_type}; const intrinsic_function = module.llvm.handle.get_intrinsic_declaration(intrinsic_id, argument_types); const intrinsic_function_type = module.llvm.context.get_intrinsic_type(intrinsic_id, argument_types); const argument_values: []const *llvm.Value = &.{alloca}; _ = module.llvm.builder.create_call(intrinsic_function_type, intrinsic_function, argument_values); const value = module.values.add(); value.* = .{ .llvm = alloca, .type = module.get_pointer_type(.{ .type = va_list_type }), .bb = .instruction, .lvalue = true, .dereference_to_assign = true, }; return value; }, .va_end => { const va_list = converter.parse_value(module, module.get_pointer_type(.{ .type = module.get_va_list_type() }), .pointer); converter.skip_space(); converter.expect_character(right_parenthesis); const intrinsic_id = module.llvm.intrinsic_table.va_end; const argument_types: []const *llvm.Type = &.{module.llvm.pointer_type}; const intrinsic_function = module.llvm.handle.get_intrinsic_declaration(intrinsic_id, argument_types); const intrinsic_function_type = module.llvm.context.get_intrinsic_type(intrinsic_id, argument_types); const argument_values: []const *llvm.Value = &.{va_list.llvm}; const llvm_value = module.llvm.builder.create_call(intrinsic_function_type, intrinsic_function, argument_values); const value = module.values.add(); value.* = .{ .llvm = llvm_value, .type = module.void_type, .bb = .instruction, .lvalue = false, .dereference_to_assign = false, }; return value; }, .va_copy => @trap(), .va_arg => { const va_list_type = module.get_va_list_type(); const raw_va_list = converter.parse_value(module, module.get_pointer_type(.{ .type = va_list_type }), .pointer); const va_list = module.llvm.builder.create_gep(.{ .type = va_list_type.llvm.handle, .aggregate = raw_va_list.llvm, .indices = &([1]*llvm.Value{module.integer_type(64, false).llvm.handle.to_integer().get_constant(0, @intFromBool(false)).to_value()} ** 2), }); converter.skip_space(); converter.expect_character(','); converter.skip_space(); const arg_type = converter.parse_type(module); converter.skip_space(); converter.expect_character(right_parenthesis); const r = Abi.SystemV.classify_argument_type(module, arg_type, .{ .available_gpr = 0, .is_named_argument = false, .is_reg_call = false, }); const abi = r[0]; const needed_register_count = r[1]; const abi_kind = abi.flags.kind; assert(abi_kind != .ignore); const va_list_struct = va_list_type.bb.array.element_type; const llvm_address = switch (needed_register_count.gpr == 0 and needed_register_count.sse == 0) { true => Abi.SystemV.emit_va_arg_from_memory(module, va_list, va_list_struct, arg_type), false => blk: { const va_list_struct_llvm = va_list_struct.llvm.handle.to_struct(); const gpr_offset_pointer = if (needed_register_count.gpr != 0) module.llvm.builder.create_struct_gep(va_list_struct_llvm, va_list, 0) else undefined; const gpr_offset = if (needed_register_count.gpr != 0) module.create_load(.{ .type = va_list_struct.bb.structure.fields[0].type, .value = gpr_offset_pointer, .alignment = 16 }) else undefined; const raw_in_regs = 48 - needed_register_count.gpr * 8; const int32 = module.integer_type(32, false); const int32_llvm = int32.llvm.handle.to_integer(); var in_regs = if (needed_register_count.gpr != 0) int32_llvm.get_constant(raw_in_regs, @intFromBool(false)).to_value() else @trap(); in_regs = if (needed_register_count.gpr != 0) module.llvm.builder.create_compare(.ule, gpr_offset, in_regs) else in_regs; const fp_offset_pointer = if (needed_register_count.sse != 0) module.llvm.builder.create_struct_gep(va_list_struct_llvm, va_list, 1) else undefined; const fp_offset = if (needed_register_count.sse != 0) module.create_load(.{ .type = va_list_struct.bb.structure.fields[1].type, .value = fp_offset_pointer }) else undefined; const raw_fits_in_fp = 176 - needed_register_count.sse * 16; var fits_in_fp = if (needed_register_count.sse != 0) int32_llvm.get_constant(raw_fits_in_fp, @intFromBool(false)).to_value() else undefined; fits_in_fp = if (needed_register_count.sse != 0) module.llvm.builder.create_compare(.ule, fp_offset, fits_in_fp) else undefined; in_regs = if (needed_register_count.sse != 0 and needed_register_count.gpr != 0) @trap() else in_regs; const in_reg_block = module.llvm.context.create_basic_block("va_arg.in_reg", null); const in_mem_block = module.llvm.context.create_basic_block("va_arg.in_mem", null); const end_block = module.llvm.context.create_basic_block("va_arg.end", null); _ = module.llvm.builder.create_conditional_branch(in_regs, in_reg_block, in_mem_block); module.emit_block(in_reg_block); const reg_save_area = module.create_load(.{ .type = va_list_struct.bb.structure.fields[3].type, .value = module.llvm.builder.create_struct_gep(va_list_struct_llvm, va_list, 3), .alignment = 16 }); const register_address = if (needed_register_count.gpr != 0 and needed_register_count.sse != 0) { @trap(); } else if (needed_register_count.gpr != 0) b: { const register_address = module.llvm.builder.create_gep(.{ .type = va_list_struct.bb.structure.fields[3].type.bb.pointer.type.llvm.handle, .aggregate = reg_save_area, .indices = &.{gpr_offset}, .inbounds = false, }); if (arg_type.get_byte_alignment() > 8) { @trap(); } break :b register_address; } else if (needed_register_count.sse == 1) { @trap(); } else { assert(needed_register_count.sse == 2); @trap(); }; if (needed_register_count.gpr != 0) { const raw_offset = needed_register_count.gpr * 8; const new_offset = module.llvm.builder.create_add(gpr_offset, int32_llvm.get_constant(raw_offset, @intFromBool(false)).to_value()); _ = module.create_store(.{ .destination_value = gpr_offset_pointer, .source_value = new_offset, .source_type = int32, .destination_type = int32, .alignment = 16 }); } if (needed_register_count.sse != 0) { @trap(); } _ = module.llvm.builder.create_branch(end_block); module.emit_block(in_mem_block); const memory_address = Abi.SystemV.emit_va_arg_from_memory(module, va_list, va_list_struct, arg_type); module.emit_block(end_block); const values = &.{ register_address, memory_address }; const blocks = &.{ in_reg_block, in_mem_block }; const phi = module.llvm.builder.create_phi(module.llvm.pointer_type); phi.add_incoming(values, blocks); break :blk phi.to_value(); }, }; switch (arg_type.get_evaluation_kind()) { .aggregate => { const result_type = module.get_pointer_type(.{ .type = arg_type }); const value = module.values.add(); value.* = .{ .type = result_type, .bb = .instruction, .llvm = llvm_address, .lvalue = true, .dereference_to_assign = true, }; return value; }, .scalar => { const value = module.values.add(); const load = module.create_load(.{ .type = arg_type, .value = llvm_address }); value.* = .{ .type = arg_type, .bb = .instruction, .llvm = load, .lvalue = false, .dereference_to_assign = false, }; return value; }, .complex => @trap(), } }, } } const TypeIntrinsic = enum { ReturnType, }; fn parse_type_intrinsic(noalias converter: *Converter, noalias module: *Module) *Type { converter.expect_character('#'); converter.skip_space(); const intrinsic_name = converter.parse_identifier(); const intrinsic_keyword = string_to_enum(TypeIntrinsic, intrinsic_name) orelse converter.report_error(); converter.skip_space(); converter.expect_character(left_parenthesis); converter.skip_space(); switch (intrinsic_keyword) { .ReturnType => { converter.skip_space(); converter.expect_character(right_parenthesis); const current_function_variable = module.current_function orelse converter.report_error(); const return_type = current_function_variable.value.type.bb.pointer.type.bb.function.return_type_abi.semantic_type; return return_type; }, } @trap(); } const ValueKeyword = enum { @"_", undefined, @"unreachable", zero, }; fn parse_single_value(noalias converter: *Converter, noalias module: *Module, expected_type: ?*Type, value_kind: ValueKind) *Value { converter.skip_space(); if (module.current_function) |function| { if (module.llvm.di_builder) |_| { const line = converter.get_line(); const column = converter.get_column(); const inlined_at: ?*llvm.DI.Metadata = null; // TODO const debug_location = llvm.DI.create_debug_location(module.llvm.context, line, column, function.value.bb.function.current_scope, inlined_at); module.llvm.builder.set_current_debug_location(debug_location); } } const prefix_offset = converter.offset; const prefix_ch = converter.content[prefix_offset]; const must_be_constant = module.current_function == null; const prefix: Prefix = switch (prefix_ch) { 'a'...'z', 'A'...'Z', '_', '0'...'9' => .none, '-' => blk: { converter.offset += 1; // TODO: should we skip space here? converter.skip_space(); break :blk .negative; }, left_brace => { converter.offset += 1; converter.skip_space(); const ty = expected_type orelse converter.report_error(); switch (ty.bb) { .structure => |*struct_type| { var field_count: u32 = 0; var field_value_buffer: [64]*Value = undefined; var field_index_buffer: [64]u32 = undefined; var is_ordered = true; var is_constant = true; var zero = false; while (true) : (field_count += 1) { converter.skip_space(); if (converter.consume_character_if_match(right_brace)) { break; } else if (converter.consume_character_if_match('.')) { const field_name = converter.parse_identifier(); const field_index: u32 = for (struct_type.fields, 0..) |*field, field_index| { if (lib.string.equal(field.name, field_name)) { break @intCast(field_index); } } else converter.report_error(); is_ordered = is_ordered and field_index == field_count; const field = struct_type.fields[field_index]; converter.skip_space(); converter.expect_character('='); converter.skip_space(); const field_value = converter.parse_value(module, field.type, .value); if (field.type != field_value.type) { @trap(); } if (field.type.llvm.handle != field_value.type.llvm.handle) { @trap(); } is_constant = is_constant and field_value.is_constant(); field_value_buffer[field_count] = field_value; field_index_buffer[field_count] = field_index; converter.skip_space(); _ = converter.consume_character_if_match(','); converter.skip_space(); } else { const identifier = converter.parse_identifier(); if (string_to_enum(ValueKeyword, identifier)) |value_keyword| switch (value_keyword) { ._ => converter.report_error(), .undefined => @trap(), .@"unreachable" => @trap(), .zero => { zero = true; converter.skip_space(); _ = converter.consume_character_if_match(','); converter.skip_space(); converter.expect_character(right_brace); // We need to break here otherwise `field_count` would be incremented break; }, } else { converter.report_error(); } } } if (must_be_constant and !is_constant) { @trap(); } if (zero) { if (field_count == struct_type.fields.len) { converter.report_error(); } if (is_ordered and is_constant) { const zero_fields = struct_type.fields[field_count..]; const zero_field_values = field_value_buffer[field_count..][0..zero_fields.len]; for (zero_fields, zero_field_values) |zero_field, *zero_field_value| { zero_field_value.* = module.get_zero_value(zero_field.type); field_count += 1; } } else { @trap(); } } if (field_count != struct_type.fields.len) { // expect: 'zero' keyword @trap(); } const llvm_value = switch (is_constant and is_ordered) { true => blk: { var llvm_value_buffer: [64]*llvm.Constant = undefined; var llvm_gc_value_buffer = [1]?*llvm.GlobalVariable{null} ** 64; const llvm_values = llvm_value_buffer[0..field_count]; const llvm_gc_values = llvm_gc_value_buffer[0..field_count]; for (field_value_buffer[0..field_count], llvm_gc_values, llvm_values, struct_type.fields) |field_value, *llvm_gc_value, *llvm_field_value, *field| { llvm_field_value.* = switch (field.type.llvm.handle == field_value.llvm.get_type()) { true => field_value.llvm.to_constant(), false => switch (field.type.bb) { .array => b: { const global_variable = field_value.llvm.to_global_variable(); const initializer = global_variable.get_initializer(); const use_empty = field_value.llvm.use_empty(); if (use_empty) { llvm_gc_value.* = global_variable; } break :b initializer; }, .structure => b: { assert(field_value.lvalue); assert(field.type == field_value.type); const global_variable = field_value.llvm.to_global_variable(); const initializer = global_variable.get_initializer(); const use_empty = field_value.llvm.use_empty(); if (use_empty) { llvm_gc_value.* = global_variable; } break :b initializer; }, else => @trap(), }, }; } const constant_struct = ty.llvm.handle.to_struct().get_constant(llvm_values); const result = switch (module.current_function == null) { true => constant_struct.to_value(), false => b: { const global_variable = module.llvm.handle.create_global_variable(.{ .linkage = .InternalLinkage, .name = module.arena.join_string(&.{ "__const.", module.current_function.?.name, if (ty.name) |n| n else "" }), .initial_value = constant_struct, .type = ty.llvm.handle, }); break :b global_variable.to_value(); }, }; for (llvm_gc_values) |maybe_gc_value| { if (maybe_gc_value) |gc_value| { gc_value.erase_from_parent(); // gc_value.delete(); } } break :blk result; }, false => @trap(), }; const value = module.values.add(); value.* = .{ .llvm = llvm_value, .type = ty, .bb = .{ .struct_initialization = .{ .is_constant = is_constant, }, }, .lvalue = true, .dereference_to_assign = false, }; return value; }, .bits => |bits| { var field_count: usize = 0; var llvm_value = bits.backing_type.llvm.handle.to_integer().get_constant(0, @intFromBool(false)).to_value(); var zero = false; while (true) : (field_count += 1) { converter.skip_space(); if (converter.consume_character_if_match(right_brace)) { break; } else if (converter.consume_character_if_match('.')) { const field_name = converter.parse_identifier(); const field_index: u32 = for (bits.fields, 0..) |*field, field_index| { if (lib.string.equal(field.name, field_name)) { break @intCast(field_index); } } else converter.report_error(); const field = bits.fields[field_index]; converter.skip_space(); converter.expect_character('='); converter.skip_space(); const field_value = converter.parse_value(module, field.type, .value); const extended_field_value = module.llvm.builder.create_zero_extend(field_value.llvm, bits.backing_type.llvm.handle); const shifted_value = module.llvm.builder.create_shl(extended_field_value, bits.backing_type.llvm.handle.to_integer().get_constant(field.bit_offset, @intFromBool(false)).to_value()); const or_value = module.llvm.builder.create_or(llvm_value, shifted_value); llvm_value = or_value; converter.skip_space(); _ = converter.consume_character_if_match(','); converter.skip_space(); } else { const identifier = converter.parse_identifier(); if (string_to_enum(ValueKeyword, identifier)) |value_keyword| switch (value_keyword) { ._ => converter.report_error(), .undefined => @trap(), .zero => { zero = true; converter.skip_space(); _ = converter.consume_character_if_match(','); converter.skip_space(); converter.expect_character(right_brace); // We need to break here otherwise `field_count` would be incremented break; }, .@"unreachable" => @trap(), } else { converter.report_error(); } } } if (field_count != bits.fields.len) { // expect: 'zero' keyword if (zero or bits.implicit_backing_type) { // TODO: should we do anything? } else { @trap(); } } const value = module.values.add(); value.* = .{ .llvm = llvm_value, .type = ty, .bb = .bits_initialization, .lvalue = false, .dereference_to_assign = false, }; return value; }, else => converter.report_error(), } }, left_bracket => { converter.offset += 1; const ty = expected_type orelse converter.report_error(); switch (ty.bb) { .array => |*array| { var element_count: usize = 0; var element_buffer: [64]*llvm.Value = undefined; var elements_are_constant = true; while (true) : (element_count += 1) { converter.skip_space(); if (converter.consume_character_if_match(right_bracket)) { break; } const element_value = converter.parse_value(module, array.element_type, .value); elements_are_constant = elements_are_constant and element_value.is_constant(); element_buffer[element_count] = element_value.llvm; converter.skip_space(); _ = converter.consume_character_if_match(','); } if (array.element_count == null) { array.element_count = element_count; ty.llvm = array_type_llvm(module, array.*); ty.name = array_type_name(module.arena, array.*); } const array_elements = element_buffer[0..element_count]; if (elements_are_constant) { const constant_array = array.element_type.llvm.handle.get_constant_array(@ptrCast(array_elements)); const global = switch (module.current_function == null) { true => constant_array.to_value(), false => b: { const global_variable = module.llvm.handle.create_global_variable(.{ .linkage = .InternalLinkage, .name = module.arena.join_string(&.{ "__const.", module.current_function.?.name, if (ty.name) |n| n else "" }), .initial_value = constant_array, .type = ty.llvm.handle, }); break :b global_variable.to_value(); }, }; const value = module.values.add(); value.* = .{ .llvm = global, .type = ty, .bb = .constant_array, .lvalue = true, .dereference_to_assign = false, }; return value; } else { @trap(); } @trap(); }, else => @trap(), } }, '#' => return converter.parse_value_intrinsic(module, expected_type), '&' => { converter.offset += 1; return converter.parse_value(module, expected_type, .pointer); }, '!' => blk: { converter.offset += 1; // TODO: should we skip space here? 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(), }; const value_offset = converter.offset; const value_start_ch = converter.content[value_offset]; var value = switch (value_start_ch) { 'a'...'z', 'A'...'Z', '_' => b: { if (module.current_function) |current_function| { const identifier = converter.parse_identifier(); if (string_to_enum(ValueKeyword, identifier)) |value_keyword| switch (value_keyword) { ._ => return module.void_value, .undefined => { const expected_ty = expected_type orelse converter.report_error(); // TODO: cache poison const value = module.values.add(); value.* = .{ .llvm = expected_ty.llvm.handle.get_poison(), .type = expected_ty, .bb = .instruction, // TODO .lvalue = false, .dereference_to_assign = false, }; return value; }, .zero => { const ty = expected_type orelse converter.report_error(); return module.get_zero_value(ty); }, .@"unreachable" => { _ = module.llvm.builder.create_unreachable(); module.llvm.builder.clear_insertion_position(); return module.unreachable_value; }, } else { const variable = if (current_function.value.bb.function.locals.find(identifier)) |local| local else if (current_function.value.bb.function.arguments.find(identifier)) |argument| argument else if (module.globals.find(identifier)) |global| global else converter.report_error(); converter.skip_space(); assert(variable.value.type.bb == .pointer); const appointee_type = variable.value.type.bb.pointer.type; if (converter.consume_character_if_match(left_parenthesis)) { if (value_kind == .pointer) { converter.report_error(); } const call = converter.parse_call(module, variable.value); break :b call; } else if (converter.consume_character_if_match('.')) { converter.skip_space(); switch (appointee_type.bb) { .structure => |*struct_type| { const field_name = converter.parse_identifier(); const field_index: u32 = for (struct_type.fields, 0..) |field, field_index| { if (lib.string.equal(field.name, field_name)) { break @intCast(field_index); } } else converter.report_error(); const field = struct_type.fields[field_index]; const gep = module.llvm.builder.create_struct_gep(appointee_type.llvm.handle.to_struct(), variable.value.llvm, field_index); switch (value_kind) { .pointer, .maybe_pointer => { @trap(); }, .value => { const load = module.values.add(); load.* = .{ .llvm = module.create_load(.{ .type = field.type, .value = gep }), .type = field.type, .bb = .instruction, .lvalue = false, .dereference_to_assign = false, }; break :b load; }, } }, .bits => |*bits| { const field_name = converter.parse_identifier(); const field_index: u32 = for (bits.fields, 0..) |field, field_index| { if (lib.string.equal(field.name, field_name)) { break @intCast(field_index); } } else converter.report_error(); const field = bits.fields[field_index]; const bitfield_load = module.create_load(.{ .type = bits.backing_type, .value = variable.value.llvm }); const bitfield_shifted = module.llvm.builder.create_lshr(bitfield_load, bits.backing_type.llvm.handle.to_integer().get_constant(field.bit_offset, @intFromBool(false)).to_value()); const bitfield_masked = module.llvm.builder.create_and(bitfield_shifted, bits.backing_type.llvm.handle.to_integer().get_constant((@as(u64, 1) << @intCast(field.type.get_bit_size())) - 1, @intFromBool(false)).to_value()); if (value_kind == .pointer) { converter.report_error(); } const value = module.values.add(); value.* = .{ .bb = .instruction, .llvm = switch (bits.backing_type == field.type) { true => bitfield_masked, false => blk: { assert(bits.backing_type.get_bit_size() > field.type.get_bit_size()); const trunc = module.llvm.builder.create_truncate(bitfield_masked, field.type.llvm.handle); break :blk trunc; }, }, .type = field.type, .lvalue = false, .dereference_to_assign = false, }; break :b value; }, .pointer => |pointer_type| { const element_type = pointer_type.type; if (converter.consume_character_if_match('&')) { const load = module.values.add(); load.* = .{ .llvm = module.create_load(.{ .type = appointee_type, .value = variable.value.llvm }), .type = appointee_type, .bb = .instruction, .lvalue = false, .dereference_to_assign = false, }; break :b load; } else { switch (element_type.bb) { .structure => |*struct_type| { const field_name = converter.parse_identifier(); const field_index: u32 = for (struct_type.fields, 0..) |field, field_index| { if (lib.string.equal(field.name, field_name)) { break @intCast(field_index); } } else converter.report_error(); const field = struct_type.fields[field_index]; const gep = module.llvm.builder.create_struct_gep(element_type.llvm.handle.to_struct(), variable.value.llvm, field_index); switch (value_kind) { .pointer, .maybe_pointer => { @trap(); }, .value => { const load = module.values.add(); load.* = .{ .llvm = module.create_load(.{ .type = field.type, .value = gep }), .type = field.type, .bb = .instruction, .lvalue = false, .dereference_to_assign = false, }; break :b load; }, } }, else => @trap(), } } }, else => @trap(), } } else if (converter.consume_character_if_match(left_bracket)) { converter.skip_space(); const index_type = module.integer_type(64, false); const llvm_index_type = module.integer_type(64, false).llvm.handle.to_integer(); const zero_index = llvm_index_type.get_constant(0, @intFromBool(false)).to_value(); const index = converter.parse_value(module, index_type, .value); converter.skip_space(); converter.expect_character(right_bracket); const gep = module.llvm.builder.create_gep(.{ .type = appointee_type.llvm.handle, .aggregate = variable.value.llvm, .indices = &.{ zero_index, index.llvm }, }); switch (value_kind) { .pointer, .maybe_pointer => { @trap(); }, .value => { const load = module.values.add(); const load_type = appointee_type.bb.array.element_type; load.* = .{ .llvm = module.create_load(.{ .type = load_type, .value = gep }), .type = load_type, .bb = .instruction, .lvalue = false, .dereference_to_assign = false, }; break :b load; }, } } else { switch (value_kind) { .pointer, .maybe_pointer => break :b variable.value, .value => switch (appointee_type.get_evaluation_kind()) { .aggregate => { const value_address = module.values.add(); value_address.* = .{ .llvm = variable.value.llvm, .type = variable.value.type, .bb = .instruction, .lvalue = true, .dereference_to_assign = true, }; break :b value_address; }, else => { const load = module.values.add(); load.* = .{ .llvm = module.create_load(.{ .type = appointee_type, .value = variable.value.llvm }), .type = appointee_type, .bb = .instruction, .lvalue = false, .dereference_to_assign = false, }; break :b load; }, }, } } } } else { converter.report_error(); } }, '0'...'9' => converter.parse_integer(module, expected_type.?, prefix == .negative), else => os.abort(), }; switch (prefix) { .none, .negative, // Already done in 'parse_integer' // TODO: => {}, .not_zero => { const llvm_value = module.llvm.builder.create_compare(.eq, value.llvm, value.type.llvm.handle.to_integer().get_constant(0, 0).to_value()); value.* = .{ .llvm = llvm_value, .bb = .instruction, .type = module.integer_type(1, false), .lvalue = false, .dereference_to_assign = false, }; }, } return value; } }; fn is_space(ch: u8) bool { return ((@intFromBool(ch == ' ') | @intFromBool(ch == '\n')) | ((@intFromBool(ch == '\t') | @intFromBool(ch == '\r')))) != 0; } const StatementStartKeyword = enum { @"return", @"if", }; pub const BuildMode = enum { debug_none, debug_fast, debug_size, soft_optimize, optimize_for_speed, optimize_for_size, aggressively_optimize_for_speed, aggressively_optimize_for_size, fn is_optimized(build_mode: BuildMode) bool { return @intFromEnum(build_mode) >= @intFromEnum(BuildMode.soft_optimize); } fn to_llvm_ir(build_mode: BuildMode) llvm.OptimizationLevel { return switch (build_mode) { .debug_none => unreachable, .debug_fast, .debug_size => .O0, .soft_optimize => .O1, .optimize_for_speed => .O2, .optimize_for_size => .Os, .aggressively_optimize_for_speed => .O3, .aggressively_optimize_for_size => .Oz, }; } fn to_llvm_machine(build_mode: BuildMode) llvm.CodeGenerationOptimizationLevel { return switch (build_mode) { .debug_none => .none, .debug_fast, .debug_size => .none, .soft_optimize => .less, .optimize_for_speed => .default, .optimize_for_size => .default, .aggressively_optimize_for_speed => .aggressive, .aggressively_optimize_for_size => .aggressive, }; } }; const CPUArchitecture = enum { x86_64, }; const OperatingSystem = enum { linux, }; pub const Target = struct { cpu: CPUArchitecture, os: OperatingSystem, pub fn get_native() Target { const builtin = @import("builtin"); return Target{ .cpu = switch (builtin.cpu.arch) { .x86_64 => .x86_64, else => @compileError("CPU not supported"), }, .os = switch (builtin.os.tag) { .linux => .linux, else => @compileError("OS not supported"), }, }; } }; pub const Abi = struct { const Kind = enum(u3) { ignore, direct, extend, indirect, indirect_aliased, expand, coerce_and_expand, in_alloca, }; const RegisterCount = union { system_v: Abi.SystemV.RegisterCount, }; const Flags = packed struct { kind: Kind, padding_in_reg: bool = false, in_alloca_sret: bool = false, in_alloca_indirect: bool = false, indirect_by_value: bool = false, indirect_realign: bool = false, sret_after_this: bool = false, in_reg: bool = false, can_be_flattened: bool = false, sign_extension: bool = false, }; const Information = struct { semantic_type: *Type, coerce_to_type: ?*Type = null, padding: union { type: ?*Type, unpadded_coerce_and_expand_type: ?*Type, } = .{ .type = null }, padding_arg_index: u16 = 0, attributes: union { direct: DirectAttributes, indirect: IndirectAttributes, alloca_field_index: u32, } = .{ .direct = .{ .offset = 0, .alignment = 0, }, }, flags: Abi.Flags, abi_start: u16 = 0, abi_count: u16 = 0, const DirectAttributes = struct { offset: u32, alignment: u32, }; const IndirectAttributes = struct { alignment: u32, address_space: u32, }; const Direct = struct { semantic_type: *Type, type: *Type, padding: ?*Type = null, offset: u32 = 0, alignment: u32 = 0, can_be_flattened: bool = true, }; pub fn get_direct(direct: Direct) Information { var result = Information{ .semantic_type = direct.semantic_type, .flags = .{ .kind = .direct, }, }; result.set_coerce_to_type(direct.type); result.set_padding_type(direct.padding); result.set_direct_offset(direct.offset); result.set_direct_alignment(direct.alignment); result.set_can_be_flattened(direct.can_be_flattened); return result; } pub const Ignore = struct { semantic_type: *Type, }; pub fn get_ignore(ignore: Ignore) Information { return Information{ .semantic_type = ignore.semantic_type, .flags = .{ .kind = .ignore, }, }; } const Extend = struct { semantic_type: *Type, type: ?*Type = null, sign: bool, }; pub fn get_extend(extend: Extend) Information { assert(extend.semantic_type.is_integral_or_enumeration_type()); var result = Information{ .semantic_type = extend.semantic_type, .flags = .{ .kind = .extend, }, }; result.set_coerce_to_type(if (extend.type) |t| t else extend.semantic_type); result.set_padding_type(null); result.set_direct_offset(0); result.set_direct_alignment(0); result.flags.sign_extension = extend.sign; return result; } const NaturalAlignIndirect = struct { semantic_type: *Type, padding_type: ?*Type = null, by_value: bool = true, realign: bool = false, }; pub fn get_natural_align_indirect(nai: NaturalAlignIndirect) Abi.Information { const alignment = nai.semantic_type.get_byte_alignment(); return get_indirect(.{ .semantic_type = nai.semantic_type, .alignment = alignment, .by_value = nai.by_value, .realign = nai.realign, .padding_type = nai.padding_type, }); } pub const Indirect = struct { semantic_type: *Type, padding_type: ?*Type = null, alignment: u32, by_value: bool = true, realign: bool = false, }; pub fn get_indirect(indirect: Indirect) Abi.Information { var result = Abi.Information{ .semantic_type = indirect.semantic_type, .attributes = .{ .indirect = .{ .address_space = 0, .alignment = 0, }, }, .flags = .{ .kind = .indirect, }, }; result.set_indirect_align(indirect.alignment); result.set_indirect_by_value(indirect.by_value); result.set_indirect_realign(indirect.realign); result.set_sret_after_this(false); result.set_padding_type(indirect.padding_type); return result; } fn set_sret_after_this(abi: *Abi.Information, sret_after_this: bool) void { assert(abi.flags.kind == .indirect); abi.flags.sret_after_this = sret_after_this; } fn set_indirect_realign(abi: *Abi.Information, realign: bool) void { assert(abi.flags.kind == .indirect); abi.flags.indirect_realign = realign; } fn set_indirect_by_value(abi: *Abi.Information, by_value: bool) void { assert(abi.flags.kind == .indirect); abi.flags.indirect_by_value = by_value; } fn set_indirect_align(abi: *Abi.Information, alignment: u32) void { assert(abi.flags.kind == .indirect or abi.flags.kind == .indirect_aliased); abi.attributes.indirect.alignment = alignment; } fn set_coerce_to_type(info: *Information, coerce_to_type: *Type) void { assert(info.can_have_coerce_to_type()); info.coerce_to_type = coerce_to_type; } fn get_coerce_to_type(info: *const Information) *Type { assert(info.can_have_coerce_to_type()); return info.coerce_to_type.?; } fn can_have_coerce_to_type(info: *const Information) bool { return switch (info.flags.kind) { .direct, .extend, .coerce_and_expand => true, else => false, }; } fn set_padding_type(info: *Information, padding_type: ?*Type) void { assert(info.can_have_padding_type()); info.padding = .{ .type = padding_type, }; } fn can_have_padding_type(info: *const Information) bool { return switch (info.flags.kind) { .direct, .extend, .indirect, .indirect_aliased, .expand => true, else => false, }; } fn get_padding_type(info: *const Information) ?*Type { return if (info.can_have_padding_type()) info.padding.type else null; } fn set_direct_offset(info: *Information, offset: u32) void { assert(info.flags.kind == .direct or info.flags.kind == .extend); info.attributes.direct.offset = offset; } fn set_direct_alignment(info: *Information, alignment: u32) void { assert(info.flags.kind == .direct or info.flags.kind == .extend); info.attributes.direct.alignment = alignment; } fn set_can_be_flattened(info: *Information, can_be_flattened: bool) void { assert(info.flags.kind == .direct); info.flags.can_be_flattened = can_be_flattened; } fn get_can_be_flattened(info: *const Information) bool { assert(info.flags.kind == .direct); return info.flags.can_be_flattened; } }; pub const SystemV = struct { pub const RegisterCount = struct { gpr: u32, sse: u32, }; pub const Class = enum { integer, sse, sseup, x87, x87up, complex_x87, none, memory, fn merge(accumulator: Class, field: Class) Class { // AMD64-ABI 3.2.3p2: Rule 4. Each field of an object is // classified recursively so that always two fields are // considered. The resulting class is calculated according to // the classes of the fields in the eightbyte: // // (a) If both classes are equal, this is the resulting class. // // (b) If one of the classes is NO_CLASS, the resulting class is // the other class. // // (c) If one of the classes is MEMORY, the result is the MEMORY // class. // // (d) If one of the classes is INTEGER, the result is the // INTEGER. // // (e) If one of the classes is X87, X87UP, COMPLEX_X87 class, // MEMORY is used as class. // // (f) Otherwise class SSE is used. // Accum should never be memory (we should have returned) or // ComplexX87 (because this cannot be passed in a structure). assert(accumulator != .memory and accumulator != .complex_x87); if (accumulator == field or field == .none) { return accumulator; } if (field == .memory) { return .memory; } if (accumulator == .none) { return field; } if (accumulator == .integer or field == .integer) { return .integer; } if (field == .x87 or field == .x87up or field == .complex_x87 or accumulator == .x87 or accumulator == .x87up) { return .memory; } return .sse; } }; const ClassifyOptions = struct { base_offset: u64, is_named_argument: bool, is_register_call: bool = false, }; fn classify(ty: *Type, options: ClassifyOptions) [2]Class { var result = [2]Class{ .none, .none }; const is_memory = options.base_offset >= 8; const current_index = @intFromBool(is_memory); const not_current_index = @intFromBool(!is_memory); assert(current_index != not_current_index); result[current_index] = .memory; switch (ty.bb) { .void, .noreturn => result[current_index] = .none, .bits => result[current_index] = .integer, .pointer => result[current_index] = .integer, .integer => |integer| { if (integer.bit_count <= 64) { result[current_index] = .integer; } else if (integer.bit_count == 128) { @trap(); } else { @trap(); } }, .structure => |struct_type| { if (struct_type.byte_size <= 64) { const has_variable_array = false; if (!has_variable_array) { // const struct_type = ty.get_payload(.@"struct"); result[current_index] = .none; const is_union = false; var member_offset: u32 = 0; for (struct_type.fields) |field| { const offset = options.base_offset + member_offset; const member_size = field.type.get_byte_size(); const member_alignment = field.type.get_byte_alignment(); member_offset = @intCast(lib.align_forward_u64(member_offset + member_size, ty.get_byte_alignment())); const native_vector_size = 16; if (ty.get_byte_size() > 16 and ((!is_union and ty.get_byte_size() != member_size) or ty.get_byte_size() > native_vector_size)) { result[0] = .memory; const r = classify_post_merge(ty.get_byte_size(), result); return r; } if (offset % member_alignment != 0) { result[0] = .memory; const r = classify_post_merge(ty.get_byte_size(), result); return r; } const member_classes = classify(field.type, .{ .base_offset = offset, .is_named_argument = false, }); for (&result, member_classes) |*r, m| { const merge_result = r.merge(m); r.* = merge_result; } if (result[0] == .memory or result[1] == .memory) break; } const final = classify_post_merge(ty.get_byte_size(), result); result = final; } } }, .array => |*array_type| { if (ty.get_byte_size() <= 64) { if (options.base_offset % ty.get_byte_alignment() == 0) { result[current_index] = .none; const vector_size = 16; if (ty.get_byte_size() > 16 and (ty.get_byte_size() != array_type.element_type.get_byte_size() or ty.get_byte_size() > vector_size)) { unreachable; } else { var offset = options.base_offset; for (0..array_type.element_count.?) |_| { const element_classes = classify(array_type.element_type, .{ .base_offset = offset, .is_named_argument = false, }); offset += array_type.element_type.get_byte_size(); const merge_result = [2]Class{ result[0].merge(element_classes[0]), result[1].merge(element_classes[1]) }; result = merge_result; if (result[0] == .memory or result[1] == .memory) { break; } } const final_result = classify_post_merge(ty.get_byte_size(), result); assert(final_result[1] != .sseup or final_result[0] != .sse); result = final_result; } } } }, else => @trap(), } return result; } fn classify_post_merge(aggregate_size: u64, classes: [2]Class) [2]Class { // AMD64-ABI 3.2.3p2: Rule 5. Then a post merger cleanup is done: // // (a) If one of the classes is Memory, the whole argument is passed in // memory. // // (b) If X87UP is not preceded by X87, the whole argument is passed in // memory. // // (c) If the size of the aggregate exceeds two eightbytes and the first // eightbyte isn't SSE or any other eightbyte isn't SSEUP, the whole // argument is passed in memory. NOTE: This is necessary to keep the // ABI working for processors that don't support the __m256 type. // // (d) If SSEUP is not preceded by SSE or SSEUP, it is converted to SSE. // // Some of these are enforced by the merging logic. Others can arise // only with unions; for example: // union { _Complex double; unsigned; } // // Note that clauses (b) and (c) were added in 0.98. var result = classes; if (result[1] == .memory) { result[0] = .memory; } if (result[1] == .x87up) { @trap(); } if (aggregate_size > 16 and (result[0] != .sse or result[1] != .sseup)) { result[0] = .memory; } if (result[1] == .sseup and result[0] != .sse) { result[0] = .sse; } return result; } fn get_int_type_at_offset(module: *Module, ty: *Type, offset: u32, source_type: *Type, source_offset: u32) *Type { switch (ty.bb) { .bits => |bits| { return get_int_type_at_offset(module, bits.backing_type, offset, if (source_type == ty) bits.backing_type else source_type, source_offset); }, .integer => |integer_type| { switch (integer_type.bit_count) { 64 => return ty, 32, 16, 8 => { if (offset != 0) unreachable; const start = source_offset + ty.get_byte_size(); const end = source_offset + 8; if (contains_no_user_data(source_type, start, end)) { return ty; } }, else => return module.integer_type(@intCast(@min(ty.get_byte_size() - source_offset, 8) * 8), integer_type.signed), } }, .pointer => return if (offset == 0) ty else @trap(), .structure => { if (get_member_at_offset(ty, offset)) |field| { return get_int_type_at_offset(module, field.type, @intCast(offset - field.byte_offset), source_type, source_offset); } unreachable; }, .array => |array_type| { const element_type = array_type.element_type; const element_size = element_type.get_byte_size(); const element_offset = (offset / element_size) * element_size; return get_int_type_at_offset(module, element_type, @intCast(offset - element_offset), source_type, source_offset); }, else => |t| @panic(@tagName(t)), } if (source_type.get_byte_size() - source_offset > 8) { return module.integer_type(64, false); } else { const byte_count = source_type.get_byte_size() - source_offset; const bit_count = byte_count * 8; return module.integer_type(@intCast(bit_count), false); } } fn get_member_at_offset(ty: *Type, offset: u32) ?*const Field { if (ty.get_byte_size() <= offset) { return null; } var offset_it: u32 = 0; var last_match: ?*const Field = null; const struct_type = &ty.bb.structure; for (struct_type.fields) |*field| { if (offset_it > offset) { break; } last_match = field; offset_it = @intCast(lib.align_forward_u64(offset_it + field.type.get_byte_size(), ty.get_byte_alignment())); } assert(last_match != null); return last_match; } fn contains_no_user_data(ty: *Type, start: u64, end: u64) bool { if (ty.get_byte_size() <= start) { return true; } switch (ty.bb) { .structure => |*struct_type| { var offset: u64 = 0; for (struct_type.fields) |field| { if (offset >= end) break; const field_start = if (offset < start) start - offset else 0; if (!contains_no_user_data(field.type, field_start, end - offset)) return false; offset += field.type.get_byte_size(); } return true; }, .array => |array_type| { for (0..array_type.element_count.?) |i| { const offset = i * array_type.element_type.get_byte_size(); if (offset >= end) break; const element_start = if (offset < start) start - offset else 0; if (!contains_no_user_data(array_type.element_type, element_start, end - offset)) return false; } return true; }, // .anonymous_struct => unreachable, else => return false, } } const ArgumentOptions = struct { available_gpr: u32, is_named_argument: bool, is_reg_call: bool, }; pub fn classify_argument_type(module: *Module, argument_type: *Type, options: ArgumentOptions) struct { Abi.Information, Abi.SystemV.RegisterCount } { const classes = classify(argument_type, .{ .base_offset = 0, .is_named_argument = options.is_named_argument, }); assert(classes[1] != .memory or classes[0] == .memory); assert(classes[1] != .sseup or classes[0] == .sse); var needed_registers = Abi.SystemV.RegisterCount{ .gpr = 0, .sse = 0, }; var low: ?*Type = null; switch (classes[0]) { .integer => { needed_registers.gpr += 1; const low_ty = Abi.SystemV.get_int_type_at_offset(module, argument_type, 0, argument_type, 0); low = low_ty; if (classes[1] == .none and low_ty.bb == .integer) { if (argument_type.bb == .enumerator) { @trap(); } if (argument_type.is_integral_or_enumeration_type() and argument_type.is_promotable_integer_type_for_abi()) { return .{ Abi.Information.get_extend(.{ .semantic_type = argument_type, .sign = argument_type.is_signed(), }), needed_registers, }; } } }, .memory, .x87, .complex_x87 => { // TODO: CXX ABI: RAA_Indirect return .{ get_indirect_result(argument_type, options.available_gpr), needed_registers }; }, else => @trap(), } var high: ?*Type = null; switch (classes[1]) { .none => {}, .integer => { needed_registers.gpr += 1; const high_ty = Abi.SystemV.get_int_type_at_offset(module, argument_type, 8, argument_type, 8); high = high_ty; if (classes[0] == .none) { @trap(); } }, else => @trap(), } const result_type = if (high) |hi| get_by_val_argument_pair(module, low orelse unreachable, hi) else low orelse unreachable; return .{ Abi.Information.get_direct(.{ .semantic_type = argument_type, .type = result_type, }), needed_registers, }; } const ClassifyArgument = struct { type: *Type, abi_start: u16, is_reg_call: bool = false, is_named_argument: bool, }; pub fn classify_argument(module: *Module, available_registers: *Abi.RegisterCount, llvm_abi_argument_type_buffer: []*llvm.Type, abi_argument_type_buffer: []*Type, options: ClassifyArgument) Abi.Information { const semantic_argument_type = options.type; const result = if (options.is_reg_call) @trap() else Abi.SystemV.classify_argument_type(module, semantic_argument_type, .{ .is_named_argument = options.is_named_argument, .is_reg_call = options.is_reg_call, .available_gpr = available_registers.system_v.gpr, }); const abi = result[0]; const needed_registers = result[1]; var argument_type_abi = switch (available_registers.system_v.gpr >= needed_registers.gpr and available_registers.system_v.sse >= needed_registers.sse) { true => blk: { available_registers.system_v.gpr -= needed_registers.gpr; available_registers.system_v.sse -= needed_registers.sse; break :blk abi; }, false => Abi.SystemV.get_indirect_result(semantic_argument_type, available_registers.system_v.gpr), }; if (argument_type_abi.get_padding_type() != null) { @trap(); } argument_type_abi.abi_start = options.abi_start; const count = switch (argument_type_abi.flags.kind) { .direct, .extend => blk: { const coerce_to_type = argument_type_abi.get_coerce_to_type(); const flattened_struct = argument_type_abi.flags.kind == .direct and argument_type_abi.get_can_be_flattened() and coerce_to_type.bb == .structure; const count: u16 = switch (flattened_struct) { false => 1, true => @intCast(argument_type_abi.get_coerce_to_type().bb.structure.fields.len), }; switch (flattened_struct) { false => { llvm_abi_argument_type_buffer[argument_type_abi.abi_start] = coerce_to_type.llvm.handle; abi_argument_type_buffer[argument_type_abi.abi_start] = coerce_to_type; }, true => { for (coerce_to_type.bb.structure.fields, 0..) |field, field_index| { const index = argument_type_abi.abi_start + field_index; llvm_abi_argument_type_buffer[index] = field.type.llvm.handle; abi_argument_type_buffer[index] = field.type; } }, } break :blk count; }, .indirect => blk: { const indirect_type = module.get_pointer_type(.{ .type = argument_type_abi.semantic_type }); abi_argument_type_buffer[argument_type_abi.abi_start] = indirect_type; llvm_abi_argument_type_buffer[argument_type_abi.abi_start] = indirect_type.llvm.handle; break :blk 1; }, else => |t| @panic(@tagName(t)), }; argument_type_abi.abi_count = count; return argument_type_abi; } pub fn get_by_val_argument_pair(module: *Module, low: *Type, high: *Type) *Type { const low_size = low.get_byte_allocation_size(); const high_alignment = high.get_byte_alignment(); const high_start = lib.align_forward_u64(low_size, high_alignment); assert(high_start != 0 and high_start <= 8); const new_low = if (high_start != 8) { @trap(); } else low; const result = module.get_anonymous_struct_pair(.{ new_low, high }); assert(result.bb.structure.fields[1].byte_offset == 8); return result; } pub fn classify_return_type(module: *Module, return_type: *Type) Abi.Information { const classes = classify(return_type, .{ .base_offset = 0, .is_named_argument = true, }); assert(classes[1] != .memory or classes[0] == .memory); assert(classes[1] != .sseup or classes[0] == .sse); var low: ?*Type = null; switch (classes[0]) { .none => { if (classes[1] == .none) { return Abi.Information.get_ignore(.{ .semantic_type = return_type, }); } @trap(); }, .integer => { const low_ty = Abi.SystemV.get_int_type_at_offset(module, return_type, 0, return_type, 0); low = low_ty; if (classes[1] == .none and low_ty.bb == .integer) { if (return_type.bb == .enumerator) { @trap(); } if (return_type.is_integral_or_enumeration_type() and return_type.is_promotable_integer_type_for_abi()) { return Abi.Information.get_extend(.{ .semantic_type = return_type, .sign = return_type.is_signed(), }); } } }, .memory => { return Abi.SystemV.get_indirect_return_result(.{ .type = return_type }); }, else => @trap(), } var high: ?*Type = null; _ = &high; switch (classes[1]) { .none => {}, .integer => { const high_offset = 8; const high_ty = Abi.SystemV.get_int_type_at_offset(module, return_type, high_offset, return_type, high_offset); high = high_ty; if (classes[0] == .none) { return Abi.Information.get_direct(.{ .semantic_type = return_type, .type = high_ty, .offset = high_offset, }); } }, else => @trap(), } if (high) |hi| { low = Abi.SystemV.get_byval_argument_pair(module, .{ low orelse unreachable, hi }); } return Abi.Information.get_direct(.{ .semantic_type = return_type, .type = low orelse unreachable, }); } pub fn get_byval_argument_pair(module: *Module, pair: [2]*Type) *Type { const low_size = pair[0].get_byte_size(); const high_alignment = pair[1].get_byte_alignment(); const high_offset = lib.align_forward_u64(low_size, high_alignment); assert(high_offset != 0 and high_offset <= 8); const low = if (high_offset != 8) if ((pair[0].bb == .float and pair[0].bb.float.kind == .half) or (pair[0].bb == .float and pair[0].bb.float.kind == .float)) { @trap(); } else { assert(pair[0].is_integer_backing()); @trap(); } else pair[0]; const high = pair[1]; const struct_type = module.get_anonymous_struct_pair(.{ low, high }); assert(struct_type.bb.structure.fields[1].byte_offset == 8); return struct_type; } const IndirectReturn = struct { type: *Type, }; pub fn get_indirect_return_result(indirect: IndirectReturn) Abi.Information { if (indirect.type.is_aggregate_type_for_abi()) { return Abi.Information.get_natural_align_indirect(.{ .semantic_type = indirect.type, }); } else { @trap(); } } pub fn get_indirect_result(ty: *Type, free_gpr: u32) Abi.Information { if (!ty.is_aggregate_type_for_abi() and !is_illegal_vector_type(ty) and !ty.is_arbitrary_bit_integer()) { return switch (ty.is_promotable_integer_type_for_abi()) { true => @trap(), false => Abi.Information.get_direct(.{ .semantic_type = ty, .type = ty, }), }; } else { // TODO CXX ABI const alignment = @max(ty.get_byte_alignment(), 8); const size = ty.get_byte_size(); return switch (free_gpr == 0 and alignment == 8 and size <= 8) { true => @trap(), false => Abi.Information.get_indirect(.{ .semantic_type = ty, .alignment = alignment, }), }; } } pub fn is_illegal_vector_type(ty: *Type) bool { return switch (ty.bb) { .vector => @trap(), else => false, }; } pub fn emit_va_arg_from_memory(module: *Module, va_list_pointer: *llvm.Value, va_list_struct: *Type, arg_type: *Type) *llvm.Value { const overflow_arg_area_pointer = module.llvm.builder.create_struct_gep(va_list_struct.llvm.handle.to_struct(), va_list_pointer, 2); const overflow_arg_area_type = va_list_struct.bb.structure.fields[2].type; const overflow_arg_area = module.create_load(.{ .type = overflow_arg_area_type, .value = overflow_arg_area_pointer }); if (arg_type.get_byte_alignment() > 8) { @trap(); } const arg_type_size = arg_type.get_byte_size(); const raw_offset = lib.align_forward_u64(arg_type_size, 8); const offset = module.integer_type(32, false).llvm.handle.to_integer().get_constant(raw_offset, @intFromBool(false)); const new_overflow_arg_area = module.llvm.builder.create_gep(.{ .type = module.integer_type(8, false).llvm.handle, .aggregate = overflow_arg_area, .indices = &.{offset.to_value()}, .inbounds = false, }); _ = module.create_store(.{ .destination_type = overflow_arg_area_type, .source_type = overflow_arg_area_type, .source_value = new_overflow_arg_area, .destination_value = overflow_arg_area_pointer }); return overflow_arg_area; } }; }; const ConvertOptions = struct { content: []const u8, path: [:0]const u8, executable: [:0]const u8, build_mode: BuildMode, name: []const u8, has_debug_info: bool, objects: []const [:0]const u8, target: Target, }; pub noinline fn convert(arena: *Arena, options: ConvertOptions) void { const build_dir = "bb-cache"; os.make_directory(build_dir); var converter = Converter{ .content = options.content, .offset = 0, .line_offset = 0, .line_character_offset = 0, }; llvm.default_initialize(); const module = Module.initialize(arena, options); defer module.deinitialize(); while (true) { converter.skip_space(); if (converter.offset == converter.content.len) { break; } var is_export = false; var is_extern = false; const global_line = converter.get_line(); const global_column = converter.get_column(); _ = global_column; if (converter.content[converter.offset] == left_bracket) { converter.offset += 1; while (converter.offset < converter.content.len) { const global_keyword_string = converter.parse_identifier(); const global_keyword = string_to_enum(GlobalKeyword, global_keyword_string) orelse converter.report_error(); switch (global_keyword) { .@"export" => is_export = true, .@"extern" => is_extern = true, } switch (converter.content[converter.offset]) { right_bracket => break, else => converter.report_error(), } } converter.expect_character(right_bracket); converter.skip_space(); } const global_name = converter.parse_identifier(); if (module.types.find(global_name) != null) @trap(); if (module.globals.find(global_name) != null) @trap(); converter.skip_space(); var global_type: ?*Type = null; if (converter.consume_character_if_match(':')) { converter.skip_space(); global_type = converter.parse_type(module); converter.skip_space(); } converter.expect_character('='); converter.skip_space(); if (is_identifier_start_ch(converter.content[converter.offset])) { 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 (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(); 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(); 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 argument_name = converter.parse_identifier(); 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; }, } 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 => {}, } } 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, .implicit_backing_type = is_implicit_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, .implicit_backing_type = is_implicit_type, }, }, .llvm = .{ .handle = backing_type.llvm.handle, .debug = debug_type, }, .name = global_name, }); }, } else { converter.report_error(); } } else { if (global_type) |expected_type| { const value = converter.parse_value(module, expected_type, .value); converter.skip_space(); converter.expect_character(';'); const global_variable = module.llvm.handle.create_global_variable(.{ .linkage = switch (is_export) { true => .ExternalLinkage, false => .InternalLinkage, }, .name = global_name, .initial_value = value.llvm.to_constant(), .type = expected_type.llvm.handle, }); global_variable.to_value().set_alignment(@intCast(expected_type.get_byte_alignment())); if (module.llvm.di_builder) |di_builder| { const linkage_name = global_name; const local_to_unit = !(is_export or is_extern); const alignment = 0; // TODO const global_variable_expression = di_builder.create_global_variable(module.llvm.global_scope, global_name, linkage_name, module.llvm.file, global_line, expected_type.llvm.debug, local_to_unit, di_builder.null_expression(), alignment); global_variable.add_debug_info(global_variable_expression); } const global_value = module.values.add(); global_value.* = .{ .llvm = global_variable.to_value(), .type = module.get_pointer_type(.{ .type = expected_type }), .bb = .global, .lvalue = true, .dereference_to_assign = false, }; const global = module.globals.add(); global.* = .{ .name = global_name, .value = global_value, }; } else { converter.report_error(); } } } if (module.llvm.di_builder) |di_builder| { di_builder.finalize(); } const verify_result = module.llvm.handle.verify(); if (!verify_result.success) { lib.print_string(module.llvm.handle.to_string()); lib.print_string("============================\n"); lib.print_string(verify_result.error_message orelse unreachable); os.abort(); } if (!lib.is_test) { const module_string = module.llvm.handle.to_string(); lib.print_string_stderr(module_string); } var error_message: llvm.String = undefined; const target_machine = llvm.Target.Machine.create(.{ .target_options = llvm.Target.Options.default(), .cpu_triple = llvm.String.from_slice(llvm.global.host_triple), .cpu_model = llvm.String.from_slice(llvm.global.host_cpu_model), .cpu_features = llvm.String.from_slice(llvm.global.host_cpu_features), .optimization_level = options.build_mode.to_llvm_machine(), .relocation_model = .default, .code_model = .none, .jit = false, }, &error_message) orelse { os.abort(); }; const object_generate_result = llvm.object_generate(module.llvm.handle, target_machine, .{ .optimize_when_possible = @intFromEnum(options.build_mode) > @intFromEnum(BuildMode.soft_optimize), .debug_info = options.has_debug_info, .optimization_level = if (options.build_mode != .debug_none) options.build_mode.to_llvm_ir() else null, .path = options.objects[0], }); switch (object_generate_result) { .success => { const result = llvm.link(module.arena, .{ .output_path = options.executable, .objects = options.objects, }); switch (result.success) { true => {}, false => os.abort(), } }, else => os.abort(), } }