const lib = @import("lib.zig"); const assert = lib.assert; const builtin = lib.builtin; const Arena = lib.Arena; const llvm = @import("LLVM.zig"); pub const CPUArchitecture = enum { x86_64, }; pub const OperatingSystem = enum { linux, }; pub const BuildMode = enum { debug_none, debug_fast, debug_size, soft_optimize, optimize_for_speed, optimize_for_size, aggressively_optimize_for_speed, aggressively_optimize_for_size, fn is_optimized(build_mode: BuildMode) bool { return @intFromEnum(build_mode) >= @intFromEnum(BuildMode.soft_optimize); } fn to_llvm_ir(build_mode: BuildMode) llvm.OptimizationLevel { return switch (build_mode) { .debug_none => unreachable, .debug_fast, .debug_size => .O0, .soft_optimize => .O1, .optimize_for_speed => .O2, .optimize_for_size => .Os, .aggressively_optimize_for_speed => .O3, .aggressively_optimize_for_size => .Oz, }; } fn to_llvm_machine(build_mode: BuildMode) llvm.CodeGenerationOptimizationLevel { return switch (build_mode) { .debug_none => .none, .debug_fast, .debug_size => .none, .soft_optimize => .less, .optimize_for_speed => .default, .optimize_for_size => .default, .aggressively_optimize_for_speed => .aggressive, .aggressively_optimize_for_size => .aggressive, }; } }; pub const Target = struct { cpu: CPUArchitecture, os: OperatingSystem, pub fn get_native() Target { return Target{ .cpu = switch (builtin.cpu.arch) { .x86_64 => .x86_64, else => @compileError("CPU not supported"), }, .os = switch (builtin.os.tag) { .linux => .linux, else => @compileError("OS not supported"), }, }; } }; fn is_space(ch: u8) bool { return ((@intFromBool(ch == ' ') | @intFromBool(ch == '\n')) | ((@intFromBool(ch == '\t') | @intFromBool(ch == '\r')))) != 0; } const left_bracket = '['; const right_bracket = ']'; const left_brace = '{'; const right_brace = '}'; const left_parenthesis = '('; const right_parenthesis = ')'; const GlobalKeyword = enum { @"export", @"extern", }; fn is_identifier_start_ch(ch: u8) bool { return (ch >= 'a' and ch <= 'z') or (ch >= 'A' and ch <= 'Z') or ch == '_'; } fn is_decimal_ch(ch: u8) bool { return ch >= '0' and ch <= '9'; } fn is_identifier_ch(ch: u8) bool { return is_identifier_start_ch(ch) or is_decimal_ch(ch); } pub const Variable = struct { storage: ?*Value = null, initial_value: *Value, type: ?*Type, scope: *Scope, name: []const u8, line: u32, column: u32, fn resolve_type(variable: *Variable, auxiliary_type: *Type) void { const resolved_type: *Type = variable.type orelse auxiliary_type; variable.type = resolved_type; } }; pub const Local = struct { variable: Variable, is_argument: bool, pub const Buffer = struct { buffer: lib.VirtualBuffer(Local), pub fn initialize() Buffer { return .{ .buffer = .initialize(), }; } pub fn find_by_name(locals: *Buffer, name: []const u8) ?*Local { const slice = locals.buffer.get_slice(); for (slice) |*local| { if (lib.string.equal(local.variable.name, name)) { return local; } } else { return null; } } pub fn get_slice(locals: *Buffer) []Local { return locals.buffer.get_slice(); } pub fn add(locals: *Buffer) *Local { return locals.buffer.add(); } }; }; pub const Linkage = enum { external, internal, }; pub const Global = struct { variable: Variable, linkage: Linkage, pub const Buffer = struct { buffer: lib.VirtualBuffer(Global), pub fn get_slice(buffer: Buffer) []Global { return buffer.buffer.get_slice(); } pub fn initialize() Buffer { return .{ .buffer = .initialize(), }; } pub fn find_by_name(globals: *Buffer, name: []const u8) ?*Global { const slice = globals.buffer.get_slice(); for (slice) |*global| { if (lib.string.equal(global.variable.name, name)) { return global; } } else { return null; } } pub fn add(globals: *Buffer) *Global { return globals.buffer.add(); } }; }; pub const Type = struct { bb: union(enum) { void, noreturn, integer: Type.Integer, enumerator, float, bits, pointer: Type.Pointer, function: Type.Function, array, structure: Type.Struct, vector, }, name: []const u8, llvm: struct { handle: ?*llvm.Type = null, debug: ?*llvm.DI.Type = null, } = .{}, pub const Integer = struct { bit_count: u32, signed: bool, }; pub const Pointer = struct { type: *Type, alignment: u32, }; pub const Function = struct { semantic_return_type: *Type, semantic_argument_types: []const *Type, calling_convention: CallingConvention, is_var_args: bool, // Filled during codegen return_abi: Abi.Information = undefined, argument_abis: []Abi.Information = undefined, abi_argument_types: []*Type = undefined, abi_return_type: *Type = undefined, available_registers: Abi.RegisterCount = undefined, }; pub const Struct = struct { fields: []Field, }; pub const Buffer = struct { buffer: lib.VirtualBuffer(Type), pub fn initialize() Buffer { return .{ .buffer = .initialize(), }; } pub fn find_by_name(types: *Buffer, name: []const u8) ?*Type { const slice = types.buffer.get_slice(); for (slice) |*ty| { if (lib.string.equal(ty.name, name)) { return ty; } } else { return null; } } pub fn get(types: *Buffer, index: u64) *Type { const slice = types.get_slice(); assert(index < slice.len); return &slice[index]; } pub fn get_slice(types: *Buffer) []Type { const slice = types.buffer.get_slice(); return slice; } pub fn append(types: *Buffer, ty: Type) *Type { return types.buffer.append(ty); } }; 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, 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 => @trap(), // .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_byte_alignment(ty: *const Type) u32 { const result: u32 = switch (ty.bb) { .void => unreachable, .integer => |integer| @intCast(@min(@divExact(@max(8, lib.next_power_of_two(integer.bit_count)), 8), 16)), .pointer => 8, .function => 1, else => @trap(), }; return result; } pub fn get_bit_alignment(ty: *const Type) u32 { _ = ty; @trap(); } pub fn get_byte_size(ty: *const Type) u64 { const byte_size: u64 = switch (ty.bb) { .integer => |integer| @max(8, lib.next_power_of_two(integer.bit_count)), else => @trap(), }; return byte_size; } pub fn get_bit_size(ty: *const Type) u64 { _ = ty; @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 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 const EvaluationKind = enum { scalar, complex, aggregate, }; pub fn get_evaluation_kind(ty: *const Type) EvaluationKind { return switch (ty.bb) { .structure, .array => .aggregate, .integer, .bits, .pointer, .enumerator => .scalar, else => @trap(), }; } pub fn is_abi_equal(ty: *const Type, other: *const Type) bool { return ty == other or ty.llvm.handle.? == other.llvm.handle.?; } }; const ConstantInteger = struct { value: u64, signed: bool, }; pub const Statement = struct { bb: union(enum) { local: *Local, @"return": ?*Value, }, line: u32, column: u32, }; const Unary = struct { value: *Value, id: Id, const Id = enum { @"-", @"+", @"&", }; }; const Binary = struct { left: *Value, right: *Value, id: Id, const Id = enum { @"+", @"-", @"*", @"/", @"%", @"&", @"|", @"^", @"<<", @">>", @"==", @"!=", @">", @"<", @">=", @"<=", }; }; pub const Value = struct { bb: union(enum) { function: Function, constant_integer: ConstantInteger, unary: Unary, binary: Binary, variable_reference: *Variable, local, }, type: ?*Type = null, llvm: ?*llvm.Value = null, fn is_constant(value: *Value) bool { return switch (value.bb) { .constant_integer => true, else => @trap(), }; } fn negate_llvm(value: *Value) *llvm.Value { return switch (value.is_constant()) { true => value.llvm.?.to_constant().negate().to_value(), false => @trap(), }; } pub const Buffer = struct { buffer: lib.VirtualBuffer(Value), pub fn initialize() Buffer { return .{ .buffer = .initialize(), }; } pub fn add(values: *Buffer) *Value { return values.buffer.add(); } }; const Kind = enum { left, right, }; const Keyword = enum { undefined, @"unreachable", zero, }; const Intrinsic = enum { byte_size, cast, cast_to, extend, integer_max, int_from_enum, int_from_pointer, pointer_cast, select, trap, truncate, va_start, va_end, va_copy, va_arg, }; const Builder = struct { kind: Kind = .right, precedence: Precedence = .none, left: ?*Value = null, token: Token = .none, allow_assignment_operators: bool = false, fn with_token(vb: Builder, token: Token) Builder { var v = vb; v.token = token; return v; } fn with_precedence(vb: Builder, precedence: Precedence) Builder { var v = vb; v.precedence = precedence; return v; } fn with_left(vb: Builder, left: ?*Value) Builder { var v = vb; v.left = left; return v; } fn with_kind(vb: Builder, kind: Kind) Builder { var v = vb; v.kind = kind; return v; } }; }; const Precedence = enum { none, assignment, @"or", @"and", comparison, bitwise, shifting, add_like, div_like, prefix, aggregate_initialization, postfix, pub fn increment(precedence: Precedence) Precedence { return @enumFromInt(@intFromEnum(precedence) + 1); } }; const GlobalKind = enum { @"fn", @"struct", bits, @"enum", }; const CallingConvention = enum { c, pub fn to_llvm(calling_convention: CallingConvention) llvm.CallingConvention { return switch (calling_convention) { .c => .c, }; } pub fn resolve(calling_convention: CallingConvention, target: Target) ResolvedCallingConvention { return switch (calling_convention) { .c => switch (target.cpu) { .x86_64 => switch (target.os) { .linux => .system_v, }, }, }; } }; pub const Scope = struct { line: u32, column: u32, llvm: ?*llvm.DI.Scope = null, kind: Kind, parent: ?*Scope = null, pub const Kind = enum { global, function, local, }; }; pub const LexicalBlock = struct { locals: lib.VirtualBuffer(*Local), statements: lib.VirtualBuffer(*Statement), scope: Scope, }; pub const Function = struct { arguments: Local.Buffer, attributes: Attributes, main_block: *LexicalBlock, scope: Scope, return_alloca: ?*llvm.Value = null, exit_block: ?*llvm.BasicBlock = null, return_block: ?*llvm.BasicBlock = null, current_scope: ?*llvm.DI.Scope = null, return_pointer: ?*Value = null, const Keyword = enum { cc, }; const Attributes = struct { inline_behavior: enum { default, always_inline, no_inline, inline_hint, } = .default, naked: bool = false, }; }; pub const ResolvedCallingConvention = enum { system_v, win64, }; pub const IndexBuffer = lib.VirtualBuffer(u32); pub const Module = struct { arena: *Arena, content: []const u8, offset: u64, line_offset: u64, line_character_offset: u64, types: Type.Buffer, locals: Local.Buffer, globals: Global.Buffer, values: Value.Buffer, pointer_types: IndexBuffer, void_type: *Type, noreturn_type: *Type, lexical_blocks: lib.VirtualBuffer(LexicalBlock), statements: lib.VirtualBuffer(Statement), current_function: ?*Global = null, current_scope: *Scope, name: []const u8, path: []const u8, executable: [:0]const u8, objects: []const [:0]const u8, scope: Scope, llvm: LLVM = undefined, build_mode: BuildMode, target: Target, has_debug_info: bool, silent: bool, const LLVM = struct { context: *llvm.Context, module: *llvm.Module, builder: *llvm.Builder, di_builder: *llvm.DI.Builder, file: *llvm.DI.File, compile_unit: *llvm.DI.CompileUnit, }; pub fn integer_type(module: *Module, bit_count: u32, sign: bool) *Type { switch (bit_count) { 1...64 => { const index = @as(u64, @intFromBool(sign)) * 64 + bit_count; const result = module.types.get(index); assert(result.bb == .integer); assert(result.bb.integer.bit_count == bit_count); assert(result.bb.integer.signed == sign); return result; }, 128 => @trap(), else => @trap(), } } fn parse_decimal(noalias module: *Module) u64 { var value: u64 = 0; while (true) { const ch = module.content[module.offset]; if (!is_decimal_ch(ch)) { break; } module.offset += 1; value = lib.parse.accumulate_decimal(value, ch); } return value; } const AttributeBuildOptions = struct { return_type_abi: Abi.Information, abi_argument_types: []const *Type, argument_type_abis: []const Abi.Information, abi_return_type: *Type, attributes: Function.Attributes, call_site: bool, }; pub fn build_attribute_list(module: *Module, options: AttributeBuildOptions) *llvm.Attribute.List { const return_attributes = llvm.Attribute.Argument{ .semantic_type = options.return_type_abi.semantic_type.llvm.handle.?, .abi_type = options.abi_return_type.llvm.handle.?, .dereferenceable_bytes = 0, .alignment = 0, .flags = .{ .no_alias = false, .non_null = false, .no_undef = false, .sign_extend = options.return_type_abi.flags.kind == .extend and options.return_type_abi.flags.sign_extension, .zero_extend = options.return_type_abi.flags.kind == .extend and !options.return_type_abi.flags.sign_extension, .in_reg = false, .no_fp_class = .{}, .struct_return = false, .writable = false, .dead_on_unwind = false, .in_alloca = false, .dereferenceable = false, .dereferenceable_or_null = false, .nest = false, .by_value = false, .by_reference = false, .no_capture = false, }, }; var argument_attribute_buffer: [128]llvm.Attribute.Argument = undefined; const argument_attributes = argument_attribute_buffer[0..options.abi_argument_types.len]; if (options.return_type_abi.flags.kind == .indirect) { const abi_index = @intFromBool(options.return_type_abi.flags.sret_after_this); const argument_attribute = &argument_attributes[abi_index]; argument_attribute.* = .{ .semantic_type = options.return_type_abi.semantic_type.llvm.handle.?, .abi_type = options.abi_argument_types[abi_index].llvm.handle.?, .dereferenceable_bytes = 0, .alignment = options.return_type_abi.semantic_type.get_byte_alignment(), .flags = .{ .no_alias = true, .non_null = false, .no_undef = false, .sign_extend = false, .zero_extend = false, .in_reg = options.return_type_abi.flags.in_reg, .no_fp_class = .{}, .struct_return = true, .writable = true, .dead_on_unwind = true, .in_alloca = false, .dereferenceable = false, .dereferenceable_or_null = false, .nest = false, .by_value = false, .by_reference = false, .no_capture = false, }, }; } for (options.argument_type_abis) |argument_type_abi| { for (argument_type_abi.abi_start..argument_type_abi.abi_start + argument_type_abi.abi_count) |abi_index| { const argument_attribute = &argument_attributes[abi_index]; argument_attribute.* = .{ .semantic_type = argument_type_abi.semantic_type.llvm.handle.?, .abi_type = options.abi_argument_types[abi_index].llvm.handle.?, .dereferenceable_bytes = 0, .alignment = if (argument_type_abi.flags.kind == .indirect) 8 else 0, .flags = .{ .no_alias = false, .non_null = false, .no_undef = false, .sign_extend = argument_type_abi.flags.kind == .extend and argument_type_abi.flags.sign_extension, .zero_extend = argument_type_abi.flags.kind == .extend and !argument_type_abi.flags.sign_extension, .in_reg = argument_type_abi.flags.in_reg, .no_fp_class = .{}, .struct_return = false, .writable = false, .dead_on_unwind = false, .in_alloca = false, .dereferenceable = false, .dereferenceable_or_null = false, .nest = false, .by_value = argument_type_abi.flags.indirect_by_value, .by_reference = false, .no_capture = false, }, }; } } return llvm.Attribute.List.build(module.llvm.context, llvm.Attribute.Function{ .prefer_vector_width = llvm.String{}, .stack_protector_buffer_size = llvm.String{}, .definition_probe_stack = llvm.String{}, .definition_stack_probe_size = llvm.String{}, .flags0 = .{ .noreturn = options.return_type_abi.semantic_type == module.noreturn_type, .cmse_ns_call = false, .returns_twice = false, .cold = false, .hot = false, .no_duplicate = false, .convergent = false, .no_merge = false, .will_return = false, .no_caller_saved_registers = false, .no_cf_check = false, .no_callback = false, .alloc_size = false, // TODO .uniform_work_group_size = false, .nounwind = true, .aarch64_pstate_sm_body = false, .aarch64_pstate_sm_enabled = false, .aarch64_pstate_sm_compatible = false, .aarch64_preserves_za = false, .aarch64_in_za = false, .aarch64_out_za = false, .aarch64_inout_za = false, .aarch64_preserves_zt0 = false, .aarch64_in_zt0 = false, .aarch64_out_zt0 = false, .aarch64_inout_zt0 = false, .optimize_for_size = false, .min_size = false, .no_red_zone = false, .indirect_tls_seg_refs = false, .no_implicit_floats = false, .sample_profile_suffix_elision_policy = false, .memory_none = false, .memory_readonly = false, .memory_inaccessible_or_arg_memory_only = false, .memory_arg_memory_only = false, .strict_fp = false, .no_inline = options.attributes.inline_behavior == .no_inline, .always_inline = options.attributes.inline_behavior == .always_inline, .guard_no_cf = false, // TODO: branch protection function attributes // TODO: cpu features // CALL-SITE ATTRIBUTES .call_no_builtins = false, // DEFINITION-SITE ATTRIBUTES .definition_frame_pointer_kind = .none, .definition_less_precise_fpmad = false, .definition_null_pointer_is_valid = false, .definition_no_trapping_fp_math = false, .definition_no_infs_fp_math = false, .definition_no_nans_fp_math = false, .definition_approx_func_fp_math = false, .definition_unsafe_fp_math = false, .definition_use_soft_float = false, .definition_no_signed_zeroes_fp_math = false, .definition_stack_realignment = false, .definition_backchain = false, .definition_split_stack = false, .definition_speculative_load_hardening = false, .definition_zero_call_used_registers = .all, // TODO: denormal builtins .definition_non_lazy_bind = false, .definition_cmse_nonsecure_entry = false, .definition_unwind_table_kind = .none, }, .flags1 = .{ .definition_disable_tail_calls = false, .definition_stack_protect_strong = false, .definition_stack_protect = false, .definition_stack_protect_req = false, .definition_aarch64_new_za = false, .definition_aarch64_new_zt0 = false, .definition_optimize_none = false, .definition_naked = !options.call_site and options.attributes.naked, .definition_inline_hint = !options.call_site and options.attributes.inline_behavior == .inline_hint, }, }, return_attributes, argument_attributes, options.call_site); } const Pointer = struct { type: *Type, alignment: ?u32 = null, }; pub fn get_pointer_type(module: *Module, pointer: Pointer) *Type { const p = Type.Pointer{ .type = pointer.type, .alignment = if (pointer.alignment) |a| a else pointer.type.get_byte_alignment(), }; const all_types = module.types.get_slice(); const pointer_type = for (module.pointer_types.get_slice()) |pointer_type_index| { const ty = &all_types[pointer_type_index]; const pointer_type = &all_types[pointer_type_index].bb.pointer; if (pointer_type.type == p.type and pointer_type.alignment == p.alignment) { break ty; } } else blk: { const pointer_name = module.arena.join_string(&.{ "&", p.type.name }); const pointer_type = module.types.append(.{ .name = pointer_name, .bb = .{ .pointer = p, }, }); const index: u32 = @intCast(pointer_type - module.types.get_slice().ptr); _ = module.pointer_types.append(index); break :blk pointer_type; }; return pointer_type; } fn parse_type(module: *Module) *Type { const start_character = module.content[module.offset]; switch (start_character) { 'a'...'z', 'A'...'Z', '_' => { const identifier = module.parse_identifier(); var int_type = identifier.len > 1 and identifier[0] == 's' or identifier[0] == 'u'; if (int_type) { for (identifier[1..]) |ch| { int_type = int_type and is_decimal_ch(ch); } } if (int_type) { const signedness = switch (identifier[0]) { 's' => true, 'u' => false, else => unreachable, }; const bit_count: u32 = @intCast(lib.parse.integer_decimal(identifier[1..])); const ty = module.integer_type(bit_count, signedness); return ty; } else if (lib.string.equal(identifier, "noreturn")) { @trap(); // return module.noreturn_type; } else { const ty = module.types.find_by_name(identifier) orelse @trap(); return ty; } }, else => @trap(), } } fn consume_character_if_match(noalias module: *Module, expected_ch: u8) bool { var is_ch = false; if (module.offset < module.content.len) { const ch = module.content[module.offset]; is_ch = expected_ch == ch; module.offset += @intFromBool(is_ch); } return is_ch; } fn expect_character(noalias module: *Module, expected_ch: u8) void { if (!module.consume_character_if_match(expected_ch)) { module.report_error(); } } fn report_error(noalias module: *Module) noreturn { @branchHint(.cold); _ = module; lib.os.abort(); } fn get_line(module: *const Module) u32 { return @intCast(module.line_offset + 1); } fn get_column(module: *const Module) u32 { return @intCast(module.offset - module.line_character_offset + 1); } pub fn parse_identifier(noalias module: *Module) []const u8 { const start = module.offset; if (is_identifier_start_ch(module.content[start])) { module.offset += 1; while (module.offset < module.content.len) { if (is_identifier_ch(module.content[module.offset])) { module.offset += 1; } else { break; } } } if (module.offset - start == 0) { module.report_error(); } return module.content[start..module.offset]; } fn skip_space(noalias module: *Module) void { while (true) { const offset = module.offset; while (module.offset < module.content.len and is_space(module.content[module.offset])) { module.line_offset += @intFromBool(module.content[module.offset] == '\n'); module.line_character_offset = if (module.content[module.offset] == '\n') module.offset else module.line_character_offset; module.offset += 1; } if (module.offset + 1 < module.content.len) { const i = module.offset; const is_comment = module.content[i] == '/' and module.content[i + 1] == '/'; if (is_comment) { while (module.offset < module.content.len and module.content[module.offset] != '\n') { module.offset += 1; } if (module.offset < module.content.len) { module.line_offset += 1; module.line_character_offset = module.offset; module.offset += 1; } } } if (module.offset - offset == 0) { break; } } } const StatementStartKeyword = enum { @"_", @"return", @"if", // TODO: make `unreachable` a statement start keyword? @"while", }; const rules = blk: { var r: [@typeInfo(Token.Id).@"enum".fields.len]Rule = undefined; var count: u32 = 0; r[@intFromEnum(Token.Id.none)] = .{ .before = null, .after = null, .precedence = .none, }; count += 1; r[@intFromEnum(Token.Id.end_of_statement)] = .{ .before = null, .after = null, .precedence = .none, }; count += 1; r[@intFromEnum(Token.Id.identifier)] = .{ .before = &rule_before_identifier, .after = null, .precedence = .none, }; count += 1; r[@intFromEnum(Token.Id.value_keyword)] = .{ .before = &rule_before_value_keyword, .after = null, .precedence = .none, }; count += 1; r[@intFromEnum(Token.Id.value_intrinsic)] = .{ .before = &rule_before_value_intrinsic, .after = null, .precedence = .none, }; count += 1; r[@intFromEnum(Token.Id.integer)] = .{ .before = &rule_before_integer, .after = null, .precedence = .none, }; count += 1; const assignment_operators = [_]Token.Id{ .@"=", .@"+=", .@"-=", .@"*=", .@"/=", .@"%=", .@"&=", .@"|=", .@"^=", .@"<<=", .@">>=", }; for (assignment_operators) |assignment_operator| { r[@intFromEnum(assignment_operator)] = .{ .before = null, .after = rule_after_binary, .precedence = .assignment, }; count += 1; } const comparison_operators = [_]Token.Id{ .@"==", .@"!=", .@"<", .@">", .@"<=", .@">=", }; for (comparison_operators) |comparison_operator| { r[@intFromEnum(comparison_operator)] = .{ .before = null, .after = rule_after_binary, .precedence = .comparison, }; count += 1; } const and_operators = [_]Token.Id{ .@"and", .@"and?", }; for (and_operators) |and_operator| { r[@intFromEnum(and_operator)] = .{ .before = null, .after = rule_after_binary, .precedence = .@"or", }; count += 1; } const or_operators = [_]Token.Id{ .@"or", .@"or?", }; for (or_operators) |or_operator| { r[@intFromEnum(or_operator)] = .{ .before = null, .after = rule_after_binary, .precedence = .@"or", }; count += 1; } const add_like_operators = [_]Token.Id{ .@"+", .@"-", }; for (add_like_operators) |add_like_operator| { r[@intFromEnum(add_like_operator)] = .{ .before = rule_before_unary, .after = rule_after_binary, .precedence = .add_like, }; count += 1; } const div_like_operators = [_]Token.Id{ .@"*", .@"/", .@"%", }; for (div_like_operators) |div_like_operator| { r[@intFromEnum(div_like_operator)] = .{ .before = null, .after = rule_after_binary, .precedence = .div_like, }; count += 1; } r[@intFromEnum(Token.Id.@"&")] = .{ .before = rule_before_unary, .after = rule_after_binary, .precedence = .bitwise, }; count += 1; const bitwise_operators = [_]Token.Id{ .@"|", .@"^", }; for (bitwise_operators) |bitwise_operator| { r[@intFromEnum(bitwise_operator)] = .{ .before = null, .after = rule_after_binary, .precedence = .bitwise, }; count += 1; } const shifting_operators = [_]Token.Id{ .@"<<", .@">>", }; for (shifting_operators) |shifting_operator| { r[@intFromEnum(shifting_operator)] = .{ .before = null, .after = rule_after_binary, .precedence = .shifting, }; count += 1; } r[@intFromEnum(Token.Id.@".&")] = .{ .before = null, .after = rule_after_dereference, .precedence = .postfix, }; count += 1; r[@intFromEnum(Token.Id.@"(")] = .{ .before = rule_before_parenthesis, .after = rule_after_call, .precedence = .postfix, }; count += 1; r[@intFromEnum(Token.Id.@")")] = .{ .before = null, .after = null, .precedence = .none, }; count += 1; assert(count == r.len); break :blk r; }; fn tokenize(module: *Module) Token { module.skip_space(); const start_index = module.offset; if (start_index == module.content.len) { module.report_error(); } const start_character = module.content[start_index]; const result: Token = switch (start_character) { ';' => blk: { module.offset += 1; break :blk .end_of_statement; }, 'a'...'z', 'A'...'Z', '_' => blk: { assert(is_identifier_start_ch(start_character)); const identifier = module.parse_identifier(); const token: Token = if (lib.string.to_enum(Value.Keyword, identifier)) |value_keyword| .{ .value_keyword = value_keyword } else .{ .identifier = identifier }; break :blk token; }, '#' => if (is_identifier_start_ch(module.content[module.offset + 1])) blk: { module.offset += 1; const value_intrinsic_identifier = module.parse_identifier(); const value_intrinsic = lib.string.to_enum(Value.Intrinsic, value_intrinsic_identifier) orelse module.report_error(); break :blk .{ .value_intrinsic = value_intrinsic, }; } else { @trap(); }, '0' => blk: { const next_ch = module.content[start_index + 1]; const token_integer_kind: Token.Integer.Kind = switch (next_ch) { 'x' => .hexadecimal, 'o' => .octal, 'b' => .binary, else => .decimal, }; const value: u64 = switch (token_integer_kind) { .decimal => switch (next_ch) { 0...9 => module.report_error(), else => b: { module.offset += 1; break :b 0; }, }, else => @trap(), }; if (module.content[module.offset] == '.') { @trap(); } else { break :blk .{ .integer = .{ .value = value, .kind = token_integer_kind } }; } }, '1'...'9' => blk: { const decimal = module.parse_decimal(); if (module.content[module.offset] == '.') { @trap(); } else { break :blk .{ .integer = .{ .value = decimal, .kind = .decimal } }; } }, '+', '-', '*', '/', '%', '&', '|', '^' => |c| blk: { const next_ch = module.content[start_index + 1]; const token_id: Token.Id = switch (next_ch) { '=' => @trap(), else => switch (c) { '+' => .@"+", '-' => .@"-", '*' => .@"*", '/' => .@"/", '%' => .@"%", '&' => .@"&", '|' => .@"|", '^' => .@"^", else => unreachable, }, }; const token = switch (token_id) { else => unreachable, inline .@"+", .@"-", .@"*", .@"/", .@"%", .@"&", .@"|", .@"^", => |tid| @unionInit(Token, @tagName(tid), {}), }; module.offset += @as(u32, 1) + @intFromBool(next_ch == '='); break :blk token; }, '<' => blk: { const next_ch = module.content[start_index + 1]; const token_id: Token.Id = switch (next_ch) { '<' => switch (module.content[start_index + 2]) { '=' => .@"<<=", else => .@"<<", }, '=' => .@"<=", else => .@"<", }; module.offset += switch (token_id) { .@"<<=" => 3, .@"<<", .@"<=" => 2, .@"<" => 1, else => unreachable, }; const token = switch (token_id) { else => unreachable, inline .@"<<=", .@"<<", .@"<=", .@"<", => |tid| @unionInit(Token, @tagName(tid), {}), }; break :blk token; }, '>' => blk: { const next_ch = module.content[start_index + 1]; const token_id: Token.Id = switch (next_ch) { '>' => switch (module.content[start_index + 2]) { '=' => .@">>=", else => .@">>", }, '=' => .@">=", else => .@">", }; module.offset += switch (token_id) { .@">>=" => 3, .@">>", .@">=" => 2, .@">" => 1, else => unreachable, }; const token = switch (token_id) { else => unreachable, inline .@">>=", .@">>", .@">=", .@">", => |tid| @unionInit(Token, @tagName(tid), {}), }; break :blk token; }, '.' => blk: { const next_ch = module.content[start_index + 1]; const token_id: Token.Id = switch (next_ch) { '&' => .@".&", else => @trap(), }; module.offset += switch (token_id) { .@".&" => 2, else => @trap(), }; const token = switch (token_id) { else => unreachable, inline .@".&", => |tid| @unionInit(Token, @tagName(tid), {}), }; break :blk token; }, '=' => blk: { const next_ch = module.content[start_index + 1]; const token_id: Token.Id = switch (next_ch) { '=' => .@"==", else => .@"=", }; module.offset += switch (token_id) { .@"==" => 2, .@"=" => 1, else => @trap(), }; const token = switch (token_id) { else => unreachable, inline .@"==", .@"=" => |tid| @unionInit(Token, @tagName(tid), {}), }; break :blk token; }, '(' => blk: { module.offset += 1; break :blk .@"("; }, ')' => blk: { module.offset += 1; break :blk .@")"; }, else => @trap(), }; assert(start_index != module.offset); return result; } const Rule = struct { before: ?*const Rule.Function, after: ?*const Rule.Function, precedence: Precedence, const Function = fn (noalias module: *Module, value_builder: Value.Builder) *Value; }; fn parse_value(module: *Module, value_builder: Value.Builder) *Value { assert(value_builder.precedence == .none); assert(value_builder.left == null); const value = module.parse_precedence(value_builder.with_precedence(.assignment)); return value; } fn parse_precedence(module: *Module, value_builder: Value.Builder) *Value { assert(value_builder.token == .none); const token = module.tokenize(); const rule = &rules[@intFromEnum(token)]; if (rule.before) |before| { const left = before(module, value_builder.with_precedence(.none).with_token(token)); const result = module.parse_precedence_left(value_builder.with_left(left)); return result; } else { module.report_error(); } } fn parse_precedence_left(module: *Module, value_builder: Value.Builder) *Value { var result = value_builder.left; _ = &result; const precedence = value_builder.precedence; while (true) { const checkpoint = module.offset; const token = module.tokenize(); const token_rule = &rules[@intFromEnum(token)]; const token_precedence: Precedence = switch (token_rule.precedence) { .assignment => switch (value_builder.allow_assignment_operators) { true => .assignment, false => .none, }, else => |p| p, }; if (@intFromEnum(precedence) > @intFromEnum(token_precedence)) { module.offset = checkpoint; break; } const after_rule = token_rule.after orelse module.report_error(); const old = result; const new = after_rule(module, value_builder.with_token(token).with_precedence(.none).with_left(old)); result = new; } return result.?; } fn parse_block(module: *Module) *LexicalBlock { const parent_scope = module.current_scope; const block = module.lexical_blocks.append(.{ .statements = .initialize(), .locals = .initialize(), .scope = .{ .kind = .local, .parent = parent_scope, .line = module.get_line(), .column = module.get_column(), }, }); module.current_scope = &block.scope; defer module.current_scope = parent_scope; module.expect_character(left_brace); while (true) { module.skip_space(); if (module.offset == module.content.len) { break; } if (module.consume_character_if_match(right_brace)) { break; } const statement_line = module.get_line(); const statement_column = module.get_column(); const statement_start_character = module.content[module.offset]; const statement = module.statements.add(); const require_semicolon = true; statement.* = .{ .bb = switch (statement_start_character) { '>' => blk: { module.offset += 1; module.skip_space(); const local_name = module.parse_identifier(); module.skip_space(); const local_type: ?*Type = if (module.consume_character_if_match(':')) b: { module.skip_space(); const t = module.parse_type(); module.skip_space(); break :b t; } else null; module.expect_character('='); const local_value = module.parse_value(.{}); const local = module.locals.add(); local.* = .{ .variable = .{ .initial_value = local_value, .type = local_type, .name = local_name, .line = statement_line, .column = statement_column, .scope = module.current_scope, }, .is_argument = false, }; assert(module.current_scope == &block.scope); _ = block.locals.append(local); break :blk .{ .local = local, }; }, '#' => { @trap(); }, 'A'...'Z', 'a'...'z' => blk: { const statement_start_identifier = module.parse_identifier(); if (lib.string.to_enum(StatementStartKeyword, statement_start_identifier)) |statement_start_keyword| { switch (statement_start_keyword) { ._ => @trap(), .@"return" => break :blk .{ .@"return" = module.parse_value(.{}), }, .@"if" => { @trap(); }, .@"while" => { @trap(); }, } } @trap(); }, else => @trap(), }, .line = statement_line, .column = statement_column, }; _ = block.statements.append(statement); if (require_semicolon) { module.expect_character(';'); } } return block; } fn rule_before_identifier(noalias module: *Module, value_builder: Value.Builder) *Value { const identifier = value_builder.token.identifier; assert(!lib.string.equal(identifier, "")); assert(!lib.string.equal(identifier, "_")); var scope_it: ?*Scope = module.current_scope; const variable = blk: while (scope_it) |scope| : (scope_it = scope.parent) { switch (scope.kind) { .global => { @trap(); }, .function => { @trap(); }, .local => { const block: *LexicalBlock = @fieldParentPtr("scope", scope); for (block.locals.get_slice()) |local| { if (lib.string.equal(local.variable.name, identifier)) { break :blk &local.variable; } } }, } } else { module.report_error(); }; const value = module.values.add(); value.* = .{ .bb = .{ .variable_reference = variable, }, }; return value; } fn rule_before_value_keyword(noalias module: *Module, value_builder: Value.Builder) *Value { _ = value_builder; _ = module; @trap(); } fn rule_before_value_intrinsic(noalias module: *Module, value_builder: Value.Builder) *Value { _ = module; _ = value_builder; @trap(); } fn rule_before_integer(noalias module: *Module, value_builder: Value.Builder) *Value { const v = value_builder.token.integer.value; const value = module.values.add(); value.* = .{ .bb = .{ .constant_integer = .{ .value = v, .signed = false, }, }, }; return value; } fn rule_after_binary(noalias module: *Module, value_builder: Value.Builder) *Value { const binary_operator_token = value_builder.token; const binary_operator_token_precedence = rules[@intFromEnum(binary_operator_token)].precedence; const left = value_builder.left orelse module.report_error(); assert(binary_operator_token_precedence != .assignment); // TODO: this may be wrong. Assignment operator is not allowed in expressions const right_precedence = if (binary_operator_token_precedence == .assignment) .assignment else binary_operator_token_precedence.increment(); const right = module.parse_precedence(value_builder.with_precedence(right_precedence).with_token(.none).with_left(null)); const binary_operation_kind: Binary.Id = switch (binary_operator_token) { .none => unreachable, .@"+" => .@"+", .@"-" => .@"-", .@"*" => .@"*", .@"/" => .@"/", .@"%" => .@"%", .@"&" => .@"&", .@"|" => .@"|", .@"^" => .@"^", .@"<<" => .@"<<", .@">>" => .@">>", .@"==" => .@"==", .@"!=" => .@"!=", else => @trap(), }; const value = module.values.add(); value.* = .{ .bb = .{ .binary = .{ .left = left, .right = right, .id = binary_operation_kind, }, }, }; return value; } fn rule_before_unary(noalias module: *Module, value_builder: Value.Builder) *Value { assert(value_builder.left == null); const unary_token = value_builder.token; const unary_id: Unary.Id = switch (unary_token) { .none => unreachable, .@"-" => .@"-", .@"+" => .@"+", else => @trap(), }; const right = module.parse_precedence(value_builder.with_precedence(.prefix).with_token(.none).with_kind(if (unary_id == .@"&") .left else value_builder.kind)); const value = switch (unary_id) { .@"+" => @trap(), .@"-" => blk: { const value = module.values.add(); value.* = .{ .bb = .{ .unary = .{ .id = unary_id, .value = right, }, }, }; break :blk value; }, else => @trap(), }; return value; } fn rule_after_dereference(noalias module: *Module, value_builder: Value.Builder) *Value { _ = module; _ = value_builder; @trap(); } fn rule_before_parenthesis(noalias module: *Module, value_builder: Value.Builder) *Value { _ = module; _ = value_builder; @trap(); } fn rule_after_call(noalias module: *Module, value_builder: Value.Builder) *Value { _ = module; _ = value_builder; @trap(); } pub fn get_anonymous_struct_pair(module: *Module, pair: [2]*Type) *Type { _ = module; _ = pair; @trap(); } pub fn parse(module: *Module) void { while (true) { module.skip_space(); if (module.offset == module.content.len) { break; } var is_export = false; var is_extern = false; const global_line = module.get_line(); const global_column = module.get_column(); if (module.consume_character_if_match(left_bracket)) { while (module.offset < module.content.len) { const global_keyword_string = module.parse_identifier(); const global_keyword = lib.string.to_enum(GlobalKeyword, global_keyword_string) orelse module.report_error(); switch (global_keyword) { .@"export" => is_export = true, .@"extern" => is_extern = true, } switch (module.content[module.offset]) { right_bracket => break, else => module.report_error(), } } module.expect_character(right_bracket); module.skip_space(); } const global_name = module.parse_identifier(); if (module.types.find_by_name(global_name) != null) { module.report_error(); } if (module.globals.find_by_name(global_name) != null) { module.report_error(); } module.skip_space(); var global_type: ?*Type = null; if (module.consume_character_if_match(':')) { module.skip_space(); global_type = module.parse_type(); module.skip_space(); } module.expect_character('='); module.skip_space(); var global_keyword = false; if (is_identifier_start_ch(module.content[module.offset])) { const global_string = module.parse_identifier(); module.skip_space(); if (lib.string.to_enum(GlobalKind, global_string)) |global_kind| { global_keyword = true; switch (global_kind) { .@"fn" => { var calling_convention = CallingConvention.c; const function_attributes = Function.Attributes{}; _ = function_attributes; var is_var_args = false; _ = &is_var_args; if (module.consume_character_if_match(left_bracket)) { while (module.offset < module.content.len) { const function_identifier = module.parse_identifier(); const function_keyword = lib.string.to_enum(Function.Keyword, function_identifier) orelse module.report_error(); module.skip_space(); switch (function_keyword) { .cc => { module.expect_character(left_parenthesis); module.skip_space(); const calling_convention_string = module.parse_identifier(); calling_convention = lib.string.to_enum(CallingConvention, calling_convention_string) orelse module.report_error(); module.skip_space(); module.expect_character(right_parenthesis); }, } module.skip_space(); switch (module.content[module.offset]) { right_bracket => break, else => module.report_error(), } } module.expect_character(right_bracket); } module.skip_space(); module.expect_character(left_parenthesis); var semantic_argument_count: u32 = 0; while (module.offset < module.content.len and module.content[module.offset] != right_parenthesis) : (semantic_argument_count += 1) { module.skip_space(); @trap(); } module.expect_character(right_parenthesis); module.skip_space(); const return_type = module.parse_type(); // TODO assert(semantic_argument_count == 0); const argument_types: []const *Type = &.{}; module.skip_space(); const is_declaration = module.consume_character_if_match(';'); const function_type = module.types.append(.{ .bb = .{ .function = .{ .semantic_return_type = return_type, .semantic_argument_types = argument_types, .calling_convention = calling_convention, .is_var_args = is_var_args, }, }, .name = "", }); const storage = module.values.add(); storage.* = .{ .bb = undefined, .type = module.get_pointer_type(.{ .type = function_type, }), }; const global = module.globals.add(); global.* = .{ .variable = .{ .storage = storage, .initial_value = undefined, .type = function_type, .name = global_name, .line = global_line, .column = global_column, .scope = &module.scope, }, .linkage = if (is_export or is_extern) .external else .internal, }; module.current_function = global; defer module.current_function = null; if (!is_declaration) { storage.bb = .{ .function = .{ .main_block = undefined, .arguments = .initialize(), .attributes = .{}, .scope = .{ .kind = .function, .line = global_line, .column = global_column, }, }, }; const global_scope = module.current_scope; module.current_scope = &storage.bb.function.scope; defer module.current_scope = global_scope; storage.bb.function.main_block = module.parse_block(); } else { // TODO: initialize value.bb @trap(); } }, else => @trap(), } } else { module.offset -= global_string.len; } } } } fn initialize_llvm(module: *Module) void { llvm.default_initialize(); const context = llvm.Context.create(); const m = context.create_module(module.name); const di_builder = if (module.has_debug_info) m.create_di_builder() else undefined; var compile_unit: *llvm.DI.CompileUnit = undefined; const file = if (module.has_debug_info) blk: { const index = lib.string.last_character(module.path, '/') orelse lib.os.abort(); const directory = module.path[0..index]; const file_name = module.path[index + 1 ..]; const file = di_builder.create_file(file_name, directory); compile_unit = di_builder.create_compile_unit(file, module.build_mode.is_optimized()); module.scope.llvm = compile_unit.to_scope(); break :blk file; } else undefined; module.llvm = LLVM{ .context = context, .module = m, .builder = context.create_builder(), .di_builder = di_builder, .file = file, .compile_unit = compile_unit, }; } pub fn emit_block(module: *Module, function: *Global, block: *llvm.BasicBlock) void { const maybe_current_block = module.llvm.builder.get_insert_block(); var emit_branch = false; if (maybe_current_block) |current_block| { emit_branch = current_block.get_terminator() == null; } if (emit_branch) { _ = module.llvm.builder.create_branch(block); } if (maybe_current_block != null and maybe_current_block.?.get_parent() != null) { module.llvm.builder.insert_basic_block_after_insert_block(block); } else { function.variable.storage.?.llvm.?.to_function().append_basic_block(block); } module.llvm.builder.position_at_end(block); } pub fn emit(module: *Module) void { module.initialize_llvm(); const llvm_pointer_type = module.llvm.context.get_pointer_type(0).to_type(); const void_type = module.llvm.context.get_void_type(); for (module.types.get_slice()) |*ty| { const llvm_type = switch (ty.bb) { .void, .noreturn => void_type, .integer => |integer| module.llvm.context.get_integer_type(integer.bit_count).to_type(), // Consider function types later since we need to deal with ABI .function => null, .pointer => llvm_pointer_type, else => @trap(), }; ty.llvm.handle = llvm_type; if (module.has_debug_info) { const debug_type = switch (ty.bb) { .void => module.llvm.di_builder.create_basic_type(ty.name, 0, .void, .{}), .noreturn => module.llvm.di_builder.create_basic_type(ty.name, 0, .void, .{ .no_return = true }), .integer => |integer| module.llvm.di_builder.create_basic_type(ty.name, integer.bit_count, switch (integer.signed) { true => .signed, false => .unsigned, }, .{}), .function => |function| blk: { var debug_argument_type_buffer: [64]*llvm.DI.Type = undefined; const semantic_debug_argument_types = debug_argument_type_buffer[0 .. function.semantic_argument_types.len + 1 + @intFromBool(function.is_var_args)]; semantic_debug_argument_types[0] = function.semantic_return_type.llvm.debug.?; for (function.semantic_argument_types, semantic_debug_argument_types[1..][0..function.semantic_argument_types.len]) |semantic_type, *debug_argument_type| { debug_argument_type.* = semantic_type.llvm.debug.?; } if (function.is_var_args) { semantic_debug_argument_types[function.semantic_argument_types.len + 1] = module.void_type.llvm.debug.?; } const subroutine_type = module.llvm.di_builder.create_subroutine_type(module.llvm.file, semantic_debug_argument_types, .{}); break :blk subroutine_type.to_type(); }, .pointer => |pointer| module.llvm.di_builder.create_pointer_type(pointer.type.llvm.debug.?, 64, 64, 0, ty.name).to_type(), else => @trap(), }; ty.llvm.debug = debug_type; } } for (module.globals.get_slice()) |*global| { switch (global.variable.storage.?.bb) { .function => { const function_type = &global.variable.storage.?.type.?.bb.pointer.type.bb.function; const argument_variables = global.variable.storage.?.bb.function.arguments.get_slice(); function_type.argument_abis = module.arena.allocate(Abi.Information, argument_variables.len); const resolved_calling_convention = function_type.calling_convention.resolve(module.target); const is_reg_call = resolved_calling_convention == .system_v and false; // TODO: regcall calling_convention var llvm_abi_argument_type_buffer: [64]*llvm.Type = undefined; var abi_argument_type_buffer: [64]*Type = undefined; var abi_argument_type_count: u16 = 0; switch (resolved_calling_convention) { .system_v => { function_type.available_registers = 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(), }; function_type.return_abi = Abi.SystemV.classify_return_type(module, function_type.semantic_return_type); const return_abi_kind = function_type.return_abi.flags.kind; function_type.abi_return_type = switch (return_abi_kind) { .direct, .extend => function_type.return_abi.coerce_to_type.?, .ignore, .indirect => module.void_type, else => |t| @panic(@tagName(t)), }; if (function_type.return_abi.flags.kind == .indirect) { assert(!function_type.return_abi.flags.sret_after_this); function_type.available_registers.system_v.gpr -= 1; const indirect_type = module.get_pointer_type(.{ .type = function_type.return_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 = function_type.semantic_argument_types.len; for (function_type.argument_abis, function_type.semantic_argument_types, 0..) |*argument_type_abi, semantic_argument_type, semantic_argument_index| { const is_named_argument = semantic_argument_index < required_arguments; assert(is_named_argument); argument_type_abi.* = Abi.SystemV.classify_argument(module, &function_type.available_registers, &llvm_abi_argument_type_buffer, &abi_argument_type_buffer, .{ .type = semantic_argument_type, .abi_start = abi_argument_type_count, .is_named_argument = is_named_argument, }); abi_argument_type_count += argument_type_abi.abi_count; } function_type.abi_argument_types = module.arena.allocate(*Type, abi_argument_type_count); @memcpy(function_type.abi_argument_types, abi_argument_type_buffer[0..function_type.abi_argument_types.len]); }, .win64 => { @trap(); }, } const llvm_abi_argument_types = llvm_abi_argument_type_buffer[0..abi_argument_type_count]; const llvm_function_type = llvm.Type.Function.get(function_type.abi_return_type.llvm.handle.?, llvm_abi_argument_types, function_type.is_var_args); const subroutine_type_flags = llvm.DI.Flags{}; const subroutine_type = if (module.has_debug_info) blk: { var debug_argument_type_buffer: [64 + 1]*llvm.DI.Type = undefined; const semantic_debug_argument_types = debug_argument_type_buffer[0 .. function_type.argument_abis.len + 1 + @intFromBool(function_type.is_var_args)]; semantic_debug_argument_types[0] = function_type.return_abi.semantic_type.llvm.debug.?; for (function_type.argument_abis, semantic_debug_argument_types[1..][0..function_type.argument_abis.len]) |argument_abi, *debug_argument_type| { debug_argument_type.* = argument_abi.semantic_type.llvm.debug.?; } if (function_type.is_var_args) { semantic_debug_argument_types[function_type.argument_abis.len + 1] = module.void_type.llvm.debug.?; } const subroutine_type = module.llvm.di_builder.create_subroutine_type(module.llvm.file, semantic_debug_argument_types, subroutine_type_flags); break :blk subroutine_type; } else undefined; global.variable.storage.?.type.?.bb.pointer.type.llvm.handle = llvm_function_type.to_type(); global.variable.storage.?.type.?.bb.pointer.type.llvm.debug = subroutine_type.to_type(); const llvm_function_value = module.llvm.module.create_function(.{ .name = global.variable.name, // TODO: make it better .linkage = switch (global.linkage) { .external => .ExternalLinkage, .internal => .InternalLinkage, }, .type = global.variable.storage.?.type.?.bb.pointer.type.llvm.handle.?.to_function(), }); global.variable.storage.?.llvm = llvm_function_value.to_value(); llvm_function_value.set_calling_convention(function_type.calling_convention.to_llvm()); const attribute_list = module.build_attribute_list(.{ .abi_return_type = function_type.abi_return_type, .abi_argument_types = function_type.abi_argument_types, .argument_type_abis = function_type.argument_abis, .return_type_abi = function_type.return_abi, .attributes = global.variable.storage.?.bb.function.attributes, .call_site = false, }); llvm_function_value.set_attributes(attribute_list); const function_scope: *llvm.DI.Scope = if (module.has_debug_info) blk: { const scope_line: u32 = @intCast(module.line_offset + 1); const local_to_unit = switch (global.linkage) { .internal => true, .external => false, }; const flags = llvm.DI.Flags{}; const is_definition = switch (global.variable.storage.?.bb) { .function => true, else => @trap(), }; const name = global.variable.name; const linkage_name = name; const subprogram = module.llvm.di_builder.create_function(module.scope.llvm.?, name, linkage_name, module.llvm.file, global.variable.line, subroutine_type, local_to_unit, is_definition, scope_line, flags, module.build_mode.is_optimized()); llvm_function_value.set_subprogram(subprogram); break :blk @ptrCast(subprogram); } else undefined; global.variable.storage.?.bb.function.scope.llvm = function_scope; const entry_block = module.llvm.context.create_basic_block("entry", llvm_function_value); global.variable.storage.?.bb.function.return_block = module.llvm.context.create_basic_block("ret_block", null); module.llvm.builder.position_at_end(entry_block); module.llvm.builder.set_current_debug_location(null); var llvm_abi_argument_buffer: [64]*llvm.Argument = undefined; llvm_function_value.get_arguments(&llvm_abi_argument_buffer); const llvm_abi_arguments = llvm_abi_argument_buffer[0..function_type.abi_argument_types.len]; const return_abi_kind = function_type.return_abi.flags.kind; switch (return_abi_kind) { .ignore => {}, .indirect => { const indirect_argument_index = @intFromBool(function_type.return_abi.flags.sret_after_this); if (function_type.return_abi.flags.sret_after_this) { @trap(); } global.variable.storage.?.bb.function.return_alloca = llvm_abi_arguments[indirect_argument_index].to_value(); if (!function_type.return_abi.flags.indirect_by_value) { @trap(); } }, .in_alloca => { @trap(); }, else => { const alloca = module.create_alloca(.{ .type = function_type.return_abi.semantic_type, .name = "retval" }); global.variable.storage.?.bb.function.return_alloca = alloca; }, } // const argument_variables = global.value.bb.function.arguments.add_many(semantic_argument_count); for ( //semantic_arguments, function_type.argument_abis, argument_variables, 0..) | //semantic_argument, argument_abi, *argument_variable, argument_index| { const abi_arguments = llvm_abi_arguments[argument_abi.abi_start..][0..argument_abi.abi_count]; assert(argument_abi.flags.kind == .ignore or argument_abi.abi_count != 0); const argument_abi_kind = argument_abi.flags.kind; const semantic_argument_storage = switch (argument_abi_kind) { .direct, .extend => blk: { const first_argument = abi_arguments[0]; const coerce_to_type = argument_abi.get_coerce_to_type(); if (coerce_to_type.bb != .structure and coerce_to_type.is_abi_equal(argument_abi.semantic_type) and argument_abi.attributes.direct.offset == 0) { assert(argument_abi.abi_count == 1); const is_promoted = false; var v = first_argument.to_value(); v = switch (coerce_to_type.llvm.handle == v.get_type()) { true => v, false => @trap(), }; if (is_promoted) { @trap(); } switch (argument_abi.semantic_type.is_arbitrary_bit_integer()) { true => { const bit_count = argument_abi.semantic_type.get_bit_size(); const abi_bit_count: u32 = @intCast(@max(8, lib.next_power_of_two(bit_count))); const is_signed = argument_abi.semantic_type.is_signed(); const destination_type = module.align_integer_type(argument_abi.semantic_type); const alloca = module.create_alloca(.{ .type = destination_type, .name = argument_variable.variable.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 = argument_variable.variable.name }); _ = module.create_store(.{ .source_value = first_argument.to_value(), .destination_value = alloca, .source_type = argument_abi.semantic_type, .destination_type = argument_abi.semantic_type }); break :blk alloca; }, } } else { const is_fixed_vector_type = false; if (is_fixed_vector_type) { @trap(); } if (coerce_to_type.bb == .structure and coerce_to_type.bb.structure.fields.len > 1 and argument_abi.flags.kind == .direct and !argument_abi.flags.can_be_flattened) { const contains_homogeneous_scalable_vector_types = false; if (contains_homogeneous_scalable_vector_types) { @trap(); } } const alloca = module.create_alloca(.{ .type = argument_abi.semantic_type }); const pointer = switch (argument_abi.attributes.direct.offset > 0) { true => @trap(), false => alloca, }; const pointer_type = switch (argument_abi.attributes.direct.offset > 0) { true => @trap(), false => argument_abi.semantic_type, }; if (coerce_to_type.bb == .structure and coerce_to_type.bb.structure.fields.len > 1 and argument_abi.flags.kind == .direct and argument_abi.flags.can_be_flattened) { const struct_size = coerce_to_type.get_byte_size(); const pointer_element_size = pointer_type.get_byte_size(); // TODO: fix const is_scalable = false; switch (is_scalable) { true => @trap(), false => { const source_size = struct_size; const destination_size = pointer_element_size; const address_alignment = argument_abi.semantic_type.get_byte_alignment(); const address = switch (source_size <= destination_size) { true => alloca, false => module.create_alloca(.{ .type = coerce_to_type, .alignment = address_alignment, .name = "coerce" }), }; assert(coerce_to_type.bb.structure.fields.len == argument_abi.abi_count); for (coerce_to_type.bb.structure.fields, abi_arguments, 0..) |field, abi_argument, field_index| { const gep = module.llvm.builder.create_struct_gep(coerce_to_type.llvm.handle.?.to_struct(), address, @intCast(field_index)); // TODO: check if alignment is right _ = module.create_store(.{ .source_value = abi_argument.to_value(), .destination_value = gep, .source_type = field.type, .destination_type = field.type }); } if (source_size > destination_size) { _ = module.llvm.builder.create_memcpy(pointer, pointer_type.get_byte_alignment(), address, address_alignment, module.integer_type(64, false).llvm.handle.?.to_integer().get_constant(destination_size, @intFromBool(false)).to_value()); } }, } } else { assert(argument_abi.abi_count == 1); const abi_argument_type = function_type.abi_argument_types[argument_abi.abi_start]; const destination_size = pointer_type.get_byte_size() - argument_abi.attributes.direct.offset; const is_volatile = false; _ = abi_argument_type; _ = destination_size; _ = is_volatile; @trap(); // 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(), }; // TODO: delete this // const argument_value = module.values.add(); // argument_value.* = .{ // .llvm = semantic_argument_storage, // .type = argument_variable.variable.value.type, // .bb = .argument, // .lvalue = true, // .dereference_to_assign = false, // }; // argument_variable.* = .{ // .value = argument_value, // .name = argument_variable.name, // }; // no pointer const argument_type = argument_variable.variable.storage.?.type.?.bb.pointer.type; if (module.has_debug_info) { const always_preserve = true; const flags = llvm.DI.Flags{}; const parameter_variable = module.llvm.di_builder.create_parameter_variable(function_scope, argument_variable.variable.name, @intCast(argument_index + 1), module.llvm.file, argument_variable.variable.line, 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, argument_variable.variable.line, argument_variable.variable.column, function_scope, inlined_at); _ = module.llvm.di_builder.insert_declare_record_at_end(semantic_argument_storage, parameter_variable, module.llvm.di_builder.null_expression(), debug_location, entry_block); } } module.analyze_block(global, global.variable.storage.?.bb.function.main_block); // Handle jump to the return block const return_block = global.variable.storage.?.bb.function.return_block orelse module.report_error(); if (module.llvm.builder.get_insert_block()) |current_basic_block| { assert(current_basic_block.get_terminator() == null); if (current_basic_block.is_empty() or current_basic_block.to_value().use_empty()) { return_block.to_value().replace_all_uses_with(current_basic_block.to_value()); return_block.delete(); } else { module.emit_block(global, return_block); } } else { var is_reachable = false; if (return_block.to_value().has_one_use()) { if (llvm.Value.to_branch(return_block.user_begin())) |branch| { is_reachable = !branch.is_conditional() and branch.get_successor(0) == return_block; if (is_reachable) { module.llvm.builder.position_at_end(branch.to_instruction().get_parent()); branch.to_instruction().erase_from_parent(); return_block.delete(); } } } if (!is_reachable) { module.emit_block(global, return_block); } } // End function debug info if (llvm_function_value.get_subprogram()) |subprogram| { module.llvm.di_builder.finalize_subprogram(subprogram); } if (function_type.return_abi.semantic_type == module.noreturn_type or global.variable.storage.?.bb.function.attributes.naked) { @trap(); } else if (function_type.return_abi.semantic_type == module.void_type) { module.llvm.builder.create_ret_void(); } else { const abi_kind = function_type.return_abi.flags.kind; const return_value: ?*llvm.Value = switch (abi_kind) { .direct, .extend => blk: { const coerce_to_type = function_type.return_abi.get_coerce_to_type(); const return_alloca = global.variable.storage.?.bb.function.return_alloca orelse unreachable; if (function_type.return_abi.semantic_type.is_abi_equal(coerce_to_type) and function_type.return_abi.attributes.direct.offset == 0) { if (module.llvm.builder.find_return_value_dominating_store(return_alloca, function_type.return_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 = function_type.return_abi.semantic_type, .value = return_alloca }); break :blk load_value; } } else { const source = switch (function_type.return_abi.attributes.direct.offset == 0) { true => return_alloca, false => @trap(), }; const source_type = function_type.return_abi.semantic_type; const destination_type = coerce_to_type; _ = source; _ = source_type; _ = destination_type; @trap(); // const result = module.create_coerced_load(source, source_type, destination_type); // break :blk result; } }, .indirect => switch (function_type.return_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 (lib.optimization_mode == .Debug) { const verify_result = llvm_function_value.verify(); if (!verify_result.success) { lib.print_string(module.llvm.module.to_string()); lib.print_string("============================\n"); lib.print_string(llvm_function_value.to_string()); lib.print_string("============================\n"); lib.print_string(verify_result.error_message orelse unreachable); lib.print_string("\n============================\n"); lib.os.abort(); } } }, else => @trap(), } } if (module.has_debug_info) { module.llvm.di_builder.finalize(); } const verify_result = module.llvm.module.verify(); if (!verify_result.success) { lib.print_string(module.llvm.module.to_string()); lib.print_string("============================\n"); lib.print_string(verify_result.error_message orelse unreachable); lib.os.abort(); } if (!module.silent) { const module_string = module.llvm.module.to_string(); lib.print_string_stderr(module_string); } var error_message: llvm.String = undefined; var target_options = llvm.Target.Options.default(); target_options.flags0.trap_unreachable = switch (module.build_mode) { .debug_none, .debug_fast, .debug_size => true, else => false, }; const target_machine = llvm.Target.Machine.create(.{ .target_options = target_options, .cpu_triple = llvm.String.from_slice(llvm.global.host_triple), .cpu_model = llvm.String.from_slice(llvm.global.host_cpu_model), .cpu_features = llvm.String.from_slice(llvm.global.host_cpu_features), .optimization_level = module.build_mode.to_llvm_machine(), .relocation_model = .default, .code_model = .none, .jit = false, }, &error_message) orelse { lib.os.abort(); }; const object_generate_result = llvm.object_generate(module.llvm.module, target_machine, .{ .optimize_when_possible = @intFromEnum(module.build_mode) > @intFromEnum(BuildMode.soft_optimize), .debug_info = module.has_debug_info, .optimization_level = if (module.build_mode != .debug_none) module.build_mode.to_llvm_ir() else null, .path = module.objects[0], }); switch (object_generate_result) { .success => { const result = llvm.link(module.arena, .{ .output_path = module.executable, .objects = module.objects, }); switch (result.success) { true => {}, false => lib.os.abort(), } }, else => lib.os.abort(), } } const ValueAnalysis = struct { type: ?*Type = null, }; pub fn analyze(module: *Module, function: *Global, value: *Value, analysis: ValueAnalysis) void { module.analyze_value_type(function, value, analysis); module.emit_value(function, value, analysis); } pub fn emit_value(module: *Module, function: *Global, value: *Value, analysis: ValueAnalysis) void { _ = function; _ = analysis; const value_type = value.type orelse unreachable; assert(value.llvm == null); const llvm_value: *llvm.Value = switch (value.bb) { .constant_integer => |constant_integer| value_type.llvm.handle.?.to_integer().get_constant(constant_integer.value, @intFromBool(constant_integer.signed)).to_value(), .unary => |unary| switch (unary.id) { .@"-" => unary.value.negate_llvm(), else => @trap(), }, .binary => |binary| switch (value_type.bb) { .integer => |integer| switch (binary.id) { .@"+" => module.llvm.builder.create_add(binary.left.llvm.?, binary.right.llvm.?), .@"-" => module.llvm.builder.create_sub(binary.left.llvm.?, binary.right.llvm.?), .@"*" => module.llvm.builder.create_mul(binary.left.llvm.?, binary.right.llvm.?), .@"/" => switch (integer.signed) { true => module.llvm.builder.create_sdiv(binary.left.llvm.?, binary.right.llvm.?), false => module.llvm.builder.create_udiv(binary.left.llvm.?, binary.right.llvm.?), }, .@"%" => switch (integer.signed) { true => module.llvm.builder.create_srem(binary.left.llvm.?, binary.right.llvm.?), false => module.llvm.builder.create_urem(binary.left.llvm.?, binary.right.llvm.?), }, .@"&" => module.llvm.builder.create_and(binary.left.llvm.?, binary.right.llvm.?), .@"|" => module.llvm.builder.create_or(binary.left.llvm.?, binary.right.llvm.?), .@"^" => module.llvm.builder.create_xor(binary.left.llvm.?, binary.right.llvm.?), .@"<<" => module.llvm.builder.create_shl(binary.left.llvm.?, binary.right.llvm.?), .@">>" => switch (integer.signed) { true => module.llvm.builder.create_ashr(binary.left.llvm.?, binary.right.llvm.?), false => module.llvm.builder.create_lshr(binary.left.llvm.?, binary.right.llvm.?), }, .@"==" => module.llvm.builder.create_integer_compare(.eq, binary.left.llvm.?, binary.right.llvm.?), .@"!=" => module.llvm.builder.create_integer_compare(.ne, binary.left.llvm.?, binary.right.llvm.?), .@">" => switch (integer.signed) { true => module.llvm.builder.create_integer_compare(.sgt, binary.left.llvm.?, binary.right.llvm.?), false => module.llvm.builder.create_integer_compare(.ugt, binary.left.llvm.?, binary.right.llvm.?), }, .@"<" => switch (integer.signed) { true => module.llvm.builder.create_integer_compare(.slt, binary.left.llvm.?, binary.right.llvm.?), false => module.llvm.builder.create_integer_compare(.ult, binary.left.llvm.?, binary.right.llvm.?), }, .@">=" => switch (integer.signed) { true => module.llvm.builder.create_integer_compare(.sge, binary.left.llvm.?, binary.right.llvm.?), false => module.llvm.builder.create_integer_compare(.uge, binary.left.llvm.?, binary.right.llvm.?), }, .@"<=" => switch (integer.signed) { true => module.llvm.builder.create_integer_compare(.sle, binary.left.llvm.?, binary.right.llvm.?), false => module.llvm.builder.create_integer_compare(.ule, binary.left.llvm.?, binary.right.llvm.?), }, }, else => @trap(), }, .variable_reference => |variable| if (variable.type == value_type) switch (value_type.get_evaluation_kind()) { .scalar => module.create_load(.{ .type = value_type, .value = variable.storage.?.llvm.?, .alignment = variable.storage.?.type.?.bb.pointer.alignment, }), .aggregate => @trap(), .complex => @trap(), } else @trap(), else => @trap(), }; value.llvm = llvm_value; } pub fn analyze_value_type(module: *Module, function: *Global, value: *Value, analysis: ValueAnalysis) void { assert(value.type == null); assert(value.llvm == null); if (analysis.type) |expected_type| switch (expected_type.bb) { .integer => |integer| switch (value.bb) { .constant_integer => |constant_integer| switch (constant_integer.signed) { true => { if (!integer.signed) { module.report_error(); } @trap(); }, false => { const type_max = (@as(u64, 1) << @intCast(integer.bit_count)) - 1; if (constant_integer.value > type_max) { module.report_error(); } }, }, .unary => |unary| { switch (unary.id) { .@"+" => @trap(), .@"-" => { module.analyze(function, unary.value, analysis); if (!unary.value.type.?.is_signed()) { module.report_error(); } assert(expected_type == unary.value.type); }, .@"&" => @trap(), } }, .binary => |binary| { const is_boolean = switch (binary.id) { .@"==", .@"!=", .@">", .@"<", .@">=", .@"<=", => true, else => false, }; const boolean_type = module.integer_type(1, false); if (is_boolean and expected_type != boolean_type) { module.report_error(); } module.analyze(function, binary.left, .{ .type = if (is_boolean) null else expected_type, }); module.analyze(function, binary.right, .{ .type = binary.left.type, }); }, .variable_reference => |variable| { if (variable.type != expected_type) { module.report_error(); } }, else => @trap(), }, else => @trap(), }; const value_type = if (analysis.type) |expected_type| expected_type else switch (value.bb) { else => @trap(), }; value.type = value_type; } pub fn analyze_block(module: *Module, function: *Global, block: *LexicalBlock) void { if (module.has_debug_info) { const lexical_block = module.llvm.di_builder.create_lexical_block(block.scope.parent.?.llvm.?, module.llvm.file, block.scope.line, block.scope.column); block.scope.llvm = lexical_block.to_scope(); } var last_line: u32 = 0; var last_column: u32 = 0; var last_statement_debug_location: *llvm.DI.Location = undefined; for (block.statements.get_slice()) |statement| { if (module.has_debug_info) { if (statement.line != last_line or statement.column != last_column) { const inlined_at: ?*llvm.DI.Metadata = null; // TODO last_statement_debug_location = llvm.DI.create_debug_location(module.llvm.context, statement.line, statement.column, block.scope.llvm.?, inlined_at); module.llvm.builder.set_current_debug_location(last_statement_debug_location); last_line = statement.line; last_column = statement.column; } } switch (statement.bb) { .@"return" => |rv| { const function_type = &function.variable.storage.?.type.?.bb.pointer.type.bb.function; const return_abi = function_type.return_abi; switch (return_abi.semantic_type.bb) { .void => { if (rv != null) { module.report_error(); } }, .noreturn => module.report_error(), else => { const return_value = rv orelse module.report_error(); module.analyze(function, return_value, .{ .type = return_abi.semantic_type, }); if (module.has_debug_info) { module.llvm.builder.set_current_debug_location(last_statement_debug_location); } // Clang equivalent: CodeGenFunction::EmitReturnStmt const return_alloca = function.variable.storage.?.bb.function.return_alloca orelse module.report_error(); switch (return_abi.semantic_type.get_evaluation_kind()) { .scalar => { switch (return_abi.flags.kind) { .indirect => { @trap(); }, else => { // assert(!return_value.?.lvalue); assert(return_value.type.?.is_abi_equal(return_abi.semantic_type)); _ = module.create_store(.{ .source_value = return_value.llvm.?, .destination_value = return_alloca, .source_type = return_abi.semantic_type, .destination_type = return_abi.semantic_type, }); }, } }, .aggregate => { @trap(); // TODO: handcoded code, might be wrong // switch (return_value.lvalue) { // true => { // const abi_alignment = return_abi.semantic_type.get_byte_alignment(); // const abi_size = return_abi.semantic_type.get_byte_size(); // switch (return_abi.flags.kind) { // .indirect => { // _ = module.llvm.builder.create_memcpy( // unreachable, //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 (return_abi.semantic_type.get_evaluation_kind()) { // .aggregate => { // // TODO: this is 100% wrong, fix // assert(abi_alignment == return_abi.semantic_type.get_byte_alignment()); // assert(abi_size == return_abi.semantic_type.get_byte_size()); // _ = module.llvm.builder.create_memcpy( // unreachable, //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 = return_abi.semantic_type; // const source_type = return_abi.semantic_type; // assert(return_value.type == source_type); // const ret_val = 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 = ret_val, .source_type = source_type, // .destination_value = unreachable, //return_alloca, // .destination_type = destination_type }); // }, // .complex => @trap(), // } // }, // } // }, // false => { // assert(!return_value.lvalue); // assert(return_value.type.is_abi_equal(return_abi.semantic_type)); // _ = module.create_store(.{ // .source_value = return_value.llvm, // .destination_value = unreachable, // return_alloca, // .source_type = return_abi.semantic_type, // .destination_type = return_abi.semantic_type, // }); // }, // } }, .complex => @trap(), } }, } const return_block = function.variable.storage.?.bb.function.return_block orelse module.report_error(); _ = module.llvm.builder.create_branch(return_block); _ = module.llvm.builder.clear_insertion_position(); }, .local => |local| { if (local.variable.type) |expected_type| { assert(local.variable.storage == null); module.analyze_value_type(function, local.variable.initial_value, .{ .type = local.variable.type }); local.variable.resolve_type(local.variable.initial_value.type.?); assert(expected_type == local.variable.type); module.emit_local_storage(local, last_statement_debug_location); module.emit_assignment(function, local.variable.storage.?, local.variable.initial_value); } else { @trap(); } }, } } } fn emit_assignment(module: *Module, function: *Global, left: *Value, right: *Value) void { assert(left.llvm != null); assert(right.llvm == null); const pointer_type = left.type.?; const value_type = right.type.?; assert(pointer_type.bb == .pointer); assert(pointer_type.bb.pointer.type == value_type); switch (value_type.get_evaluation_kind()) { .scalar => { module.emit_value(function, right, .{}); _ = module.create_store(.{ .source_value = right.llvm.?, .destination_value = left.llvm.?, .source_type = value_type, .destination_type = value_type, .alignment = pointer_type.bb.pointer.alignment, }); }, .aggregate => @trap(), .complex => @trap(), } } pub fn emit_local_storage(module: *Module, local: *Local, statement_debug_location: *llvm.DI.Location) void { assert(local.variable.storage == null); const resolved_type = local.variable.type.?; const pointer_type = module.get_pointer_type(.{ .type = resolved_type }); const storage = module.values.add(); storage.* = .{ .type = pointer_type, .bb = .local, .llvm = module.create_alloca(.{ .type = pointer_type.bb.pointer.type, .alignment = pointer_type.bb.pointer.alignment, .name = local.variable.name, }), }; if (module.has_debug_info) { module.llvm.builder.set_current_debug_location(statement_debug_location); const debug_type = resolved_type.llvm.debug.?; const always_preserve = true; // TODO: const alignment = 0; const flags = llvm.DI.Flags{}; const local_variable = module.llvm.di_builder.create_auto_variable(local.variable.scope.llvm.?, local.variable.name, module.llvm.file, local.variable.line, debug_type, always_preserve, flags, alignment); const inlined_at: ?*llvm.DI.Metadata = null; // TODO const debug_location = llvm.DI.create_debug_location(module.llvm.context, local.variable.line, local.variable.column, local.variable.scope.llvm.?, inlined_at); _ = module.llvm.di_builder.insert_declare_record_at_end(storage.llvm.?, local_variable, module.llvm.di_builder.null_expression(), debug_location, module.llvm.builder.get_insert_block().?); module.llvm.builder.set_current_debug_location(statement_debug_location); } local.variable.storage = storage; } 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; } } const LoadOptions = struct { type: *Type, value: *llvm.Value, alignment: ?c_uint = null, }; pub fn create_load(module: *Module, options: LoadOptions) *llvm.Value { switch (options.type.bb) { .void, .noreturn, => 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 AllocaOptions = struct { type: *Type, name: []const u8 = "", alignment: ?c_uint = null, }; pub fn create_alloca(module: *Module, options: AllocaOptions) *llvm.Value { const abi_type = switch (options.type.is_arbitrary_bit_integer()) { true => module.align_integer_type(options.type), false => options.type, }; const alignment: c_uint = if (options.alignment) |a| a else @intCast(abi_type.get_byte_alignment()); const v = module.llvm.builder.create_alloca(abi_type.llvm.handle.?, options.name); v.set_alignment(alignment); return v; } const StoreOptions = struct { source_value: *llvm.Value, destination_value: *llvm.Value, source_type: *Type, destination_type: *Type, alignment: ?c_uint = null, }; pub fn create_store(module: *Module, options: StoreOptions) *llvm.Value { const raw_store_type = switch (options.source_type.is_arbitrary_bit_integer()) { true => module.align_integer_type(options.source_type), false => options.source_type, }; const source_value = switch (raw_store_type == options.source_type) { true => options.source_value, false => module.raw_int_cast(.{ .source_type = options.source_type, .destination_type = raw_store_type, .value = options.source_value }), }; const alignment = if (options.alignment) |a| a else options.destination_type.get_byte_alignment(); const v = module.llvm.builder.create_store(source_value, options.destination_value); v.set_alignment(alignment); return v; } 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; } }; pub const Options = struct { content: []const u8, path: [:0]const u8, executable: [:0]const u8, name: []const u8, objects: []const [:0]const u8, target: Target, build_mode: BuildMode, has_debug_info: bool, silent: bool, }; const Token = union(Id) { none, end_of_statement, integer: Integer, identifier: []const u8, value_keyword: Value.Keyword, value_intrinsic: Value.Intrinsic, // Assignment operators @"=", @"+=", @"-=", @"*=", @"/=", @"%=", @"&=", @"|=", @"^=", @"<<=", @">>=", // Comparison operators @"==", @"!=", @"<", @">", @"<=", @">=", // Logical AND @"and", @"and?", // Logical OR @"or", @"or?", // Add-like operators @"+", @"-", // Div-like operators @"*", @"/", @"%", // Bitwise operators @"&", @"|", @"^", // Shifting operators @"<<", @">>", // Pointer dereference @".&", // Parenthesis @"(", @")", const Id = enum { none, end_of_statement, integer, identifier, value_keyword, value_intrinsic, // Assignment operators @"=", @"+=", @"-=", @"*=", @"/=", @"%=", @"&=", @"|=", @"^=", @"<<=", @">>=", // Comparison operators @"==", @"!=", @"<", @">", @"<=", @">=", // Logical AND @"and", @"and?", // Logical OR @"or", @"or?", // Add-like operators @"+", @"-", // Div-like operators @"*", @"/", @"%", // Bitwise operators @"&", @"|", @"^", // Shifting operators @"<<", @">>", // Pointer dereference @".&", // Parenthesis @"(", @")", }; const Integer = struct { value: u64, kind: Integer.Kind, const Kind = enum { hexadecimal, decimal, octal, binary, }; }; }; pub const Abi = struct { const Kind = enum(u3) { ignore, direct, extend, indirect, indirect_aliased, expand, coerce_and_expand, in_alloca, }; const RegisterCount = union { system_v: Abi.SystemV.RegisterCount, }; const Flags = packed struct { kind: Kind, padding_in_reg: bool = false, in_alloca_sret: bool = false, in_alloca_indirect: bool = false, indirect_by_value: bool = false, indirect_realign: bool = false, sret_after_this: bool = false, in_reg: bool = false, can_be_flattened: bool = false, sign_extension: bool = false, }; const Information = struct { semantic_type: *Type, coerce_to_type: ?*Type = null, padding: union { type: ?*Type, unpadded_coerce_and_expand_type: ?*Type, } = .{ .type = null }, padding_arg_index: u16 = 0, attributes: union { direct: DirectAttributes, indirect: IndirectAttributes, alloca_field_index: u32, } = .{ .direct = .{ .offset = 0, .alignment = 0, }, }, flags: Abi.Flags, abi_start: u16 = 0, abi_count: u16 = 0, const DirectAttributes = struct { offset: u32, alignment: u32, }; const IndirectAttributes = struct { alignment: u32, address_space: u32, }; const Direct = struct { semantic_type: *Type, type: *Type, padding: ?*Type = null, offset: u32 = 0, alignment: u32 = 0, can_be_flattened: bool = true, }; pub fn get_direct(direct: Direct) Information { var result = Information{ .semantic_type = direct.semantic_type, .flags = .{ .kind = .direct, }, }; result.set_coerce_to_type(direct.type); result.set_padding_type(direct.padding); result.set_direct_offset(direct.offset); result.set_direct_alignment(direct.alignment); result.set_can_be_flattened(direct.can_be_flattened); return result; } pub const Ignore = struct { semantic_type: *Type, }; pub fn get_ignore(ignore: Ignore) Information { return Information{ .semantic_type = ignore.semantic_type, .flags = .{ .kind = .ignore, }, }; } const Extend = struct { semantic_type: *Type, type: ?*Type = null, sign: bool, }; pub fn get_extend(extend: Extend) Information { assert(extend.semantic_type.is_integral_or_enumeration_type()); var result = Information{ .semantic_type = extend.semantic_type, .flags = .{ .kind = .extend, }, }; result.set_coerce_to_type(if (extend.type) |t| t else extend.semantic_type); result.set_padding_type(null); result.set_direct_offset(0); result.set_direct_alignment(0); result.flags.sign_extension = extend.sign; return result; } const NaturalAlignIndirect = struct { semantic_type: *Type, padding_type: ?*Type = null, by_value: bool = true, realign: bool = false, }; pub fn get_natural_align_indirect(nai: NaturalAlignIndirect) Abi.Information { const alignment = nai.semantic_type.get_byte_alignment(); return get_indirect(.{ .semantic_type = nai.semantic_type, .alignment = alignment, .by_value = nai.by_value, .realign = nai.realign, .padding_type = nai.padding_type, }); } pub const Indirect = struct { semantic_type: *Type, padding_type: ?*Type = null, alignment: u32, by_value: bool = true, realign: bool = false, }; pub fn get_indirect(indirect: Indirect) Abi.Information { var result = Abi.Information{ .semantic_type = indirect.semantic_type, .attributes = .{ .indirect = .{ .address_space = 0, .alignment = 0, }, }, .flags = .{ .kind = .indirect, }, }; result.set_indirect_align(indirect.alignment); result.set_indirect_by_value(indirect.by_value); result.set_indirect_realign(indirect.realign); result.set_sret_after_this(false); result.set_padding_type(indirect.padding_type); return result; } fn set_sret_after_this(abi: *Abi.Information, sret_after_this: bool) void { assert(abi.flags.kind == .indirect); abi.flags.sret_after_this = sret_after_this; } fn set_indirect_realign(abi: *Abi.Information, realign: bool) void { assert(abi.flags.kind == .indirect); abi.flags.indirect_realign = realign; } fn set_indirect_by_value(abi: *Abi.Information, by_value: bool) void { assert(abi.flags.kind == .indirect); abi.flags.indirect_by_value = by_value; } fn set_indirect_align(abi: *Abi.Information, alignment: u32) void { assert(abi.flags.kind == .indirect or abi.flags.kind == .indirect_aliased); abi.attributes.indirect.alignment = alignment; } fn set_coerce_to_type(info: *Information, coerce_to_type: *Type) void { assert(info.can_have_coerce_to_type()); info.coerce_to_type = coerce_to_type; } fn get_coerce_to_type(info: *const Information) *Type { assert(info.can_have_coerce_to_type()); return info.coerce_to_type.?; } fn can_have_coerce_to_type(info: *const Information) bool { return switch (info.flags.kind) { .direct, .extend, .coerce_and_expand => true, else => false, }; } fn set_padding_type(info: *Information, padding_type: ?*Type) void { assert(info.can_have_padding_type()); info.padding = .{ .type = padding_type, }; } fn can_have_padding_type(info: *const Information) bool { return switch (info.flags.kind) { .direct, .extend, .indirect, .indirect_aliased, .expand => true, else => false, }; } fn get_padding_type(info: *const Information) ?*Type { return if (info.can_have_padding_type()) info.padding.type else null; } fn set_direct_offset(info: *Information, offset: u32) void { assert(info.flags.kind == .direct or info.flags.kind == .extend); info.attributes.direct.offset = offset; } fn set_direct_alignment(info: *Information, alignment: u32) void { assert(info.flags.kind == .direct or info.flags.kind == .extend); info.attributes.direct.alignment = alignment; } fn set_can_be_flattened(info: *Information, can_be_flattened: bool) void { assert(info.flags.kind == .direct); info.flags.can_be_flattened = can_be_flattened; } fn get_can_be_flattened(info: *const Information) bool { assert(info.flags.kind == .direct); return info.flags.can_be_flattened; } }; pub const SystemV = struct { pub const RegisterCount = struct { gpr: u32, sse: u32, }; pub const Class = enum { integer, sse, sseup, x87, x87up, complex_x87, none, memory, fn merge(accumulator: Class, field: Class) Class { // AMD64-ABI 3.2.3p2: Rule 4. Each field of an object is // classified recursively so that always two fields are // considered. The resulting class is calculated according to // the classes of the fields in the eightbyte: // // (a) If both classes are equal, this is the resulting class. // // (b) If one of the classes is NO_CLASS, the resulting class is // the other class. // // (c) If one of the classes is MEMORY, the result is the MEMORY // class. // // (d) If one of the classes is INTEGER, the result is the // INTEGER. // // (e) If one of the classes is X87, X87UP, COMPLEX_X87 class, // MEMORY is used as class. // // (f) Otherwise class SSE is used. // Accum should never be memory (we should have returned) or // ComplexX87 (because this cannot be passed in a structure). assert(accumulator != .memory and accumulator != .complex_x87); if (accumulator == field or field == .none) { return accumulator; } if (field == .memory) { return .memory; } if (accumulator == .none) { return field; } if (accumulator == .integer or field == .integer) { return .integer; } if (field == .x87 or field == .x87up or field == .complex_x87 or accumulator == .x87 or accumulator == .x87up) { return .memory; } return .sse; } }; const ClassifyOptions = struct { base_offset: u64, is_named_argument: bool, is_register_call: bool = false, }; fn classify(ty: *Type, options: ClassifyOptions) [2]Class { var result = [2]Class{ .none, .none }; const is_memory = options.base_offset >= 8; const current_index = @intFromBool(is_memory); const not_current_index = @intFromBool(!is_memory); assert(current_index != not_current_index); result[current_index] = .memory; switch (ty.bb) { .void => result[current_index] = .none, // .noreturn => result[current_index] = .none, // .bits => result[current_index] = .integer, .pointer => result[current_index] = .integer, .integer => |integer| { if (integer.bit_count <= 64) { result[current_index] = .integer; } else if (integer.bit_count == 128) { @trap(); } else { @trap(); } }, // .structure => |struct_type| { // if (struct_type.byte_size <= 64) { // const has_variable_array = false; // if (!has_variable_array) { // // const struct_type = ty.get_payload(.@"struct"); // result[current_index] = .none; // const is_union = false; // var member_offset: u32 = 0; // for (struct_type.fields) |field| { // const offset = options.base_offset + member_offset; // const member_size = field.type.get_byte_size(); // const member_alignment = field.type.get_byte_alignment(); // member_offset = @intCast(lib.align_forward_u64(member_offset + member_size, ty.get_byte_alignment())); // const native_vector_size = 16; // if (ty.get_byte_size() > 16 and ((!is_union and ty.get_byte_size() != member_size) or ty.get_byte_size() > native_vector_size)) { // result[0] = .memory; // const r = classify_post_merge(ty.get_byte_size(), result); // return r; // } // // if (offset % member_alignment != 0) { // result[0] = .memory; // const r = classify_post_merge(ty.get_byte_size(), result); // return r; // } // // const member_classes = classify(field.type, .{ // .base_offset = offset, // .is_named_argument = false, // }); // for (&result, member_classes) |*r, m| { // const merge_result = r.merge(m); // r.* = merge_result; // } // // if (result[0] == .memory or result[1] == .memory) break; // } // // const final = classify_post_merge(ty.get_byte_size(), result); // result = final; // } // } // }, // .array => |*array_type| { // if (ty.get_byte_size() <= 64) { // if (options.base_offset % ty.get_byte_alignment() == 0) { // result[current_index] = .none; // // const vector_size = 16; // if (ty.get_byte_size() > 16 and (ty.get_byte_size() != array_type.element_type.get_byte_size() or ty.get_byte_size() > vector_size)) { // unreachable; // } else { // var offset = options.base_offset; // // for (0..array_type.element_count.?) |_| { // const element_classes = classify(array_type.element_type, .{ // .base_offset = offset, // .is_named_argument = false, // }); // offset += array_type.element_type.get_byte_size(); // const merge_result = [2]Class{ result[0].merge(element_classes[0]), result[1].merge(element_classes[1]) }; // result = merge_result; // if (result[0] == .memory or result[1] == .memory) { // break; // } // } // // const final_result = classify_post_merge(ty.get_byte_size(), result); // assert(final_result[1] != .sseup or final_result[0] != .sse); // result = final_result; // } // } // } // }, else => @trap(), } return result; } fn classify_post_merge(aggregate_size: u64, classes: [2]Class) [2]Class { // AMD64-ABI 3.2.3p2: Rule 5. Then a post merger cleanup is done: // // (a) If one of the classes is Memory, the whole argument is passed in // memory. // // (b) If X87UP is not preceded by X87, the whole argument is passed in // memory. // // (c) If the size of the aggregate exceeds two eightbytes and the first // eightbyte isn't SSE or any other eightbyte isn't SSEUP, the whole // argument is passed in memory. NOTE: This is necessary to keep the // ABI working for processors that don't support the __m256 type. // // (d) If SSEUP is not preceded by SSE or SSEUP, it is converted to SSE. // // Some of these are enforced by the merging logic. Others can arise // only with unions; for example: // union { _Complex double; unsigned; } // // Note that clauses (b) and (c) were added in 0.98. var result = classes; if (result[1] == .memory) { result[0] = .memory; } if (result[1] == .x87up) { @trap(); } if (aggregate_size > 16 and (result[0] != .sse or result[1] != .sseup)) { result[0] = .memory; } if (result[1] == .sseup and result[0] != .sse) { result[0] = .sse; } return result; } fn get_int_type_at_offset(module: *Module, ty: *Type, offset: u32, source_type: *Type, source_offset: u32) *Type { switch (ty.bb) { // .bits => |bits| { // return get_int_type_at_offset(module, bits.backing_type, offset, if (source_type == ty) bits.backing_type else source_type, source_offset); // }, .integer => |integer_type| { switch (integer_type.bit_count) { 64 => return ty, 32, 16, 8 => { if (offset != 0) unreachable; const start = source_offset + ty.get_byte_size(); const end = source_offset + 8; if (contains_no_user_data(source_type, start, end)) { return ty; } }, else => return module.integer_type(@intCast(@min(ty.get_byte_size() - source_offset, 8) * 8), integer_type.signed), } }, .pointer => return if (offset == 0) ty else @trap(), // .structure => { // if (get_member_at_offset(ty, offset)) |field| { // return get_int_type_at_offset(module, field.type, @intCast(offset - field.byte_offset), source_type, source_offset); // } // unreachable; // }, // .array => |array_type| { // const element_type = array_type.element_type; // const element_size = element_type.get_byte_size(); // const element_offset = (offset / element_size) * element_size; // return get_int_type_at_offset(module, element_type, @intCast(offset - element_offset), source_type, source_offset); // }, else => |t| @panic(@tagName(t)), } if (source_type.get_byte_size() - source_offset > 8) { return module.integer_type(64, false); } else { const byte_count = source_type.get_byte_size() - source_offset; const bit_count = byte_count * 8; return module.integer_type(@intCast(bit_count), false); } } fn get_member_at_offset(ty: *Type, offset: u32) ?*const Field { if (ty.get_byte_size() <= offset) { return null; } var offset_it: u32 = 0; var last_match: ?*const Field = null; const struct_type = &ty.bb.structure; for (struct_type.fields) |*field| { if (offset_it > offset) { break; } last_match = field; offset_it = @intCast(lib.align_forward_u64(offset_it + field.type.get_byte_size(), ty.get_byte_alignment())); } assert(last_match != null); return last_match; } fn contains_no_user_data(ty: *Type, start: u64, end: u64) bool { if (ty.get_byte_size() <= start) { return true; } _ = end; // TODO: uncomment the structure and array below because otherwise a bug is going to happen if (true) @trap(); switch (ty.bb) { // .structure => |*struct_type| { // var offset: u64 = 0; // // for (struct_type.fields) |field| { // if (offset >= end) break; // const field_start = if (offset < start) start - offset else 0; // if (!contains_no_user_data(field.type, field_start, end - offset)) return false; // offset += field.type.get_byte_size(); // } // // return true; // }, // .array => |array_type| { // for (0..array_type.element_count.?) |i| { // const offset = i * array_type.element_type.get_byte_size(); // if (offset >= end) break; // const element_start = if (offset < start) start - offset else 0; // if (!contains_no_user_data(array_type.element_type, element_start, end - offset)) return false; // } // // return true; // }, else => return false, } } const ArgumentOptions = struct { available_gpr: u32, is_named_argument: bool, is_reg_call: bool, }; pub fn classify_argument_type(module: *Module, argument_type: *Type, options: ArgumentOptions) struct { Abi.Information, Abi.SystemV.RegisterCount } { const classes = classify(argument_type, .{ .base_offset = 0, .is_named_argument = options.is_named_argument, }); assert(classes[1] != .memory or classes[0] == .memory); assert(classes[1] != .sseup or classes[0] == .sse); var needed_registers = Abi.SystemV.RegisterCount{ .gpr = 0, .sse = 0, }; var low: ?*Type = null; switch (classes[0]) { .integer => { needed_registers.gpr += 1; const low_ty = Abi.SystemV.get_int_type_at_offset(module, argument_type, 0, argument_type, 0); low = low_ty; if (classes[1] == .none and low_ty.bb == .integer) { // TODO: // if (argument_type.bb == .enumerator) { // @trap(); // } if (argument_type.is_integral_or_enumeration_type() and argument_type.is_promotable_integer_type_for_abi()) { return .{ Abi.Information.get_extend(.{ .semantic_type = argument_type, .sign = argument_type.is_signed(), }), needed_registers, }; } } }, .memory, .x87, .complex_x87 => { // TODO: CXX ABI: RAA_Indirect return .{ get_indirect_result(argument_type, options.available_gpr), needed_registers }; }, else => @trap(), } var high: ?*Type = null; switch (classes[1]) { .none => {}, .integer => { needed_registers.gpr += 1; const high_ty = Abi.SystemV.get_int_type_at_offset(module, argument_type, 8, argument_type, 8); high = high_ty; if (classes[0] == .none) { @trap(); } }, else => @trap(), } const result_type = if (high) |hi| get_by_val_argument_pair(module, low orelse unreachable, hi) else low orelse unreachable; return .{ Abi.Information.get_direct(.{ .semantic_type = argument_type, .type = result_type, }), needed_registers, }; } const ClassifyArgument = struct { type: *Type, abi_start: u16, is_reg_call: bool = false, is_named_argument: bool, }; pub fn classify_argument(module: *Module, available_registers: *Abi.RegisterCount, llvm_abi_argument_type_buffer: []*llvm.Type, abi_argument_type_buffer: []*Type, options: ClassifyArgument) Abi.Information { const semantic_argument_type = options.type; const result = if (options.is_reg_call) @trap() else Abi.SystemV.classify_argument_type(module, semantic_argument_type, .{ .is_named_argument = options.is_named_argument, .is_reg_call = options.is_reg_call, .available_gpr = available_registers.system_v.gpr, }); const abi = result[0]; const needed_registers = result[1]; var argument_type_abi = switch (available_registers.system_v.gpr >= needed_registers.gpr and available_registers.system_v.sse >= needed_registers.sse) { true => blk: { available_registers.system_v.gpr -= needed_registers.gpr; available_registers.system_v.sse -= needed_registers.sse; break :blk abi; }, false => Abi.SystemV.get_indirect_result(semantic_argument_type, available_registers.system_v.gpr), }; if (argument_type_abi.get_padding_type() != null) { @trap(); } argument_type_abi.abi_start = options.abi_start; const count = switch (argument_type_abi.flags.kind) { .direct, .extend => blk: { const coerce_to_type = argument_type_abi.get_coerce_to_type(); const flattened_struct = argument_type_abi.flags.kind == .direct and argument_type_abi.get_can_be_flattened() and coerce_to_type.bb == .structure; const count: u16 = switch (flattened_struct) { false => 1, true => @trap(), // 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 => { @trap(); // for (coerce_to_type.bb.structure.fields, 0..) |field, field_index| { // const index = argument_type_abi.abi_start + field_index; // llvm_abi_argument_type_buffer[index] = field.type.llvm.handle.?; // abi_argument_type_buffer[index] = field.type; // } }, } break :blk count; }, .indirect => blk: { const indirect_type = module.get_pointer_type(.{ .type = argument_type_abi.semantic_type }); abi_argument_type_buffer[argument_type_abi.abi_start] = indirect_type; llvm_abi_argument_type_buffer[argument_type_abi.abi_start] = indirect_type.llvm.handle.?; break :blk 1; }, else => |t| @panic(@tagName(t)), }; argument_type_abi.abi_count = count; return argument_type_abi; } pub fn get_by_val_argument_pair(module: *Module, low: *Type, high: *Type) *Type { const low_size = low.get_byte_allocation_size(); const high_alignment = high.get_byte_alignment(); const high_start = lib.align_forward_u64(low_size, high_alignment); assert(high_start != 0 and high_start <= 8); const new_low = if (high_start != 8) { @trap(); } else low; const result = module.get_anonymous_struct_pair(.{ new_low, high }); _ = result; @trap(); // assert(result.bb.structure.fields[1].byte_offset == 8); // return result; } pub fn classify_return_type(module: *Module, return_type: *Type) Abi.Information { const classes = classify(return_type, .{ .base_offset = 0, .is_named_argument = true, }); assert(classes[1] != .memory or classes[0] == .memory); assert(classes[1] != .sseup or classes[0] == .sse); var low: ?*Type = null; switch (classes[0]) { .none => { if (classes[1] == .none) { return Abi.Information.get_ignore(.{ .semantic_type = return_type, }); } @trap(); }, .integer => { const low_ty = Abi.SystemV.get_int_type_at_offset(module, return_type, 0, return_type, 0); low = low_ty; if (classes[1] == .none and low_ty.bb == .integer) { if (return_type.bb == .enumerator) { @trap(); } if (return_type.is_integral_or_enumeration_type() and return_type.is_promotable_integer_type_for_abi()) { return Abi.Information.get_extend(.{ .semantic_type = return_type, .sign = return_type.is_signed(), }); } } }, .memory => { return Abi.SystemV.get_indirect_return_result(.{ .type = return_type }); }, else => @trap(), } var high: ?*Type = null; 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); _ = module; @trap(); // const low = if (high_offset != 8) // if ((pair[0].bb == .float and pair[0].bb.float.kind == .half) or (pair[0].bb == .float and pair[0].bb.float.kind == .float)) { // @trap(); // } else { // assert(pair[0].is_integer_backing()); // @trap(); // } // else // pair[0]; // const high = pair[1]; // const struct_type = module.get_anonymous_struct_pair(.{ low, high }); // assert(struct_type.bb.structure.fields[1].byte_offset == 8); // // return struct_type; } const IndirectReturn = struct { type: *Type, }; pub fn get_indirect_return_result(indirect: IndirectReturn) Abi.Information { if (indirect.type.is_aggregate_type_for_abi()) { return Abi.Information.get_natural_align_indirect(.{ .semantic_type = indirect.type, }); } else { @trap(); } } pub fn get_indirect_result(ty: *Type, free_gpr: u32) Abi.Information { if (!ty.is_aggregate_type_for_abi() and !is_illegal_vector_type(ty) and !ty.is_arbitrary_bit_integer()) { return switch (ty.is_promotable_integer_type_for_abi()) { true => @trap(), false => Abi.Information.get_direct(.{ .semantic_type = ty, .type = ty, }), }; } else { // TODO CXX ABI const alignment = @max(ty.get_byte_alignment(), 8); const size = ty.get_byte_size(); return switch (free_gpr == 0 and alignment == 8 and size <= 8) { true => @trap(), false => Abi.Information.get_indirect(.{ .semantic_type = ty, .alignment = alignment, }), }; } } pub fn is_illegal_vector_type(ty: *Type) bool { return switch (ty.bb) { .vector => @trap(), else => false, }; } pub fn emit_va_arg_from_memory(module: *Module, va_list_pointer: *llvm.Value, va_list_struct: *Type, arg_type: *Type) *llvm.Value { const overflow_arg_area_pointer = module.llvm.builder.create_struct_gep(va_list_struct.llvm.handle.to_struct(), va_list_pointer, 2); const overflow_arg_area_type = va_list_struct.bb.structure.fields[2].type; const overflow_arg_area = module.create_load(.{ .type = overflow_arg_area_type, .value = overflow_arg_area_pointer }); if (arg_type.get_byte_alignment() > 8) { @trap(); } const arg_type_size = arg_type.get_byte_size(); const raw_offset = lib.align_forward_u64(arg_type_size, 8); const offset = module.integer_type(32, false).llvm.handle.to_integer().get_constant(raw_offset, @intFromBool(false)); const new_overflow_arg_area = module.llvm.builder.create_gep(.{ .type = module.integer_type(8, false).llvm.handle, .aggregate = overflow_arg_area, .indices = &.{offset.to_value()}, .inbounds = false, }); _ = module.create_store(.{ .destination_type = overflow_arg_area_type, .source_type = overflow_arg_area_type, .source_value = new_overflow_arg_area, .destination_value = overflow_arg_area_pointer }); return overflow_arg_area; } }; }; const Field = struct { name: []const u8, type: *Type, bit_offset: u64, byte_offset: u64, }; pub fn compile(arena: *Arena, options: Options) void { var types = Type.Buffer.initialize(); const void_type = types.append(.{ .name = "void", .bb = .void, }); for ([2]bool{ false, true }) |sign| { for (1..64 + 1) |bit_count| { const name_buffer = [3]u8{ if (sign) 's' else 'u', @intCast(if (bit_count < 10) bit_count % 10 + '0' else bit_count / 10 + '0'), if (bit_count > 9) @intCast(bit_count % 10 + '0') else 0 }; const name_length = @as(u64, 2) + @intFromBool(bit_count > 9); const name = arena.duplicate_string(name_buffer[0..name_length]); const ty = types.append(.{ .name = name, .bb = .{ .integer = .{ .bit_count = @intCast(bit_count), .signed = sign, }, }, }); _ = ty; } } for ([2]bool{ false, true }) |sign| { const name = if (sign) "s128" else "u128"; const ty = types.append(.{ .name = name, .bb = .{ .integer = .{ .bit_count = 128, .signed = sign, }, }, }); _ = ty; } const noreturn_type = types.append(.{ .name = "noreturn", .bb = .noreturn, }); const globals = Global.Buffer.initialize(); const values = Value.Buffer.initialize(); var module = Module{ .arena = arena, .content = options.content, .has_debug_info = options.has_debug_info, .offset = 0, .line_offset = 0, .line_character_offset = 0, .types = types, .globals = globals, .locals = .initialize(), .values = values, .pointer_types = .initialize(), .lexical_blocks = .initialize(), .statements = .initialize(), .void_type = void_type, .noreturn_type = noreturn_type, .current_scope = undefined, .scope = .{ .kind = .global, .column = 0, .line = 0, }, .name = options.name, .path = options.path, .executable = options.executable, .objects = options.objects, .target = options.target, .build_mode = options.build_mode, .silent = options.silent, }; module.current_scope = &module.scope; module.parse(); module.emit(); }