diff --git a/build.zig b/build.zig index d5ffd1e..e1aa692 100644 --- a/build.zig +++ b/build.zig @@ -78,9 +78,11 @@ const LLVM = struct { else => "HOME", }; const home_path = env.get(home_env) orelse unreachable; + const is_ci = std.mem.eql(u8, (env.get("BB_CI") orelse "0"), "1"); const download_dir = try std.mem.concat(b.allocator, u8, &.{ home_path, "/Downloads" }); std.fs.makeDirAbsolute(download_dir) catch {}; - const llvm_base = try std.mem.concat(b.allocator, u8, &.{ "llvm-", @tagName(target.result.cpu.arch), "-", @tagName(target.result.os.tag), "-", @tagName(CmakeBuildType.from_zig_build_type(optimize)) }); + const cmake_build_type = if (is_ci) CmakeBuildType.from_zig_build_type(optimize) else CmakeBuildType.Release; + const llvm_base = try std.mem.concat(b.allocator, u8, &.{ "llvm-", @tagName(target.result.cpu.arch), "-", @tagName(target.result.os.tag), "-", @tagName(cmake_build_type) }); const base = try std.mem.concat(b.allocator, u8, &.{ download_dir, "/", llvm_base }); const full_path = try std.mem.concat(b.allocator, u8, &.{ base, "/bin/llvm-config" }); @@ -306,7 +308,7 @@ pub fn build(b: *std.Build) !void { env = try std.process.getEnvMap(b.allocator); target = b.standardTargetOptions(.{}); optimize = b.standardOptimizeOption(.{}); - system_llvm = b.option(bool, "system_llvm", "Link against system LLVM libraries") orelse true; + system_llvm = b.option(bool, "system_llvm", "Link against system LLVM libraries") orelse false; const path = env.get("PATH") orelse unreachable; const c_abi_module = b.createModule(.{ @@ -322,7 +324,7 @@ pub fn build(b: *std.Build) !void { .optimize = optimize, }); c_abi.addCSourceFiles(.{ - .files = &.{"src/c_abi.c"}, + .files = &.{"tests/c_abi.c"}, .flags = &.{"-g"}, }); @@ -363,8 +365,8 @@ pub fn build(b: *std.Build) !void { const exe_unit_tests = b.addTest(.{ .root_module = exe_mod, + .link_libc = true, }); - exe_unit_tests.linkLibC(); llvm.link(exe); diff --git a/src/LLVM.zig b/src/LLVM.zig index b131129..0262634 100644 --- a/src/LLVM.zig +++ b/src/LLVM.zig @@ -33,6 +33,28 @@ pub const Intrinsic = enum { }; pub const Attribute = opaque { + pub const List = opaque { + pub const Options = extern struct { + function: Attribute.Function, + @"return": Attribute.Argument, + argument_pointer: [*]const Attribute.Argument, + argument_length: u64, + + comptime { + assert(@sizeOf(Options) == @sizeOf(Attribute.Function) + @sizeOf(Attribute.Argument) + @sizeOf([*]const Attribute.Argument) + @sizeOf(u64)); + } + }; + + pub fn build(context: *Context, function_attributes: Attribute.Function, return_attributes: Attribute.Argument, argument_attributes: []const Attribute.Argument, call_site: bool) *Attribute.List { + return api.llvm_attribute_list_build(context, &Options{ + .function = function_attributes, + .@"return" = return_attributes, + .argument_pointer = argument_attributes.ptr, + .argument_length = argument_attributes.len, + }, call_site); + } + }; + pub const Index = enum(c_uint) { @"return" = 0, function = 0xffff_ffff, @@ -42,6 +64,172 @@ pub const Attribute = opaque { pub const Kind = enum(c_uint) { _, }; + + pub const FramePointerKind = enum(u2) { + none = 0, + reserved = 1, + non_leaf = 2, + all = 3, + }; + + pub const ZeroCallUsedRegsKind = enum(u4) { + all = 0, + skip = 1 << 0, + used_gpr_arg = only_used | only_gpr | only_arg, + used_gpr = only_used | only_gpr, + used_arg = only_used | only_arg, + used = only_used, + all_gpr_arg = only_gpr | only_arg, + all_gpr = only_gpr, + all_arg = only_arg, + + const only_used = 1 << 1; + const only_gpr = 1 << 2; + const only_arg = 1 << 3; + }; + + pub const FPClassTest = packed struct(u10) { + s_nan: bool = false, + q_nan: bool = false, + neg_inf: bool = false, + neg_normal: bool = false, + neg_subnormal: bool = false, + neg_zero: bool = false, + pos_zero: bool = false, + pos_subnormal: bool = false, + pos_normal: bool = false, + pos_inf: bool = false, + }; + + pub const UnwindTableKind = enum(u2) { + none = 0, + sync = 1, + @"async" = 2, + + pub const default = UnwindTableKind.@"async"; + }; + + pub const Argument = extern struct { + semantic_type: *Type, + abi_type: *Type, + dereferenceable_bytes: u64, + alignment: u32, + flags: packed struct(u32) { + no_alias: bool, + non_null: bool, + no_undef: bool, + sign_extend: bool, + zero_extend: bool, + in_reg: bool, + no_fp_class: FPClassTest, + struct_return: bool, + writable: bool, + dead_on_unwind: bool, + in_alloca: bool, + dereferenceable: bool, + dereferenceable_or_null: bool, + nest: bool, + by_value: bool, + by_reference: bool, + no_capture: bool, + _: u6 = 0, + }, + + comptime { + assert(@sizeOf(Attribute.Argument) == 2 * @sizeOf(*Type) + 2 * @sizeOf(u64)); + } + }; + + pub const Function = extern struct { + prefer_vector_width: String, + stack_protector_buffer_size: String, + definition_probe_stack: String, + definition_stack_probe_size: String, + flags0: packed struct(u64) { + noreturn: bool, + cmse_ns_call: bool, + nounwind: bool, + returns_twice: bool, + cold: bool, + hot: bool, + no_duplicate: bool, + convergent: bool, + no_merge: bool, + will_return: bool, + no_caller_saved_registers: bool, + no_cf_check: bool, + no_callback: bool, + alloc_size: bool, + uniform_work_group_size: bool, + aarch64_pstate_sm_body: bool, + aarch64_pstate_sm_enabled: bool, + aarch64_pstate_sm_compatible: bool, + aarch64_preserves_za: bool, + aarch64_in_za: bool, + aarch64_out_za: bool, + aarch64_inout_za: bool, + aarch64_preserves_zt0: bool, + aarch64_in_zt0: bool, + aarch64_out_zt0: bool, + aarch64_inout_zt0: bool, + optimize_for_size: bool, + min_size: bool, + no_red_zone: bool, + indirect_tls_seg_refs: bool, + no_implicit_floats: bool, + sample_profile_suffix_elision_policy: bool, + memory_none: bool, + memory_readonly: bool, + memory_inaccessible_or_arg_memory_only: bool, + memory_arg_memory_only: bool, + strict_fp: bool, + no_inline: bool, + always_inline: bool, + guard_no_cf: bool, + // TODO: branch protection function attributes + // TODO: cpu features + + // CALL-SITE ATTRIBUTES + call_no_builtins: bool, + + // DEFINITION-SITE ATTRIBUTES + definition_frame_pointer_kind: FramePointerKind, + definition_less_precise_fpmad: bool, + definition_null_pointer_is_valid: bool, + definition_no_trapping_fp_math: bool, + definition_no_infs_fp_math: bool, + definition_no_nans_fp_math: bool, + definition_approx_func_fp_math: bool, + definition_unsafe_fp_math: bool, + definition_use_soft_float: bool, + definition_no_signed_zeroes_fp_math: bool, + definition_stack_realignment: bool, + definition_backchain: bool, + definition_split_stack: bool, + definition_speculative_load_hardening: bool, + definition_zero_call_used_registers: ZeroCallUsedRegsKind, + // TODO: denormal builtins + definition_non_lazy_bind: bool, + definition_cmse_nonsecure_entry: bool, + definition_unwind_table_kind: UnwindTableKind, + }, + flags1: packed struct(u64) { + definition_disable_tail_calls: bool, + definition_stack_protect_strong: bool, + definition_stack_protect: bool, + definition_stack_protect_req: bool, + definition_aarch64_new_za: bool, + definition_aarch64_new_zt0: bool, + definition_optimize_none: bool, + definition_naked: bool, + definition_inline_hint: bool, + reserved: u55 = 0, + }, + + comptime { + assert(@sizeOf(Attribute.Function) == 10 * @sizeOf(u64)); + } + }; }; pub const CodeModel = enum(u8) { @@ -74,19 +262,19 @@ pub const Target = opaque { /// This is ABI-compatible with C++ pub const Options = extern struct { flags0: packed struct(u64) { - unsafe_fp_math: u1, - no_infs_fp_math: u1, - no_nans_fp_math: u1, - no_trapping_fp_math: u1, - no_signed_zeroes_fp_math: u1, - approx_func_fp_match: u1, - enable_aix_extended_altivec_abi: u1, - honor_sign_dependent_rounding_fp_math: u1, - no_zeroes_in_bss: u1, - guaranteed_tail_call_optimization: u1, - stack_symbol_ordering: u1, - enable_fast_isel: u1, - enable_global_isel: u1, + unsafe_fp_math: bool, + no_infs_fp_math: bool, + no_nans_fp_math: bool, + no_trapping_fp_math: bool, + no_signed_zeroes_fp_math: bool, + approx_func_fp_math: bool, + enable_aix_extended_altivec_abi: bool, + honor_sign_dependent_rounding_fp_math: bool, + no_zeroes_in_bss: bool, + guaranteed_tail_call_optimization: bool, + stack_symbol_ordering: bool, + enable_fast_isel: bool, + enable_global_isel: bool, global_isel_abort_mode: enum(u2) { disable = 0, enable = 1, @@ -97,27 +285,27 @@ pub const Target = opaque { always = 1, never = 2, }, - use_init_array: u1, - disable_integrated_assembler: u1, - function_sections: u1, - data_sections: u1, - ignore_xcoff_visibility: u1, - xcoff_traceback_table: u1, - unique_section_names: u1, - unique_basic_block_section_names: u1, - separate_named_sections: u1, - trap_unreachable: u1, - no_trap_after_noreturn: u1, + use_init_array: bool, + disable_integrated_assembler: bool, + function_sections: bool, + data_sections: bool, + ignore_xcoff_visibility: bool, + xcoff_traceback_table: bool, + unique_section_names: bool, + unique_basic_block_section_names: bool, + separate_named_sections: bool, + trap_unreachable: bool, + no_trap_after_noreturn: bool, tls_size: u8, - emulated_tls: u1, - enable_tls_descriptors: u1, - enable_ipra: u1, - emit_stack_size_section: u1, - enable_machine_outliner: u1, - enable_machine_function_splitter: u1, - supports_default_outlining: u1, - emit_address_significance_table: u1, - bb_address_map: u1, + emulated_tls: bool, + enable_tls_descriptors: bool, + enable_ipra: bool, + emit_stack_size_section: bool, + enable_machine_outliner: bool, + enable_machine_function_splitter: bool, + supports_default_outlining: bool, + emit_address_significance_table: bool, + bb_address_map: bool, bb_sections: enum(u3) { all = 0, list = 1, @@ -125,19 +313,19 @@ pub const Target = opaque { preset = 3, none = 4, }, - emit_call_site_information: u1, - supports_debug_entry_values: u1, - enable_debug_entry_values: u1, - value_tracking_variable_locations: u1, - force_dwarf_frame_section: u1, - xray_function_index: u1, - debug_strict_dwarf: u1, - hotpatch: u1, - ppc_gen_scalar_mass_entries: u1, - jmc_instrument: u1, - enable_cfi_fixup: u1, - mis_expect: u1, - xcoff_read_only_pointers: u1, + emit_call_site_information: bool, + supports_debug_entry_values: bool, + enable_debug_entry_values: bool, + value_tracking_variable_locations: bool, + force_dwarf_frame_section: bool, + xray_function_index: bool, + debug_strict_dwarf: bool, + hotpatch: bool, + ppc_gen_scalar_mass_entries: bool, + jmc_instrument: bool, + enable_cfi_fixup: bool, + mis_expect: bool, + xcoff_read_only_pointers: bool, float_abi: enum(u2) { default = 0, soft = 1, @@ -200,56 +388,56 @@ pub const Target = opaque { return .{ .binutils_version = .{ 0, 0 }, .flags0 = .{ - .unsafe_fp_math = 0, - .no_infs_fp_math = 0, - .no_nans_fp_math = 0, - .no_trapping_fp_math = 1, - .no_signed_zeroes_fp_math = 0, - .approx_func_fp_match = 0, - .enable_aix_extended_altivec_abi = 0, - .honor_sign_dependent_rounding_fp_math = 0, - .no_zeroes_in_bss = 0, - .guaranteed_tail_call_optimization = 0, - .stack_symbol_ordering = 1, - .enable_fast_isel = 0, - .enable_global_isel = 0, + .unsafe_fp_math = false, + .no_infs_fp_math = false, + .no_nans_fp_math = false, + .no_trapping_fp_math = true, + .no_signed_zeroes_fp_math = false, + .approx_func_fp_math = false, + .enable_aix_extended_altivec_abi = false, + .honor_sign_dependent_rounding_fp_math = false, + .no_zeroes_in_bss = false, + .guaranteed_tail_call_optimization = false, + .stack_symbol_ordering = true, + .enable_fast_isel = false, + .enable_global_isel = false, .global_isel_abort_mode = .enable, .swift_async_frame_pointer = .always, - .use_init_array = 0, - .disable_integrated_assembler = 0, - .function_sections = 0, - .data_sections = 0, - .ignore_xcoff_visibility = 0, - .xcoff_traceback_table = 1, - .unique_section_names = 1, - .unique_basic_block_section_names = 0, - .separate_named_sections = 0, - .trap_unreachable = 0, - .no_trap_after_noreturn = 0, + .use_init_array = false, + .disable_integrated_assembler = false, + .function_sections = false, + .data_sections = false, + .ignore_xcoff_visibility = false, + .xcoff_traceback_table = true, + .unique_section_names = true, + .unique_basic_block_section_names = false, + .separate_named_sections = false, + .trap_unreachable = false, + .no_trap_after_noreturn = false, .tls_size = 0, - .emulated_tls = 0, - .enable_tls_descriptors = 0, - .enable_ipra = 0, - .emit_stack_size_section = 0, - .enable_machine_outliner = 0, - .enable_machine_function_splitter = 0, - .supports_default_outlining = 0, - .emit_address_significance_table = 0, - .bb_address_map = 0, + .emulated_tls = false, + .enable_tls_descriptors = false, + .enable_ipra = false, + .emit_stack_size_section = false, + .enable_machine_outliner = false, + .enable_machine_function_splitter = false, + .supports_default_outlining = false, + .emit_address_significance_table = false, + .bb_address_map = false, .bb_sections = .none, - .emit_call_site_information = 0, - .supports_debug_entry_values = 0, - .enable_debug_entry_values = 0, - .value_tracking_variable_locations = 0, - .force_dwarf_frame_section = 0, - .xray_function_index = 1, - .debug_strict_dwarf = 0, - .hotpatch = 0, - .ppc_gen_scalar_mass_entries = 0, - .jmc_instrument = 0, - .enable_cfi_fixup = 0, - .mis_expect = 0, - .xcoff_read_only_pointers = 0, + .emit_call_site_information = false, + .supports_debug_entry_values = false, + .enable_debug_entry_values = false, + .value_tracking_variable_locations = false, + .force_dwarf_frame_section = false, + .xray_function_index = true, + .debug_strict_dwarf = false, + .hotpatch = false, + .ppc_gen_scalar_mass_entries = false, + .jmc_instrument = false, + .enable_cfi_fixup = false, + .mis_expect = false, + .xcoff_read_only_pointers = false, .float_abi = .default, .thread_model = .posix, }, @@ -271,28 +459,28 @@ pub const Target = opaque { .integrated_assembler_search_path_pointer = null, .integrated_assembler_search_path_count = 0, .flags = .{ - .relax_all = 0, - .no_exec_stack = 0, - .fatal_warnings = 0, - .no_warn = 0, - .no_deprecated_warn = 0, - .no_type_check = 0, - .save_temp_labels = 0, - .incremental_linker_compatible = 0, - .fdpic = 0, - .show_mc_encoding = 0, - .show_mc_inst = 0, - .asm_verbose = 0, - .preserve_asm_comments = 1, - .dwarf64 = 0, - .crel = 0, - .x86_relax_relocations = 1, - .x86_sse2_avx = 0, + .relax_all = false, + .no_exec_stack = false, + .fatal_warnings = false, + .no_warn = false, + .no_deprecated_warn = false, + .no_type_check = false, + .save_temp_labels = false, + .incremental_linker_compatible = false, + .fdpic = false, + .show_mc_encoding = false, + .show_mc_inst = false, + .asm_verbose = false, + .preserve_asm_comments = true, + .dwarf64 = false, + .crel = false, + .x86_relax_relocations = true, + .x86_sse2_avx = false, .emit_dwarf_unwind = .default, .use_dwarf_directory = .default, .debug_compression_type = .none, - .emit_compact_unwind_non_canonical = 0, - .ppc_use_full_register_names = 0, + .emit_compact_unwind_non_canonical = false, + .ppc_use_full_register_names = false, }, }, }; @@ -337,23 +525,23 @@ pub const MCTargetOptions = extern struct { integrated_assembler_search_path_pointer: ?[*]const String, integrated_assembler_search_path_count: u64, flags: packed struct(u32) { - relax_all: u1, - no_exec_stack: u1, - fatal_warnings: u1, - no_warn: u1, - no_deprecated_warn: u1, - no_type_check: u1, - save_temp_labels: u1, - incremental_linker_compatible: u1, - fdpic: u1, - show_mc_encoding: u1, - show_mc_inst: u1, - asm_verbose: u1, - preserve_asm_comments: u1, - dwarf64: u1, - crel: u1, - x86_relax_relocations: u1, - x86_sse2_avx: u1, + relax_all: bool, + no_exec_stack: bool, + fatal_warnings: bool, + no_warn: bool, + no_deprecated_warn: bool, + no_type_check: bool, + save_temp_labels: bool, + incremental_linker_compatible: bool, + fdpic: bool, + show_mc_encoding: bool, + show_mc_inst: bool, + asm_verbose: bool, + preserve_asm_comments: bool, + dwarf64: bool, + crel: bool, + x86_relax_relocations: bool, + x86_sse2_avx: bool, emit_dwarf_unwind: enum(u2) { always = 0, no_compact_unwind = 1, @@ -369,8 +557,8 @@ pub const MCTargetOptions = extern struct { zlib = 1, zstd = 2, }, - emit_compact_unwind_non_canonical: u1, - ppc_use_full_register_names: u1, + emit_compact_unwind_non_canonical: bool, + ppc_use_full_register_names: bool, reserved: PaddingType = 0, }, @@ -410,16 +598,16 @@ pub const OptimizationLevel = enum(u3) { /// This is ABI-compatible with C++ pub const OptimizationPipelineOptions = packed struct(u64) { optimization_level: OptimizationLevel, - debug_info: u1, - loop_unrolling: u1, - loop_interleaving: u1, - loop_vectorization: u1, - slp_vectorization: u1, - merge_functions: u1, - call_graph_profile: u1, - unified_lto: u1, - assignment_tracking: u1, - verify_module: u1, + debug_info: bool, + loop_unrolling: bool, + loop_interleaving: bool, + loop_vectorization: bool, + slp_vectorization: bool, + merge_functions: bool, + call_graph_profile: bool, + unified_lto: bool, + assignment_tracking: bool, + verify_module: bool, reserved: PaddingType = 0, const padding_bit_count = 51; @@ -437,10 +625,10 @@ pub const OptimizationPipelineOptions = packed struct(u64) { const Create = packed struct { optimization_level: OptimizationLevel, - debug_info: u1, + debug_info: bool, }; pub fn default(create: Create) OptimizationPipelineOptions { - const pref_speed = @intFromBool(create.optimization_level.prefers_speed()); + const pref_speed = create.optimization_level.prefers_speed(); return .{ .optimization_level = create.optimization_level, .debug_info = create.debug_info, @@ -449,10 +637,10 @@ pub const OptimizationPipelineOptions = packed struct(u64) { .loop_vectorization = pref_speed, .slp_vectorization = pref_speed, .merge_functions = pref_speed, - .call_graph_profile = 0, - .unified_lto = 0, + .call_graph_profile = false, + .unified_lto = false, .assignment_tracking = create.debug_info, - .verify_module = @intFromBool(lib.optimization_mode == .ReleaseSafe or lib.optimization_mode == .Debug), + .verify_module = lib.optimization_mode == .ReleaseSafe or lib.optimization_mode == .Debug, }; } }; @@ -467,8 +655,8 @@ pub const CodeGenerationPipelineOptions = extern struct { object_file = 1, null = 2, }, - optimize_when_possible: u1, - verify_module: u1, + optimize_when_possible: bool, + verify_module: bool, reserved: PaddingType = 0, }, @@ -515,7 +703,7 @@ pub const Context = opaque { pub const create_builder = api.LLVMCreateBuilderInContext; - pub fn create_basic_block(context: *Context, name: []const u8, parent: *Function) *BasicBlock { + pub fn create_basic_block(context: *Context, name: []const u8, parent: ?*Function) *BasicBlock { return api.llvm_context_create_basic_block(context, String.from_slice(name), parent); } @@ -541,16 +729,30 @@ pub const Context = opaque { return api.LLVMIntrinsicGetType(context, intrinsic_id, parameter_types.ptr, parameter_types.len); } - pub fn create_string_attribute(context: *Context, attribute_name: []const u8, attribute_value: []const u8) *Attribute { - return api.LLVMCreateStringAttribute(context, attribute_name.ptr, @intCast(attribute_name.len), attribute_value.ptr, @intCast(attribute_value.len)); + pub fn get_anonymous_constant_struct(context: *Context, constant_values: []const *Constant, is_packed: bool) *Constant { + return api.LLVMConstStructInContext(context, constant_values.ptr, @intCast(constant_values.len), @intFromBool(is_packed)); } - pub const create_enum_attribute = api.LLVMCreateEnumAttribute; - pub const create_type_attribute = api.LLVMCreateTypeAttribute; + // pub fn create_string_attribute(context: *Context, attribute_name: []const u8, attribute_value: []const u8) *Attribute { + // return api.LLVMCreateStringAttribute(context, attribute_name.ptr, @intCast(attribute_name.len), attribute_value.ptr, @intCast(attribute_value.len)); + // } + // + // pub const create_enum_attribute = api.LLVMCreateEnumAttribute; + // pub const create_type_attribute = api.LLVMCreateTypeAttribute; }; pub const BasicBlock = opaque { + pub const delete = api.llvm_basic_block_delete; + pub const erase_from_parent = api.LLVMDeleteBasicBlock; pub const get_terminator = api.LLVMGetBasicBlockTerminator; + pub const is_empty = api.llvm_basic_block_is_empty; + pub const user_begin = api.llvm_basic_block_user_begin; + pub const get_next = api.LLVMGetNextBasicBlock; + pub const get_parent = api.LLVMGetBasicBlockParent; + + pub fn to_value(basic_block: *BasicBlock) *Value { + return @ptrCast(basic_block); + } }; pub const Module = opaque { @@ -610,6 +812,7 @@ pub const VerifyResult = struct { pub const Builder = opaque { pub const position_at_end = api.LLVMPositionBuilderAtEnd; + pub const clear_insertion_position = api.LLVMClearInsertionPosition; pub const get_insert_block = api.LLVMGetInsertBlock; pub const create_ret = api.LLVMBuildRet; @@ -690,6 +893,7 @@ pub const Builder = opaque { return api.LLVMBuildICmp(builder, predicate, left, right, ""); } + pub const create_branch = api.LLVMBuildBr; pub const create_conditional_branch = api.LLVMBuildCondBr; pub fn create_call(builder: *Builder, function_type: *Type.Function, function_value: *Value, arguments: []const *Value) *Value { @@ -708,6 +912,10 @@ pub const Builder = opaque { return api.LLVMBuildInsertValue(builder, aggregate, element, index, ""); } + pub fn create_extract_value(builder: *Builder, aggregate: *Value, index: c_uint) *Value { + return api.LLVMBuildExtractValue(builder, aggregate, index, ""); + } + pub fn create_zero_extend(builder: *Builder, value: *Value, destination_type: *Type) *Value { return api.LLVMBuildZExt(builder, value, destination_type, ""); } @@ -731,6 +939,12 @@ pub const Builder = opaque { pub const create_unreachable = api.LLVMBuildUnreachable; pub const create_memcpy = api.LLVMBuildMemCpy; + + pub fn create_vaarg(builder: *Builder, va_list: *Value, arg_type: *Type) *Value { + return api.LLVMBuildVAArg(builder, va_list, arg_type, ""); + } + + pub const find_return_value_dominating_store = api.llvm_find_return_value_dominating_store; }; pub const GlobalValue = opaque { @@ -739,6 +953,10 @@ pub const GlobalValue = opaque { pub const GlobalVariable = opaque { pub const add_debug_info = api.llvm_global_variable_add_debug_info; + pub const get_initializer = api.LLVMGetInitializer; + pub const set_initializer = api.LLVMSetInitializer; + pub const erase_from_parent = api.LLVMDeleteGlobal; + pub const delete = api.llvm_global_variable_delete; pub fn to_value(global_variable: *GlobalVariable) *Value { return @ptrCast(global_variable); } @@ -776,7 +994,9 @@ pub const Function = opaque { pub const get_arguments = api.LLVMGetParams; - pub const add_attribute = api.LLVMAddAttributeAtIndex; + pub const set_attributes = api.llvm_function_set_attributes; + pub const get_last_basic_block = api.LLVMGetLastBasicBlock; + pub const append_basic_block = api.LLVMAppendExistingBasicBlock; }; pub const Constant = opaque { @@ -784,17 +1004,19 @@ pub const Constant = opaque { return @ptrCast(constant); } + pub fn to_global_variable(constant: *Constant) *GlobalVariable { + assert(constant.to_value().get_kind() == .global_variable); + return @ptrCast(constant); + } + pub const Integer = opaque { pub fn to_value(constant: *Constant.Integer) *Value { return @ptrCast(constant); } }; - pub const Array = opaque { - pub fn to_value(constant: *Constant.Array) *Value { - return @ptrCast(constant); - } - }; + pub const get_sign_extended_value = api.LLVMConstIntGetSExtValue; + pub const get_zero_extended_value = api.LLVMConstIntGetZExtValue; }; pub const Argument = opaque { @@ -808,7 +1030,20 @@ pub const Value = opaque { pub const get_kind = api.LLVMGetValueKind; pub const set_alignment = api.LLVMSetAlignment; + pub fn set_name(value: *Value, name: []const u8) void { + api.LLVMSetValueName2(value, name.ptr, name.len); + } + + // The operand API is from the User class, but it would work nonetheless + pub const get_operand = api.LLVMGetOperand; + pub const is_call_instruction = api.LLVMIsACallInst; + pub const use_empty = api.llvm_value_use_empty; + pub const has_one_use = api.llvm_value_has_one_use; + + pub const replace_all_uses_with = api.LLVMReplaceAllUsesWith; + + pub const to_branch = api.llvm_value_to_branch; pub fn is_constant(value: *Value) bool { return api.LLVMIsConstant(value) != 0; @@ -829,19 +1064,9 @@ pub const Value = opaque { return @ptrCast(value); } - pub fn get_calling_convention(value: *Value) CallingConvention { - const kind = value.get_kind(); - switch (kind) { - .Instruction => { - const call = value.to_instruction().to_call(); - return call.get_calling_convention(); - }, - .Function => { - const function = value.to_function(); - return function.get_calling_convention(); - }, - else => unreachable, - } + pub fn to_global_variable(value: *Value) *GlobalVariable { + assert(value.get_kind() == .GlobalVariable); + return @ptrCast(value); } pub const Kind = enum(c_uint) { @@ -884,14 +1109,35 @@ pub const Instruction = opaque { pub fn to_value(instruction: *Instruction) *Value { return @ptrCast(instruction); } - pub fn to_call(instruction: *Instruction) *Instruction.Call { - assert(instruction.to_value().is_call_instruction() != null); + + pub fn to_call_base(instruction: *Instruction) *Instruction.CallBase { + assert(instruction.is_call_base()); return @ptrCast(instruction); } - pub const Call = opaque { + + pub const is_call_base = api.llvm_instruction_is_call_base; + + pub const erase_from_parent = api.LLVMInstructionEraseFromParent; + pub const get_parent = api.LLVMGetInstructionParent; + + pub const Branch = opaque { + pub const is_conditional = api.LLVMIsConditional; + pub const get_successor = api.LLVMGetSuccessor; + + pub fn to_instruction(branch: *Branch) *Instruction { + return @ptrCast(branch); + } + }; + + pub const CallBase = opaque { pub const set_calling_convention = api.LLVMSetInstructionCallConv; - pub const get_calling_convention = api.LLVMGetInstructionCallConv; - pub const add_attribute = api.LLVMAddCallSiteAttribute; + pub const set_attributes = api.llvm_call_base_set_attributes; + }; + + pub const Store = opaque { + pub fn to_instruction(store: *Store) *Instruction { + return @ptrCast(store); + } }; }; @@ -999,6 +1245,10 @@ pub const DI = struct { pub const Record = opaque {}; pub const Type = opaque { + // TODO: typecheck + pub fn to_subroutine(ty: *DI.Type) *Subroutine { + return @ptrCast(ty); + } pub const Subroutine = opaque { pub fn to_type(subroutine: *Subroutine) *DI.Type { return @ptrCast(subroutine); @@ -1126,6 +1376,10 @@ pub const Type = opaque { }; pub const Struct = opaque { + pub fn get_constant(struct_type: *Type.Struct, constant_values: []const *Constant) *Constant { + return api.LLVMConstNamedStruct(struct_type, constant_values.ptr, @intCast(constant_values.len)); + } + pub fn to_type(struct_type: *Type.Struct) *Type { return @ptrCast(struct_type); } @@ -1152,9 +1406,12 @@ pub const Type = opaque { return api.LLVMArrayType2(element_type, element_count); } - pub fn get_constant_array(element_type: *Type, values: []const *Constant) *Constant.Array { + pub fn get_constant_array(element_type: *Type, values: []const *Constant) *Constant { return api.LLVMConstArray2(element_type, values.ptr, values.len); } + + pub const get_size = api.LLVMSizeOf; + pub const get_alignment = api.LLVMAlignOf; }; pub const Dwarf = struct { @@ -1285,9 +1542,9 @@ pub fn lookup_intrinsic_id(name: []const u8) Intrinsic.Id { return api.LLVMLookupIntrinsicID(name.ptr, name.len); } -pub fn lookup_attribute_kind(name: []const u8) Attribute.Kind { - return api.LLVMGetEnumAttributeKindForName(name.ptr, name.len); -} +// pub fn lookup_attribute_kind(name: []const u8) Attribute.Kind { +// return api.LLVMGetEnumAttributeKindForName(name.ptr, name.len); +// } pub const IntPredicate = enum(c_int) { eq = 32, @@ -1429,14 +1686,14 @@ pub const ObjectGenerate = struct { path: []const u8, optimization_level: ?OptimizationLevel, debug_info: bool, - optimize_when_possible: u1, + optimize_when_possible: bool, }; pub fn object_generate(module: *Module, target_machine: *Target.Machine, generate: ObjectGenerate) CodeGenerationPipelineResult { module.set_target(target_machine); if (generate.optimization_level) |optimization_level| { - module.run_optimization_pipeline(target_machine, OptimizationPipelineOptions.default(.{ .optimization_level = optimization_level, .debug_info = @intFromBool(generate.debug_info) })); + module.run_optimization_pipeline(target_machine, OptimizationPipelineOptions.default(.{ .optimization_level = optimization_level, .debug_info = generate.debug_info })); } // const mod_string = module.to_string(); @@ -1448,7 +1705,7 @@ pub fn object_generate(module: *Module, target_machine: *Target.Machine, generat .flags = .{ .code_generation_file_type = .object_file, .optimize_when_possible = generate.optimize_when_possible, - .verify_module = 1, + .verify_module = true, }, }); diff --git a/src/converter.zig b/src/converter.zig index bbe7fbf..bfb9d4b 100644 --- a/src/converter.zig +++ b/src/converter.zig @@ -8,14 +8,6 @@ test { _ = @import("converter_test.zig"); } -const LexerResult = struct { - token: Token, - offset: u32, - character_count: u32, -}; - -const Token = enum {}; - const left_bracket = '['; const right_bracket = ']'; const left_brace = '{'; @@ -23,6 +15,8 @@ const right_brace = '}'; const left_parenthesis = '('; const right_parenthesis = ')'; +const max_argument_count = 64; + fn array_type_name(arena: *Arena, element_count: u64, noalias array: *const ArrayType) [:0]const u8 { var buffer: [256]u8 = undefined; var i: usize = 0; @@ -82,20 +76,34 @@ const FunctionKeyword = enum { }; const CallingConvention = enum { - unknown, c, pub fn to_llvm(calling_convention: CallingConvention) llvm.CallingConvention { return switch (calling_convention) { - .unknown => .fast, .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 = .{}, @@ -103,12 +111,417 @@ const Module = struct { debug_tag: c_uint = 0, void_type: *Type = undefined, noreturn_type: *Type = undefined, + va_list_type: ?*Type = null, + void_value: *Value = undefined, anonymous_pair_type_buffer: [64]u32 = undefined, - pointer_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, + 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 }; + } + + pub fn build_attribute_list(module: *Module, function_type: *const FunctionType, function_attributes: Function.Attributes, call_site: bool) *llvm.Attribute.List { + const return_attributes = llvm.Attribute.Argument{ + .semantic_type = function_type.return_type_abi.semantic_type.llvm.handle, + .abi_type = function_type.abi_return_type.llvm.handle, + .dereferenceable_bytes = 0, + .alignment = 0, + .flags = .{ + .no_alias = false, + .non_null = false, + .no_undef = false, + .sign_extend = function_type.return_type_abi.flags.kind == .extend and function_type.return_type_abi.flags.sign_extension, + .zero_extend = function_type.return_type_abi.flags.kind == .extend and !function_type.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..function_type.abi_argument_types.len]; + + if (function_type.return_type_abi.flags.kind == .indirect) { + const abi_index = @intFromBool(function_type.return_type_abi.flags.sret_after_this); + const argument_attribute = &argument_attributes[abi_index]; + argument_attribute.* = .{ + .semantic_type = function_type.return_type_abi.semantic_type.llvm.handle, + .abi_type = function_type.abi_argument_types[abi_index].llvm.handle, + .dereferenceable_bytes = 0, + .alignment = function_type.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 = function_type.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 (function_type.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 = function_type.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 = function_type.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 = function_attributes.inline_behavior == .no_inline, + .always_inline = function_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 = !call_site and function_attributes.naked, + .definition_inline_hint = !call_site and function_attributes.inline_behavior == .inline_hint, + }, + }, return_attributes, argument_attributes, 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 = 8, + .byte_size = 24, + .bit_size = 24 * 8, + .fields = fields, + }, + }, + }); + module.va_list_type = result; + return result; + } + } + const AllocaOptions = struct { type: *Type, name: []const u8 = "", @@ -116,12 +529,36 @@ const Module = struct { }; pub fn create_alloca(module: *Module, options: AllocaOptions) *llvm.Value { - const alignment: c_uint = if (options.alignment) |a| a else @intCast(options.type.get_byte_alignment()); - const v = module.llvm.builder.create_alloca(options.type.llvm.handle, options.name); + 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, @@ -129,27 +566,52 @@ const Module = struct { }; pub fn create_load(module: *Module, options: LoadOptions) *llvm.Value { - const alignment: c_uint = if (options.alignment) |a| a else @intCast(options.type.get_byte_alignment()); - const v = module.llvm.builder.create_load(options.type.llvm.handle, options.value); - v.set_alignment(alignment); - return v; + 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: *llvm.Value, - destination: *llvm.Value, - alignment: c_uint, + 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 alignment = options.alignment; - const v = module.llvm.builder.create_store(options.source, options.destination); + 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(); + return module.llvm.builder.get_insert_block() orelse unreachable; } const LLVM = struct { @@ -161,40 +623,19 @@ const Module = struct { file: *llvm.DI.File, pointer_type: *llvm.Type, intrinsic_table: IntrinsicTable, - attribute_table: AttributeTable, - attribute_kind_table: AttributeKindTable, const IntrinsicTable = struct { trap: llvm.Intrinsic.Id, - }; - - const AttributeTable = struct { - frame_pointer_all: *llvm.Attribute, - ssp: *llvm.Attribute, - @"stack-protector-buffer-size": *llvm.Attribute, - @"no-trapping-math": *llvm.Attribute, - alwaysinline: *llvm.Attribute, - @"noinline": *llvm.Attribute, - noreturn: *llvm.Attribute, - nounwind: *llvm.Attribute, - naked: *llvm.Attribute, - signext: *llvm.Attribute, - zeroext: *llvm.Attribute, - inreg: *llvm.Attribute, - @"noalias": *llvm.Attribute, - }; - - const AttributeKindTable = struct { - @"align": llvm.Attribute.Kind, - byval: llvm.Attribute.Kind, - sret: llvm.Attribute.Kind, + 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.@"struct".fields; + 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; } @@ -219,7 +660,7 @@ const Module = struct { const pair_type = module.types.add(.{ .name = "", .bb = .{ - .@"struct" = .{ + .structure = .{ .bit_alignment = byte_alignment * 8, .byte_alignment = byte_alignment, .byte_size = byte_size, @@ -265,6 +706,39 @@ const Module = struct { } } + 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(); @@ -294,6 +768,7 @@ const Module = struct { const default_address_space = 0; module.* = .{ .arena = arena, + .target = options.target, .llvm = .{ .global_scope = global_scope, .file = file, @@ -304,26 +779,9 @@ const Module = struct { .pointer_type = context.get_pointer_type(default_address_space).to_type(), .intrinsic_table = .{ .trap = llvm.lookup_intrinsic_id("llvm.trap"), - }, - .attribute_table = .{ - .frame_pointer_all = context.create_string_attribute("frame-pointer", "all"), - .ssp = context.create_enum_attribute(llvm.lookup_attribute_kind("ssp"), 0), - .@"stack-protector-buffer-size" = context.create_string_attribute("stack-protector-buffer-size", "8"), - .@"no-trapping-math" = context.create_string_attribute("no-trapping-math", "true"), - .@"noinline" = context.create_enum_attribute(llvm.lookup_attribute_kind("noinline"), 0), - .alwaysinline = context.create_enum_attribute(llvm.lookup_attribute_kind("alwaysinline"), 0), - .noreturn = context.create_enum_attribute(llvm.lookup_attribute_kind("noreturn"), 0), - .nounwind = context.create_enum_attribute(llvm.lookup_attribute_kind("nounwind"), 0), - .naked = context.create_enum_attribute(llvm.lookup_attribute_kind("naked"), 0), - .signext = context.create_enum_attribute(llvm.lookup_attribute_kind("signext"), 0), - .zeroext = context.create_enum_attribute(llvm.lookup_attribute_kind("zeroext"), 0), - .inreg = context.create_enum_attribute(llvm.lookup_attribute_kind("inreg"), 0), - .@"noalias" = context.create_enum_attribute(llvm.lookup_attribute_kind("noalias"), 0), - }, - .attribute_kind_table = .{ - .byval = llvm.lookup_attribute_kind("byval"), - .sret = llvm.lookup_attribute_kind("sret"), - .@"align" = llvm.lookup_attribute_kind("align"), + .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, @@ -401,11 +859,13 @@ const Module = struct { .bb = .noreturn, }); - const infer_or_ignore_value = module.values.add(); - infer_or_ignore_value.* = .{ + module.void_value = module.values.add(); + module.void_value.* = .{ .llvm = undefined, .bb = .infer_or_ignore, - .type = undefined, + .type = module.void_type, + .lvalue = false, + .dereference_to_assign = false, }; return module; @@ -417,25 +877,39 @@ const Module = struct { defer arena.restore(position); } - pub fn get_pointer_type(module: *Module, element_type: *Type) *Type { + 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 pointer_type = &all_types[pointer_type_index]; - if (pointer_type.bb.pointer == element_type) { - break pointer_type; + 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 (element_type.name) |name| module.arena.join_string(&.{ "&", name }) else "unknownptr"; + 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(element_type.llvm.debug, 64, 64, 0, pointer_name).to_type() else undefined, + .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 = element_type, + .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; }; @@ -475,6 +949,9 @@ fn llvm_add_argument_attribute(value: *llvm.Value, attribute: *llvm.Attribute, i } 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, @@ -486,6 +963,7 @@ pub const Function = struct { default, always_inline, no_inline, + inline_hint, } = .default, naked: bool = false, }; @@ -503,7 +981,9 @@ pub const Value = struct { global, argument, instruction, - struct_initialization, + struct_initialization: struct { + is_constant: bool, + }, bits_initialization, infer_or_ignore, constant_integer: ConstantInteger, @@ -512,6 +992,8 @@ pub const Value = struct { }, type: *Type, llvm: *llvm.Value, + lvalue: bool, + dereference_to_assign: bool, const Array = struct { buffer: [1024]Value = undefined, @@ -526,7 +1008,8 @@ pub const Value = struct { pub fn is_constant(value: *Value) bool { return switch (value.bb) { - .constant_integer => true, + .constant_integer, .constant_array => true, + .struct_initialization => |si| si.is_constant, else => @trap(), }; } @@ -541,22 +1024,12 @@ const Field = struct { const FunctionType = struct { return_type_abi: Abi.Information, - semantic_return_type: *Type, - semantic_argument_types: [*]const *Type, - argument_type_abis: [*]const Abi.Information, - abi_argument_types: [*]const *Type, + argument_type_abis: []const Abi.Information, abi_return_type: *Type, - semantic_argument_count: u32, - abi_argument_count: u32, + abi_argument_types: []const *Type, calling_convention: CallingConvention, - - fn get_semantic_argument_types(function_type: *const FunctionType) []const *Type { - return function_type.semantic_argument_types[0..function_type.semantic_argument_count]; - } - - fn get_argument_type_abis(function_type: *const FunctionType) []const Abi.Information { - return function_type.argument_type_abis[0..function_type.semantic_argument_count]; - } + // 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]; @@ -567,8 +1040,8 @@ const StructType = struct { fields: []const Field, bit_size: u64, byte_size: u64, - bit_alignment: u64, - byte_alignment: u64, + bit_alignment: u32, + byte_alignment: u32, }; const Bits = struct { @@ -581,74 +1054,178 @@ pub const ArrayType = struct { 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 {}; + +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: struct { - bit_count: u32, - signed: bool, - }, - @"struct": StructType, + integer: IntegerType, + float: FloatType, + structure: StructType, bits: Bits, function: FunctionType, array: ArrayType, - pointer: *Type, + pointer: PointerType, + enumerator: Enumerator, + vector, }; - pub fn is_aggregate(ty: *const Type) bool { + 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) { - .@"struct" => true, + .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 => .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, - .@"struct" => |struct_type| struct_type.bit_size, + .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 => @trap(), + .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), - .@"struct" => |struct_type| struct_type.byte_size, + .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) u64 { + pub fn get_bit_alignment(ty: *const Type) u32 { return switch (ty.bb) { .integer => |integer| integer.bit_count, - .@"struct" => |struct_type| struct_type.bit_alignment, + .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) u64 { + pub fn get_byte_alignment(ty: *const Type) u32 { return switch (ty.bb) { - .integer => |integer| @divExact(@max(8, lib.next_power_of_two(integer.bit_count)), 8), - .@"struct" => |struct_type| struct_type.byte_alignment, + .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(), - .void, .forward_declaration, .function, .noreturn => unreachable, + .function => 1, + .void, .forward_declaration, .noreturn => unreachable, .array => |*array| array.element_type.get_byte_alignment(), .pointer => 8, + .enumerator => @trap(), + .float => @trap(), + .vector => @trap(), }; } @@ -848,7 +1425,9 @@ const Converter = struct { const element_type = converter.parse_type(module); - return module.get_pointer_type(element_type); + return module.get_pointer_type(.{ + .type = element_type, + }); }, else => @trap(), } @@ -985,6 +1564,8 @@ const Converter = struct { .signed = sign, }, }, + .lvalue = false, + .dereference_to_assign = false, }; return integer_value; } @@ -995,83 +1576,51 @@ const Converter = struct { } } - fn emit_direct_coerce(module: *Module, ty: *Type, original_value: *Value) *llvm.Value { - const source_type = original_value.type; - const alloca = module.create_alloca(.{ .type = source_type }); - _ = module.create_store(.{ .source = original_value.llvm, .destination = alloca, .alignment = @intCast(source_type.get_byte_alignment()) }); - - const target_type = ty; - const target_size = ty.get_byte_size(); - const target_alignment = ty.get_byte_alignment(); - const source_size = source_type.get_byte_size(); - const source_alignment = source_type.get_byte_alignment(); - const target_is_scalable_vector_type = false; - const source_is_scalable_vector_type = false; - if (source_size >= target_size and !source_is_scalable_vector_type and !target_is_scalable_vector_type) { - _ = source_alignment; - _ = target_alignment; - return module.create_load(.{ .type = target_type, .value = alloca }); - } else { - @trap(); - // const alignment = @max(target_alignment, source_alignment); - // const temporal = emit_local_symbol(analyzer, thread, .{ - // .name = 0, - // .initial_value = null, - // .type = args.coerced_type, - // .line = 0, - // .column = 0, - // }); - // emit_memcpy(analyzer, thread, .{ - // .destination = &temporal.instruction.value, - // .source = &local.instruction.value, - // .destination_alignment = .{ - // .alignment = alignment, - // }, - // .source_alignment = .{ - // .alignment = source_alignment, - // }, - // .size = source_size, - // .line = 0, - // .column = 0, - // .scope = analyzer.current_scope, - // }); - // - // const load = emit_load(analyzer, thread, .{ - // .value = &temporal.instruction.value, - // .type = args.coerced_type, - // .line = 0, - // .column = 0, - // .scope = analyzer.current_scope, - // }); - // return &load.instruction.value; - } - } - fn parse_call(noalias converter: *Converter, noalias module: *Module, may_be_callable: *Value) *Value { - const llvm_callable = switch (may_be_callable.type.bb) { - .function => may_be_callable.llvm, - .pointer => module.create_load(.{ .type = may_be_callable.type, .value = may_be_callable.llvm }), + 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 raw_function_type = switch (may_be_callable.type.bb) { - .function => may_be_callable.type, - .pointer => may_be_callable.type.bb.pointer, - else => @trap(), - }; 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: [64]*llvm.Value = undefined; - var abi_argument_count: usize = 0; + var llvm_abi_argument_value_buffer: [max_argument_count]*llvm.Value = undefined; + var abi_argument_count: u32 = 0; + _ = &abi_argument_count; + var semantic_argument_count: u32 = 0; + const function_semantic_argument_count = function_type.argument_type_abis.len; - const llvm_indirect_return_value: *llvm.Value = switch (function_type.return_type_abi.kind) { - .indirect => |indirect| blk: { - if (indirect.alignment <= indirect.type.get_byte_alignment()) { - const alloca = module.create_alloca(.{ .type = indirect.type }); - llvm_abi_argument_value_buffer[abi_argument_count] = alloca; + // 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_count += 1; - break :blk alloca; + break :blk temporal_alloca; + } else if (function_type.return_type_abi.flags.kind == .in_alloca) { + @trap(); } else { @trap(); } @@ -1079,8 +1628,8 @@ const Converter = struct { else => undefined, }; - var semantic_argument_count: usize = 0; - const function_semantic_argument_count = function_type.semantic_argument_count; + // var available_registers = function_type.available_registers; + // _ = &available_registers; while (true) : (semantic_argument_count += 1) { converter.skip_space(); @@ -1090,125 +1639,271 @@ const Converter = struct { } const semantic_argument_index = semantic_argument_count; - if (semantic_argument_index >= function_semantic_argument_count) { - converter.report_error(); - } - const semantic_argument_value = converter.parse_value(module, function_type.semantic_argument_types[semantic_argument_index], .value); + if (semantic_argument_index < function_semantic_argument_count or function_type.is_var_args) { + const expected_semantic_argument_type: ?*Type = if (semantic_argument_index < function_semantic_argument_count) 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(','); + _ = converter.consume_character_if_match(','); - const argument_abi = function_type.argument_type_abis[semantic_argument_index]; - const semantic_argument_type = function_type.semantic_argument_types[semantic_argument_index]; + const semantic_argument_type = if (semantic_argument_index < function_semantic_argument_count) function_type.argument_type_abis[semantic_argument_index].semantic_type else semantic_argument_value.type; + const argument_abi = if (semantic_argument_index < function_semantic_argument_count) function_type.argument_type_abis[semantic_argument_index] else @trap(); // Abi.SystemV.abi_from_semantic_type(module, &available_registers, semantic_argument_type); - switch (argument_abi.kind) { - .direct => { - llvm_abi_argument_value_buffer[abi_argument_count] = semantic_argument_value.llvm; - abi_argument_count += 1; - }, - .ignore => unreachable, - .direct_pair => |pair| { - const pair_struct_type = module.get_anonymous_struct_pair(pair); + if (argument_abi.padding.type) |padding_type| { + _ = padding_type; + @trap(); + } - if (pair_struct_type == semantic_argument_type) { - @trap(); - } else { - const alloca_type = if (semantic_argument_type.get_byte_alignment() < pair_struct_type.get_byte_alignment()) pair_struct_type else semantic_argument_type; - const alloca = module.create_alloca(.{ .type = alloca_type }); - _ = module.create_store(.{ .source = semantic_argument_value.llvm, .destination = alloca, .alignment = @intCast(alloca_type.get_byte_alignment()) }); - for (0..2) |i| { - const gep = module.llvm.builder.create_struct_gep(pair_struct_type.llvm.handle.to_struct(), alloca, @intCast(i)); - const load = module.create_load(.{ .type = pair[i], .value = gep }); - llvm_abi_argument_value_buffer[abi_argument_count] = load; + const argument_abi_start = argument_abi.abi_start; + const argument_abi_count = argument_abi.abi_count; + _ = argument_abi_start; + _ = argument_abi_count; + + 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 != 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 + llvm_abi_argument_value_buffer[abi_argument_count] = v.llvm; abi_argument_count += 1; - } - } - }, - .direct_coerce => |coerced_type| { - const v = emit_direct_coerce(module, coerced_type, semantic_argument_value); - llvm_abi_argument_value_buffer[abi_argument_count] = v; - abi_argument_count += 1; - }, - .direct_coerce_int => unreachable, - .expand_coerce => unreachable, - .direct_split_struct_i32 => unreachable, - .indirect => |indirect| { - assert(semantic_argument_type == indirect.type); - const direct = false; // TODO: compute properly + } 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; + } + } - if (direct) { @trap(); - } else { - const alloca = module.create_alloca(.{ .type = semantic_argument_type }); - _ = module.create_store(.{ .source = semantic_argument_value.llvm, .destination = alloca, .alignment = @intCast(semantic_argument_type.get_byte_alignment()) }); - llvm_abi_argument_value_buffer[abi_argument_count] = alloca; - abi_argument_count += 1; - } - }, - .expand => unreachable, + }, + .ignore => unreachable, + else => @trap(), + } + } else { + converter.report_error(); } } - assert(abi_argument_count == function_type.abi_argument_count); + 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); - llvm_call.to_instruction().to_call().set_calling_convention(llvm_calling_convention); + const attribute_list = module.build_attribute_list(function_type, .{}, false); - llvm_emit_function_attributes(module, llvm_call, function_type, Function.Attributes{}, .call); + const call_base = llvm_call.to_instruction().to_call_base(); + call_base.set_calling_convention(llvm_calling_convention); + call_base.set_attributes(attribute_list); - for (function_type.get_argument_type_abis()) |argument_type_abi| { - if (argument_type_abi.attributes.zero_extend) { - llvm_add_argument_attribute(llvm_call, module.llvm.attribute_table.zeroext, argument_type_abi.indices[0] + 1, .call); - } + const return_type_abi = &function_type.return_type_abi; + const return_abi_kind = return_type_abi.flags.kind; - if (argument_type_abi.attributes.sign_extend) { - llvm_add_argument_attribute(llvm_call, module.llvm.attribute_table.signext, argument_type_abi.indices[0] + 1, .call); - } + 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(); - switch (argument_type_abi.kind) { - .indirect => |indirect| { - if (argument_type_abi.attributes.by_value) { - const by_value_attribute = module.llvm.context.create_type_attribute(module.llvm.attribute_kind_table.byval, indirect.type.llvm.handle); - llvm_add_argument_attribute(llvm_call, by_value_attribute, argument_type_abi.indices[0] + 1, .call); + 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; + }, } + } - const align_attribute = module.llvm.context.create_enum_attribute(module.llvm.attribute_kind_table.@"align", indirect.alignment); - llvm_add_argument_attribute(llvm_call, align_attribute, argument_type_abi.indices[0] + 1, .call); - // TODO: alignment - }, - else => {}, - } - } + // TODO: if + const fixed_vector_type = false; + if (fixed_vector_type) { + @trap(); + } - const llvm_value = llvm_call; - - switch (function_type.return_type_abi.kind) { - .indirect => |indirect| { - const sret_attribute = module.llvm.context.create_type_attribute(module.llvm.attribute_kind_table.sret, indirect.type.llvm.handle); - llvm_add_argument_attribute(llvm_call, sret_attribute, 1, .call); - - const align_attribute = module.llvm.context.create_enum_attribute(module.llvm.attribute_kind_table.@"align", indirect.alignment); - llvm_add_argument_attribute(llvm_call, align_attribute, 1, .call); - - const result = module.values.add(); - result.* = .{ - .llvm = module.create_load(.{ .type = function_type.semantic_return_type, .value = llvm_indirect_return_value }), - .type = function_type.semantic_return_type, - .bb = .instruction, + 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(), }; - return result; - }, - else => { - const result = module.values.add(); - result.* = .{ - .llvm = llvm_value, - .type = function_type.semantic_return_type, + _ = &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 result; + 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(), } } @@ -1217,7 +1912,7 @@ const Converter = struct { 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.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(); @@ -1271,7 +1966,7 @@ const Converter = struct { converter.skip_space(); - const local_type_inference: ?*Type = switch (has_type) { + const local_type_stated: ?*Type = switch (has_type) { true => converter.parse_type(module), false => null, }; @@ -1280,14 +1975,54 @@ const Converter = struct { converter.expect_character('='); - const value = converter.parse_value(module, local_type_inference, .value); - const local_type = local_type_inference orelse value.type; + const value = converter.parse_value(module, local_type_stated, .value); const local_storage = module.values.add(); - local_storage.* = .{ - .llvm = module.create_alloca(.{ .type = local_type, .name = local_name }), - .type = local_type, - .bb = .local, + 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, + }; + + // local_storage.* = switch (is_inferred) { + // true => .{ + // .llvm = module.create_alloca(.{ .type = resolved_type.bb.pointer.type, .name = local_name }), + // .type = resolved_type, + // .bb = .local, + // .lvalue = true, + // .dereference_to_assign = false, + // }, + // false => .{ + // .llvm = module.create_alloca(.{ .type = resolved_type, .name = local_name }), + // .type = module.get_pointer_type(.{ .type = resolved_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); @@ -1302,7 +2037,18 @@ const Converter = struct { _ = 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); } - _ = module.create_store(.{ .source = value.llvm, .destination = local_storage.llvm, .alignment = @intCast(local_type.get_byte_alignment()) }); + + 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.* = .{ @@ -1322,57 +2068,85 @@ const Converter = struct { switch (statement_start_keyword) { .@"return" => { converter.skip_space(); - if (converter.consume_character_if_match(';')) { + + 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, current_function_global.value.type.bb.function.semantic_return_type, .value); + 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); } - switch (current_function_type.return_type_abi.kind) { - .direct => { - module.llvm.builder.create_ret(return_value.llvm); - }, - .indirect => |indirect| { - _ = module.create_store(.{ .source = return_value.llvm, .destination = current_function.return_pointer.llvm, .alignment = indirect.alignment }); - _ = module.llvm.builder.create_ret_void(); - }, - .direct_coerce => |coerced_type| { - //assert(return_value.type != coerced_type); - const abi_return_value = emit_direct_coerce(module, coerced_type, return_value); - module.llvm.builder.create_ret(abi_return_value); - }, - .direct_pair => |pair| { - const anon_pair_type = module.get_anonymous_struct_pair(pair); - assert(return_value.type != anon_pair_type); - - const alloca = module.create_alloca(.{ .type = return_value.type }); - _ = module.create_store(.{ .source = return_value.llvm, .destination = alloca, .alignment = @intCast(return_value.type.get_byte_alignment()) }); - - const source_is_scalable_vector_type = false; - const target_is_scalable_vector_type = false; - if (return_value.type.get_byte_size() >= anon_pair_type.get_byte_size() and !source_is_scalable_vector_type and !target_is_scalable_vector_type) { - const load = module.create_load(.{ .type = anon_pair_type, .value = alloca }); - module.llvm.builder.create_ret(load); - } else { - const alignment = @max(return_value.type.get_byte_alignment(), anon_pair_type.get_byte_alignment()); - const temporal = module.create_alloca(.{ .type = anon_pair_type }); - const size = module.integer_type(64, false).llvm.handle.to_integer().get_constant(return_value.type.get_byte_size(), @intFromBool(false)); - _ = module.llvm.builder.create_memcpy(temporal, @intCast(alignment), alloca, @intCast(anon_pair_type.get_byte_alignment()), size.to_value()); - const load = module.create_load(.{ .type = anon_pair_type, .value = temporal }); - module.llvm.builder.create_ret(load); + // 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, + }); + }, } }, - else => @trap(), + .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("", current_function_global.value.llvm.to_function()); - const not_taken_block = module.llvm.context.create_basic_block("", current_function_global.value.llvm.to_function()); + 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(); @@ -1387,12 +2161,14 @@ const Converter = struct { _ = 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 is_first_block_terminated = module.current_basic_block().get_terminator() != null; - if (!is_first_block_terminated) { - @trap(); - } + const if_final_block = module.llvm.builder.get_insert_block(); converter.skip_space(); @@ -1408,20 +2184,41 @@ const Converter = struct { 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.current_basic_block().get_terminator() != null; + 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 (!(is_first_block_terminated and is_second_block_terminated)) { - if (!is_first_block_terminated) { - @trap(); + 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 {} + // 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; @@ -1445,10 +2242,16 @@ const Converter = struct { if (left.type.bb != .pointer) { converter.report_error(); } - const store_type = left.type.bb.pointer; + 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); - _ = module.create_store(.{ .source = right.llvm, .destination = left.llvm, .alignment = @intCast(store_type.get_byte_alignment()) }); + 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; @@ -1536,26 +2339,29 @@ const Converter = struct { converter.skip_space(); - const left = previous_value; - const right = current_value; + 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.?.llvm, right.llvm), - .add => module.llvm.builder.create_add(left.?.llvm, right.llvm), - .mul => module.llvm.builder.create_mul(left.?.llvm, right.llvm), - .sdiv => module.llvm.builder.create_sdiv(left.?.llvm, right.llvm), - .udiv => module.llvm.builder.create_udiv(left.?.llvm, right.llvm), - .srem => module.llvm.builder.create_srem(left.?.llvm, right.llvm), - .urem => module.llvm.builder.create_urem(left.?.llvm, right.llvm), - .shl => module.llvm.builder.create_shl(left.?.llvm, right.llvm), - .ashr => module.llvm.builder.create_ashr(left.?.llvm, right.llvm), - .lshr => module.llvm.builder.create_lshr(left.?.llvm, right.llvm), - .@"and" => module.llvm.builder.create_and(left.?.llvm, right.llvm), - .@"or" => module.llvm.builder.create_or(left.?.llvm, right.llvm), - .xor => module.llvm.builder.create_xor(left.?.llvm, right.llvm), - .icmp_ne, .icmp_eq => |icmp| module.llvm.builder.create_compare(icmp.to_int_predicate(), left.?.llvm, right.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) { @@ -1583,6 +2389,8 @@ const Converter = struct { => next_ty, }, .bb = .instruction, + .lvalue = false, + .dereference_to_assign = false, }; }, } @@ -1703,6 +2511,10 @@ const Converter = struct { extend, trap, truncate, + va_start, + va_end, + va_copy, + va_arg, }; fn parse_intrinsic(noalias converter: *Converter, noalias module: *Module, expected_type: ?*Type) *Value { @@ -1734,6 +2546,8 @@ const Converter = struct { .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 { @@ -1746,7 +2560,9 @@ const Converter = struct { 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()) { + 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(); } @@ -1759,6 +2575,8 @@ const Converter = struct { .llvm = extension_instruction, .type = destination_type, .bb = .instruction, + .lvalue = false, + .dereference_to_assign = false, }; return value; @@ -1767,19 +2585,22 @@ const Converter = struct { converter.expect_character(right_parenthesis); // TODO: lookup in advance - const intrinsic_id = llvm.lookup_intrinsic_id("llvm.trap"); + 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; @@ -1796,10 +2617,121 @@ const Converter = struct { .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 load = module.create_load(.{ .type = va_list_type, .value = alloca }); + + const value = module.values.add(); + value.* = .{ + .llvm = load, + .type = va_list_type, + .bb = .instruction, + .lvalue = false, + .dereference_to_assign = false, + }; + + 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 = converter.parse_value(module, module.get_pointer_type(.{ .type = module.get_va_list_type() }), .pointer); + + 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 byte_size = arg_type.get_byte_size(); + const aligned_size = lib.align_forward_u64(byte_size, 8); + const gpr_size = 48; + if (aligned_size < gpr_size) { + const gpr_offset: u32 = @intCast(gpr_size - aligned_size); + const gpr_offset_gep = module.llvm.builder.create_struct_gep(module.get_va_list_type().llvm.handle.to_struct(), va_list.llvm, 0); + const gpr_offset_type = module.integer_type(32, false); + const gpr_offset_load = module.create_load(.{ + .value = gpr_offset_gep, + .type = gpr_offset_type, + }); + const fits_in_gpr = module.llvm.builder.create_compare(.ule, gpr_offset_load, gpr_offset_type.llvm.handle.to_integer().get_constant(gpr_offset, @intFromBool(false)).to_value()); + const current_function = module.current_function orelse unreachable; + const reg_block = module.llvm.context.create_basic_block("fits_in_register", current_function.value.llvm.to_function()); + const mem_block = module.llvm.context.create_basic_block("fits_in_memory", current_function.value.llvm.to_function()); + const end_block = module.llvm.context.create_basic_block("va_arg.end", current_function.value.llvm.to_function()); + + _ = module.llvm.builder.create_conditional_branch(fits_in_gpr, reg_block, mem_block); + + module.llvm.builder.position_at_end(reg_block); + + module.llvm.builder.position_at_end(mem_block); + + module.llvm.builder.position_at_end(end_block); + _ = @trap(); + } else { + @trap(); + } + // const va_arg = module.llvm.builder.create_vaarg(va_list.llvm, arg_type.llvm.handle); + // + // const value = module.values.add(); + // if (given_arg_type.bb == .@"struct") { + // const load = module.create_load(.{ .type = given_arg_type, .value = va_arg }); + // value.* = .{ + // .llvm = load, + // .type = given_arg_type, + // .bb = .instruction, + // }; + // } else { + // value.* = .{ + // .llvm = va_arg, + // .type = arg_type, + // .bb = .instruction, + // }; + // } + // + // return value; + }, } } @@ -1836,13 +2768,14 @@ const Converter = struct { const ty = expected_type orelse converter.report_error(); switch (ty.bb) { - .@"struct" => |*struct_type| { - var field_count: usize = 0; + .structure => |*struct_type| { + var field_count: u32 = 0; - var llvm_value = switch (must_be_constant) { - true => @trap(), - false => ty.llvm.handle.get_poison(), - }; + var field_value_buffer: [64]*Value = undefined; + var field_index_buffer: [64]u32 = undefined; + + var is_ordered = true; + var is_constant = true; while (converter.consume_character_if_match('.')) : (field_count += 1) { converter.skip_space(); @@ -1854,6 +2787,7 @@ const Converter = struct { } } else converter.report_error(); + is_ordered = is_ordered and field_index == field_count; const field = struct_type.fields[field_index]; converter.skip_space(); @@ -1863,15 +2797,15 @@ const Converter = struct { converter.skip_space(); const field_value = converter.parse_value(module, field.type, .value); - - if (must_be_constant) { - if (field_index != field_count) { - converter.report_error(); - } + if (field.type != field_value.type) { @trap(); - } else { - llvm_value = module.llvm.builder.create_insert_value(llvm_value, field_value.llvm, field_index); } + 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(); @@ -1880,18 +2814,89 @@ const Converter = struct { converter.skip_space(); } + converter.expect_character(right_brace); + + if (must_be_constant and !is_constant) { + @trap(); + } + if (field_count != struct_type.fields.len) { // expect: 'zero' keyword @trap(); } - converter.expect_character(right_brace); + 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, + .bb = .{ + .struct_initialization = .{ + .is_constant = is_constant, + }, + }, + .lvalue = true, + .dereference_to_assign = false, }; return value; @@ -1945,14 +2950,14 @@ const Converter = struct { .llvm = llvm_value, .type = ty, .bb = .bits_initialization, + .lvalue = false, + .dereference_to_assign = false, }; return value; }, else => converter.report_error(), } - - @trap(); }, left_bracket => { converter.offset += 1; @@ -1989,12 +2994,26 @@ const Converter = struct { const array_elements = element_buffer[0..element_count]; if (elements_are_constant) { - const array_constant = array.element_type.llvm.handle.get_constant_array(@ptrCast(array_elements)); + 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 = array_constant.to_value(), + .llvm = global, .type = ty, .bb = .constant_array, + .lvalue = true, + .dereference_to_assign = false, }; return value; } else { @@ -2037,23 +3056,18 @@ const Converter = struct { .llvm = expected_ty.llvm.handle.get_poison(), .type = expected_ty, .bb = .instruction, // TODO + .lvalue = false, + .dereference_to_assign = false, }; return value; } else { - const variable = blk: { - if (current_function.value.bb.function.locals.find(identifier)) |local| { - break :blk local; - } else if (current_function.value.bb.function.arguments.find(identifier)) |argument| { - break :blk argument; - } else if (module.globals.find(identifier)) |global| { - break :blk global; - } else { - converter.report_error(); - } - }; + 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(); @@ -2063,8 +3077,8 @@ const Converter = struct { } else if (converter.consume_character_if_match('.')) { converter.skip_space(); - switch (variable.value.type.bb) { - .@"struct" => |*struct_type| { + 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)) { @@ -2072,7 +3086,7 @@ const Converter = struct { } } else converter.report_error(); const field = struct_type.fields[field_index]; - const gep = module.llvm.builder.create_struct_gep(variable.value.type.llvm.handle.to_struct(), variable.value.llvm, 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 => { @@ -2084,6 +3098,8 @@ const Converter = struct { .llvm = module.create_load(.{ .type = field.type, .value = gep }), .type = field.type, .bb = .instruction, + .lvalue = false, + .dereference_to_assign = false, }; break :b load; }, @@ -2111,26 +3127,54 @@ const Converter = struct { .type = bits.backing_type, .llvm = bitfield_masked, .bb = .instruction, + .lvalue = false, + .dereference_to_assign = false, }; break :b value; }, - .pointer => { - converter.expect_character('&'); - - switch (value_kind) { - .pointer, .maybe_pointer => { - break :b variable.value; - }, - .value => { - const load = module.values.add(); - load.* = .{ - .llvm = module.create_load(.{ .type = variable.value.type, .value = variable.value.llvm }), - .type = variable.value.type, - .bb = .instruction, - }; - break :b load; - }, + .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(), @@ -2146,7 +3190,7 @@ const Converter = struct { converter.skip_space(); converter.expect_character(right_bracket); - const gep = module.llvm.builder.create_gep(variable.value.type.llvm.handle, variable.value.llvm, &.{ zero_index, index.llvm }); + const gep = module.llvm.builder.create_gep(appointee_type.llvm.handle, variable.value.llvm, &.{ zero_index, index.llvm }); switch (value_kind) { .pointer, .maybe_pointer => { @@ -2154,38 +3198,33 @@ const Converter = struct { }, .value => { const load = module.values.add(); - const load_type = variable.value.type.bb.array.element_type; + 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 => switch (variable.value.bb) { - .external_function, .function => { - const pointer_type = module.get_pointer_type(variable.value.type); - const value = module.values.add(); - value.* = .{ - .llvm = variable.value.llvm, - .type = pointer_type, - .bb = .global, + .pointer, .maybe_pointer => break :b variable.value, + .value => switch (appointee_type.get_evaluation_kind()) { + .aggregate => break :b variable.value, + 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 value; + break :b load; }, - else => break :b variable.value, - }, - .value => { - const load = module.values.add(); - load.* = .{ - .llvm = module.create_load(.{ .type = variable.value.type, .value = variable.value.llvm }), - .type = variable.value.type, - .bb = .instruction, - }; - break :b load; }, } } @@ -2197,7 +3236,6 @@ const Converter = struct { '0'...'9' => converter.parse_integer(module, expected_type.?, prefix == .negative), else => os.abort(), }; - _ = &value; switch (prefix) { .none, @@ -2209,6 +3247,8 @@ const Converter = struct { .llvm = llvm_value, .bb = .instruction, .type = module.integer_type(1, false), + .lvalue = false, + .dereference_to_assign = false, }; }, } @@ -2268,9 +3308,11 @@ pub const BuildMode = enum { const CPUArchitecture = enum { x86_64, }; + const OperatingSystem = enum { linux, }; + pub const Target = struct { cpu: CPUArchitecture, os: OperatingSystem, @@ -2291,33 +3333,246 @@ pub const Target = struct { }; pub const Abi = struct { - const Kind = union(enum) { + const Kind = enum(u3) { ignore, direct, - direct_pair: [2]*Type, - direct_coerce: *Type, - direct_coerce_int, - direct_split_struct_i32, - expand_coerce, - indirect: struct { - type: *Type, - alignment: u32, - }, + extend, + indirect, + indirect_aliased, expand, + coerce_and_expand, + in_alloca, }; - const Attributes = struct { - by_reg: bool = false, - zero_extend: bool = false, - sign_extend: bool = false, - realign: bool = false, - by_value: bool = false, + 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 { - kind: Kind, - indices: [2]u16 = .{ 0, 0 }, - attributes: Abi.Attributes = .{}, + 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 { @@ -2325,64 +3580,95 @@ pub const Abi = struct { gpr: u32, sse: u32, }; + pub const Class = enum { - none, - memory, integer, sse, sseup, + x87, + x87up, + complex_x87, + none, + memory, fn merge(accumulator: Class, field: Class) Class { - assert(accumulator != .memory); - if (accumulator == field) { - return accumulator; - } else { - var a = accumulator; - var f = field; - if (@intFromEnum(accumulator) > @intFromEnum(field)) { - a = field; - f = accumulator; - } + // 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. - return switch (a) { - .none => f, - .memory => .memory, - .integer => .integer, - .sse, .sseup => .sse, - }; + // 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; } }; - fn classify(ty: *Type, base_offset: u64) [2]Class { - var result: [2]Class = undefined; - const is_memory = base_offset >= 8; + 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; - result[not_current_index] = .none; switch (ty.bb) { - .void, .noreturn => result[current_index] = .none, + .void, .noreturn => {}, .bits => result[current_index] = .integer, - .integer => result[current_index] = .integer, // TODO: weird cases - // const integer_index = ty.get_integer_index(); - // switch (integer_index) { - // 8 - 1, - // 16 - 1, - // 32 - 1, - // 64 - 1, - // 64 + 8 - 1, - // 64 + 16 - 1, - // 64 + 32 - 1, - // 64 + 64 - 1, - // => result[current_index] = .integer, - // else => unreachable, - // } - // }, .pointer => result[current_index] = .integer, - .@"struct" => |struct_type| { + .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) { @@ -2391,7 +3677,7 @@ pub const Abi = struct { const is_union = false; var member_offset: u32 = 0; for (struct_type.fields) |field| { - const offset = base_offset + member_offset; + 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())); @@ -2408,7 +3694,10 @@ pub const Abi = struct { return r; } - const member_classes = classify(field.type, offset); + 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; @@ -2424,17 +3713,20 @@ pub const Abi = struct { }, .array => |*array_type| { if (ty.get_byte_size() <= 64) { - if (base_offset % ty.get_byte_alignment() == 0) { + 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 = base_offset; + var offset = options.base_offset; for (0..array_type.element_count.?) |_| { - const element_classes = classify(array_type.element_type, offset); + 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; @@ -2450,22 +3742,52 @@ pub const Abi = struct { } } }, - else => |t| @panic(@tagName(t)), + else => @trap(), } return result; } - fn classify_post_merge(size: u64, classes: [2]Class) [2]Class { - if (classes[1] == .memory) { - return .{ .memory, .memory }; - } else if (size > 16 and (classes[0] != .sse or classes[1] != .sseup)) { - return .{ .memory, classes[1] }; - } else if (classes[1] == .sseup and classes[0] != .sse and classes[0] != .sseup) { - return .{ classes[0], .sse }; - } else { - return classes; + 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 { @@ -2484,11 +3806,11 @@ pub const Abi = struct { return ty; } }, - else => unreachable, + 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(), - .@"struct" => { + .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); } @@ -2510,8 +3832,6 @@ pub const Abi = struct { const bit_count = byte_count * 8; return module.integer_type(@intCast(bit_count), false); } - - unreachable; } fn get_member_at_offset(ty: *Type, offset: u32) ?*const Field { @@ -2522,7 +3842,7 @@ pub const Abi = struct { var offset_it: u32 = 0; var last_match: ?*const Field = null; - const struct_type = &ty.bb.@"struct"; + const struct_type = &ty.bb.structure; for (struct_type.fields) |*field| { if (offset_it > offset) { break; @@ -2542,7 +3862,7 @@ pub const Abi = struct { } switch (ty.bb) { - .@"struct" => |*struct_type| { + .structure => |*struct_type| { var offset: u64 = 0; for (struct_type.fields) |field| { @@ -2569,79 +3889,231 @@ pub const Abi = struct { } } - fn get_argument_pair(types: [2]*Type) Abi.Information { - const low_size = types[0].get_byte_size(); - const high_alignment = types[1].get_byte_alignment(); - const high_start = lib.align_forward_u64(low_size, high_alignment); - assert(high_start == 8); - return .{ - .kind = .{ - .direct_pair = types, + 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 = true, + }); + 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, }; } - fn indirect_argument(ty: *Type, free_integer_registers: u32) Abi.Information { - const is_illegal_vector = false; - if (!ty.is_aggregate() and !is_illegal_vector) { - if (ty.bb == .integer and ty.get_bit_size() < 32) { - unreachable; - } else { - return .{ - .kind = .direct, - }; - } - } else { - if (free_integer_registers == 0) { - if (ty.get_byte_alignment() <= 8 and ty.get_byte_size() <= 8) { - unreachable; - } - } + 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); - if (ty.get_byte_alignment() < 8) { - return .{ - .kind = .{ - .indirect = .{ - .type = ty, - .alignment = 8, - }, - }, - .attributes = .{ - .realign = true, - .by_value = true, - }, - }; - } else { - return .{ - .kind = .{ - .indirect = .{ - .type = ty, - .alignment = @intCast(ty.get_byte_alignment()), - }, - }, - .attributes = .{ - .by_value = true, - }, - }; - } - } - unreachable; + 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; } - fn indirect_return(ty: *Type) Abi.Information { - if (ty.is_aggregate()) { - return .{ - .kind = .{ - .indirect = .{ - .type = ty, - .alignment = @intCast(ty.get_byte_alignment()), - }, - }, + 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 { - unreachable; + // 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, + }; + } }; }; @@ -2656,93 +4128,6 @@ const ConvertOptions = struct { target: Target, }; -fn llvm_emit_function_attributes(module: *Module, value: *llvm.Value, function_type: *FunctionType, function_attributes: Function.Attributes, container_type: AttributeContainerType) void { - const enable_frame_pointer = true; - - if (enable_frame_pointer) { - llvm_add_function_attribute(value, module.llvm.attribute_table.frame_pointer_all, container_type); - llvm_add_function_attribute(value, module.llvm.attribute_table.ssp, container_type); - } - - llvm_add_function_attribute(value, module.llvm.attribute_table.@"stack-protector-buffer-size", container_type); - llvm_add_function_attribute(value, module.llvm.attribute_table.@"no-trapping-math", container_type); - llvm_add_function_attribute(value, module.llvm.attribute_table.nounwind, container_type); - - switch (function_attributes.inline_behavior) { - .default => {}, - .no_inline => llvm_add_function_attribute(value, module.llvm.attribute_table.@"noinline", container_type), - .always_inline => llvm_add_function_attribute(value, module.llvm.attribute_table.alwaysinline, container_type), - } - - if (function_attributes.naked) { - llvm_add_function_attribute(value, module.llvm.attribute_table.naked, container_type); - } - - if (function_type.abi_return_type == module.noreturn_type) { - llvm_add_function_attribute(value, module.llvm.attribute_table.noreturn, container_type); - } -} - -fn llvm_emit_function_site_argument_attributes(noalias module: *Module, function: *llvm.Value, argument_abi: Abi.Information, is_return: bool) void { - - // assert(argument_abi.indices[1] == argument_abi.indices[0] or argument_abi.kind == .direct_pair or argument_abi.kind == .direct or argument_abi.kind == .ignore or argument_abi.kind == .expand or argument_abi.kind == .direct_coerce or argument_abi.kind == .direct_coerce_int or argument_abi.kind == .expand_coerce or argument_abi.kind == .direct_split_struct_i32); - - if (argument_abi.attributes.zero_extend) { - llvm_add_argument_attribute(function, module.llvm.attribute_table.zeroext, argument_abi.indices[0] + @intFromBool(!is_return), .function); - } - - if (argument_abi.attributes.sign_extend) { - llvm_add_argument_attribute(function, module.llvm.attribute_table.signext, argument_abi.indices[0] + @intFromBool(!is_return), .function); - } - - if (argument_abi.attributes.by_reg) { - @trap(); - } - - switch (argument_abi.kind) { - .direct => {}, - .indirect => |indirect| { - const attribute_index = if (is_return) 1 else argument_abi.indices[0] + 1; - const align_attribute = module.llvm.context.create_enum_attribute(module.llvm.attribute_kind_table.@"align", indirect.alignment); - - switch (is_return) { - true => { - const sret_attribute = module.llvm.context.create_type_attribute(module.llvm.attribute_kind_table.sret, indirect.type.llvm.handle); - llvm_add_argument_attribute(function, sret_attribute, attribute_index, .function); - llvm_add_argument_attribute(function, module.llvm.attribute_table.@"noalias", attribute_index, .function); - llvm_add_argument_attribute(function, align_attribute, attribute_index, .function); - }, - false => { - if (argument_abi.attributes.by_value) { - const by_value_attribute = module.llvm.context.create_type_attribute(module.llvm.attribute_kind_table.byval, indirect.type.llvm.handle); - llvm_add_argument_attribute(function, by_value_attribute, attribute_index, .function); - } - - llvm_add_argument_attribute(function, align_attribute, attribute_index, .function); - }, - } - }, - else => {}, - } -} - -fn llvm_emit_function_site_attributes(module: *Module, value: *Value) void { - const llvm_value = value.llvm; - const function_type = &value.type.bb.function; - const function_attributes = switch (value.bb) { - .function => value.bb.function.attributes, - else => Function.Attributes{}, - }; - - llvm_emit_function_attributes(module, llvm_value, function_type, function_attributes, .function); - - llvm_emit_function_site_argument_attributes(module, llvm_value, function_type.return_type_abi, true); - - for (function_type.get_argument_type_abis()) |argument_type_abi| { - llvm_emit_function_site_argument_attributes(module, llvm_value, argument_type_abi, false); - } -} - pub noinline fn convert(arena: *Arena, options: ConvertOptions) void { const build_dir = "bb-cache"; os.make_directory(build_dir); @@ -2823,9 +4208,9 @@ pub noinline fn convert(arena: *Arena, options: ConvertOptions) void { if (string_to_enum(GlobalKind, global_string)) |global_kind| { switch (global_kind) { .@"fn" => { - var calling_convention = CallingConvention.unknown; - var function_attributes = Function.Attributes{}; - _ = &function_attributes; + 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) { @@ -2867,13 +4252,12 @@ pub noinline fn convert(arena: *Arena, options: ConvertOptions) void { converter.expect_character(left_parenthesis); - const Argument = struct { + var argument_buffer: [max_argument_count]struct { name: []const u8, type: *Type, line: u32, column: u32, - }; - var argument_buffer: [64]Argument = undefined; + } = undefined; var semantic_argument_count: u32 = 0; while (converter.offset < converter.content.len and converter.content[converter.offset] != right_parenthesis) : (semantic_argument_count += 1) { @@ -2882,6 +4266,25 @@ pub noinline fn convert(arena: *Arena, options: ConvertOptions) void { 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(); @@ -2893,7 +4296,6 @@ pub noinline fn convert(arena: *Arena, options: ConvertOptions) void { const argument_type = converter.parse_type(module); converter.skip_space(); - _ = converter.consume_character_if_match(','); argument_buffer[semantic_argument_count] = .{ @@ -2911,329 +4313,190 @@ pub noinline fn convert(arena: *Arena, options: ConvertOptions) void { const semantic_return_type = converter.parse_type(module); const linkage_name = global_name; - var debug_argument_type_buffer: [argument_buffer.len + 1]*llvm.DI.Type = undefined; - - const semantic_debug_argument_types = debug_argument_type_buffer[0 .. semantic_argument_count + 1]; const semantic_arguments = argument_buffer[0..semantic_argument_count]; - const semantic_argument_types = module.arena.allocate(*Type, semantic_argument_count); - - semantic_debug_argument_types[0] = semantic_return_type.llvm.debug; - - for (semantic_arguments, semantic_argument_types, semantic_debug_argument_types[1..]) |argument, *argument_type, *debug_argument_type| { - argument_type.* = argument.type; - debug_argument_type.* = argument.type.llvm.debug; - } + const argument_type_abis = module.arena.allocate(Abi.Information, semantic_arguments.len); var return_type_abi: Abi.Information = undefined; - var argument_type_abi_buffer: [64]Abi.Information = undefined; - switch (calling_convention) { - .unknown => { - return_type_abi = .{ .kind = .direct }; + const resolved_calling_convention = calling_convention.resolve(module.target); + const is_reg_call = resolved_calling_convention == .system_v and false; // TODO: regcall calling_convention - for (0..semantic_argument_count) |i| { - argument_type_abi_buffer[i] = .{ - .kind = .direct, - .indices = .{ @intCast(i), @intCast(i + 1) }, - }; - } - }, - .c => { - // Return type abi - switch (options.target.cpu) { - .x86_64 => switch (options.target.os) { - .linux => { - return_type_abi = ret_ty_abi: { - const type_classes = Abi.SystemV.classify(semantic_return_type, 0); - assert(type_classes[1] != .memory or type_classes[0] == .memory); - assert(type_classes[1] != .sseup or type_classes[0] == .sse); - - const result_type = switch (type_classes[0]) { - .none => switch (type_classes[1]) { - .none => break :ret_ty_abi .{ - .kind = .ignore, - }, - else => |t| @panic(@tagName(t)), - }, - .integer => b: { - const result_type = Abi.SystemV.get_int_type_at_offset(module, semantic_return_type, 0, semantic_return_type, 0); - if (type_classes[1] == .none and semantic_return_type.get_bit_size() < 32) { - const signed = switch (semantic_return_type.bb) { - .integer => |integer_type| integer_type.signed, - .bits => false, - else => |t| @panic(@tagName(t)), - }; - // _ = signed; - break :ret_ty_abi .{ - .kind = .{ - .direct_coerce = semantic_return_type, - }, - .attributes = .{ - .sign_extend = signed, - .zero_extend = !signed, - }, - }; - } - - break :b result_type; - }, - .memory => break :ret_ty_abi Abi.SystemV.indirect_return(semantic_return_type), - else => |t| @panic(@tagName(t)), - }; - - const high_part: ?*Type = switch (type_classes[1]) { - .none, .memory => null, - .integer => b: { - assert(type_classes[0] != .none); - const high_part = Abi.SystemV.get_int_type_at_offset(module, semantic_return_type, 8, semantic_return_type, 8); - break :b high_part; - }, - else => |t| @panic(@tagName(t)), - }; - - if (high_part) |hp| { - const expected_result = Abi.SystemV.get_argument_pair(.{ result_type, hp }); - break :ret_ty_abi expected_result; - } else { - // TODO - const is_type = true; - if (is_type) { - if (result_type == semantic_return_type) { - break :ret_ty_abi Abi.Information{ - .kind = .direct, - }; - } else { - break :ret_ty_abi Abi.Information{ - .kind = .{ - .direct_coerce = result_type, - }, - }; - } - } else { - unreachable; - } - } - }; - - var available_registers = Abi.SystemV.RegisterCount{ - .gpr = 6, - .sse = 8, - }; - - if (return_type_abi.kind == .indirect) { - available_registers.gpr -= 1; - } - - const return_by_reference = false; - if (return_by_reference) { - @trap(); - } - - for (semantic_arguments, argument_type_abi_buffer[0..semantic_arguments.len]) |semantic_argument, *argument_type_abi| { - const semantic_argument_type = semantic_argument.type; - var needed_registers = Abi.SystemV.RegisterCount{ - .gpr = 0, - .sse = 0, - }; - const argument_type_abi_classification: Abi.Information = ata: { - const type_classes = Abi.SystemV.classify(semantic_argument_type, 0); - assert(type_classes[1] != .memory or type_classes[0] == .memory); - assert(type_classes[1] != .sseup or type_classes[0] == .sse); - - const result_type = switch (type_classes[0]) { - .integer => b: { - needed_registers.gpr += 1; - const result_type = Abi.SystemV.get_int_type_at_offset(module, semantic_argument_type, 0, semantic_argument_type, 0); - if (type_classes[1] == .none and semantic_argument_type.get_bit_size() < 32) { - const signed = switch (semantic_argument_type.bb) { - .integer => |integer_type| integer_type.signed, - .bits => false, // TODO: signedness? - else => |t| @panic(@tagName(t)), - }; - - break :ata .{ - .kind = .{ - .direct_coerce = result_type, - }, - .attributes = .{ - .sign_extend = signed, - .zero_extend = !signed, - }, - }; - } - - break :b result_type; - }, - .memory => break :ata Abi.SystemV.indirect_argument(semantic_argument_type, available_registers.gpr), - else => |t| @panic(@tagName(t)), - }; - const high_part: ?*Type = switch (type_classes[1]) { - .none, .memory => null, - .integer => b: { - assert(type_classes[0] != .none); - needed_registers.gpr += 1; - const high_part = Abi.SystemV.get_int_type_at_offset(module, semantic_argument_type, 8, semantic_argument_type, 8); - break :b high_part; - }, - else => |t| @panic(@tagName(t)), - }; - - if (high_part) |hp| { - break :ata Abi.SystemV.get_argument_pair(.{ result_type, hp }); - } else { - // TODO - const is_type = true; - if (is_type) { - if (result_type == semantic_argument_type) { - break :ata Abi.Information{ - .kind = .direct, - }; - } else if (result_type.bb == .integer and semantic_argument_type.bb == .integer and semantic_argument_type.get_byte_size() == result_type.get_byte_size()) { - unreachable; - } else { - break :ata Abi.Information{ - .kind = .{ - .direct_coerce = result_type, - }, - }; - } - } - unreachable; - } - }; - argument_type_abi.* = if (available_registers.sse < needed_registers.sse or available_registers.gpr < needed_registers.gpr) b: { - break :b Abi.SystemV.indirect_argument(semantic_argument_type, available_registers.gpr); - } else b: { - available_registers.gpr -= needed_registers.gpr; - available_registers.sse -= needed_registers.sse; - break :b argument_type_abi_classification; - }; - } + 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; + + const result = if (is_reg_call) @trap() else Abi.SystemV.classify_argument_type(module, semantic_argument_type, .{ + .is_named_argument = is_named_argument, + .is_reg_call = is_reg_call, + .available_gpr = available_registers.system_v.gpr, + }); + const abi = result[0]; + const needed_registers = result[1]; + + 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 = abi_argument_type_count; + + 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; + + abi_argument_type_count += 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, + }, + }, + .llvm = .{ + .handle = llvm_function_type.to_type(), + .debug = subroutine_type.to_type(), + }, + .name = null, + }); + break :ft result; }, - } - - const argument_type_abis = module.arena.allocate(Abi.Information, semantic_arguments.len); - @memcpy(argument_type_abis, argument_type_abi_buffer[0..semantic_arguments.len]); - - var abi_argument_type_buffer: [64]*Type = undefined; - var abi_argument_type_count: usize = 0; - - var llvm_abi_argument_type_buffer: [64]*llvm.Type = undefined; - - const abi_return_type = switch (return_type_abi.kind) { - .ignore, .direct => semantic_return_type, - .direct_coerce => |coerced_type| coerced_type, - .indirect => |indirect| b: { - const indirect_pointer_type = module.get_pointer_type(indirect.type); - abi_argument_type_buffer[abi_argument_type_count] = indirect_pointer_type; - llvm_abi_argument_type_buffer[abi_argument_type_count] = indirect_pointer_type.llvm.handle; - abi_argument_type_count += 1; - break :b module.void_type; + .win64 => { + @trap(); }, - .direct_pair => |pair| module.get_anonymous_struct_pair(pair), - else => |t| @panic(@tagName(t)), }; - for (argument_type_abis, semantic_argument_types) |*argument_abi, original_argument_type| { - const start: u16 = @intCast(abi_argument_type_count); - switch (argument_abi.kind) { - .direct => { - abi_argument_type_buffer[abi_argument_type_count] = original_argument_type; - llvm_abi_argument_type_buffer[abi_argument_type_count] = original_argument_type.llvm.handle; - abi_argument_type_count += 1; - }, - .direct_coerce => |coerced_type| { - abi_argument_type_buffer[abi_argument_type_count] = coerced_type; - llvm_abi_argument_type_buffer[abi_argument_type_count] = coerced_type.llvm.handle; - abi_argument_type_count += 1; - }, - .direct_pair => |pair| { - abi_argument_type_buffer[abi_argument_type_count] = pair[0]; - llvm_abi_argument_type_buffer[abi_argument_type_count] = pair[0].llvm.handle; - abi_argument_type_count += 1; - abi_argument_type_buffer[abi_argument_type_count] = pair[1]; - llvm_abi_argument_type_buffer[abi_argument_type_count] = pair[1].llvm.handle; - abi_argument_type_count += 1; - }, - .indirect => |indirect| { - const indirect_pointer_type = module.get_pointer_type(indirect.type); - abi_argument_type_buffer[abi_argument_type_count] = indirect_pointer_type; - llvm_abi_argument_type_buffer[abi_argument_type_count] = indirect_pointer_type.llvm.handle; - abi_argument_type_count += 1; - }, - else => |t| @panic(@tagName(t)), - } - - const end: u16 = @intCast(abi_argument_type_count); - argument_abi.indices = .{ start, end }; - } - - const abi_argument_types = module.arena.allocate(*Type, abi_argument_type_count); - @memcpy(abi_argument_types, abi_argument_type_buffer[0..abi_argument_type_count]); - 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, false); const llvm_handle = module.llvm.handle.create_function(.{ .name = global_name, .linkage = switch (is_export or is_extern) { true => .ExternalLinkage, false => .InternalLinkage, }, - .type = llvm_function_type, - }); - - var subroutine_type: *llvm.DI.Type.Subroutine = undefined; - const function_scope: *llvm.DI.Scope = if (module.llvm.di_builder) |di_builder| blk: { - const subroutine_type_flags = llvm.DI.Flags{}; - subroutine_type = di_builder.create_subroutine_type(module.llvm.file, semantic_debug_argument_types, subroutine_type_flags); - 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, subroutine_type, 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 function_type = module.types.add(.{ - .name = null, - .llvm = .{ - .handle = llvm_function_type.to_type(), - .debug = subroutine_type.to_type(), - }, - .bb = .{ - .function = .{ - .calling_convention = calling_convention, - .semantic_return_type = semantic_return_type, - .semantic_argument_types = blk: { - const sema_arg_types = module.arena.allocate(*Type, semantic_argument_count); - for (semantic_arguments, sema_arg_types) |argument, *argument_type| { - argument_type.* = argument.type; - } - - break :blk sema_arg_types.ptr; - }, - .semantic_argument_count = semantic_argument_count, - .abi_argument_count = @intCast(abi_argument_type_count), - .abi_argument_types = abi_argument_types.ptr, - .abi_return_type = abi_return_type, - .argument_type_abis = argument_type_abis.ptr, - .return_type_abi = return_type_abi, - }, - }, + .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 = function_type, + .type = module.get_pointer_type(.{ .type = function_type }), .bb = switch (has_semicolon) { true => .external_function, false => .{ @@ -3241,9 +4504,14 @@ pub noinline fn convert(arena: *Arena, options: ConvertOptions) void { .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(); @@ -3252,171 +4520,315 @@ pub noinline fn convert(arena: *Arena, options: ConvertOptions) void { .name = global_name, }; - llvm_emit_function_site_attributes(module, value); + const attribute_list = module.build_attribute_list(&function_type.bb.function, function_attributes, false); + llvm_handle.set_attributes(attribute_list); if (!has_semicolon) { const entry_block = module.llvm.context.create_basic_block("entry", llvm_handle); - module.llvm.builder.position_at_end(entry_block); + value.bb.function.return_block = module.llvm.context.create_basic_block("ret_block", null); - var llvm_argument_buffer: [argument_buffer.len]*llvm.Argument = undefined; - llvm_handle.get_arguments(&llvm_argument_buffer); - const llvm_arguments = llvm_argument_buffer[0..abi_argument_type_count]; + 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.kind) { - .indirect => |indirect| { - if (indirect.alignment <= indirect.type.get_byte_alignment()) { - const return_pointer_value = module.values.add(); - return_pointer_value.* = .{ - .llvm = llvm_arguments[0].to_value(), - .type = indirect.type, - .bb = .instruction, - }; - value.bb.function.return_pointer = return_pointer_value; - } else { + 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(); } }, - else => {}, + .in_alloca => { + @trap(); + }, + else => { + const alloca = module.create_alloca(.{ .type = return_type_abi.semantic_type, .name = "retval" }); + value.bb.function.return_alloca = alloca; + }, } - module.llvm.builder.set_current_debug_location(null); - - if (semantic_arguments.len > 0) { - 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| { - if (module.llvm.di_builder) |_| {} - - const argument_abi_count = argument_abi.indices[1] - argument_abi.indices[0]; - const LowerKind = union(enum) { - direct, - direct_pair: [2]*Type, - direct_coerce: *Type, - indirect, - }; - const lower_kind: LowerKind = switch (argument_abi.kind) { - .direct => .direct, - .direct_coerce => |coerced_type| if (semantic_argument.type == coerced_type) .direct else .{ .direct_coerce = coerced_type }, - .direct_pair => |pair| .{ .direct_pair = pair }, - .indirect => .indirect, - else => @trap(), - }; - - const argument_alloca = if (lower_kind == .indirect) llvm_arguments[argument_abi.indices[0]].to_value() else module.create_alloca(.{ .type = semantic_argument.type, .name = semantic_argument.name }); - const argument_alloca_alignment: c_uint = @intCast(semantic_argument.type.get_byte_alignment()); - switch (lower_kind) { - .direct => { - assert(argument_abi_count == 1); - const abi_argument_index = argument_abi.indices[0]; - const llvm_argument = llvm_arguments[abi_argument_index]; - _ = module.create_store(.{ .source = llvm_argument.to_value(), .destination = argument_alloca, .alignment = argument_alloca_alignment }); - }, - .direct_pair => |pair| { - assert(argument_abi_count == 2); - const abi_argument_index = argument_abi.indices[0]; - const direct_pair_args = llvm_arguments[abi_argument_index..][0..2]; - _ = module.create_store(.{ .source = direct_pair_args[0].to_value(), .destination = argument_alloca, .alignment = argument_alloca_alignment }); - const llvm_index_type = module.integer_type(32, false).llvm.handle.to_integer(); - const struct_type = module.get_anonymous_struct_pair(pair); - const zero_index = llvm_index_type.get_constant(0, @intFromBool(false)).to_value(); - const index = llvm_index_type.get_constant(1, @intFromBool(false)).to_value(); - const gep = module.llvm.builder.create_gep(struct_type.llvm.handle, argument_alloca, &.{ zero_index, index }); - _ = module.create_store(.{ .source = direct_pair_args[1].to_value(), .destination = gep, .alignment = argument_alloca_alignment }); - }, - .indirect => { - assert(argument_abi_count == 1); - }, - .direct_coerce => |coerced_type| { - assert(coerced_type != semantic_argument.type); - assert(argument_abi_count == 1); - - switch (semantic_argument.type.bb) { - .@"struct" => |*struct_type| { - const is_vector = false; - _ = struct_type; - - if (coerced_type.get_byte_size() <= semantic_argument.type.get_byte_size() and !is_vector) { - assert(argument_abi_count == 1); - _ = module.create_store(.{ .source = llvm_arguments[argument_abi.indices[0]].to_value(), .destination = argument_alloca, .alignment = argument_alloca_alignment }); - } else { - @trap(); - // const temporal = emit_local_symbol(&analyzer, thread, .{ - // .name = 0, - // .initial_value = &argument_abi_instructions.slice()[0].value, - // .type = coerced_type, - // .line = 0, - // .column = 0, - // }); - // emit_memcpy(&analyzer, thread, .{ - // .destination = &argument_symbol.instruction.value, - // .source = &temporal.instruction.value, - // .destination_alignment = .{ - // .type = argument_symbol.type, - // }, - // .source_alignment = .{ - // .type = temporal.type, - // }, - // .size = argument.type.size, - // .line = 0, - // .column = 0, - // .scope = analyzer.current_scope, - // }); - } - }, - .bits => |bits| { - // TODO: this should not be happening, figure out what's going on - if (bits.backing_type == coerced_type) { - const abi_argument_index = argument_abi.indices[0]; - const llvm_argument = llvm_arguments[abi_argument_index]; - _ = module.create_store(.{ .source = llvm_argument.to_value(), .destination = argument_alloca, .alignment = argument_alloca_alignment }); - } else { - @trap(); - } - }, - else => @trap(), + const argument_variables = global.value.bb.function.arguments.add_many(semantic_argument_count); + for (semantic_arguments, argument_type_abis, argument_variables, 0..) |semantic_argument, argument_abi, *argument_variable, argument_index| { + const abi_arguments = llvm_abi_arguments[argument_abi.abi_start..][0..argument_abi.abi_count]; + assert(argument_abi.flags.kind == .ignore or argument_abi.abi_count != 0); + const argument_abi_kind = argument_abi.flags.kind; + const semantic_argument_storage = switch (argument_abi_kind) { + .direct, .extend => blk: { + const first_argument = abi_arguments[0]; + const coerce_to_type = argument_abi.get_coerce_to_type(); + if (coerce_to_type.bb != .structure and coerce_to_type.is_abi_equal(argument_abi.semantic_type) and argument_abi.attributes.direct.offset == 0) { + assert(argument_abi.abi_count == 1); + const is_promoted = false; + var v = first_argument.to_value(); + v = switch (coerce_to_type.llvm.handle == v.get_type()) { + true => v, + false => @trap(), + }; + if (is_promoted) { + @trap(); } - }, - } - const argument_value = module.values.add(); - argument_value.* = .{ - .llvm = argument_alloca, - .type = semantic_argument.type, - .bb = .argument, - }; - argument_variable.* = .{ - .value = argument_value, - .name = semantic_argument.name, - }; + 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 (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(argument_alloca, parameter_variable, di_builder.null_expression(), debug_location, module.current_basic_block()); - } + 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); - const is_final_block_terminated = module.current_basic_block().get_terminator() != null; - if (!is_final_block_terminated) { - switch (abi_return_type.bb) { - .void => { - module.llvm.builder.create_ret_void(); - }, - else => @trap(), + // 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 { + @trap(); + } + } 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) { + 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(return_block); + } + + module.llvm.builder.clear_insertion_position(); + + if (maybe_current_block) |current_block| { + _ = current_block; + @trap(); + } else { + llvm_handle.append_basic_block(return_block); + } + + module.llvm.builder.position_at_end(return_block); } } - } - if (module.llvm.di_builder) |di_builder| { - di_builder.finalize_subprogram(llvm_handle.get_subprogram()); + // 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) { @@ -3441,12 +4853,11 @@ pub noinline fn convert(arena: *Arena, options: ConvertOptions) void { @trap(); } - const llvm_struct_type = module.llvm.context.create_forward_declared_struct_type(global_name); const struct_type = module.types.add(.{ .name = global_name, .bb = .forward_declaration, .llvm = .{ - .handle = llvm_struct_type.to_type(), + .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; @@ -3460,8 +4871,8 @@ pub noinline fn convert(arena: *Arena, options: ConvertOptions) void { 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: u64 = 1; - var bit_alignment: u64 = 1; + var byte_alignment: u32 = 1; + var bit_alignment: u32 = 1; while (true) { converter.skip_space(); @@ -3528,7 +4939,7 @@ pub noinline fn convert(arena: *Arena, options: ConvertOptions) void { @memcpy(fields, field_buffer[0..field_count]); const element_types = llvm_field_type_buffer[0..field_count]; - llvm_struct_type.set_body(element_types); + 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]; @@ -3539,7 +4950,7 @@ pub noinline fn convert(arena: *Arena, options: ConvertOptions) void { } struct_type.bb = .{ - .@"struct" = .{ + .structure = .{ .bit_size = byte_size * 8, .byte_size = byte_size, .bit_alignment = bit_alignment, @@ -3667,8 +5078,10 @@ pub noinline fn convert(arena: *Arena, options: ConvertOptions) void { const global_value = module.values.add(); global_value.* = .{ .llvm = global_variable.to_value(), - .type = expected_type, + .type = module.get_pointer_type(.{ .type = expected_type }), .bb = .global, + .lvalue = true, + .dereference_to_assign = false, }; const global = module.globals.add(); @@ -3714,7 +5127,7 @@ pub noinline fn convert(arena: *Arena, options: ConvertOptions) void { }; const object_generate_result = llvm.object_generate(module.llvm.handle, target_machine, .{ - .optimize_when_possible = @intFromBool(@intFromEnum(options.build_mode) > @intFromEnum(BuildMode.soft_optimize)), + .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], diff --git a/src/converter_test.zig b/src/converter_test.zig index 59f9f7c..b7ababb 100644 --- a/src/converter_test.zig +++ b/src/converter_test.zig @@ -22,7 +22,7 @@ fn invoke(name: []const u8) !void { inline for (@typeInfo(BuildMode).@"enum".fields) |f| { const build_mode = @field(BuildMode, f.name); - inline for ([2]bool{ false, true }) |has_debug_info| { + inline for ([2]bool{ true, false }) |has_debug_info| { // Bootstrap { var tmp_dir = std.testing.tmpDir(.{}); @@ -329,6 +329,30 @@ test "c_struct_with_array" { try invsrc(@src()); } +test "indirect" { + try invsrc(@src()); +} + +test "indirect_struct" { + try invsrc(@src()); +} + +test "u1_return" { + try invsrc(@src()); +} + +test "small_struct_ints" { + try invsrc(@src()); +} + +test "c_med_struct_ints" { + try invsrc(@src()); +} + test "c_abi" { try invsrc(@src()); } + +// test "varargs" { +// try invsrc(@src()); +// } diff --git a/src/lib.zig b/src/lib.zig index 062c1cf..4016595 100644 --- a/src/lib.zig +++ b/src/lib.zig @@ -122,7 +122,6 @@ pub const file = struct { }, .{ .read = 1, }); - defer fd.close(); var result: []u8 = undefined; const ptr = @as(*[2]u64, @ptrCast(&result)); ptr[0] = 0; @@ -133,6 +132,7 @@ pub const file = struct { const file_buffer = arena.allocate_bytes(file_size, 1); result = file_buffer[0..file_size]; fd.read(result, file_size); + fd.close(); } return result; @@ -2504,7 +2504,18 @@ pub fn format_va(buffer: []u8, format_string: [*:0]const u8, variable_arguments: switch (next_ch) { left_brace => os.abort(), 'c' => { - os.abort(); + read_byte_count += 1; + assert(format_string[read_byte_count] == 's'); + read_byte_count += 1; + assert(format_string[read_byte_count] == 't'); + read_byte_count += 1; + assert(format_string[read_byte_count] == 'r'); + read_byte_count += 1; + + const c_string = @cVaArg(variable_arguments, [*:0]const u8); + const str = cstring.to_slice(c_string); + @memcpy(buffer[written_byte_count..][0..str.len], str); + written_byte_count += str.len; }, 'f' => { os.abort(); @@ -2653,12 +2664,12 @@ test "parse integer hexadecimal" { fn vprint(format_string: [*:0]const u8, args: *VariableArguments) void { var buffer: [16 * 1024]u8 = undefined; - const slice = format_va(&buffer, format_string, args); - print_string(slice); + const byte_count = format_va(&buffer, format_string, args); + print_string(buffer[0..byte_count]); } pub fn print(format_string: [*:0]const u8, ...) callconv(.C) void { - const args = @cVaStart(); + var args = @cVaStart(); vprint(format_string, &args); @cVaEnd(&args); } diff --git a/src/llvm.cpp b/src/llvm.cpp index 3736d45..c40de70 100644 --- a/src/llvm.cpp +++ b/src/llvm.cpp @@ -7,6 +7,7 @@ typedef uint64_t u64; #define EXPORT extern "C" #define fn static +#define array_length(arr) (sizeof(arr) / sizeof((arr)[0])) #include "llvm/Config/llvm-config.h" @@ -70,6 +71,11 @@ EXPORT void llvm_global_variable_add_debug_info(GlobalVariable& global_variable, global_variable.addDebugInfo(debug_global_variable); } +EXPORT void llvm_global_variable_delete(GlobalVariable* global_variable) +{ + delete global_variable; +} + EXPORT Function* llvm_module_create_function(Module* module, FunctionType* function_type, GlobalValue::LinkageTypes linkage_type, unsigned address_space, BBLLVMString name) { auto* function = Function::Create(function_type, linkage_type, address_space, name.string_ref(), module); @@ -102,6 +108,102 @@ EXPORT BasicBlock* llvm_context_create_basic_block(LLVMContext& context, BBLLVMS return basic_block; } +EXPORT bool llvm_value_has_one_use(Value& value) +{ + auto result = value.hasOneUse(); + return result; +} + +EXPORT Value* llvm_basic_block_user_begin(BasicBlock* basic_block) +{ + Value* value = *basic_block->user_begin(); + return value; +} + +EXPORT void llvm_basic_block_delete(BasicBlock* basic_block) +{ + delete basic_block; +} + +EXPORT BranchInst* llvm_value_to_branch(Value* value) +{ + auto* result = dyn_cast(value); + return result; +} + +// If there are multiple uses of the return-value slot, just check +// for something immediately preceding the IP. Sometimes this can +// happen with how we generate implicit-returns; it can also happen +// with noreturn cleanups. +fn StoreInst* get_store_if_valid(User* user, Value* return_alloca, Type* element_type) +{ + auto *SI = dyn_cast(user); + if (!SI || SI->getPointerOperand() != return_alloca || + SI->getValueOperand()->getType() != element_type) + return nullptr; + // These aren't actually possible for non-coerced returns, and we + // only care about non-coerced returns on this code path. + // All memory instructions inside __try block are volatile. + assert(!SI->isAtomic() && + (!SI->isVolatile() + //|| CGF.currentFunctionUsesSEHTry()) + )); + return SI; +} + +// copy of static llvm::StoreInst *findDominatingStoreToReturnValue(CodeGenFunction &CGF) { +// in clang/lib/CodeGen/CGCall.cpp:3526 in LLVM 19 +EXPORT StoreInst* llvm_find_return_value_dominating_store(IRBuilder<>& builder, Value* return_alloca, Type* element_type) +{ + // Check if a User is a store which pointerOperand is the ReturnValue. + // We are looking for stores to the ReturnValue, not for stores of the + // ReturnValue to some other location. + if (!return_alloca->hasOneUse()) { + llvm::BasicBlock *IP = builder.GetInsertBlock(); + if (IP->empty()) return nullptr; + + // Look at directly preceding instruction, skipping bitcasts and lifetime + // markers. + for (llvm::Instruction &I : make_range(IP->rbegin(), IP->rend())) { + if (isa(&I)) + continue; + if (auto *II = dyn_cast(&I)) + if (II->getIntrinsicID() == llvm::Intrinsic::lifetime_end) + continue; + + return get_store_if_valid(&I, return_alloca, element_type); + } + return nullptr; + } + + llvm::StoreInst *store = get_store_if_valid(return_alloca->user_back(), return_alloca, element_type); + if (!store) return nullptr; + + // Now do a first-and-dirty dominance check: just walk up the + // single-predecessors chain from the current insertion point. + llvm::BasicBlock *StoreBB = store->getParent(); + llvm::BasicBlock *IP = builder.GetInsertBlock(); + llvm::SmallPtrSet SeenBBs; + while (IP != StoreBB) { + if (!SeenBBs.insert(IP).second || !(IP = IP->getSinglePredecessor())) + return nullptr; + } + + // Okay, the store's basic block dominates the insertion point; we + // can do our thing. + return store; +} + +EXPORT bool llvm_value_use_empty(Value& value) +{ + return value.use_empty(); +} + +EXPORT bool llvm_basic_block_is_empty(BasicBlock& basic_block) +{ + return basic_block.empty(); +} + EXPORT AllocaInst* llvm_builder_create_alloca(IRBuilder<>& builder, Type* type, unsigned address_space, BBLLVMString name) { const DataLayout &data_layout = builder.GetInsertBlock()->getDataLayout(); @@ -109,6 +211,721 @@ EXPORT AllocaInst* llvm_builder_create_alloca(IRBuilder<>& builder, Type* type, return builder.Insert(new AllocaInst(type, address_space, 0, alignment), name.string_ref()); } +enum class BBLLVMAttributeFramePointerKind : u8 +{ + None = 0, + Reserved = 1, + NonLeaf = 2, + All = 3, +}; + +const unsigned BB_LLVM_ONLY_USED = 1U << 1; +const unsigned BB_LLVM_ONLY_GPR = 1U << 2; +const unsigned BB_LLVM_ONLY_ARG = 1U << 3; + +enum class BBLLVMZeroCallUsedRegsKind : unsigned int +{ + // Don't zero any call-used regs. + Skip = 1U << 0, + // Only zeros call-used GPRs used in the fn and pass args. + UsedGPRArg = BB_LLVM_ONLY_USED | BB_LLVM_ONLY_GPR | BB_LLVM_ONLY_ARG, + // Only zeros call-used GPRs used in the fn. + UsedGPR = BB_LLVM_ONLY_USED | BB_LLVM_ONLY_GPR, + // Only zeros call-used regs used in the fn and pass args. + UsedArg = BB_LLVM_ONLY_USED | BB_LLVM_ONLY_ARG, + // Only zeros call-used regs used in the fn. + Used = BB_LLVM_ONLY_USED, + // Zeros all call-used GPRs that pass args. + AllGPRArg = BB_LLVM_ONLY_GPR | BB_LLVM_ONLY_ARG, + // Zeros all call-used GPRs. + AllGPR = BB_LLVM_ONLY_GPR, + // Zeros all call-used regs that pass args. + AllArg = BB_LLVM_ONLY_ARG, + // Zeros all call-used regs. + All = 0, +}; + +enum class BBLLVMFPClassTest : unsigned +{ + None = 0, + + SNan = 0x0001, + QNan = 0x0002, + NegInf = 0x0004, + NegNormal = 0x0008, + NegSubnormal = 0x0010, + NegZero = 0x0020, + PosZero = 0x0040, + PosSubnormal = 0x0080, + PosNormal = 0x0100, + PosInf = 0x0200, + + Nan = SNan | QNan, + Inf = PosInf | NegInf, + Normal = PosNormal | NegNormal, + Subnormal = PosSubnormal | NegSubnormal, + Zero = PosZero | NegZero, + PosFinite = PosNormal | PosSubnormal | PosZero, + NegFinite = NegNormal | NegSubnormal | NegZero, + Finite = PosFinite | NegFinite, + Positive = PosFinite | PosInf, + Negative = NegFinite | NegInf, + + AllFlags = Nan | Inf | Finite, +}; + +enum class BBLLVMUWTableKind +{ + None = 0, ///< No unwind table requested + Sync = 1, ///< "Synchronous" unwind tables + Async = 2, ///< "Asynchronous" unwind tables (instr precise) + Default = 2, +}; + +struct BBLLVMArgumentAttributes +{ + Type* semantic_type; + Type* abi_type; + u64 dereferenceable_bytes; + u32 alignment; + u32 no_alias:1; + u32 non_null:1; + u32 no_undef:1; + u32 sign_extend:1; + u32 zero_extend:1; + u32 in_reg:1; + u32 no_fp_class:10; + u32 struct_return:1; + u32 writable:1; + u32 dead_on_unwind:1; + u32 in_alloca:1; + u32 dereferenceable:1; + u32 dereferenceable_or_null:1; + u32 nest:1; + u32 by_value:1; + u32 by_reference:1; + u32 no_capture:1; + u32 _:6; +}; + +static_assert(sizeof(BBLLVMArgumentAttributes) == 2 * sizeof(Type*) + 2 * sizeof(u64)); + +fn AttributeSet build_argument_attributes(LLVMContext& context, const BBLLVMArgumentAttributes& attributes) +{ + AttrBuilder builder(context); + + if (attributes.alignment) + { + builder.addAlignmentAttr(attributes.alignment); + } + + if (attributes.no_alias) + { + builder.addAttribute(Attribute::NoAlias); + } + + if (attributes.non_null) + { + builder.addAttribute(Attribute::NonNull); + } + + if (attributes.no_undef) + { + builder.addAttribute(Attribute::NoUndef); + } + + if (attributes.sign_extend) + { + builder.addAttribute(Attribute::SExt); + } + + if (attributes.zero_extend) + { + builder.addAttribute(Attribute::ZExt); + } + + if (attributes.in_reg) + { + builder.addAttribute(Attribute::InReg); + } + + if (attributes.no_fp_class) + { + __builtin_trap(); // TODO + } + + if (attributes.struct_return) + { + builder.addStructRetAttr(attributes.semantic_type); + } + + if (attributes.writable) + { + builder.addAttribute(Attribute::Writable); + } + + if (attributes.dead_on_unwind) + { + builder.addAttribute(Attribute::DeadOnUnwind); + } + + if (attributes.in_alloca) + { + __builtin_trap(); // TODO + } + + if (attributes.dereferenceable) + { + builder.addDereferenceableAttr(attributes.dereferenceable_bytes); + } + + if (attributes.dereferenceable_or_null) + { + builder.addDereferenceableOrNullAttr(attributes.dereferenceable_bytes); + } + + if (attributes.nest) + { + builder.addAttribute(Attribute::Nest); + } + + if (attributes.by_value) + { + builder.addByValAttr(attributes.semantic_type); + } + + if (attributes.by_reference) + { + builder.addByRefAttr(attributes.semantic_type); + } + + if (attributes.no_capture) + { + builder.addAttribute(Attribute::NoCapture); + } + + auto attribute_set = AttributeSet::get(context, builder); + return attribute_set; +} + +struct BBLLVMFunctionAttributesFlags0 +{ + u64 noreturn:1; + u64 cmse_ns_call:1; + u64 nounwind:1; + u64 returns_twice:1; + u64 cold:1; + u64 hot:1; + u64 no_duplicate:1; + u64 convergent:1; + u64 no_merge:1; + u64 will_return:1; + u64 no_caller_saved_registers:1; + u64 no_cf_check:1; + u64 no_callback:1; + u64 alloc_size:1; + u64 uniform_work_group_size:1; + u64 aarch64_pstate_sm_body:1; + u64 aarch64_pstate_sm_enabled:1; + u64 aarch64_pstate_sm_compatible:1; + u64 aarch64_preserves_za:1; + u64 aarch64_in_za:1; + u64 aarch64_out_za:1; + u64 aarch64_inout_za:1; + u64 aarch64_preserves_zt0:1; + u64 aarch64_in_zt0:1; + u64 aarch64_out_zt0:1; + u64 aarch64_inout_zt0:1; + u64 optimize_for_size:1; + u64 min_size:1; + u64 no_red_zone:1; + u64 indirect_tls_seg_refs:1; + u64 no_implicit_floats:1; + u64 sample_profile_suffix_elision_policy:1; + u64 memory_none:1; + u64 memory_readonly:1; + u64 memory_inaccessible_or_arg_memory_only:1; + u64 memory_arg_memory_only:1; + u64 strict_fp:1; + u64 no_inline:1; + u64 always_inline:1; + u64 guard_no_cf:1; + + // TODO: branch protection function attributes + // TODO: cpu features + + // Call-site begin + u64 call_no_builtins:1; + + u64 definition_frame_pointer_kind:2; + u64 definition_less_precise_fpmad:1; + u64 definition_null_pointer_is_valid:1; + u64 definition_no_trapping_fp_math:1; + u64 definition_no_infs_fp_math:1; + u64 definition_no_nans_fp_math:1; + u64 definition_approx_func_fp_math:1; + u64 definition_unsafe_fp_math:1; + u64 definition_use_soft_float:1; + u64 definition_no_signed_zeroes_fp_math:1; + u64 definition_stack_realignment:1; + u64 definition_backchain:1; + u64 definition_split_stack:1; + u64 definition_speculative_load_hardening:1; + u64 definition_zero_call_used_registers:4; + // TODO: denormal builtins + u64 definition_non_lazy_bind:1; + u64 definition_cmse_nonsecure_entry:1; + u64 definition_unwind_table_kind:2; +}; + +static_assert(sizeof(BBLLVMFunctionAttributesFlags0) == sizeof(u64)); + +struct BBLLVMFunctionAttributesFlags1 +{ + u64 definition_disable_tail_calls:1; + u64 definition_stack_protect_strong:1; + u64 definition_stack_protect:1; + u64 definition_stack_protect_req:1; + u64 definition_aarch64_new_za:1; + u64 definition_aarch64_new_zt0:1; + u64 definition_optimize_none:1; + u64 definition_naked:1; + u64 definition_inline_hint:1; + u64 _:55; +}; + +static_assert(sizeof(BBLLVMFunctionAttributesFlags1) == sizeof(u64)); + +struct BBLLVMFunctionAttributes +{ + BBLLVMString prefer_vector_width; + BBLLVMString stack_protector_buffer_size; + BBLLVMString definition_probe_stack; + BBLLVMString definition_stack_probe_size; + + BBLLVMFunctionAttributesFlags0 flags0; + BBLLVMFunctionAttributesFlags1 flags1; +}; + +static_assert(sizeof(BBLLVMFunctionAttributes) == 10 * sizeof(u64)); + +struct BBLLVMAttributeList +{ + BBLLVMFunctionAttributes function; + BBLLVMArgumentAttributes return_; + const BBLLVMArgumentAttributes* argument_pointer; + u64 argument_count; +}; + +static_assert(sizeof(BBLLVMAttributeList) == sizeof(BBLLVMFunctionAttributes) + sizeof(BBLLVMArgumentAttributes) + sizeof(void*) + sizeof(u64)); + +typedef void* BBLLVMAttributeListHandle; + +EXPORT BBLLVMAttributeListHandle llvm_attribute_list_build(LLVMContext& context, const BBLLVMAttributeList& attributes, bool call_site) +{ + AttrBuilder function_attribute_builder(context); + + if (attributes.function.prefer_vector_width.length) + { + function_attribute_builder.addAttribute("prefer-vector-width", attributes.function.prefer_vector_width.string_ref()); + } + + if (attributes.function.stack_protector_buffer_size.length) + { + function_attribute_builder.addAttribute("stack-protector-buffer-size", attributes.function.stack_protector_buffer_size.string_ref()); + } + + if (attributes.function.flags0.noreturn) + { + function_attribute_builder.addAttribute(Attribute::NoReturn); + } + + if (attributes.function.flags0.cmse_ns_call) + { + function_attribute_builder.addAttribute("cmse_nonsecure_call"); + } + + if (attributes.function.flags0.nounwind) + { + function_attribute_builder.addAttribute(Attribute::NoUnwind); + } + + if (attributes.function.flags0.returns_twice) + { + function_attribute_builder.addAttribute(Attribute::ReturnsTwice); + } + + if (attributes.function.flags0.cold) + { + function_attribute_builder.addAttribute(Attribute::Cold); + } + + if (attributes.function.flags0.hot) + { + function_attribute_builder.addAttribute(Attribute::Hot); + } + + if (attributes.function.flags0.no_duplicate) + { + function_attribute_builder.addAttribute(Attribute::NoDuplicate); + } + + if (attributes.function.flags0.convergent) + { + function_attribute_builder.addAttribute(Attribute::Convergent); + } + + if (attributes.function.flags0.no_merge) + { + function_attribute_builder.addAttribute(Attribute::NoMerge); + } + + if (attributes.function.flags0.will_return) + { + function_attribute_builder.addAttribute(Attribute::WillReturn); + } + + if (attributes.function.flags0.no_caller_saved_registers) + { + function_attribute_builder.addAttribute("no-caller-saved-registers"); + } + + if (attributes.function.flags0.no_cf_check) + { + function_attribute_builder.addAttribute(Attribute::NoCfCheck); + } + + if (attributes.function.flags0.no_callback) + { + function_attribute_builder.addAttribute(Attribute::NoCallback); + } + + if (attributes.function.flags0.alloc_size) + { + __builtin_trap(); // TODO + } + + if (attributes.function.flags0.uniform_work_group_size) + { + __builtin_trap(); // TODO + } + + if (attributes.function.flags0.aarch64_pstate_sm_body) + { + function_attribute_builder.addAttribute("aarch64_pstate_sm_body"); + } + + if (attributes.function.flags0.aarch64_pstate_sm_enabled) + { + function_attribute_builder.addAttribute("aarch64_pstate_sm_enabled"); + } + + if (attributes.function.flags0.aarch64_pstate_sm_compatible) + { + function_attribute_builder.addAttribute("aarch64_pstate_sm_compatible"); + } + + if (attributes.function.flags0.aarch64_preserves_za) + { + function_attribute_builder.addAttribute("aarch64_preserves_za"); + } + + if (attributes.function.flags0.aarch64_in_za) + { + function_attribute_builder.addAttribute("aarch64_in_za"); + } + + if (attributes.function.flags0.aarch64_out_za) + { + function_attribute_builder.addAttribute("aarch64_out_za"); + } + + if (attributes.function.flags0.aarch64_inout_za) + { + function_attribute_builder.addAttribute("aarch64_inout_za"); + } + + if (attributes.function.flags0.aarch64_preserves_zt0) + { + function_attribute_builder.addAttribute("aarch64_preserves_zt0"); + } + + if (attributes.function.flags0.aarch64_in_zt0) + { + function_attribute_builder.addAttribute("aarch64_in_zt0"); + } + + if (attributes.function.flags0.aarch64_out_zt0) + { + function_attribute_builder.addAttribute("aarch64_out_zt0"); + } + + if (attributes.function.flags0.aarch64_inout_zt0) + { + function_attribute_builder.addAttribute("aarch64_inout_zt0"); + } + + if (attributes.function.flags0.optimize_for_size) + { + function_attribute_builder.addAttribute(Attribute::OptimizeForSize); + } + + if (attributes.function.flags0.min_size) + { + function_attribute_builder.addAttribute(Attribute::MinSize); + } + + if (attributes.function.flags0.no_red_zone) + { + function_attribute_builder.addAttribute(Attribute::NoRedZone); + } + + if (attributes.function.flags0.indirect_tls_seg_refs) + { + function_attribute_builder.addAttribute("indirect-tls-seg-refs"); + } + + if (attributes.function.flags0.no_implicit_floats) + { + function_attribute_builder.addAttribute(Attribute::NoImplicitFloat); + } + + if (attributes.function.flags0.sample_profile_suffix_elision_policy) + { + function_attribute_builder.addAttribute("sample-profile-suffix-elision-policy", "selected"); + } + + if (attributes.function.flags0.memory_none) + { + function_attribute_builder.addMemoryAttr(llvm::MemoryEffects::none()); + } + + if (attributes.function.flags0.memory_readonly) + { + function_attribute_builder.addMemoryAttr(llvm::MemoryEffects::readOnly()); + } + + if (attributes.function.flags0.memory_inaccessible_or_arg_memory_only) + { + function_attribute_builder.addMemoryAttr(llvm::MemoryEffects::inaccessibleOrArgMemOnly()); + } + + if (attributes.function.flags0.memory_arg_memory_only) + { + Attribute attribute = function_attribute_builder.getAttribute(Attribute::Memory); + function_attribute_builder.addMemoryAttr(attribute.getMemoryEffects() | llvm::MemoryEffects::argMemOnly()); + } + + // TODO: branch protection function attributes + + // TODO: cpu features + + if (call_site) + { + if (attributes.function.flags0.call_no_builtins) + { + function_attribute_builder.addAttribute(Attribute::NoBuiltin); + } + } + else + { + if (attributes.function.definition_probe_stack.length) + { + function_attribute_builder.addAttribute("probe-stack", attributes.function.definition_probe_stack.string_ref()); + } + + if (attributes.function.definition_stack_probe_size.length) + { + function_attribute_builder.addAttribute("stack-probe-size", attributes.function.definition_stack_probe_size.string_ref()); + } + + StringRef frame_pointer_kind_name; + switch ((BBLLVMAttributeFramePointerKind) attributes.function.flags0.definition_frame_pointer_kind) + { + case BBLLVMAttributeFramePointerKind::None: frame_pointer_kind_name = "none"; break; + case BBLLVMAttributeFramePointerKind::Reserved: frame_pointer_kind_name = "reserved"; break; + case BBLLVMAttributeFramePointerKind::NonLeaf: frame_pointer_kind_name = "non-leaf"; break; + case BBLLVMAttributeFramePointerKind::All: frame_pointer_kind_name = "all"; break; + } + function_attribute_builder.addAttribute("frame-pointer", frame_pointer_kind_name); + + if (attributes.function.flags0.definition_less_precise_fpmad) + { + function_attribute_builder.addAttribute("less-precise-fp-mad", "true"); + } + + if (attributes.function.flags0.definition_null_pointer_is_valid) + { + function_attribute_builder.addAttribute(Attribute::NullPointerIsValid); + } + + if (attributes.function.flags0.definition_no_trapping_fp_math) + { + function_attribute_builder.addAttribute("no-trapping-math", "true"); + } + + if (attributes.function.flags0.definition_no_infs_fp_math) + { + function_attribute_builder.addAttribute("no-infs-fp-math", "true"); + } + + if (attributes.function.flags0.definition_no_nans_fp_math) + { + function_attribute_builder.addAttribute("no-nans-fp-math", "true"); + } + + if (attributes.function.flags0.definition_approx_func_fp_math) + { + function_attribute_builder.addAttribute("approx-func-fp-math", "true"); + } + + if (attributes.function.flags0.definition_unsafe_fp_math) + { + function_attribute_builder.addAttribute("unsafe-fp-math", "true"); + } + + if (attributes.function.flags0.definition_use_soft_float) + { + function_attribute_builder.addAttribute("use-soft-float", "true"); + } + + if (attributes.function.flags0.definition_no_signed_zeroes_fp_math) + { + function_attribute_builder.addAttribute("no-signed-zeros-fp-math", "true"); + } + + if (attributes.function.flags0.definition_stack_realignment) + { + function_attribute_builder.addAttribute("stackrealign"); + } + + if (attributes.function.flags0.definition_backchain) + { + function_attribute_builder.addAttribute("backchain"); + } + + if (attributes.function.flags0.definition_split_stack) + { + function_attribute_builder.addAttribute("split-stack"); + } + + if (attributes.function.flags0.definition_speculative_load_hardening) + { + function_attribute_builder.addAttribute("split-stack"); + } + + if (attributes.function.flags0.definition_zero_call_used_registers) + { + __builtin_trap(); // TODO + } + + // TODO: denormal builtins + + if (attributes.function.flags0.definition_non_lazy_bind) + { + function_attribute_builder.addAttribute(Attribute::NonLazyBind); + } + + if (attributes.function.flags0.definition_cmse_nonsecure_entry) + { + function_attribute_builder.addAttribute("cmse_nonsecure_entry"); + } + + UWTableKind unwind_table_kind; + switch ((BBLLVMUWTableKind)attributes.function.flags0.definition_unwind_table_kind) + { + case BBLLVMUWTableKind::None: unwind_table_kind = UWTableKind::None; break; + case BBLLVMUWTableKind::Sync: unwind_table_kind = UWTableKind::Sync; break; + case BBLLVMUWTableKind::Async: unwind_table_kind = UWTableKind::Async; break; + } + + function_attribute_builder.addUWTableAttr(unwind_table_kind); + + if (attributes.function.flags1.definition_disable_tail_calls) + { + function_attribute_builder.addAttribute("disable-tail-calls", "true"); + } + + if (attributes.function.flags1.definition_stack_protect_strong) + { + function_attribute_builder.addAttribute(Attribute::StackProtectStrong); + } + + if (attributes.function.flags1.definition_stack_protect) + { + function_attribute_builder.addAttribute(Attribute::StackProtect); + } + + if (attributes.function.flags1.definition_stack_protect_req) + { + function_attribute_builder.addAttribute(Attribute::StackProtectReq); + } + + if (attributes.function.flags1.definition_aarch64_new_za) + { + function_attribute_builder.addAttribute("aarch64_new_za"); + } + + if (attributes.function.flags1.definition_aarch64_new_zt0) + { + function_attribute_builder.addAttribute("aarch64_new_zt0"); + } + + if (attributes.function.flags1.definition_optimize_none) + { + function_attribute_builder.addAttribute(Attribute::OptimizeNone); + } + + if (attributes.function.flags1.definition_naked) + { + function_attribute_builder.addAttribute(Attribute::Naked); + } + + if (attributes.function.flags1.definition_inline_hint) + { + function_attribute_builder.addAttribute(Attribute::InlineHint); + } + } + + auto function_attributes = AttributeSet::get(context, function_attribute_builder); + + auto return_attributes = build_argument_attributes(context, attributes.return_); + + AttributeSet argument_attribute_buffer[128]; + assert(attributes.argument_count < array_length(argument_attribute_buffer)); + + for (u64 i = 0; i < attributes.argument_count; i += 1) + { + auto attribute_set = build_argument_attributes(context, attributes.argument_pointer[i]); + argument_attribute_buffer[i] = attribute_set; + } + + ArrayRef argument_attributes = ArrayRef(argument_attribute_buffer, attributes.argument_count); + + auto attribute_list = AttributeList::get(context, function_attributes, return_attributes, argument_attributes); + + static_assert(sizeof(AttributeList) == sizeof(uintptr_t)); + + return *(BBLLVMAttributeListHandle*)&attribute_list; +} + +EXPORT bool llvm_instruction_is_call_base(Instruction* instruction) +{ + return isa(instruction); +} + +EXPORT void llvm_function_set_attributes(Function& function, BBLLVMAttributeListHandle attribute_list_handle) +{ + auto attribute_list = *(AttributeList*)&attribute_list_handle; + function.setAttributes(attribute_list); +} + +EXPORT void llvm_call_base_set_attributes(CallBase& call, BBLLVMAttributeListHandle attribute_list_handle) +{ + auto attribute_list = *(AttributeList*)&attribute_list_handle; + call.setAttributes(attribute_list); +} + fn BBLLVMString stream_to_string(raw_string_ostream& stream) { // No need to call stream.flush(); because it's string-based @@ -150,7 +967,6 @@ EXPORT bool llvm_function_verify(Function& function, BBLLVMString* error_message return !result; } - EXPORT bool llvm_module_verify(const Module& module, BBLLVMString* error_message) { std::string message_buffer; @@ -396,7 +1212,7 @@ struct BBLLVMTargetOptions u64 no_nans_fp_math:1; u64 no_trapping_fp_math:1; u64 no_signed_zeroes_fp_math:1; - u64 approx_func_fp_match:1; + u64 approx_func_fp_math:1; u64 enable_aix_extended_altivec_abi:1; u64 honor_sign_dependent_rounding_fp_math:1; u64 no_zeroes_in_bss:1; @@ -525,7 +1341,7 @@ EXPORT TargetMachine* llvm_create_target_machine(const BBLLVMTargetMachineCreate target_options.NoNaNsFPMath = create.target_options.no_nans_fp_math; target_options.NoTrappingFPMath = create.target_options.no_trapping_fp_math; target_options.NoSignedZerosFPMath = create.target_options.no_signed_zeroes_fp_math; - target_options.ApproxFuncFPMath = create.target_options.approx_func_fp_match; + target_options.ApproxFuncFPMath = create.target_options.approx_func_fp_math; target_options.EnableAIXExtendedAltivecABI = create.target_options.enable_aix_extended_altivec_abi; target_options.HonorSignDependentRoundingFPMathOption = create.target_options.honor_sign_dependent_rounding_fp_math; target_options.NoZerosInBSS = create.target_options.no_zeroes_in_bss; diff --git a/src/llvm_api.zig b/src/llvm_api.zig index d65dea9..2a88e29 100644 --- a/src/llvm_api.zig +++ b/src/llvm_api.zig @@ -7,21 +7,39 @@ pub extern fn llvm_context_create_module(context: *llvm.Context, name: llvm.Stri pub extern fn LLVMContextCreate() *llvm.Context; pub extern fn LLVMCreateBuilderInContext(context: *llvm.Context) *llvm.Builder; -pub extern fn LLVMIsACallInst(value: *llvm.Value) ?*llvm.Instruction.Call; +pub extern fn LLVMGetOperand(value: *llvm.Value, index: c_uint) *llvm.Value; pub extern fn LLVMSetAlignment(value: *llvm.Value, alignment: c_uint) void; +pub extern fn llvm_instruction_is_call_base(instruction: *llvm.Instruction) bool; // Module pub extern fn llvm_module_create_global_variable(module: *llvm.Module, global_type: *llvm.Type, is_constant: bool, linkage: llvm.LinkageType, initial_value: *llvm.Constant, name: llvm.String, before: ?*llvm.GlobalVariable, thread_local_mode: llvm.ThreadLocalMode, address_space: c_uint, externally_initialized: bool) *llvm.GlobalVariable; pub extern fn llvm_module_create_function(module: *llvm.Module, function_type: *llvm.Type.Function, linkage_type: llvm.LinkageType, address_space: c_uint, name: llvm.String) *llvm.Function; -pub extern fn llvm_context_create_basic_block(context: *llvm.Context, name: llvm.String, parent: *llvm.Function) *llvm.BasicBlock; +pub extern fn llvm_context_create_basic_block(context: *llvm.Context, name: llvm.String, parent: ?*llvm.Function) *llvm.BasicBlock; +pub extern fn LLVMGetNextBasicBlock(basic_block: *llvm.BasicBlock) ?*llvm.BasicBlock; +pub extern fn LLVMDeleteBasicBlock(basic_block: *llvm.BasicBlock) void; +pub extern fn LLVMGetLastBasicBlock(function: *llvm.Function) *llvm.BasicBlock; +pub extern fn LLVMGetBasicBlockParent(basic_block: *llvm.BasicBlock) ?*llvm.BasicBlock; +pub extern fn LLVMAppendExistingBasicBlock(function: *llvm.Function, basic_block: *llvm.BasicBlock) void; +pub extern fn LLVMSetValueName2(value: *llvm.Value, name_pointer: [*]const u8, name_length: usize) void; +pub extern fn llvm_value_use_empty(value: *llvm.Value) bool; +pub extern fn llvm_value_has_one_use(value: *llvm.Value) bool; +pub extern fn llvm_value_to_branch(value: ?*llvm.Value) ?*llvm.Instruction.Branch; +pub extern fn LLVMReplaceAllUsesWith(old: *llvm.Value, new: *llvm.Value) void; + +pub extern fn LLVMGetSuccessor(branch: *llvm.Instruction.Branch, index: c_uint) *llvm.BasicBlock; +pub extern fn LLVMIsConditional(branch: *llvm.Instruction.Branch) bool; +pub extern fn LLVMGetInstructionParent(instruction: *llvm.Instruction) *llvm.BasicBlock; + +pub extern fn llvm_basic_block_is_empty(basic_block: *llvm.BasicBlock) bool; +pub extern fn llvm_basic_block_user_begin(basic_block: *llvm.BasicBlock) ?*llvm.Value; +pub extern fn llvm_basic_block_delete(basic_block: *llvm.BasicBlock) void; pub extern fn LLVMGetBasicBlockTerminator(basic_block: *llvm.BasicBlock) ?*llvm.Value; pub extern fn LLVMSetFunctionCallConv(function: *llvm.Function, calling_convention: llvm.CallingConvention) void; pub extern fn LLVMGetFunctionCallConv(function: *llvm.Function) llvm.CallingConvention; -pub extern fn LLVMSetInstructionCallConv(instruction: *llvm.Instruction.Call, calling_convention: llvm.CallingConvention) void; -pub extern fn LLVMGetInstructionCallConv(instruction: *llvm.Instruction.Call) llvm.CallingConvention; +pub extern fn LLVMSetInstructionCallConv(instruction: *llvm.Instruction.CallBase, calling_convention: llvm.CallingConvention) void; pub extern fn LLVMGetParams(function: *llvm.Function, argument_buffer: [*]*llvm.Argument) void; @@ -33,7 +51,13 @@ pub extern fn llvm_module_to_string(module: *llvm.Module) llvm.String; // Builder API pub extern fn LLVMPositionBuilderAtEnd(builder: *llvm.Builder, basic_block: *llvm.BasicBlock) void; -pub extern fn LLVMGetInsertBlock(builder: *llvm.Builder) *llvm.BasicBlock; +pub extern fn LLVMClearInsertionPosition(builder: *llvm.Builder) void; +pub extern fn LLVMGetInsertBlock(builder: *llvm.Builder) ?*llvm.BasicBlock; + +pub extern fn llvm_find_return_value_dominating_store(builder: *llvm.Builder, return_alloca: *llvm.Value, element_type: *llvm.Type) ?*llvm.Instruction.Store; + +pub extern fn LLVMDeleteInstruction(instruction: *llvm.Instruction) void; +pub extern fn LLVMInstructionEraseFromParent(instruction: *llvm.Instruction) void; pub extern fn LLVMBuildRet(builder: *llvm.Builder, value: ?*llvm.Value) void; pub extern fn LLVMBuildAdd(builder: *llvm.Builder, left: *llvm.Value, right: *llvm.Value, name: [*:0]const u8) *llvm.Value; @@ -50,6 +74,7 @@ pub extern fn LLVMBuildAnd(builder: *llvm.Builder, left: *llvm.Value, right: *ll pub extern fn LLVMBuildOr(builder: *llvm.Builder, left: *llvm.Value, right: *llvm.Value, name: [*:0]const u8) *llvm.Value; pub extern fn LLVMBuildXor(builder: *llvm.Builder, left: *llvm.Value, right: *llvm.Value, name: [*:0]const u8) *llvm.Value; pub extern fn LLVMBuildICmp(builder: *llvm.Builder, predicate: llvm.IntPredicate, left: *llvm.Value, right: *llvm.Value, name: [*:0]const u8) *llvm.Value; +pub extern fn LLVMBuildBr(builder: *llvm.Builder, block: *llvm.BasicBlock) *llvm.Value; pub extern fn LLVMBuildCondBr(builder: *llvm.Builder, condition: *llvm.Value, taken: *llvm.BasicBlock, not_taken: *llvm.BasicBlock) *llvm.Value; pub extern fn llvm_builder_create_alloca(builder: *llvm.Builder, ty: *llvm.Type, address_space: c_uint, name: llvm.String) *llvm.Value; @@ -60,9 +85,12 @@ pub extern fn LLVMBuildStructGEP2(builder: *llvm.Builder, struct_type: *llvm.Typ pub extern fn LLVMBuildInBoundsGEP2(builder: *llvm.Builder, ty: *llvm.Type, aggregate: *llvm.Value, index_pointer: [*]const *llvm.Value, index_count: c_uint, name: [*:0]const u8) *llvm.Value; pub extern fn LLVMBuildInsertValue(builder: *llvm.Builder, aggregate: *llvm.Value, element: *llvm.Value, index: c_uint, name: [*:0]const u8) *llvm.Value; +pub extern fn LLVMBuildExtractValue(builder: *llvm.Builder, aggregate: *llvm.Value, index: c_uint, name: [*:0]const u8) *llvm.Value; pub extern fn LLVMBuildUnreachable(builder: *llvm.Builder) *llvm.Value; pub extern fn LLVMBuildMemCpy(builder: *llvm.Builder, destination: *llvm.Value, destination_alignment: c_uint, source: *llvm.Value, source_alignment: c_uint, size: *llvm.Value) *llvm.Value; +pub extern fn LLVMBuildVAArg(builder: *llvm.Builder, va_list: *llvm.Value, arg_type: *llvm.Type, name: [*:0]const u8) *llvm.Value; + // Casts pub extern fn LLVMBuildZExt(builder: *llvm.Builder, value: *llvm.Value, destination_type: *llvm.Type, name: [*:0]const u8) *llvm.Value; pub extern fn LLVMBuildSExt(builder: *llvm.Builder, value: *llvm.Value, destination_type: *llvm.Type, name: [*:0]const u8) *llvm.Value; @@ -73,7 +101,13 @@ pub extern fn LLVMBuildTrunc(builder: *llvm.Builder, value: *llvm.Value, destina pub extern fn LLVMSetCurrentDebugLocation2(builder: *llvm.Builder, location: ?*llvm.DI.Location) void; pub extern fn LLVMTypeOf(value: *llvm.Value) *llvm.Type; +pub extern fn LLVMSizeOf(ty: *llvm.Type) *llvm.Constant; +pub extern fn LLVMAlignOf(ty: *llvm.Type) *llvm.Constant; pub extern fn LLVMGlobalGetValueType(value: *llvm.GlobalValue) *llvm.Type; +pub extern fn LLVMGetInitializer(global_variable: *llvm.GlobalVariable) *llvm.Constant; +pub extern fn LLVMSetInitializer(global_variable: *llvm.GlobalVariable, initializer: *llvm.Constant) void; +pub extern fn LLVMDeleteGlobal(global_variable: *llvm.GlobalVariable) void; +pub extern fn llvm_global_variable_delete(global_variable: *llvm.GlobalVariable) void; pub extern fn llvm_value_is_instruction(value: *llvm.Value) bool; // Intrinsics @@ -82,15 +116,19 @@ pub extern fn LLVMGetIntrinsicDeclaration(module: *llvm.Module, intrinsic_id: ll pub extern fn LLVMIntrinsicGetType(context: *llvm.Context, intrinsic_id: llvm.Intrinsic.Id, parameter_type_pointer: [*]const *llvm.Type, parameter_type_count: usize) *llvm.Type.Function; // Attributes -pub extern fn LLVMGetEnumAttributeKindForName(name_pointer: [*]const u8, name_length: usize) llvm.Attribute.Kind; +pub extern fn llvm_attribute_list_build(context: *llvm.Context, options: *const llvm.Attribute.List.Options, call_site: bool) *llvm.Attribute.List; +pub extern fn llvm_function_set_attributes(function: *llvm.Function, attribute_list: *llvm.Attribute.List) void; +pub extern fn llvm_call_base_set_attributes(function: *llvm.Instruction.CallBase, attribute_list: *llvm.Attribute.List) void; -pub extern fn LLVMCreateEnumAttribute(context: *llvm.Context, kind: llvm.Attribute.Kind, value: u64) *llvm.Attribute; -pub extern fn LLVMCreateTypeAttribute(context: *llvm.Context, kind: llvm.Attribute.Kind, ty: *llvm.Type) *llvm.Attribute; -pub extern fn LLVMCreateConstantRangeAttribute(context: *llvm.Context, kind: llvm.Attribute.Kind, bit_count: c_uint, lower_words: [*]const u64, upper_words: [*]const u64) *llvm.Attribute; -pub extern fn LLVMCreateStringAttribute(context: *llvm.Context, key_pointer: [*]const u8, key_length: c_uint, value_pointer: [*]const u8, value_length: usize) *llvm.Attribute; - -pub extern fn LLVMAddAttributeAtIndex(function: *llvm.Function, attribute_index: llvm.Attribute.Index, attribute: *llvm.Attribute) void; -pub extern fn LLVMAddCallSiteAttribute(call: *llvm.Instruction.Call, attribute_index: llvm.Attribute.Index, attribute: *llvm.Attribute) void; +// pub extern fn LLVMGetEnumAttributeKindForName(name_pointer: [*]const u8, name_length: usize) llvm.Attribute.Kind; +// +// pub extern fn LLVMCreateEnumAttribute(context: *llvm.Context, kind: llvm.Attribute.Kind, value: u64) *llvm.Attribute; +// pub extern fn LLVMCreateTypeAttribute(context: *llvm.Context, kind: llvm.Attribute.Kind, ty: *llvm.Type) *llvm.Attribute; +// pub extern fn LLVMCreateConstantRangeAttribute(context: *llvm.Context, kind: llvm.Attribute.Kind, bit_count: c_uint, lower_words: [*]const u64, upper_words: [*]const u64) *llvm.Attribute; +// pub extern fn LLVMCreateStringAttribute(context: *llvm.Context, key_pointer: [*]const u8, key_length: c_uint, value_pointer: [*]const u8, value_length: usize) *llvm.Attribute; +// +// pub extern fn LLVMAddAttributeAtIndex(function: *llvm.Function, attribute_index: llvm.Attribute.Index, attribute: *llvm.Attribute) void; +// pub extern fn LLVMAddCallSiteAttribute(call: *llvm.Instruction.Call, attribute_index: llvm.Attribute.Index, attribute: *llvm.Attribute) void; // TYPES // Types: integers @@ -115,7 +153,7 @@ pub extern fn LLVMFunctionType(return_type: *llvm.Type, parameter_type_pointer: pub extern fn LLVMIsFunctionVarArg(function_type: *llvm.Type.Function) Bool; pub extern fn LLVMGetReturnType(function_type: *llvm.Type.Function) *llvm.Type; pub extern fn LLVMSetSubprogram(function: *llvm.Function, subprogram: *llvm.DI.Subprogram) void; -pub extern fn LLVMGetSubprogram(function: *llvm.Function) *llvm.DI.Subprogram; +pub extern fn LLVMGetSubprogram(function: *llvm.Function) ?*llvm.DI.Subprogram; pub extern fn LLVMCountParamTypes(function_type: *llvm.Type.Function) c_uint; pub extern fn LLVMGetParamTypes(function_type: *llvm.Type.Function, types: [*]*llvm.Type) void; @@ -142,7 +180,11 @@ pub extern fn llvm_integer_type_get_bit_count(integer_type: *llvm.Type.Integer) // VALUES pub extern fn LLVMGetPoison(type: *llvm.Type) *llvm.Value; pub extern fn LLVMConstInt(type: *llvm.Type.Integer, value: c_ulonglong, sign_extend: Bool) *llvm.Constant.Integer; -pub extern fn LLVMConstArray2(element_type: *llvm.Type, value_pointer: [*]const *llvm.Constant, value_length: u64) *llvm.Constant.Array; +pub extern fn LLVMConstIntGetZExtValue(constant: *llvm.Constant) u64; +pub extern fn LLVMConstIntGetSExtValue(constant: *llvm.Constant) i64; +pub extern fn LLVMConstArray2(element_type: *llvm.Type, value_pointer: [*]const *llvm.Constant, value_length: u64) *llvm.Constant; +pub extern fn LLVMConstStructInContext(context: *llvm.Context, constant_value_pointer: [*]const *llvm.Constant, constant_value_count: c_uint, is_packed: c_uint) *llvm.Constant; +pub extern fn LLVMConstNamedStruct(struct_type: *llvm.Type.Struct, constant_value_pointer: [*]const *llvm.Constant, constant_value_count: c_uint) *llvm.Constant; pub extern fn LLVMGetValueKind(value: *llvm.Value) llvm.Value.Kind; pub extern fn LLVMIsConstant(value: *llvm.Value) Bool; diff --git a/src/main.zig b/src/main.zig index d8b79d0..d1b9635 100644 --- a/src/main.zig +++ b/src/main.zig @@ -45,7 +45,7 @@ pub fn main(argc: c_int, argv: [*:null]const ?[*:0]const u8) callconv(.C) c_int .build_mode = .debug_none, .content = file_content, .path = file_path, - .has_debug_info = true, + .has_debug_info = false, .target = converter.Target.get_native(), }); return 0; diff --git a/tests/c_abi.bbb b/tests/c_abi.bbb index 03bc042..8cd1dfd 100644 --- a/tests/c_abi.bbb +++ b/tests/c_abi.bbb @@ -280,7 +280,7 @@ require = fn (ok: u1) void require(x.a == 4); require(x.b == 155); - >res = c_modify_by_ref_param({ .val = 1, .arr = undefined, }); + >res = c_modify_by_ref_param({ .val = 1, .arr = [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 ] }); require(res.val == 42); >function_pointer = &c_func_ptr_byval; diff --git a/src/c_abi.c b/tests/c_abi.c similarity index 100% rename from src/c_abi.c rename to tests/c_abi.c diff --git a/tests/c_med_struct_ints.bbb b/tests/c_med_struct_ints.bbb new file mode 100644 index 0000000..01e366b --- /dev/null +++ b/tests/c_med_struct_ints.bbb @@ -0,0 +1,51 @@ +require = fn (ok: u1) void +{ + if (!ok) + { + #trap(); + } +} +MedStructInts = struct +{ + x: s32, + y: s32, + z: s32, +} + +bb_ret_med_struct_ints = fn [cc(c)] () MedStructInts +{ + return { + .x = 1, + .y = 2, + .z = 3, + }; +} + +c_med_struct_ints = fn [cc(c)] (s: MedStructInts) void +{ + require(s.x == 1); + require(s.y == 2); + require(s.z == 3); + + >s2 = bb_ret_med_struct_ints(); + + require(s2.x == 1); + require(s2.y == 2); + require(s2.z == 3); +} + +[export] main = fn [cc(c)] () s32 +{ + >med: MedStructInts = { + .x = 1, + .y = 2, + .z = 3, + }; + c_med_struct_ints(med); + >med2 = bb_ret_med_struct_ints(); + require(med2.x == 1); + require(med2.y == 2); + require(med2.z == 3); + + return 0; +} diff --git a/tests/c_split_struct_ints.bbb b/tests/c_split_struct_ints.bbb index 2746783..69a62d5 100644 --- a/tests/c_split_struct_ints.bbb +++ b/tests/c_split_struct_ints.bbb @@ -5,8 +5,19 @@ SplitStructInt = struct c: u32, } +require = fn (ok: u1) void +{ + if (!ok) + { + #trap(); + } +} + bb_split_struct_ints = fn [cc(c)] (x: SplitStructInt) void { + require(x.a == 1234); + require(x.b == 100); + require(x.c == 1337); } [export] main = fn [cc(c)] () s32 @@ -20,6 +31,6 @@ bb_split_struct_ints = fn [cc(c)] (x: SplitStructInt) void bb_split_struct_ints(split); >a: s32 = #truncate(split.a); >b: s32 = #extend(split.b); - >c: s32 = split.c; + >c: s32 = #extend(split.c); return a + b + 3 - c; } diff --git a/tests/if_no_else_void.bbb b/tests/if_no_else_void.bbb index 5c5701f..3875fda 100644 --- a/tests/if_no_else_void.bbb +++ b/tests/if_no_else_void.bbb @@ -1,4 +1,4 @@ -require = fn (ok: u1) void +require = fn [cc(c)] (ok: u1) void { if (!ok) { diff --git a/tests/indirect.bbb b/tests/indirect.bbb new file mode 100644 index 0000000..ff02f67 --- /dev/null +++ b/tests/indirect.bbb @@ -0,0 +1,45 @@ +require = fn (ok: u1) void +{ + if (!ok) + { + #trap(); + } +} + +S = struct +{ + a: u32, + b: u32, + c: u32, + d: u32, + e: u32, + f: u32, +} + +ret = fn [cc(c)] () S +{ + return { .a = 56, .b = 57, .c = 58, .d = 59, .e = 60, .f = 61 }; +} + +arg = fn [cc(c)] (s: S) void +{ + require(s.a == 56); + require(s.b == 57); + require(s.c == 58); + require(s.d == 59); + require(s.e == 60); + require(s.f == 61); +} + +[export] main = fn [cc(c)] () s32 +{ + >s = ret(); + require(s.a == 56); + require(s.b == 57); + require(s.c == 58); + require(s.d == 59); + require(s.e == 60); + require(s.f == 61); + arg(s); + return 0; +} diff --git a/tests/indirect_struct.bbb b/tests/indirect_struct.bbb new file mode 100644 index 0000000..8fcc69d --- /dev/null +++ b/tests/indirect_struct.bbb @@ -0,0 +1,46 @@ +Struct_u64_u64 = struct +{ + a: u64, + b: u64, +} + +require = fn (ok: u1) void +{ + if (!ok) + { + #trap(); + } +} + +c_struct_u64_u64_5 = fn [cc(c)] (a: u64, b: u64, c: u64, d: u64, e: u64, s: Struct_u64_u64) void +{ + require(s.a == 33); + require(s.b == 34); +} + +c_struct_u64_u64_6 = fn [cc(c)] (a: u64, b: u64, c: u64, d: u64, e: u64, f: u64, s: Struct_u64_u64) void +{ + require(s.a == 35); + require(s.b == 36); +} + +c_struct_u64_u64_7 = fn [cc(c)] (a: u64, b: u64, c: u64, d: u64, e: u64, f: u64, g: u64, s: Struct_u64_u64) void +{ + require(s.a == 37); + require(s.b == 38); +} + +c_struct_u64_u64_8 = fn [cc(c)] (a: u64, b: u64, c: u64, d: u64, e: u64, f: u64, g: u64, h: u64, s: Struct_u64_u64) void +{ + require(s.a == 39); + require(s.b == 40); +} + +[export] main = fn [cc(c)] () s32 +{ + c_struct_u64_u64_5(0, 0, 0, 0, 0, { .a = 33, .b = 34, }); + c_struct_u64_u64_6(0, 0, 0, 0, 0, 0, { .a = 35, .b = 36, }); + c_struct_u64_u64_7(0, 0, 0, 0, 0, 0, 0, { .a = 37, .b = 38, }); + c_struct_u64_u64_8(0, 0, 0, 0, 0, 0, 0, 0, { .a = 39, .b = 40, }); + return 0; +} diff --git a/tests/pointer.bbb b/tests/pointer.bbb index 6c93b4b..35e938e 100644 --- a/tests/pointer.bbb +++ b/tests/pointer.bbb @@ -7,5 +7,5 @@ modify = fn (v: &s32) void { >value: s32 = 0; modify(&value); - return #extend(value == 1); + return #extend(value == 0); } diff --git a/tests/small_struct_ints.bbb b/tests/small_struct_ints.bbb new file mode 100644 index 0000000..2e0e25a --- /dev/null +++ b/tests/small_struct_ints.bbb @@ -0,0 +1,47 @@ +SmallStructInts = struct +{ + a: u8, + b: u8, + c: u8, + d: u8, +} + +bb_ret_small_struct_ints = fn [cc(c)] () SmallStructInts +{ + return { + .a = 1, + .b = 2, + .c = 3, + .d = 4, + }; +} + +require = fn(ok: u1) void +{ + if (!ok) + { + #trap(); + } +} + +c_small_struct_ints = fn [cc(c)] (x: SmallStructInts) void +{ + require(x.a == 1); + require(x.b == 2); + require(x.c == 3); + require(x.d == 4); + + >y = bb_ret_small_struct_ints(); + + require(y.a == 1); + require(y.b == 2); + require(y.c == 3); + require(y.d == 4); +} + +[export] main = fn [cc(c)] () s32 +{ + >s: SmallStructInts = { .a = 1, .b = 2, .c = 3, .d = 4 }; + c_small_struct_ints(s); + return 0; +} diff --git a/tests/u1_return.bbb b/tests/u1_return.bbb new file mode 100644 index 0000000..b7dee5c --- /dev/null +++ b/tests/u1_return.bbb @@ -0,0 +1,11 @@ +foo = fn [cc(c)] () u1 +{ + >result: u1 = 0; + return result; +} + +[export] main = fn [cc(c)] () s32 +{ + >result = foo(); + return #extend(result); +} diff --git a/tests/varargs.bbb b/tests/varargs.bbb new file mode 100644 index 0000000..147a6ed --- /dev/null +++ b/tests/varargs.bbb @@ -0,0 +1,66 @@ +S = struct +{ + a: u32, + b: u32, + c: u64, + d: u64, + e: u64 +} + +require = fn (ok: u1) void +{ + if (!ok) + { + #trap(); + } +} + +va_arg_function = fn [cc(c)] (first_arg: u32, ...) void +{ + >va = #va_start(); + + >a = #va_arg(&va, u32); + >b = #va_arg(&va, S); + >c = #va_arg(&va, s64); + >d = #va_arg(&va, s32); + + require(first_arg == 123456789); + require(a == 123); + require(b.a == 1); + require(b.b == 2); + require(b.c == 3); + require(b.d == 4); + require(b.e == 5); + require(c == -1); + require(d == -2); + + #va_end(&va); +} + +S2 = struct +{ + a: u64, + b: u64, +} + +va_arg_function2 = fn [cc(c)] (...) void +{ + >va = #va_start(); + >s2 = #va_arg(&va, S2); + require(s2.a == 8); + require(s2.b == 9); + #va_end(&va); +} + +[export] main = fn [cc(c)] () s32 +{ + >first_arg: u32 = 123456789; + >a: u32 = 123; + >b: S = { .a = 1, .b = 2, .c = 3, .d = 4, .e = 5 }; + >c: s64 = -1; + >d: s32 = -2; + va_arg_function(first_arg, a, b, c, d); + >s2: S2 = { .a = 8, .b = 9 }; + va_arg_function2(s2); + return 0; +}