10676 lines
456 KiB
Zig
10676 lines
456 KiB
Zig
const compiler = @This();
|
|
const configuration = @import("configuration");
|
|
const std = @import("std");
|
|
const builtin = @import("builtin");
|
|
const library = @import("library.zig");
|
|
const assert = library.assert;
|
|
const Arena = library.Arena;
|
|
const PinnedArray = library.PinnedArray;
|
|
const PinnedHashMap = library.PinnedHashMap;
|
|
const byte_equal = library.byte_equal;
|
|
const first_byte = library.first_byte;
|
|
const hash_bytes = library.my_hash;
|
|
const last_byte = library.last_byte;
|
|
const starts_with_slice = library.starts_with_slice;
|
|
const Atomic = std.atomic.Value;
|
|
|
|
const weak_memory_model = switch (builtin.cpu.arch) {
|
|
.aarch64 => true,
|
|
.x86_64 => false,
|
|
else => @compileError("Error: unknown arch"),
|
|
};
|
|
|
|
fn fail() noreturn {
|
|
@setCold(true);
|
|
@breakpoint();
|
|
std.posix.exit(1);
|
|
}
|
|
|
|
fn fail_term(message: []const u8, term: []const u8) noreturn {
|
|
@setCold(true);
|
|
write(message);
|
|
write(": '");
|
|
write(term);
|
|
write("'\n");
|
|
fail();
|
|
}
|
|
|
|
fn is_space(ch: u8, next_ch: u8) bool {
|
|
const is_comment = ch == '/' and next_ch == '/';
|
|
const is_whitespace = ch == ' ';
|
|
const is_vertical_tab = ch == 0x0b;
|
|
const is_horizontal_tab = ch == '\t';
|
|
const is_line_feed = ch == '\n';
|
|
const is_carry_return = ch == '\r';
|
|
const result = ((is_vertical_tab or is_horizontal_tab) or (is_line_feed or is_carry_return)) or (is_comment or is_whitespace);
|
|
return result;
|
|
}
|
|
|
|
pub fn write(string: []const u8) void {
|
|
std.io.getStdOut().writeAll(string) catch unreachable;
|
|
}
|
|
|
|
fn fail_message(string: []const u8) noreturn {
|
|
@setCold(true);
|
|
write("error: ");
|
|
write(string);
|
|
write("\n");
|
|
fail();
|
|
}
|
|
|
|
fn is_lower(ch: u8) bool {
|
|
return ch >= 'a' and ch <= 'z';
|
|
}
|
|
|
|
fn is_upper(ch: u8) bool {
|
|
return ch >= 'A' and ch <= 'Z';
|
|
}
|
|
|
|
fn is_decimal_digit(ch: u8) bool {
|
|
return ch >= '0' and ch <= '9';
|
|
}
|
|
|
|
fn is_hex_digit(ch: u8) bool {
|
|
return (is_decimal_digit(ch) or ((ch == 'a' or ch == 'A') or (ch == 'b' or ch == 'B'))) or (((ch == 'c' or ch == 'C') or (ch == 'd' or ch == 'D')) or ((ch == 'e' or ch == 'E') or (ch == 'f' or ch == 'F')));
|
|
}
|
|
|
|
fn is_alphabetic(ch: u8) bool {
|
|
const lower = is_lower(ch);
|
|
const upper = is_upper(ch);
|
|
return lower or upper;
|
|
}
|
|
|
|
fn is_identifier_char_start(ch: u8) bool {
|
|
const is_alpha = is_alphabetic(ch);
|
|
const is_underscore = ch == '_';
|
|
return is_alpha or is_underscore;
|
|
}
|
|
|
|
fn is_identifier_char(ch: u8) bool {
|
|
const is_identifier_start_ch = is_identifier_char_start(ch);
|
|
const is_digit = is_decimal_digit(ch);
|
|
return is_identifier_start_ch or is_digit;
|
|
}
|
|
|
|
const Side = enum {
|
|
left,
|
|
right,
|
|
};
|
|
|
|
const GlobalSymbol = struct{
|
|
attributes: Attributes = .{},
|
|
global_declaration: GlobalDeclaration,
|
|
type: *Type,
|
|
pointer_type: *Type,
|
|
alignment: u32,
|
|
value: Value,
|
|
id: GlobalSymbol.Id,
|
|
|
|
const Id = enum{
|
|
function_declaration,
|
|
function_definition,
|
|
global_variable,
|
|
};
|
|
|
|
const Attributes = struct{
|
|
@"export": bool = false,
|
|
@"extern": bool = false,
|
|
mutability: Mutability = .@"var",
|
|
};
|
|
const Attribute = enum{
|
|
@"export",
|
|
@"extern",
|
|
|
|
const Mask = std.EnumSet(Attribute);
|
|
};
|
|
|
|
const id_to_global_symbol_map = std.EnumArray(Id, type).init(.{
|
|
.function_declaration = Function.Declaration,
|
|
.function_definition = Function,
|
|
.global_variable = GlobalVariable,
|
|
});
|
|
|
|
fn get_payload(global_symbol: *GlobalSymbol, comptime id: Id) *id_to_global_symbol_map.get(id) {
|
|
if (id == .function_definition) {
|
|
const function_declaration: *Function.Declaration = @alignCast(@fieldParentPtr("global_symbol", global_symbol));
|
|
const function_definition: *Function = @alignCast(@fieldParentPtr("declaration", function_declaration));
|
|
return function_definition;
|
|
}
|
|
|
|
return @fieldParentPtr("global_symbol", global_symbol);
|
|
}
|
|
};
|
|
|
|
const GlobalVariable = struct {
|
|
global_symbol: GlobalSymbol,
|
|
initial_value: *Value,
|
|
};
|
|
|
|
const Mutability = enum(u1){
|
|
@"const" = 0,
|
|
@"var" = 1,
|
|
};
|
|
|
|
const ArgumentSymbol = struct {
|
|
attributes: Attributes = .{},
|
|
argument_declaration: ArgumentDeclaration,
|
|
type: *Type,
|
|
pointer_type: *Type,
|
|
instruction: Instruction,
|
|
alignment: u32,
|
|
index: u32,
|
|
|
|
const Attributes = struct{
|
|
};
|
|
const Attribute = enum{
|
|
const Mask = std.EnumSet(Attribute);
|
|
};
|
|
};
|
|
|
|
const LocalSymbol = struct {
|
|
attributes: Attributes = .{},
|
|
local_declaration: LocalDeclaration,
|
|
type: *Type,
|
|
pointer_type: *Type,
|
|
instruction: Instruction,
|
|
alignment: u32,
|
|
|
|
const Attributes = struct{
|
|
mutability: Mutability = .@"const",
|
|
};
|
|
const Attribute = enum{
|
|
const Mask = std.EnumSet(Attribute);
|
|
};
|
|
};
|
|
|
|
const Parser = struct{
|
|
i: u64 = 0,
|
|
line: u32 = 0,
|
|
column: u32 = 0,
|
|
|
|
fn get_debug_line(parser: *const Parser) u32 {
|
|
return parser.line + 1;
|
|
}
|
|
|
|
fn get_debug_column(parser: *const Parser) u32 {
|
|
return @intCast(parser.i - parser.column + 1);
|
|
}
|
|
|
|
fn safe_flag(value: anytype, boolean: bool) @TypeOf(value) {
|
|
const result = value & (@as(@TypeOf(value), 0) -% @intFromBool(boolean));
|
|
return result;
|
|
}
|
|
|
|
fn get_next_ch_safe(file: []const u8, index: u64) u8 {
|
|
const next_index = index + 1;
|
|
const is_in_range = next_index < file.len;
|
|
const safe_index = safe_flag(next_index, is_in_range);
|
|
const unsafe_result = file[safe_index];
|
|
const safe_result = safe_flag(unsafe_result, is_in_range);
|
|
return safe_result;
|
|
}
|
|
|
|
fn skip_space(parser: *Parser, file: []const u8) void {
|
|
const original_i = parser.i;
|
|
|
|
if (original_i == file.len or !is_space(file[original_i], get_next_ch_safe(file, original_i))) return;
|
|
|
|
while (parser.i < file.len) : (parser.i += 1) {
|
|
const ch = file[parser.i];
|
|
const new_line = ch == '\n';
|
|
parser.line += @intFromBool(new_line);
|
|
|
|
if (new_line) {
|
|
parser.column = @intCast(parser.i + 1);
|
|
}
|
|
|
|
if (!is_space(ch, get_next_ch_safe(file, parser.i))) {
|
|
return;
|
|
}
|
|
|
|
if (file[parser.i] == '/') {
|
|
parser.i += 2;
|
|
|
|
while (parser.i < file.len) : (parser.i += 1) {
|
|
const is_line_feed = file[parser.i] == '\n';
|
|
if (is_line_feed) {
|
|
parser.line += 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (parser.i == file.len) break;
|
|
}
|
|
}
|
|
}
|
|
|
|
const ParseFieldData = struct{
|
|
type: *Type,
|
|
name: u32,
|
|
line: u32,
|
|
column: u32,
|
|
};
|
|
|
|
fn parse_field(parser: *Parser, thread: *Thread, file: *File) ?ParseFieldData{
|
|
const src = file.source_code;
|
|
parser.skip_space(src);
|
|
|
|
if (src[parser.i] == brace_close) {
|
|
return null;
|
|
}
|
|
|
|
const field_line = parser.get_debug_line();
|
|
const field_column = parser.get_debug_column();
|
|
const field_name = parser.parse_identifier(thread, src);
|
|
|
|
parser.skip_space(src);
|
|
|
|
parser.expect_character(src, ':');
|
|
|
|
parser.skip_space(src);
|
|
|
|
const field_type = parser.parse_type_expression(thread, file, &file.scope.scope);
|
|
|
|
parser.skip_space(src);
|
|
|
|
switch (src[parser.i]) {
|
|
',' => parser.i += 1,
|
|
'=' => {
|
|
fail_message("TODO: field default value");
|
|
},
|
|
else => fail(),
|
|
}
|
|
|
|
return ParseFieldData{
|
|
.type = field_type,
|
|
.name = field_name,
|
|
.line = field_line,
|
|
.column = field_column,
|
|
};
|
|
}
|
|
|
|
const ExpectFailureAction = enum{
|
|
@"unreachable",
|
|
trap,
|
|
};
|
|
fn parse_boolean_expect_intrinsic(parser: *Parser, analyzer: *Analyzer, thread: *Thread, file: *File, failure_action: ExpectFailureAction, debug_line: u32, debug_column: u32) void {
|
|
const condition = parser.parse_condition(analyzer, thread, file);
|
|
const expect_true_block = create_basic_block(thread);
|
|
const expect_false_block = create_basic_block(thread);
|
|
_ = emit_branch(analyzer, thread, .{
|
|
.condition = condition,
|
|
.taken = expect_true_block,
|
|
.not_taken = expect_false_block,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
analyzer.current_basic_block = expect_false_block;
|
|
|
|
switch (failure_action) {
|
|
.@"unreachable" => emit_unreachable(analyzer, thread, .{
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
}),
|
|
.trap => emit_trap(analyzer, thread, .{
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
}),
|
|
}
|
|
|
|
analyzer.current_basic_block = expect_true_block;
|
|
}
|
|
|
|
fn parse_intrinsic(parser: *Parser, analyzer: *Analyzer, thread: *Thread, file: *File, ty: ?*Type) ?*Value {
|
|
const src = file.source_code;
|
|
const debug_line = parser.get_debug_line();
|
|
const debug_column = parser.get_debug_column();
|
|
parser.expect_character(src, '#');
|
|
|
|
// TODO: make it more efficient
|
|
const identifier = parser.parse_raw_identifier(src);
|
|
if (identifier[0] == '"') {
|
|
unreachable;
|
|
} else {
|
|
const intrinsic_id = inline for (@typeInfo(Intrinsic).Enum.fields) |i_field| {
|
|
if (byte_equal(i_field.name, identifier)) {
|
|
break @field(Intrinsic, i_field.name);
|
|
}
|
|
} else {
|
|
fail_term("Unknown intrinsic", identifier);
|
|
};
|
|
|
|
switch (intrinsic_id) {
|
|
.assert => {
|
|
parser.parse_boolean_expect_intrinsic(analyzer, thread, file, .@"unreachable", debug_line, debug_column);
|
|
return null;
|
|
},
|
|
.require => {
|
|
parser.parse_boolean_expect_intrinsic(analyzer, thread, file, .trap, debug_line, debug_column);
|
|
return null;
|
|
},
|
|
.size => {
|
|
parser.skip_space(src);
|
|
parser.expect_character(src, '(');
|
|
parser.skip_space(src);
|
|
const size_type = parser.parse_type_expression(thread, file, analyzer.current_scope);
|
|
parser.skip_space(src);
|
|
parser.expect_character(src, ')');
|
|
const constant_int = create_constant_int(thread, .{
|
|
.n = size_type.size,
|
|
.type = ty orelse unreachable,
|
|
});
|
|
return &constant_int.value;
|
|
},
|
|
.trailing_zeroes => {
|
|
parser.skip_space(src);
|
|
parser.expect_character(src, '(');
|
|
parser.skip_space(src);
|
|
const value = parser.parse_expression(analyzer, thread, file, ty, .right);
|
|
parser.skip_space(src);
|
|
parser.expect_character(src, ')');
|
|
const tz = thread.trailing_zeroes.append(.{
|
|
.value = value,
|
|
.instruction = new_instruction(thread, .{
|
|
.id = .trailing_zeroes,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
}),
|
|
});
|
|
analyzer.append_instruction(&tz.instruction);
|
|
return &tz.instruction.value;
|
|
},
|
|
.leading_zeroes => {
|
|
parser.skip_space(src);
|
|
parser.expect_character(src, '(');
|
|
parser.skip_space(src);
|
|
const value = parser.parse_expression(analyzer, thread, file, ty, .right);
|
|
parser.skip_space(src);
|
|
parser.expect_character(src, ')');
|
|
const lz = thread.leading_zeroes.append(.{
|
|
.value = value,
|
|
.instruction = new_instruction(thread, .{
|
|
.id = .leading_zeroes,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
}),
|
|
});
|
|
analyzer.append_instruction(&lz.instruction);
|
|
return &lz.instruction.value;
|
|
},
|
|
.transmute => {
|
|
const destination_type = ty orelse fail();
|
|
parser.skip_space(src);
|
|
parser.expect_character(src, '(');
|
|
parser.skip_space(src);
|
|
const value = parser.parse_expression(analyzer, thread, file, null, .right);
|
|
parser.skip_space(src);
|
|
parser.expect_character(src, ')');
|
|
|
|
const source_type = value.get_type();
|
|
if (destination_type == source_type) {
|
|
fail();
|
|
}
|
|
|
|
const cast_id: Cast.Id = switch (destination_type.sema.id) {
|
|
.integer => block: {
|
|
const destination_integer = destination_type.get_payload(.integer);
|
|
_ = destination_integer; // autofix
|
|
switch (source_type.sema.id) {
|
|
.bitfield => {
|
|
const source_bitfield = source_type.get_payload(.bitfield);
|
|
if (source_bitfield.backing_type == destination_type) {
|
|
break :block .int_from_bitfield;
|
|
} else {
|
|
fail();
|
|
}
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
},
|
|
.bitfield => block: {
|
|
const destination_bitfield = destination_type.get_payload(.bitfield);
|
|
if (destination_bitfield.backing_type == source_type) {
|
|
break :block .int_from_bitfield;
|
|
} else {
|
|
fail();
|
|
}
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
|
|
const cast = emit_cast(analyzer, thread, .{
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
.value = value,
|
|
.type = destination_type,
|
|
.id = cast_id,
|
|
});
|
|
|
|
return &cast.instruction.value;
|
|
},
|
|
.int_from_pointer => {
|
|
if (ty) |expected_type| {
|
|
_ = expected_type; // autofix
|
|
unreachable;
|
|
}
|
|
|
|
const expected_type = &thread.integers[63].type;
|
|
|
|
parser.skip_space(src);
|
|
parser.expect_character(src, '(');
|
|
parser.skip_space(src);
|
|
const value = parser.parse_expression(analyzer, thread, file, null, .right);
|
|
switch (value.get_type().sema.id) {
|
|
.typed_pointer => {},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
parser.skip_space(src);
|
|
parser.expect_character(src, ')');
|
|
|
|
const cast = emit_cast(analyzer, thread, .{
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
.value = value,
|
|
.type = expected_type,
|
|
.id = .int_from_pointer,
|
|
});
|
|
return &cast.instruction.value;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn parse_raw_identifier(parser: *Parser, file: []const u8) []const u8 {
|
|
const identifier_start = parser.i;
|
|
const is_string_literal_identifier = file[identifier_start] == '"';
|
|
parser.i += @intFromBool(is_string_literal_identifier);
|
|
const start_ch = file[parser.i];
|
|
const is_valid_identifier_start = is_identifier_char_start(start_ch);
|
|
parser.i += @intFromBool(is_valid_identifier_start);
|
|
|
|
if (is_valid_identifier_start) {
|
|
while (parser.i < file.len) {
|
|
const ch = file[parser.i];
|
|
const is_ident = is_identifier_char(ch);
|
|
parser.i += @intFromBool(is_ident);
|
|
|
|
if (!is_ident) {
|
|
if (is_string_literal_identifier) {
|
|
if (file[parser.i] != '"') {
|
|
fail();
|
|
}
|
|
}
|
|
|
|
const identifier = file[identifier_start..parser.i];
|
|
return identifier;
|
|
}
|
|
} else {
|
|
fail();
|
|
}
|
|
} else {
|
|
fail();
|
|
}
|
|
}
|
|
|
|
fn parse_identifier(parser: *Parser, thread: *Thread, file: []const u8) u32 {
|
|
const identifier = parser.parse_raw_identifier(file);
|
|
const keyword = parse_keyword(identifier);
|
|
if (keyword != ~(@as(u32, 0))) {
|
|
fail();
|
|
}
|
|
|
|
if (byte_equal(identifier, "_")) {
|
|
return 0;
|
|
} else return intern_identifier(&thread.identifiers, identifier);
|
|
}
|
|
|
|
fn parse_non_escaped_string_literal(parser: *Parser, src: []const u8) []const u8 {
|
|
const start = parser.i;
|
|
const is_double_quote = src[start] == '"';
|
|
parser.i += @intFromBool(is_double_quote);
|
|
|
|
if (!is_double_quote) {
|
|
fail();
|
|
}
|
|
|
|
while (src[parser.i] != '"') : (parser.i += 1) {
|
|
if (src[parser.i] == '\\') fail();
|
|
}
|
|
|
|
parser.i += 1;
|
|
|
|
const end = parser.i;
|
|
|
|
return src[start..end];
|
|
}
|
|
|
|
fn parse_non_escaped_string_literal_content(parser: *Parser, src: []const u8) []const u8 {
|
|
const string_literal = parser.parse_non_escaped_string_literal(src);
|
|
return string_literal[1..][0..string_literal.len - 2];
|
|
}
|
|
|
|
fn expect_character(parser: *Parser, file: []const u8, expected: u8) void {
|
|
const index = parser.i;
|
|
if (index < file.len) {
|
|
const ch = file[index];
|
|
const matches = ch == expected;
|
|
parser.i += @intFromBool(matches);
|
|
if (!matches) {
|
|
fail();
|
|
}
|
|
} else {
|
|
fail();
|
|
}
|
|
}
|
|
|
|
pub fn parse_hex(slice: []const u8) u64 {
|
|
var value: u64 = 0;
|
|
for (slice) |ch| {
|
|
const byte = switch (ch) {
|
|
'0'...'9' => ch - '0',
|
|
'a'...'f' => ch - 'a' + 10,
|
|
'A'...'F' => ch - 'A' + 10,
|
|
else => fail(),
|
|
};
|
|
value = (value << 4) | (byte & 0xf);
|
|
}
|
|
|
|
return value;
|
|
}
|
|
|
|
fn parse_type_expression(parser: *Parser, thread: *Thread, file: *File, current_scope: *Scope) *Type {
|
|
const src = file.source_code;
|
|
const starting_index = parser.i;
|
|
const starting_ch = src[starting_index];
|
|
const is_array_start = starting_ch == '[';
|
|
const is_start_u = starting_ch == 'u';
|
|
const is_start_s = starting_ch == 's';
|
|
const float_start = starting_ch == 'f';
|
|
const is_void_start = starting_ch == 'v';
|
|
const is_pointer_sign_start = starting_ch == '*';
|
|
const integer_start = is_start_s or is_start_u;
|
|
const is_number_type_start = integer_start or float_start;
|
|
|
|
if (is_void_start) {
|
|
const id = parser.parse_raw_identifier(src);
|
|
if (byte_equal(id, "void")) {
|
|
return &thread.void;
|
|
} else {
|
|
parser.i = starting_index;
|
|
}
|
|
} else if (is_number_type_start) {
|
|
const expected_digit_start = parser.i + 1;
|
|
var i = expected_digit_start;
|
|
var decimal_digit_count: u32 = 0;
|
|
|
|
const top = i + 5;
|
|
|
|
while (i < top) : (i += 1) {
|
|
const ch = src[i];
|
|
const is_digit = is_decimal_digit(ch);
|
|
decimal_digit_count += @intFromBool(is_digit);
|
|
if (!is_digit) {
|
|
const is_alpha = is_alphabetic(ch);
|
|
if (is_alpha) decimal_digit_count = 0;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (decimal_digit_count != 0) {
|
|
parser.i += 1;
|
|
|
|
if (integer_start) {
|
|
const signedness: Type.Integer.Signedness = @enumFromInt(@intFromBool(is_start_s));
|
|
const bit_count: u32 = switch (decimal_digit_count) {
|
|
0 => unreachable,
|
|
1 => src[parser.i] - '0',
|
|
2 => @as(u32, src[parser.i] - '0') * 10 + (src[parser.i + 1] - '0'),
|
|
else => fail(),
|
|
};
|
|
parser.i += decimal_digit_count;
|
|
|
|
const index = bit_count - 1 + @intFromEnum(signedness) * @as(usize, 64);
|
|
const result = &thread.integers[index];
|
|
assert(result.type.bit_size == bit_count);
|
|
assert(result.signedness == signedness);
|
|
return &result.type;
|
|
} else if (float_start) {
|
|
fail();
|
|
} else {
|
|
unreachable;
|
|
}
|
|
} else {
|
|
fail();
|
|
}
|
|
} else if (is_pointer_sign_start) {
|
|
parser.i += 1;
|
|
|
|
const pointee_type = parser.parse_type_expression(thread, file, current_scope);
|
|
const typed_pointer = get_typed_pointer(thread, .{
|
|
.pointee = pointee_type,
|
|
});
|
|
return typed_pointer;
|
|
} else if (is_array_start) {
|
|
parser.i += 1;
|
|
|
|
parser.skip_space(src);
|
|
|
|
const element_count = parser.parse_constant_expression(thread, file, null);
|
|
switch (element_count.sema.id) {
|
|
.constant_int => {
|
|
const constant_int = element_count.get_payload(.constant_int);
|
|
parser.skip_space(src);
|
|
parser.expect_character(src, ']');
|
|
parser.skip_space(src);
|
|
|
|
const element_type = parser.parse_type_expression(thread, file, current_scope);
|
|
const array_type = get_array_type(thread, .{
|
|
.element_type = element_type,
|
|
.element_count = constant_int.n,
|
|
});
|
|
return array_type;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
}
|
|
|
|
const identifier = parser.parse_identifier(thread, src);
|
|
if (current_scope.get_declaration(identifier)) |lookup| {
|
|
const declaration = lookup.declaration.*;
|
|
switch (declaration.id) {
|
|
.@"struct" => {
|
|
const struct_type = declaration.get_payload(.@"struct");
|
|
return &struct_type.type;
|
|
},
|
|
.bitfield => {
|
|
const bitfield_type = declaration.get_payload(.bitfield);
|
|
return &bitfield_type.type;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
} else {
|
|
fail_term("Unrecognized type expression", thread.identifiers.get(identifier).?);
|
|
}
|
|
}
|
|
|
|
fn parse_constant_integer(parser: *Parser, thread: *Thread, file: *File, ty: *Type) *ConstantInt {
|
|
const src = file.source_code;
|
|
const starting_index = parser.i;
|
|
const starting_ch = src[starting_index];
|
|
if (starting_ch == '0') {
|
|
const follow_up_character = src[parser.i + 1];
|
|
const is_hex_start = follow_up_character == 'x';
|
|
const is_octal_start = follow_up_character == 'o';
|
|
const is_bin_start = follow_up_character == 'b';
|
|
const is_prefixed_start = is_hex_start or is_octal_start or is_bin_start;
|
|
const follow_up_alpha = is_alphabetic(follow_up_character);
|
|
const follow_up_digit = is_decimal_digit(follow_up_character);
|
|
const is_valid_after_zero = is_space(follow_up_character, get_next_ch_safe(src, follow_up_character)) or (!follow_up_digit and !follow_up_alpha);
|
|
|
|
if (is_prefixed_start) {
|
|
const Prefix = enum {
|
|
hexadecimal,
|
|
octal,
|
|
binary,
|
|
};
|
|
const prefix: Prefix = switch (follow_up_character) {
|
|
'x' => .hexadecimal,
|
|
'o' => .octal,
|
|
'b' => .binary,
|
|
else => unreachable,
|
|
};
|
|
|
|
parser.i += 2;
|
|
|
|
const start = parser.i;
|
|
|
|
switch (prefix) {
|
|
.hexadecimal => {
|
|
while (is_hex_digit(src[parser.i])) {
|
|
parser.i += 1;
|
|
}
|
|
|
|
const slice = src[start..parser.i];
|
|
const number = parse_hex(slice);
|
|
|
|
const constant_int = create_constant_int(thread, .{
|
|
.n = number,
|
|
.type = ty,
|
|
});
|
|
return constant_int;
|
|
},
|
|
.octal => {
|
|
unreachable;
|
|
},
|
|
.binary => {
|
|
unreachable;
|
|
},
|
|
}
|
|
fail();
|
|
} else if (is_valid_after_zero) {
|
|
parser.i += 1;
|
|
const constant_int = create_constant_int(thread, .{
|
|
.n = 0,
|
|
.type = ty,
|
|
});
|
|
return constant_int;
|
|
} else {
|
|
fail();
|
|
}
|
|
}
|
|
|
|
while (is_decimal_digit(src[parser.i])) {
|
|
parser.i += 1;
|
|
}
|
|
|
|
const character_count = parser.i - starting_index;
|
|
const slice = src[starting_index..][0..character_count];
|
|
var i = character_count;
|
|
var integer: u64 = 0;
|
|
var factor: u64 = 1;
|
|
|
|
while (i > 0) {
|
|
i -= 1;
|
|
const ch = slice[i];
|
|
const int = ch - '0';
|
|
const extra = int * factor;
|
|
integer += extra;
|
|
factor *= 10;
|
|
}
|
|
|
|
const constant_int = create_constant_int(thread, .{
|
|
.n = integer,
|
|
.type = ty,
|
|
});
|
|
|
|
return constant_int;
|
|
}
|
|
|
|
const ParseFieldInitialization = struct{
|
|
value: *Value,
|
|
name: u32,
|
|
index: u32,
|
|
line: u32,
|
|
column: u32,
|
|
};
|
|
|
|
fn parse_field_initialization(parser: *Parser, analyzer: *Analyzer, thread: *Thread, file: *File, names_initialized: []const u32, fields: []const *Type.AggregateField) ?ParseFieldInitialization{
|
|
const src = file.source_code;
|
|
parser.skip_space(src);
|
|
|
|
if (src[parser.i] == brace_close) {
|
|
return null;
|
|
}
|
|
|
|
const line = parser.get_debug_line();
|
|
const column = parser.get_debug_column();
|
|
|
|
parser.expect_character(src, '.');
|
|
const name = parser.parse_identifier(thread, src);
|
|
for (names_initialized) |initialized_name| {
|
|
if (initialized_name == name) {
|
|
fail();
|
|
}
|
|
}
|
|
const field_index = for (fields) |field| {
|
|
if (field.name == name) {
|
|
break field.index;
|
|
}
|
|
} else {
|
|
fail();
|
|
};
|
|
const field_type = fields[field_index].type;
|
|
parser.skip_space(src);
|
|
parser.expect_character(src, '=');
|
|
parser.skip_space(src);
|
|
|
|
const field_value = parser.parse_expression(analyzer, thread, file, field_type, .right);
|
|
|
|
parser.skip_space(src);
|
|
|
|
switch (src[parser.i]) {
|
|
brace_close => {},
|
|
',' => parser.i += 1,
|
|
else => fail(),
|
|
}
|
|
|
|
return ParseFieldInitialization{
|
|
.value = field_value,
|
|
.name = name,
|
|
.index = field_index,
|
|
.line = line,
|
|
.column = column,
|
|
};
|
|
}
|
|
|
|
fn parse_single_expression(parser: *Parser, analyzer: *Analyzer, thread: *Thread, file: *File, maybe_type: ?*Type, side: Side) *Value {
|
|
const src = file.source_code;
|
|
const Unary = enum{
|
|
none,
|
|
one_complement,
|
|
negation,
|
|
};
|
|
|
|
const unary: Unary = switch (src[parser.i]) {
|
|
'A'...'Z', 'a'...'z', '_' => Unary.none,
|
|
'0'...'9' => Unary.none,
|
|
'-' => block: {
|
|
parser.i += 1;
|
|
break :block .negation;
|
|
},
|
|
'~' => block: {
|
|
parser.i += 1;
|
|
break :block .one_complement;
|
|
},
|
|
'#' => {
|
|
// parse intrinsic
|
|
const value = parser.parse_intrinsic(analyzer, thread, file, maybe_type) orelse unreachable;
|
|
return value;
|
|
},
|
|
brace_open => {
|
|
parser.i += 1;
|
|
|
|
// This is a composite initialization
|
|
const ty = maybe_type orelse fail();
|
|
switch (ty.sema.id) {
|
|
.@"struct" => {
|
|
const struct_type = ty.get_payload(.@"struct");
|
|
|
|
var names_initialized = PinnedArray(u32){};
|
|
var is_constant = true;
|
|
var values = PinnedArray(*Value){};
|
|
var field_index: u32 = 0;
|
|
var is_field_ordered = true;
|
|
var line_info = PinnedArray(struct{ line: u32, column: u32 }){};
|
|
|
|
while (parser.parse_field_initialization(analyzer, thread, file, names_initialized.const_slice(), struct_type.fields)) |field_data| : (field_index += 1) {
|
|
is_field_ordered = is_field_ordered and field_index == field_data.index;
|
|
_ = names_initialized.append(field_data.name);
|
|
_ = values.append(field_data.value);
|
|
_ = line_info.append(.{ .line = field_data.line, .column = field_data.column });
|
|
is_constant = is_constant and field_data.value.is_constant();
|
|
}
|
|
|
|
parser.i += 1;
|
|
|
|
if (is_field_ordered and field_index == struct_type.fields.len) {
|
|
if (is_constant) {
|
|
const constant_struct = thread.constant_structs.append(.{
|
|
.value = .{
|
|
.sema = .{
|
|
.thread = thread.get_index(),
|
|
.resolved = true,
|
|
.id = .constant_struct,
|
|
},
|
|
},
|
|
.values = values.const_slice(),
|
|
.type = ty,
|
|
});
|
|
return &constant_struct.value;
|
|
} else {
|
|
const undefined_value = thread.undefined_values.append(.{
|
|
.value = .{
|
|
.sema = .{
|
|
.id = .undefined,
|
|
.resolved = true,
|
|
.thread = thread.get_index(),
|
|
},
|
|
},
|
|
.type = ty,
|
|
});
|
|
|
|
if (true) unreachable;
|
|
var aggregate = &undefined_value.value;
|
|
for (values.const_slice(), line_info.const_slice(), 0..) |value, li, index| {
|
|
if (true) unreachable;
|
|
const insert_value = emit_insert_value(thread, analyzer, .{
|
|
.aggregate = aggregate,
|
|
.value = value,
|
|
.index = @intCast(index),
|
|
.line = li.line,
|
|
.column = li.column,
|
|
.scope = analyzer.current_scope,
|
|
.type = ty,
|
|
});
|
|
aggregate = &insert_value.instruction.value;
|
|
}
|
|
|
|
return aggregate;
|
|
}
|
|
} else {
|
|
fail();
|
|
}
|
|
},
|
|
.bitfield => {
|
|
const bitfield_type = ty.get_payload(.bitfield);
|
|
|
|
var names_initialized = PinnedArray(u32){};
|
|
var is_constant = true;
|
|
var values = PinnedArray(*Value){};
|
|
|
|
var field_index: u32 = 0;
|
|
var is_field_ordered = true;
|
|
while (parser.parse_field_initialization(analyzer, thread, file, names_initialized.const_slice(), bitfield_type.fields)) |field_data| : (field_index += 1) {
|
|
is_field_ordered = is_field_ordered and field_index == field_data.index;
|
|
_ = names_initialized.append(field_data.name);
|
|
_ = values.append(field_data.value);
|
|
is_constant = is_constant and field_data.value.is_constant();
|
|
}
|
|
|
|
parser.i += 1;
|
|
|
|
if (is_constant) {
|
|
if (is_field_ordered and field_index == bitfield_type.fields.len) {
|
|
var result: u64 = 0;
|
|
var bit_offset: u16 = 0;
|
|
for (values.const_slice()) |value| {
|
|
switch (value.sema.id) {
|
|
.constant_int => {
|
|
const constant_int = value.get_payload(.constant_int);
|
|
const field_bit_count = constant_int.type.bit_size;
|
|
const field_value = constant_int.n;
|
|
const offset_value = field_value << @as(u6, @intCast(bit_offset));
|
|
result |= offset_value;
|
|
bit_offset += @intCast(field_bit_count);
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
}
|
|
|
|
const constant_int = create_constant_int(thread, .{
|
|
.n = result,
|
|
.type = bitfield_type.backing_type,
|
|
});
|
|
return &constant_int.value; // autofix
|
|
} else {
|
|
unreachable;
|
|
}
|
|
} else {
|
|
fail_message("TODO: runtime struct initialization");
|
|
}
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
},
|
|
'[' => {
|
|
// This is an array expression
|
|
parser.i += 1;
|
|
|
|
const ty = maybe_type orelse fail();
|
|
const array_type = ty.get_payload(.array);
|
|
const element_count = array_type.descriptor.element_count;
|
|
const element_type = array_type.descriptor.element_type;
|
|
|
|
parser.skip_space(src);
|
|
|
|
var values = PinnedArray(*Value){};
|
|
|
|
var is_constant = true;
|
|
while (true) {
|
|
parser.skip_space(src);
|
|
|
|
if (src[parser.i] == ']') {
|
|
break;
|
|
}
|
|
|
|
const value = parser.parse_expression(analyzer, thread, file, element_type, .right);
|
|
is_constant = is_constant and value.is_constant();
|
|
_ = values.append(value);
|
|
|
|
parser.skip_space(src);
|
|
|
|
switch (src[parser.i]) {
|
|
']' => {},
|
|
',' => parser.i += 1,
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
parser.i += 1;
|
|
|
|
if (values.length != element_count) {
|
|
fail();
|
|
}
|
|
|
|
if (is_constant) {
|
|
const constant_array = thread.constant_arrays.append(.{
|
|
.value = .{
|
|
.sema = .{
|
|
.thread = thread.get_index(),
|
|
.resolved = true,
|
|
.id = .constant_array,
|
|
},
|
|
},
|
|
.values = values.const_slice(),
|
|
.type = ty,
|
|
});
|
|
return &constant_array.value;
|
|
} else {
|
|
unreachable;
|
|
}
|
|
},
|
|
else => unreachable,
|
|
};
|
|
|
|
const starting_index = parser.i;
|
|
const starting_ch = src[starting_index];
|
|
const is_digit_start = is_decimal_digit(starting_ch);
|
|
const is_alpha_start = is_alphabetic(starting_ch);
|
|
const debug_line = parser.get_debug_line();
|
|
const debug_column = parser.get_debug_column();
|
|
|
|
if (is_digit_start) {
|
|
const ty = maybe_type orelse switch (unary) {
|
|
.none => &thread.integers[63].type,
|
|
.one_complement => fail(),
|
|
.negation => fail(),
|
|
};
|
|
|
|
switch (ty.sema.id) {
|
|
.integer => {
|
|
const constant_int = parser.parse_constant_integer(thread, file, ty);
|
|
switch (unary) {
|
|
.none => return &constant_int.value,
|
|
.one_complement => unreachable,
|
|
.negation => {
|
|
const integer_ty = ty.get_payload(.integer);
|
|
switch (integer_ty.signedness) {
|
|
.signed => {
|
|
var n: i64 = @intCast(constant_int.n);
|
|
n = 0 - n;
|
|
const result = create_constant_int(thread, .{
|
|
.n = @bitCast(n),
|
|
.type = ty,
|
|
});
|
|
return &result.value;
|
|
},
|
|
.unsigned => fail(),
|
|
}
|
|
},
|
|
}
|
|
},
|
|
else => unreachable,
|
|
}
|
|
} else if (is_alpha_start) {
|
|
var resolved = true;
|
|
const name = parser.parse_raw_identifier(src);
|
|
const keyword = parse_keyword(name);
|
|
if (keyword != ~(@as(u32, 0))) {
|
|
switch (@as(Keyword, @enumFromInt(keyword))) {
|
|
.undefined => {
|
|
const ty = maybe_type orelse fail();
|
|
const undef = thread.undefined_values.append(.{
|
|
.value = .{
|
|
.sema = .{
|
|
.thread = thread.get_index(),
|
|
.resolved = true,
|
|
.id = .undefined,
|
|
},
|
|
},
|
|
.type = ty,
|
|
});
|
|
|
|
return &undef.value;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
}
|
|
|
|
const identifier: u32 = if (byte_equal(name, "_")) 0 else intern_identifier(&thread.identifiers, name);
|
|
|
|
var initial_type: ?*Type = null;
|
|
const initial_value = if (analyzer.current_scope.get_declaration(identifier)) |lookup_result| blk: {
|
|
switch (lookup_result.declaration.*.id) {
|
|
.local => {
|
|
const local_declaration = lookup_result.declaration.*.get_payload(.local);
|
|
const local_symbol = local_declaration.to_symbol();
|
|
initial_type = local_symbol.type;
|
|
break :blk &local_symbol.instruction.value;
|
|
},
|
|
.global => {
|
|
const global_declaration = lookup_result.declaration.*.get_payload(.global);
|
|
switch (global_declaration.id) {
|
|
.global_symbol => {
|
|
const global_symbol = global_declaration.to_symbol();
|
|
initial_type = global_symbol.type;
|
|
break :blk &global_symbol.value;
|
|
},
|
|
.unresolved_import => {
|
|
const import: *Import = global_declaration.get_payload(.unresolved_import);
|
|
assert(!import.resolved);
|
|
resolved = false;
|
|
const import_index = for (file.imports.slice(), 0..) |existing_import, i| {
|
|
if (import == existing_import) break i;
|
|
} else unreachable;
|
|
const lazy_expression = thread.lazy_expressions.append(LazyExpression.init(@ptrCast(lookup_result.declaration), thread));
|
|
|
|
while (true) {
|
|
switch (src[parser.i]) {
|
|
'.' => {
|
|
parser.i += 1;
|
|
const right = parser.parse_identifier(thread, src);
|
|
lazy_expression.add(right);
|
|
},
|
|
'(' => break,
|
|
else => @panic((src.ptr + parser.i)[0..1]),
|
|
}
|
|
}
|
|
|
|
switch (src[parser.i]) {
|
|
'(' => {
|
|
parser.i += 1;
|
|
// TODO: arguments
|
|
parser.expect_character(src, ')');
|
|
|
|
const call = thread.calls.append(.{
|
|
.instruction = new_instruction(thread, .{
|
|
.id = .call,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
.resolved = false,
|
|
}),
|
|
.callable = &lazy_expression.value,
|
|
.arguments = &.{},
|
|
.calling_convention = .c,
|
|
});
|
|
analyzer.append_instruction(&call.instruction);
|
|
|
|
_ = file.values_per_import.get(@enumFromInt(import_index)).append(&call.instruction.value);
|
|
return &call.instruction.value;
|
|
},
|
|
else => @panic((src.ptr + parser.i)[0..1]),
|
|
}
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
},
|
|
.argument => {
|
|
const argument_declaration = lookup_result.declaration.*.get_payload(.argument);
|
|
const argument_symbol = argument_declaration.to_symbol();
|
|
initial_type = argument_symbol.type;
|
|
break :blk &argument_symbol.instruction.value;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
} else blk: {
|
|
resolved = false;
|
|
const lazy_expression = thread.local_lazy_expressions.append(.{
|
|
.value = .{
|
|
.sema = .{
|
|
.id = .local_lazy_expression,
|
|
.thread = thread.get_index(),
|
|
.resolved = false,
|
|
},
|
|
},
|
|
.name = identifier,
|
|
});
|
|
_ = file.local_lazy_expressions.append(lazy_expression);
|
|
break :blk &lazy_expression.value;
|
|
};
|
|
|
|
switch (src[parser.i]) {
|
|
' ', ',', ';', ')' => {
|
|
return switch (unary) {
|
|
.none => switch (side) {
|
|
.right => right: {
|
|
const ty = if (initial_type) |source_ty| if (maybe_type) |destination_ty| blk: {
|
|
switch (typecheck(destination_ty, source_ty)) {
|
|
.success => {},
|
|
}
|
|
break :blk source_ty;
|
|
} else source_ty
|
|
else if (maybe_type) |ty| ty else fail();
|
|
|
|
const load = emit_load(analyzer, thread, .{
|
|
.value = initial_value,
|
|
.type = ty,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
break :right &load.instruction.value;
|
|
},
|
|
.left => initial_value,
|
|
},
|
|
.negation => neg: {
|
|
assert(side == .right);
|
|
var r = initial_value;
|
|
if (initial_value.get_type() != initial_type.?) {
|
|
const load = emit_load(analyzer, thread, .{
|
|
.value = initial_value,
|
|
.type = initial_type.?,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
r = &load.instruction.value;
|
|
}
|
|
|
|
const operand = create_constant_int(thread, .{
|
|
.type = initial_type.?,
|
|
.n = 0,
|
|
});
|
|
|
|
const sub = emit_integer_binary_operation(analyzer, thread, .{
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
.left = &operand.value,
|
|
.right = r,
|
|
.id = .sub,
|
|
.type = initial_type.?,
|
|
});
|
|
break :neg &sub.instruction.value;
|
|
},
|
|
.one_complement => oc: {
|
|
assert(side == .right);
|
|
var r = initial_value;
|
|
if (initial_value.get_type() != initial_type.?) {
|
|
const load = emit_load(analyzer, thread, .{
|
|
.value = initial_value,
|
|
.type = initial_type.?,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
r = &load.instruction.value;
|
|
}
|
|
|
|
const operand = create_constant_int(thread, .{
|
|
.type = initial_type.?,
|
|
.n = std.math.maxInt(u64),
|
|
});
|
|
|
|
const xor = emit_integer_binary_operation(analyzer, thread, .{
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
.left = r,
|
|
.right = &operand.value,
|
|
.id = .xor,
|
|
.type = initial_type.?,
|
|
});
|
|
break :oc &xor.instruction.value;
|
|
},
|
|
};
|
|
},
|
|
'&' => {
|
|
parser.i += 1;
|
|
|
|
return initial_value;
|
|
},
|
|
'(' => {
|
|
parser.i += 1;
|
|
parser.skip_space(src);
|
|
|
|
switch (initial_value.sema.resolved) {
|
|
true => {
|
|
const FunctionCallData = struct{
|
|
type: *Type.Function,
|
|
value: *Value,
|
|
calling_convention: CallingConvention,
|
|
};
|
|
|
|
const function_call_data: FunctionCallData = switch (initial_type.?.sema.id) {
|
|
.function => .{
|
|
.type = initial_type.?.get_payload(.function),
|
|
.value = initial_value,
|
|
.calling_convention = switch (initial_value.sema.id) {
|
|
.global_symbol => b: {
|
|
const global_symbol = initial_value.get_payload(.global_symbol);
|
|
switch (global_symbol.id) {
|
|
.function_definition => {
|
|
const function_definition = global_symbol.get_payload(.function_definition);
|
|
break :b function_definition.declaration.get_function_type().abi.calling_convention;
|
|
},
|
|
.function_declaration => {
|
|
const function_declaration = global_symbol.get_payload(.function_declaration);
|
|
break :b function_declaration.get_function_type().abi.calling_convention;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
},
|
|
},
|
|
.typed_pointer => switch (initial_value.sema.id) {
|
|
.instruction => blk: {
|
|
const instruction = initial_value.get_payload(.instruction);
|
|
switch (instruction.id) {
|
|
.local_symbol => {
|
|
const local_symbol = instruction.get_payload(.local_symbol);
|
|
const pointer_type = local_symbol.type.get_payload(.typed_pointer);
|
|
const function_type = pointer_type.descriptor.pointee.get_payload(.function);
|
|
|
|
const load = emit_load(analyzer, thread, .{
|
|
.value = &local_symbol.instruction.value,
|
|
.type = local_symbol.type,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
break :blk .{
|
|
.type = function_type,
|
|
.value = &load.instruction.value,
|
|
// TODO:
|
|
.calling_convention = .c,
|
|
};
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
|
|
const function_type = function_call_data.type;
|
|
const function_value = function_call_data.value;
|
|
|
|
var abi_argument_values = PinnedArray(*Value){};
|
|
|
|
const indirect_return_value: ?*Value = switch (function_type.abi.return_type_abi.kind) {
|
|
.indirect => |indirect| ind: {
|
|
if (indirect.alignment <= indirect.type.alignment) {
|
|
const return_local = emit_local_symbol(analyzer, thread, .{
|
|
.name = 0,
|
|
.initial_value = null,
|
|
.type = indirect.type,
|
|
.line = 0,
|
|
.column = 0,
|
|
});
|
|
const return_value = &return_local.instruction.value;
|
|
_ = abi_argument_values.append(return_value);
|
|
|
|
break :ind return_value;
|
|
} else {
|
|
unreachable;
|
|
}
|
|
},
|
|
else => null,
|
|
};
|
|
|
|
var original_argument_i: u32 = 0;
|
|
const declaration_argument_count = function_type.abi.original_argument_types.len;
|
|
while (true) {
|
|
parser.skip_space(src);
|
|
|
|
if (src[parser.i] == ')') {
|
|
break;
|
|
}
|
|
|
|
const argument_index = original_argument_i;
|
|
if (argument_index >= declaration_argument_count) {
|
|
fail();
|
|
}
|
|
|
|
const argument_abi = function_type.abi.argument_types_abi[argument_index];
|
|
const argument_type = function_type.abi.original_argument_types[argument_index];
|
|
const argument_value = parser.parse_expression(analyzer, thread, file, argument_type, .right);
|
|
const argument_value_type = argument_value.get_type();
|
|
|
|
parser.skip_space(src);
|
|
|
|
switch (src[parser.i]) {
|
|
',' => parser.i += 1,
|
|
')' => {},
|
|
else => unreachable,
|
|
}
|
|
|
|
switch (argument_abi.kind) {
|
|
.direct => {
|
|
assert(argument_value_type == argument_type);
|
|
_ = abi_argument_values.append(argument_value);
|
|
},
|
|
.direct_coerce => |coerced_type| {
|
|
const coerced_value = emit_direct_coerce(analyzer, thread, .{
|
|
.original_value = argument_value,
|
|
.coerced_type = coerced_type,
|
|
});
|
|
_ = abi_argument_values.append(coerced_value);
|
|
},
|
|
.indirect => |indirect| {
|
|
assert(argument_type == indirect.type);
|
|
const direct = if (!argument_abi.attributes.by_value) false else switch (argument_type.sema.id) {
|
|
.typed_pointer => unreachable,
|
|
else => false,
|
|
};
|
|
|
|
if (direct) {
|
|
unreachable;
|
|
} else {
|
|
const indirect_local = emit_local_symbol(analyzer, thread, .{
|
|
.type = argument_type,
|
|
.name = 0,
|
|
.initial_value = argument_value,
|
|
.line = 0,
|
|
.column = 0,
|
|
});
|
|
_ = abi_argument_values.append(&indirect_local.instruction.value);
|
|
}
|
|
},
|
|
.direct_pair => |pair| {
|
|
const pair_struct_type = get_anonymous_two_field_struct(thread, pair);
|
|
const are_similar = b: {
|
|
if (pair_struct_type == argument_type) {
|
|
break :b true;
|
|
} else {
|
|
switch (argument_type.sema.id) {
|
|
.@"struct" => {
|
|
const original_struct_type = argument_type.get_payload(.@"struct");
|
|
if (original_struct_type.fields.len == 2) {
|
|
for (original_struct_type.fields, pair) |field, pair_type| {
|
|
if (field.type != pair_type) break :b false;
|
|
}
|
|
break :b true;
|
|
} else break :b false;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
}
|
|
};
|
|
|
|
if (are_similar) {
|
|
const extract_0 = emit_extract_value(thread, analyzer, .{
|
|
.aggregate = argument_value,
|
|
.index = 0,
|
|
.line = 0,
|
|
.column = 0,
|
|
.scope = analyzer.current_scope,
|
|
.type = pair[0],
|
|
});
|
|
_ = abi_argument_values.append(&extract_0.instruction.value);
|
|
const extract_1 = emit_extract_value(thread, analyzer, .{
|
|
.aggregate = argument_value,
|
|
.index = 1,
|
|
.line = 0,
|
|
.column = 0,
|
|
.scope = analyzer.current_scope,
|
|
.type = pair[1],
|
|
});
|
|
_ = abi_argument_values.append(&extract_1.instruction.value);
|
|
} else {
|
|
const local_value = if (argument_type.alignment < pair_struct_type.alignment) b: {
|
|
const coerced_local = emit_local_symbol(analyzer, thread, .{
|
|
.type = pair_struct_type,
|
|
.name = 0,
|
|
.initial_value = argument_value,
|
|
.line = 0,
|
|
.column = 0,
|
|
});
|
|
break :b &coerced_local.instruction.value;
|
|
} else b: {
|
|
const argument_local = emit_local_symbol(analyzer, thread, .{
|
|
.type = argument_type,
|
|
.name = 0,
|
|
.initial_value = argument_value,
|
|
.line = 0,
|
|
.column = 0,
|
|
});
|
|
break :b &argument_local.instruction.value;
|
|
};
|
|
const gep0 = emit_gep(thread, analyzer, .{
|
|
.pointer = local_value,
|
|
.type = pair[0],
|
|
.aggregate_type = pair_struct_type,
|
|
.is_struct = true,
|
|
.index = &create_constant_int(thread, .{
|
|
.n = 0,
|
|
.type = &thread.integers[31].type,
|
|
}).value,
|
|
.line = 0,
|
|
.column = 0,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
const load0 = emit_load(analyzer, thread, .{
|
|
.type = pair[0],
|
|
.value = &gep0.instruction.value,
|
|
.scope = analyzer.current_scope,
|
|
.line = 0,
|
|
.column = 0,
|
|
});
|
|
_ = abi_argument_values.append(&load0.instruction.value);
|
|
const gep1 = emit_gep(thread, analyzer, .{
|
|
.pointer = local_value,
|
|
.type = pair[1],
|
|
.aggregate_type = pair_struct_type,
|
|
.is_struct = true,
|
|
.index = &create_constant_int(thread, .{
|
|
.n = 1,
|
|
.type = &thread.integers[31].type,
|
|
}).value,
|
|
.line = 0,
|
|
.column = 0,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
const load1 = emit_load(analyzer, thread, .{
|
|
.type = pair[1],
|
|
.value = &gep1.instruction.value,
|
|
.scope = analyzer.current_scope,
|
|
.line = 0,
|
|
.column = 0,
|
|
});
|
|
_ = abi_argument_values.append(&load1.instruction.value);
|
|
}
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
|
|
original_argument_i += 1;
|
|
}
|
|
|
|
parser.i += 1;
|
|
const call = thread.calls.append(.{
|
|
.instruction = new_instruction(thread, .{
|
|
.id = .call,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
}),
|
|
.callable = function_value,
|
|
.arguments = abi_argument_values.const_slice(),
|
|
.calling_convention = function_call_data.calling_convention,
|
|
});
|
|
analyzer.append_instruction(&call.instruction);
|
|
|
|
if (indirect_return_value) |irv| {
|
|
const load = emit_load(analyzer, thread, .{
|
|
.value = irv,
|
|
.type = function_type.abi.original_return_type,
|
|
.line = 0,
|
|
.column = 0,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
return &load.instruction.value;
|
|
} else {
|
|
return &call.instruction.value;
|
|
}
|
|
},
|
|
false => {
|
|
var argument_values = PinnedArray(*Value){};
|
|
|
|
while (true) {
|
|
parser.skip_space(src);
|
|
|
|
if (src[parser.i] == ')') {
|
|
break;
|
|
}
|
|
|
|
const passed_argument_value = parser.parse_expression(analyzer, thread, file, null, .right);
|
|
_ = argument_values.append(passed_argument_value);
|
|
|
|
parser.skip_space(src);
|
|
|
|
switch (src[parser.i]) {
|
|
',' => parser.i += 1,
|
|
')' => {},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
parser.i += 1;
|
|
|
|
const call = thread.calls.append(.{
|
|
.instruction = new_instruction(thread, .{
|
|
.id = .call,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
.resolved = false,
|
|
}),
|
|
.callable = initial_value,
|
|
.arguments = argument_values.const_slice(),
|
|
.calling_convention = .c,
|
|
});
|
|
switch (initial_value.sema.id) {
|
|
.local_lazy_expression => {
|
|
const local_lazy_expression = initial_value.get_payload(.local_lazy_expression);
|
|
_ = local_lazy_expression.values.append(&call.instruction.value);
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
analyzer.append_instruction(&call.instruction);
|
|
return &call.instruction.value;
|
|
},
|
|
}
|
|
},
|
|
'[' => {
|
|
parser.i += 1;
|
|
|
|
parser.skip_space(src);
|
|
|
|
const declaration_element_type = switch (initial_type.?.sema.id) {
|
|
.array => block: {
|
|
const array_type = initial_type.?.get_payload(.array);
|
|
break :block array_type.descriptor.element_type;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
|
|
const index = parser.parse_expression(analyzer, thread, file, null, .right);
|
|
|
|
parser.skip_space(src);
|
|
|
|
parser.expect_character(src, ']');
|
|
const gep = emit_gep(thread, analyzer, .{
|
|
.pointer = initial_value,
|
|
.index = index,
|
|
.type = declaration_element_type,
|
|
.aggregate_type = initial_type.?,
|
|
.is_struct = false,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
|
|
return switch (side) {
|
|
.left => &gep.instruction.value,
|
|
.right => block: {
|
|
const load = emit_load(analyzer, thread, .{
|
|
.value = &gep.instruction.value,
|
|
.type = declaration_element_type,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
break :block &load.instruction.value;
|
|
},
|
|
};
|
|
},
|
|
'.' => {
|
|
const result = parser.parse_field_access(analyzer, thread, file, maybe_type, side, initial_value, initial_type.?, debug_line, debug_column);
|
|
return result;
|
|
},
|
|
'@' => {
|
|
parser.i += 1;
|
|
|
|
assert(initial_type.?.sema.id == .typed_pointer);
|
|
|
|
const load = emit_load(analyzer, thread, .{
|
|
.value = initial_value,
|
|
.type = initial_type.?,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
|
|
return switch (side) {
|
|
.left => &load.instruction.value,
|
|
.right => block: {
|
|
const pointer_load_type = switch (initial_type.?.sema.id) {
|
|
.typed_pointer => b: {
|
|
const typed_pointer = initial_type.?.get_payload(.typed_pointer);
|
|
break :b typed_pointer.descriptor.pointee;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
|
|
if (maybe_type) |ty| {
|
|
switch (typecheck(ty, pointer_load_type)) {
|
|
.success => {},
|
|
}
|
|
}
|
|
|
|
const pointer_load = emit_load(analyzer, thread, .{
|
|
.value = &load.instruction.value,
|
|
.type = pointer_load_type,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
|
|
break :block &pointer_load.instruction.value;
|
|
},
|
|
};
|
|
},
|
|
else => unreachable,
|
|
}
|
|
} else {
|
|
fail();
|
|
}
|
|
}
|
|
|
|
const CurrentOperation = enum{
|
|
none,
|
|
assign,
|
|
add,
|
|
add_assign,
|
|
sub,
|
|
sub_assign,
|
|
mul,
|
|
mul_assign,
|
|
udiv,
|
|
udiv_assign,
|
|
sdiv,
|
|
sdiv_assign,
|
|
@"and",
|
|
and_assign,
|
|
@"or",
|
|
or_assign,
|
|
@"xor",
|
|
xor_assign,
|
|
shift_left,
|
|
shift_left_assign,
|
|
arithmetic_shift_right,
|
|
arithmetic_shift_right_assign,
|
|
logical_shift_right,
|
|
logical_shift_right_assign,
|
|
|
|
compare_equal,
|
|
compare_not_equal,
|
|
compare_unsigned_greater,
|
|
compare_unsigned_greater_equal,
|
|
compare_signed_greater,
|
|
compare_signed_greater_equal,
|
|
compare_unsigned_less,
|
|
compare_unsigned_less_equal,
|
|
compare_signed_less,
|
|
compare_signed_less_equal,
|
|
|
|
@"orelse",
|
|
};
|
|
|
|
fn parse_constant_expression(parser: *Parser, thread: *Thread, file: *File, maybe_type: ?*Type) *Value {
|
|
const src = file.source_code;
|
|
const starting_index = parser.i;
|
|
const starting_ch = src[starting_index];
|
|
const is_digit_start = is_decimal_digit(starting_ch);
|
|
if (is_digit_start) {
|
|
const ty = maybe_type orelse &thread.integers[63].type;
|
|
switch (ty.sema.id) {
|
|
.integer => {
|
|
const constant_int = parser.parse_constant_integer(thread, file, ty);
|
|
return &constant_int.value;
|
|
},
|
|
else => unreachable,
|
|
}
|
|
} else {
|
|
unreachable;
|
|
}
|
|
}
|
|
|
|
fn parse_field_access(parser: *Parser, analyzer: *Analyzer, thread: *Thread, file: *File, expected_type: ?*Type, side: Side, value: *Value, ty: *Type, line: u32, column: u32) *Value{
|
|
const src = file.source_code;
|
|
parser.expect_character(src, '.');
|
|
|
|
switch (ty.sema.id) {
|
|
.@"struct" => {
|
|
const struct_type = ty.get_payload(.@"struct");
|
|
const field_name = parser.parse_identifier(thread, src);
|
|
const field_index = for (struct_type.fields) |field| {
|
|
if (field.name == field_name) {
|
|
break field.index;
|
|
}
|
|
} else fail();
|
|
|
|
const index_value = create_constant_int(thread, .{
|
|
.type = &thread.integers[31].type,
|
|
.n = field_index,
|
|
});
|
|
|
|
const gep = emit_gep(thread, analyzer, .{
|
|
.line = line,
|
|
.column = column,
|
|
.scope = analyzer.current_scope,
|
|
.pointer = value,
|
|
.index = &index_value.value,
|
|
.aggregate_type = ty,
|
|
.type = struct_type.fields[field_index].type,
|
|
.is_struct = true,
|
|
});
|
|
|
|
const result = switch (side) {
|
|
.right => block: {
|
|
const load = emit_load(analyzer, thread, .{
|
|
.value = &gep.instruction.value,
|
|
.type = gep.type,
|
|
.scope = analyzer.current_scope,
|
|
.line = line,
|
|
.column = column,
|
|
});
|
|
|
|
break :block &load.instruction.value;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
|
|
const result_type = result.get_type();
|
|
if (expected_type) |expected| {
|
|
switch (typecheck(expected, result_type)) {
|
|
.success => {},
|
|
}
|
|
}
|
|
|
|
return result;
|
|
},
|
|
.bitfield => {
|
|
const bitfield_type = ty.get_payload(.bitfield);
|
|
const field_name = parser.parse_identifier(thread, src);
|
|
const field_index = for (bitfield_type.fields) |field| {
|
|
if (field.name == field_name) {
|
|
break field.index;
|
|
}
|
|
} else fail();
|
|
const field = bitfield_type.fields[field_index];
|
|
|
|
const load = emit_load(analyzer, thread, .{
|
|
.value = value,
|
|
.type = bitfield_type.backing_type,
|
|
.line = line,
|
|
.column = column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
|
|
var result = &load.instruction.value;
|
|
if (field.member_offset > 0) {
|
|
const shifter = create_constant_int(thread, .{
|
|
.n = field.member_offset,
|
|
.type = ty,
|
|
});
|
|
const shift = emit_integer_binary_operation(analyzer, thread, .{
|
|
.line = line,
|
|
.column = column,
|
|
.scope = analyzer.current_scope,
|
|
.left = result,
|
|
.right = &shifter.value,
|
|
.id = .logical_shift_right,
|
|
.type = bitfield_type.backing_type,
|
|
});
|
|
result = &shift.instruction.value;
|
|
}
|
|
|
|
if (field.type != bitfield_type.backing_type) {
|
|
const cast = emit_cast(analyzer, thread, .{
|
|
.value = result,
|
|
.type = field.type,
|
|
.id = .truncate,
|
|
.line = line,
|
|
.column = column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
result = &cast.instruction.value;
|
|
}
|
|
|
|
assert(result != value);
|
|
return result;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
}
|
|
|
|
fn parse_expression(parser: *Parser, analyzer: *Analyzer, thread: *Thread, file: *File, ty: ?*Type, side: Side) *Value {
|
|
const src = file.source_code;
|
|
|
|
var current_operation = CurrentOperation.none;
|
|
var previous_value: *Value = undefined;
|
|
var iterations: usize = 0;
|
|
var it_ty: ?*Type = ty;
|
|
|
|
while (true) {
|
|
if (iterations == 1 and it_ty == null) {
|
|
it_ty = previous_value.get_type();
|
|
}
|
|
const debug_line = parser.get_debug_line();
|
|
const debug_column = parser.get_debug_column();
|
|
|
|
var current_value: *Value = undefined;
|
|
if (src[parser.i] == '(') {
|
|
parser.i += 1;
|
|
current_value = parser.parse_expression(analyzer, thread, file, it_ty, side);
|
|
parser.expect_character(src, ')');
|
|
} else {
|
|
current_value = parser.parse_single_expression(analyzer, thread, file, it_ty, side);
|
|
}
|
|
|
|
parser.skip_space(src);
|
|
|
|
switch (current_operation) {
|
|
.none => {
|
|
previous_value = current_value;
|
|
},
|
|
.compare_equal, .compare_not_equal, .compare_unsigned_greater, .compare_unsigned_greater_equal, .compare_signed_greater, .compare_signed_greater_equal, .compare_unsigned_less, .compare_unsigned_less_equal, .compare_signed_less, .compare_signed_less_equal => {
|
|
switch (current_operation) {
|
|
else => unreachable,
|
|
inline .compare_equal, .compare_not_equal, .compare_unsigned_greater, .compare_unsigned_greater_equal, .compare_signed_greater, .compare_signed_greater_equal, .compare_unsigned_less, .compare_unsigned_less_equal, .compare_signed_less, .compare_signed_less_equal => |co| {
|
|
const string = @tagName(co)["compare_".len..];
|
|
const comparison = @field(IntegerCompare.Id, string);
|
|
const compare = thread.integer_compares.append(.{
|
|
.instruction = new_instruction(thread, .{
|
|
.id = .integer_compare,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
}),
|
|
.left = previous_value,
|
|
.right = current_value,
|
|
.id = comparison,
|
|
});
|
|
analyzer.append_instruction(&compare.instruction);
|
|
previous_value = &compare.instruction.value;
|
|
}
|
|
}
|
|
},
|
|
.add, .sub, .mul, .udiv, .sdiv, .@"and", .@"or", .xor, .shift_left, .arithmetic_shift_right, .logical_shift_right => {
|
|
const i = emit_integer_binary_operation(analyzer, thread, .{
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
.left = previous_value,
|
|
.right = current_value,
|
|
.id = switch (current_operation) {
|
|
else => unreachable,
|
|
inline
|
|
.add,
|
|
.sub,
|
|
.mul,
|
|
.udiv,
|
|
.sdiv,
|
|
.@"and",
|
|
.@"or",
|
|
.@"xor",
|
|
.shift_left,
|
|
.arithmetic_shift_right,
|
|
.logical_shift_right,
|
|
=> |co| @field(IntegerBinaryOperation.Id, @tagName(co)),
|
|
},
|
|
.type = if (it_ty) |t| t else current_value.get_type(),
|
|
});
|
|
previous_value = &i.instruction.value;
|
|
},
|
|
.assign, .add_assign, .sub_assign, .mul_assign, .udiv_assign, .sdiv_assign, .and_assign, .or_assign, .xor_assign, .shift_left_assign, .logical_shift_right_assign, .arithmetic_shift_right_assign => unreachable,
|
|
.@"orelse" => {
|
|
const orelse_type = ty orelse unreachable;
|
|
const condition = emit_condition(analyzer, thread, .{
|
|
.condition = previous_value,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
const true_block = create_basic_block(thread);
|
|
const false_block = create_basic_block(thread);
|
|
const phi_block = create_basic_block(thread);
|
|
_ = emit_branch(analyzer, thread, .{
|
|
.condition = condition,
|
|
.taken = true_block,
|
|
.not_taken = false_block,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
const phi = thread.phis.append(.{
|
|
.instruction = new_instruction(thread, .{
|
|
.id = .phi,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
}),
|
|
.type = orelse_type,
|
|
});
|
|
_ = phi.nodes.append(.{
|
|
.value = previous_value,
|
|
.basic_block = true_block,
|
|
});
|
|
_ = phi.nodes.append(.{
|
|
.value = current_value,
|
|
.basic_block = false_block,
|
|
});
|
|
analyzer.current_basic_block = true_block;
|
|
_ = emit_jump(analyzer, thread, .{
|
|
.basic_block = phi_block,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
analyzer.current_basic_block = false_block;
|
|
_ = emit_jump(analyzer, thread, .{
|
|
.basic_block = phi_block,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
|
|
analyzer.current_basic_block = phi_block;
|
|
analyzer.append_instruction(&phi.instruction);
|
|
|
|
previous_value = &phi.instruction.value;
|
|
},
|
|
}
|
|
|
|
const original_index = parser.i;
|
|
const original = src[original_index];
|
|
switch (original) {
|
|
')', ';', ',', ']' => return previous_value,
|
|
'o' => {
|
|
const identifier = parser.parse_raw_identifier(src);
|
|
if (byte_equal(identifier, "orelse")) {
|
|
current_operation = .@"orelse";
|
|
} else {
|
|
parser.i = original;
|
|
fail();
|
|
}
|
|
|
|
parser.skip_space(src);
|
|
},
|
|
'=' => {
|
|
current_operation = .assign;
|
|
parser.i += 1;
|
|
|
|
switch (src[parser.i]) {
|
|
'=' => {
|
|
current_operation = .compare_equal;
|
|
parser.i += 1;
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
parser.skip_space(src);
|
|
},
|
|
'!' => {
|
|
current_operation = undefined;
|
|
parser.i += 1;
|
|
|
|
switch (src[parser.i]) {
|
|
'=' => {
|
|
current_operation = .compare_not_equal;
|
|
parser.i += 1;
|
|
},
|
|
else => unreachable,
|
|
}
|
|
|
|
parser.skip_space(src);
|
|
},
|
|
'+' => {
|
|
current_operation = .add;
|
|
parser.i += 1;
|
|
|
|
switch (src[parser.i]) {
|
|
'=' => {
|
|
current_operation = .add_assign;
|
|
parser.i += 1;
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
parser.skip_space(src);
|
|
},
|
|
'-' => {
|
|
current_operation = .sub;
|
|
parser.i += 1;
|
|
|
|
switch (src[parser.i]) {
|
|
'=' => {
|
|
current_operation = .sub_assign;
|
|
parser.i += 1;
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
parser.skip_space(src);
|
|
},
|
|
'*' => {
|
|
current_operation = .mul;
|
|
parser.i += 1;
|
|
|
|
switch (src[parser.i]) {
|
|
'=' => {
|
|
current_operation = .mul_assign;
|
|
parser.i += 1;
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
parser.skip_space(src);
|
|
},
|
|
'/' => {
|
|
const int_ty = it_ty orelse previous_value.get_type();
|
|
const integer_type = int_ty.get_payload(.integer);
|
|
current_operation = switch (integer_type.signedness) {
|
|
.unsigned => .udiv,
|
|
.signed => .sdiv,
|
|
};
|
|
parser.i += 1;
|
|
|
|
switch (src[parser.i]) {
|
|
'=' => {
|
|
current_operation = switch (integer_type.signedness) {
|
|
.unsigned => .udiv_assign,
|
|
.signed => .sdiv_assign,
|
|
};
|
|
parser.i += 1;
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
parser.skip_space(src);
|
|
},
|
|
'&' => {
|
|
current_operation = .@"and";
|
|
parser.i += 1;
|
|
|
|
switch (src[parser.i]) {
|
|
'=' => {
|
|
current_operation = .and_assign;
|
|
parser.i += 1;
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
parser.skip_space(src);
|
|
},
|
|
'|' => {
|
|
current_operation = .@"or";
|
|
parser.i += 1;
|
|
|
|
switch (src[parser.i]) {
|
|
'=' => {
|
|
current_operation = .or_assign;
|
|
parser.i += 1;
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
parser.skip_space(src);
|
|
},
|
|
'^' => {
|
|
current_operation = .@"xor";
|
|
parser.i += 1;
|
|
|
|
switch (src[parser.i]) {
|
|
'=' => {
|
|
current_operation = .xor_assign;
|
|
parser.i += 1;
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
parser.skip_space(src);
|
|
},
|
|
'<' => {
|
|
const int_ty = it_ty orelse previous_value.get_type();
|
|
const integer_type = int_ty.get_payload(.integer);
|
|
current_operation = switch (integer_type.signedness) {
|
|
.unsigned => .compare_unsigned_less,
|
|
.signed => .compare_signed_less,
|
|
};
|
|
parser.i += 1;
|
|
|
|
switch (src[parser.i]) {
|
|
'<' => {
|
|
current_operation = .shift_left;
|
|
parser.i += 1;
|
|
|
|
switch (src[parser.i]) {
|
|
'=' => {
|
|
current_operation = .shift_left_assign;
|
|
parser.i += 1;
|
|
},
|
|
else => {},
|
|
}
|
|
},
|
|
'=' => {
|
|
unreachable;
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
parser.skip_space(src);
|
|
},
|
|
'>' => {
|
|
const int_ty = it_ty orelse previous_value.get_type();
|
|
const integer_type = int_ty.get_payload(.integer);
|
|
current_operation = switch (integer_type.signedness) {
|
|
.unsigned => .compare_unsigned_greater,
|
|
.signed => .compare_signed_greater,
|
|
};
|
|
parser.i += 1;
|
|
|
|
switch (src[parser.i]) {
|
|
'>' => {
|
|
current_operation = switch (integer_type.signedness) {
|
|
.unsigned => .logical_shift_right,
|
|
.signed => .arithmetic_shift_right,
|
|
};
|
|
parser.i += 1;
|
|
|
|
switch (src[parser.i]) {
|
|
'=' => {
|
|
current_operation = switch (integer_type.signedness) {
|
|
.unsigned => .logical_shift_right_assign,
|
|
.signed => .arithmetic_shift_right_assign,
|
|
};
|
|
|
|
parser.i += 1;
|
|
},
|
|
else => {},
|
|
}
|
|
},
|
|
'=' => {
|
|
current_operation = switch (integer_type.signedness) {
|
|
.unsigned => .compare_unsigned_greater_equal,
|
|
.signed => .compare_signed_greater_equal,
|
|
};
|
|
|
|
parser.i += 1;
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
parser.skip_space(src);
|
|
},
|
|
else => @panic((src.ptr + parser.i)[0..1]),
|
|
}
|
|
|
|
iterations += 1;
|
|
}
|
|
}
|
|
|
|
const IfResult = struct {
|
|
terminated: bool,
|
|
};
|
|
|
|
fn parse_condition(parser: *Parser, analyzer: *Analyzer, thread: *Thread, file: *File) *Value {
|
|
const src = file.source_code;
|
|
parser.expect_character(src, '(');
|
|
|
|
parser.skip_space(src);
|
|
|
|
const debug_line = parser.get_debug_line();
|
|
const debug_column = parser.get_debug_column();
|
|
const scope = analyzer.current_scope;
|
|
const condition = parser.parse_expression(analyzer, thread, file, null, .right);
|
|
|
|
parser.skip_space(src);
|
|
|
|
parser.expect_character(src, ')');
|
|
|
|
parser.skip_space(src);
|
|
|
|
return emit_condition(analyzer, thread, .{
|
|
.condition = condition,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = scope,
|
|
});
|
|
}
|
|
|
|
fn parse_if_expression(parser: *Parser, analyzer: *Analyzer, thread: *Thread, file: *File) IfResult {
|
|
const src = file.source_code;
|
|
parser.i += 2;
|
|
|
|
parser.skip_space(src);
|
|
|
|
const debug_line = parser.get_debug_line();
|
|
const debug_column = parser.get_debug_line();
|
|
const compare = parser.parse_condition(analyzer, thread, file);
|
|
|
|
const original_block = analyzer.current_basic_block;
|
|
|
|
const taken_block = create_basic_block(thread);
|
|
const exit_block = create_basic_block(thread);
|
|
_ = analyzer.exit_blocks.append(exit_block);
|
|
const exit_block_count = analyzer.exit_blocks.length;
|
|
|
|
const branch_emission = emit_branch(analyzer, thread, .{
|
|
.condition = compare,
|
|
.taken = taken_block,
|
|
.not_taken = exit_block,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
_ = branch_emission; // autofix
|
|
|
|
analyzer.current_basic_block = taken_block;
|
|
|
|
var if_terminated = false;
|
|
var else_terminated = false;
|
|
var if_jump_emission: JumpEmission = undefined;
|
|
switch (src[parser.i]) {
|
|
brace_open => {
|
|
const if_block = analyze_local_block(thread, analyzer, parser, file);
|
|
if_terminated = if_block.terminated;
|
|
|
|
if (!if_terminated) {
|
|
if_jump_emission = emit_jump(analyzer, thread, .{
|
|
.basic_block = exit_block,
|
|
.line = 0,
|
|
.column = 0,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
}
|
|
},
|
|
else => @panic((src.ptr + parser.i)[0..1]),
|
|
}
|
|
|
|
parser.skip_space(src);
|
|
|
|
if (src[parser.i] == 'e' and byte_equal(src[parser.i..][0.."else".len], "else")) {
|
|
// TODO: create not taken block
|
|
parser.i += "else".len;
|
|
analyzer.current_basic_block = exit_block;
|
|
|
|
parser.skip_space(src);
|
|
|
|
switch (src[parser.i]) {
|
|
brace_open => {
|
|
const else_block = analyze_local_block(thread, analyzer, parser, file);
|
|
else_terminated = else_block.terminated;
|
|
},
|
|
'i' => {
|
|
if (src[parser.i + 1] == 'f') {
|
|
const else_if = parser.parse_if_expression(analyzer, thread, file);
|
|
else_terminated = else_if.terminated;
|
|
} else {
|
|
unreachable;
|
|
}
|
|
},
|
|
else => @panic((src.ptr + parser.i)[0..1]),
|
|
}
|
|
|
|
if (!if_terminated or !else_terminated) {
|
|
const new_exit_block = create_basic_block(thread);
|
|
const not_taken_block = exit_block;
|
|
// Fix jump
|
|
|
|
if (!if_terminated) {
|
|
assert(if_jump_emission.jump.basic_block == not_taken_block);
|
|
if_jump_emission.jump.basic_block.predecessors.length = 0;
|
|
_ = if_jump_emission.jump.basic_block.predecessors.append(original_block);
|
|
_ = new_exit_block.predecessors.append(if_jump_emission.jump.basic_block);
|
|
if_jump_emission.jump.basic_block = new_exit_block;
|
|
}
|
|
|
|
if (!else_terminated) {
|
|
// Emit jump to the new exit block
|
|
_ = emit_jump(analyzer, thread, .{
|
|
.basic_block = new_exit_block,
|
|
.line = 0,
|
|
.column = 0,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
}
|
|
|
|
analyzer.current_basic_block = new_exit_block;
|
|
}
|
|
} else {
|
|
_ = exit_block.predecessors.append(original_block);
|
|
analyzer.current_basic_block = exit_block;
|
|
}
|
|
|
|
if (!if_terminated and !else_terminated) {
|
|
assert(analyzer.exit_blocks.length == exit_block_count);
|
|
analyzer.exit_blocks.length -= 1;
|
|
}
|
|
|
|
return .{
|
|
.terminated = if_terminated and else_terminated,
|
|
};
|
|
}
|
|
};
|
|
|
|
const LocalLazyExpression = struct{
|
|
value: Value,
|
|
name: u32,
|
|
values: PinnedArray(*Value) = .{},
|
|
};
|
|
|
|
const LazyExpression = struct {
|
|
value: Value,
|
|
u: union(enum) {
|
|
dynamic: struct{
|
|
names: PinnedArray(u32) = .{},
|
|
outsider: GlobalDeclaration.Reference,
|
|
},
|
|
static: struct {
|
|
names: [4]u32 = .{0} ** 4,
|
|
outsider: GlobalDeclaration.Reference,
|
|
},
|
|
},
|
|
|
|
fn init(global_declaration: GlobalDeclaration.Reference, thread: *Thread) LazyExpression {
|
|
return .{
|
|
.value = .{
|
|
.sema = .{
|
|
.thread = thread.get_index(),
|
|
.resolved = false,
|
|
.id = .lazy_expression,
|
|
},
|
|
},
|
|
.u = .{
|
|
.static = .{
|
|
.outsider = global_declaration,
|
|
},
|
|
}
|
|
};
|
|
}
|
|
|
|
fn length(lazy_expression: *LazyExpression) u32 {
|
|
return switch (lazy_expression.u) {
|
|
.dynamic => |*d| d.names.length,
|
|
.static => |*s| for (s.names, 0..) |n, i| {
|
|
if (n == 0) break @intCast(i);
|
|
} else s.names.len,
|
|
};
|
|
}
|
|
|
|
fn names(lazy_expression: *LazyExpression) []const u32 {
|
|
return switch (lazy_expression.u) {
|
|
.dynamic => |*d| d.names.slice(),
|
|
.static => |*s| s.names[0.. for (s.names, 0..) |n, i| {
|
|
if (n == 0) break @intCast(i);
|
|
} else s.names.len],
|
|
};
|
|
}
|
|
|
|
fn add(lazy_expression: *LazyExpression, name: u32) void {
|
|
const index = lazy_expression.length();
|
|
if (index < 4) {
|
|
lazy_expression.u.static.names[index] = name;
|
|
} else {
|
|
unreachable;
|
|
}
|
|
}
|
|
};
|
|
|
|
const Value = struct {
|
|
llvm: ?*LLVM.Value = null,
|
|
sema: struct {
|
|
thread: u16,
|
|
resolved: bool,
|
|
reserved: u7 = 0,
|
|
id: Id,
|
|
},
|
|
reserved: u32 = 0,
|
|
|
|
const Id = enum(u8){
|
|
argument,
|
|
basic_block,
|
|
constant_array,
|
|
constant_struct,
|
|
constant_bitfield,
|
|
constant_int,
|
|
instruction,
|
|
global_symbol,
|
|
lazy_expression,
|
|
local_lazy_expression,
|
|
undefined,
|
|
};
|
|
|
|
const id_to_value_map = std.EnumArray(Id, type).init(.{
|
|
.argument = ArgumentSymbol,
|
|
.basic_block = BasicBlock,
|
|
.constant_array = ConstantArray,
|
|
.constant_struct = ConstantStruct,
|
|
.constant_bitfield = ConstantBitfield,
|
|
.constant_int = ConstantInt,
|
|
.global_symbol = GlobalSymbol,
|
|
.instruction = Instruction,
|
|
.lazy_expression = LazyExpression,
|
|
.local_lazy_expression = LocalLazyExpression,
|
|
.undefined = Undefined,
|
|
});
|
|
|
|
fn is_constant(value: *Value) bool {
|
|
return switch (value.sema.id) {
|
|
.constant_int,
|
|
.constant_struct,
|
|
.undefined,
|
|
=> true,
|
|
.instruction => false,
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
}
|
|
|
|
fn get_payload(value: *Value, comptime id: Id) *id_to_value_map.get(id) {
|
|
assert(value.sema.id == id);
|
|
return @fieldParentPtr("value", value);
|
|
}
|
|
|
|
fn get_type(value: *Value) *Type {
|
|
return switch (value.sema.id) {
|
|
.instruction => blk: {
|
|
const instruction = value.get_payload(.instruction);
|
|
break :blk switch (instruction.id) {
|
|
.integer_binary_operation => block: {
|
|
const bin_op = instruction.get_payload(.integer_binary_operation);
|
|
break :block bin_op.type;
|
|
},
|
|
.load => block: {
|
|
const load = instruction.get_payload(.load);
|
|
break :block load.type;
|
|
},
|
|
.call => {
|
|
const call = instruction.get_payload(.call);
|
|
const function_type = call.get_function_type();
|
|
return function_type.abi.original_return_type;
|
|
},
|
|
.integer_compare => {
|
|
return &instance.threads[value.sema.thread].integers[0].type;
|
|
},
|
|
.trailing_zeroes => {
|
|
const tz = instruction.get_payload(.trailing_zeroes);
|
|
return tz.value.get_type();
|
|
},
|
|
.leading_zeroes => {
|
|
const lz = instruction.get_payload(.leading_zeroes);
|
|
return lz.value.get_type();
|
|
},
|
|
.local_symbol => {
|
|
const local_symbol = instruction.get_payload(.local_symbol);
|
|
return local_symbol.pointer_type;
|
|
},
|
|
.argument_storage => {
|
|
const argument_symbol = instruction.get_payload(.argument_storage);
|
|
return argument_symbol.pointer_type;
|
|
},
|
|
.cast => {
|
|
const cast = instruction.get_payload(.cast);
|
|
return cast.type;
|
|
},
|
|
.insert_value => {
|
|
const insert_value = instruction.get_payload(.insert_value);
|
|
return insert_value.type;
|
|
},
|
|
.phi => {
|
|
const phi = instruction.get_payload(.phi);
|
|
return phi.type;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
},
|
|
.constant_int => {
|
|
const constant_int = value.get_payload(.constant_int);
|
|
return constant_int.type;
|
|
},
|
|
.global_symbol => {
|
|
const global_symbol = value.get_payload(.global_symbol);
|
|
return global_symbol.pointer_type;
|
|
},
|
|
.constant_struct => {
|
|
const constant_struct = value.get_payload(.constant_struct);
|
|
return constant_struct.type;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
}
|
|
};
|
|
|
|
const Type = struct {
|
|
llvm: ?*LLVM.Type = null,
|
|
llvm_debug: ?*LLVM.DebugInfo.Type = null,
|
|
// TODO: ZIG BUG: if this is a packed struct, the initialization is broken
|
|
sema: struct {
|
|
thread: u16,
|
|
id: Id,
|
|
resolved: bool,
|
|
reserved: u7 = 0,
|
|
},
|
|
size: u64,
|
|
bit_size: u64,
|
|
alignment: u32,
|
|
|
|
const Id = enum(u8){
|
|
unresolved,
|
|
void,
|
|
noreturn,
|
|
integer,
|
|
array,
|
|
opaque_pointer,
|
|
typed_pointer,
|
|
function,
|
|
@"struct",
|
|
bitfield,
|
|
anonymous_struct,
|
|
};
|
|
|
|
const Integer = struct {
|
|
type: Type,
|
|
signedness: Signedness,
|
|
|
|
const Signedness = enum(u1){
|
|
unsigned,
|
|
signed,
|
|
};
|
|
};
|
|
|
|
const Array = struct {
|
|
type: Type,
|
|
descriptor: Descriptor,
|
|
|
|
const Descriptor = struct{
|
|
element_count: u64,
|
|
element_type: *Type,
|
|
};
|
|
};
|
|
|
|
const Function = struct{
|
|
type: Type,
|
|
abi: compiler.Function.Abi,
|
|
};
|
|
|
|
const Struct = struct {
|
|
type: Type,
|
|
declaration: Declaration,
|
|
fields: []const *AggregateField,
|
|
};
|
|
|
|
const AnonymousStruct = struct{
|
|
type: Type,
|
|
fields: []const *AggregateField,
|
|
};
|
|
|
|
const Bitfield = struct{
|
|
type: Type,
|
|
declaration: Declaration,
|
|
fields: []const *AggregateField,
|
|
backing_type: *Type,
|
|
};
|
|
|
|
const AggregateField = struct{
|
|
type: *Type,
|
|
parent: *Type,
|
|
member_offset: u64,
|
|
name: u32,
|
|
index: u32,
|
|
line: u32,
|
|
column: u32,
|
|
};
|
|
|
|
const TypedPointer = struct{
|
|
type: Type,
|
|
descriptor: Descriptor,
|
|
|
|
const Descriptor = struct{
|
|
pointee: *Type,
|
|
};
|
|
};
|
|
|
|
const id_to_type_map = std.EnumArray(Id, type).init(.{
|
|
.unresolved = void,
|
|
.void = void,
|
|
.noreturn = void,
|
|
.integer = Integer,
|
|
.array = Array,
|
|
.opaque_pointer = void,
|
|
.function = Type.Function,
|
|
.@"struct" = Type.Struct,
|
|
.bitfield = Type.Bitfield,
|
|
.typed_pointer = TypedPointer,
|
|
.anonymous_struct = AnonymousStruct,
|
|
});
|
|
|
|
fn get_payload(ty: *Type, comptime id: Id) *id_to_type_map.get(id) {
|
|
assert(ty.sema.id == id);
|
|
return @fieldParentPtr("type", ty);
|
|
}
|
|
|
|
fn clone(ty: *Type, args: struct{
|
|
destination_thread: *Thread,
|
|
source_thread_index: u16,
|
|
}) *Type {
|
|
assert(args.destination_thread.get_index() == 0);
|
|
const result: *Type = if (args.destination_thread.cloned_types.get(ty)) |result| blk: {
|
|
assert(result.sema.thread == args.destination_thread.get_index());
|
|
break :blk result;
|
|
} else blk: {
|
|
assert(ty.sema.resolved);
|
|
assert(ty.sema.thread == args.source_thread_index);
|
|
|
|
const result: *Type = switch (ty.sema.id) {
|
|
.integer => {
|
|
const source_thread = &instance.threads[args.source_thread_index];
|
|
const source_integer_type = ty.get_payload(.integer);
|
|
const index = @divExact(@intFromPtr(source_integer_type) - @intFromPtr(&source_thread.integers), @sizeOf(Type.Integer));
|
|
break :blk &args.destination_thread.integers[index].type;
|
|
},
|
|
.function => block: {
|
|
const source_function_type = ty.get_payload(.function);
|
|
const original_return_type = source_function_type.abi.original_return_type.clone(.{
|
|
.destination_thread = args.destination_thread,
|
|
.source_thread_index = args.source_thread_index,
|
|
});
|
|
const abi_return_type = source_function_type.abi.abi_return_type.clone(.{
|
|
.destination_thread = args.destination_thread,
|
|
.source_thread_index = args.source_thread_index,
|
|
});
|
|
const return_type_abi = source_function_type.abi.return_type_abi.clone(.{
|
|
.destination_thread = args.destination_thread,
|
|
.source_thread_index = args.source_thread_index,
|
|
});
|
|
|
|
var original_argument_types = PinnedArray(*Type){};
|
|
for (source_function_type.abi.original_argument_types) |original_argument_type| {
|
|
const new = original_argument_type.clone(.{
|
|
.destination_thread = args.destination_thread,
|
|
.source_thread_index = args.source_thread_index,
|
|
});
|
|
_ = original_argument_types.append(new);
|
|
}
|
|
var abi_argument_types = PinnedArray(*Type){};
|
|
for (source_function_type.abi.original_argument_types) |abi_argument_type| {
|
|
const new = abi_argument_type.clone(.{
|
|
.destination_thread = args.destination_thread,
|
|
.source_thread_index = args.source_thread_index,
|
|
});
|
|
_ = abi_argument_types.append(new);
|
|
}
|
|
|
|
var argument_type_abis = PinnedArray(compiler.Function.Abi.Information){};
|
|
for (source_function_type.abi.argument_types_abi) |argument_type_abi| {
|
|
const new = argument_type_abi.clone(.{
|
|
.destination_thread = args.destination_thread,
|
|
.source_thread_index = args.source_thread_index,
|
|
});
|
|
_ = argument_type_abis.append(new);
|
|
}
|
|
|
|
const function_type = args.destination_thread.function_types.append(.{
|
|
.type = source_function_type.type,
|
|
.abi = .{
|
|
.return_type_abi = return_type_abi,
|
|
.original_argument_types = original_argument_types.const_slice(),
|
|
.original_return_type = original_return_type,
|
|
.abi_return_type = abi_return_type,
|
|
.abi_argument_types = abi_argument_types.const_slice(),
|
|
.argument_types_abi = argument_type_abis.const_slice(),
|
|
.calling_convention = source_function_type.abi.calling_convention,
|
|
},
|
|
});
|
|
break :block &function_type.type;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
|
|
result.llvm = null;
|
|
result.sema.thread = args.destination_thread.get_index();
|
|
|
|
args.destination_thread.cloned_types.put_no_clobber(ty, result);
|
|
|
|
break :blk result;
|
|
};
|
|
|
|
assert(result.llvm == null);
|
|
assert(result.sema.thread == args.destination_thread.get_index());
|
|
return result;
|
|
}
|
|
|
|
fn is_aggregate(ty: *Type) bool {
|
|
return switch (ty.sema.id) {
|
|
.unresolved => unreachable,
|
|
.array => unreachable,
|
|
.function => unreachable,
|
|
.void,
|
|
.noreturn,
|
|
.integer,
|
|
.opaque_pointer,
|
|
.typed_pointer,
|
|
.bitfield,
|
|
=>
|
|
false,
|
|
.@"struct", .anonymous_struct => true,
|
|
};
|
|
}
|
|
|
|
fn returns_nothing(ty: *Type) bool {
|
|
return switch (ty.sema.id) {
|
|
.void, .noreturn => true,
|
|
else => false,
|
|
};
|
|
}
|
|
|
|
const HomogeneousAggregate = struct{
|
|
type: *Type,
|
|
count: u32,
|
|
};
|
|
|
|
fn get_homogeneous_aggregate(ty: *Type) ?HomogeneousAggregate {
|
|
switch (ty.sema.id) {
|
|
.@"struct" => {
|
|
const struct_type = ty.get_payload(.@"struct");
|
|
for (struct_type.fields) |field| {
|
|
while (field.type.sema.id == .array) {
|
|
unreachable;
|
|
}
|
|
|
|
if (field.type.get_homogeneous_aggregate()) |homogeneous_aggregate| {
|
|
_ = homogeneous_aggregate; // autofix
|
|
unreachable;
|
|
} else {
|
|
return null;
|
|
}
|
|
|
|
unreachable;
|
|
}
|
|
|
|
unreachable;
|
|
},
|
|
.integer => {
|
|
return null;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
}
|
|
|
|
fn get_integer_index(ty: *Type) usize {
|
|
assert(ty.sema.id == .integer);
|
|
const thread = &instance.threads[ty.sema.thread];
|
|
comptime assert(@offsetOf(Type.Integer, "type") == 0);
|
|
const index = @divExact(@intFromPtr(ty) - @intFromPtr(&thread.integers[0]), @sizeOf(Type.Integer));
|
|
return index;
|
|
}
|
|
};
|
|
|
|
const Keyword = enum{
|
|
@"break",
|
|
@"else",
|
|
@"for",
|
|
@"if",
|
|
@"loop",
|
|
@"orelse",
|
|
@"undefined",
|
|
};
|
|
|
|
const Intrinsic = enum{
|
|
assert,
|
|
int_from_pointer,
|
|
leading_zeroes,
|
|
require,
|
|
size,
|
|
trailing_zeroes,
|
|
trap,
|
|
transmute,
|
|
@"unreachable",
|
|
};
|
|
|
|
fn parse_keyword(identifier: []const u8) u32 {
|
|
assert(identifier.len > 0);
|
|
if (identifier[0] != '"') {
|
|
inline for (@typeInfo(Keyword).Enum.fields) |keyword| {
|
|
if (byte_equal(identifier, keyword.name)) {
|
|
return keyword.value;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ~@as(u32, 0);
|
|
}
|
|
|
|
const Scope = struct {
|
|
declarations: PinnedHashMap(u32, *Declaration) = .{},
|
|
parent: ?*Scope,
|
|
llvm: ?*LLVM.DebugInfo.Scope = null,
|
|
line: u32,
|
|
column: u32,
|
|
file: u32,
|
|
id: Id,
|
|
|
|
pub fn get_global_declaration(scope: *Scope, name: u32) ?*GlobalDeclaration {
|
|
assert(scope.id == .file);
|
|
if (scope.get_declaration_one_level(name)) |decl_ref| {
|
|
return decl_ref.*.get_payload(.global);
|
|
} else {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
pub fn get_declaration_one_level(scope: *Scope, name: u32) ?Declaration.Reference {
|
|
const result = scope.declarations.get_pointer(name);
|
|
return result;
|
|
}
|
|
|
|
const DeclarationLookupResult = struct {
|
|
declaration: Declaration.Reference,
|
|
scope: *Scope,
|
|
};
|
|
|
|
pub fn get_declaration(scope: *Scope, name: u32) ?DeclarationLookupResult{
|
|
var s: ?*Scope = scope;
|
|
while (s) |search_scope| {
|
|
if (search_scope.get_declaration_one_level(name)) |declaration| {
|
|
return .{
|
|
.declaration = declaration,
|
|
.scope = search_scope,
|
|
};
|
|
}
|
|
|
|
s = search_scope.parent;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
const Id = enum{
|
|
file,
|
|
function,
|
|
local,
|
|
};
|
|
};
|
|
|
|
const Range = struct{
|
|
start: u32,
|
|
end: u32,
|
|
};
|
|
|
|
const ArgumentDeclaration = struct {
|
|
declaration: Declaration,
|
|
|
|
fn to_symbol(argument_declaration: *ArgumentDeclaration) *ArgumentSymbol {
|
|
return @alignCast(@fieldParentPtr("argument_declaration", argument_declaration));
|
|
}
|
|
};
|
|
|
|
const LocalDeclaration = struct {
|
|
declaration: Declaration,
|
|
|
|
fn to_symbol(local_declaration: *LocalDeclaration) *LocalSymbol {
|
|
return @alignCast(@fieldParentPtr("local_declaration", local_declaration));
|
|
}
|
|
};
|
|
|
|
const GlobalDeclaration = struct {
|
|
declaration: Declaration,
|
|
id: Id,
|
|
|
|
const Id = enum(u8) {
|
|
file,
|
|
unresolved_import,
|
|
global_symbol,
|
|
};
|
|
|
|
const id_to_global_declaration_map = std.EnumArray(Id, type).init(.{
|
|
.file = File,
|
|
.global_symbol = GlobalSymbol,
|
|
.unresolved_import = Import,
|
|
});
|
|
|
|
fn get_payload(global_declaration: *GlobalDeclaration, comptime id: Id) *id_to_global_declaration_map.get(id) {
|
|
assert(global_declaration.id == id);
|
|
|
|
return @alignCast(@fieldParentPtr("global_declaration", global_declaration));
|
|
}
|
|
|
|
fn to_symbol(global_declaration: *GlobalDeclaration) *GlobalSymbol {
|
|
assert(global_declaration.id == .global_symbol);
|
|
return @alignCast(@fieldParentPtr("global_declaration", global_declaration));
|
|
}
|
|
|
|
const Reference = **GlobalDeclaration;
|
|
};
|
|
|
|
const BasicBlock = struct{
|
|
value: Value,
|
|
instructions: PinnedArray(*Instruction) = .{},
|
|
predecessors: PinnedArray(*BasicBlock) = .{},
|
|
is_terminated: bool = false,
|
|
command_node: CommandList.Node = .{
|
|
.data = {},
|
|
},
|
|
|
|
pub const Reference = **BasicBlock;
|
|
|
|
const CommandList = std.DoublyLinkedList(void);
|
|
|
|
pub fn get_llvm(basic_block: *BasicBlock) *LLVM.Value.BasicBlock{
|
|
return basic_block.value.llvm.?.toBasicBlock() orelse unreachable;
|
|
}
|
|
};
|
|
|
|
const Declaration = struct {
|
|
name: u32,
|
|
id: Id,
|
|
line: u32,
|
|
column: u32,
|
|
scope: *Scope,
|
|
|
|
const Reference = **Declaration;
|
|
|
|
const Id = enum {
|
|
local,
|
|
global,
|
|
argument,
|
|
@"struct",
|
|
@"bitfield",
|
|
};
|
|
|
|
const id_to_declaration_map = std.EnumArray(Id, type).init(.{
|
|
.global = GlobalDeclaration,
|
|
.local = LocalDeclaration,
|
|
.argument = ArgumentDeclaration,
|
|
.@"struct" = Type.Struct,
|
|
.bitfield = Type.Bitfield,
|
|
});
|
|
|
|
fn get_payload(declaration: *Declaration, comptime id: Id) *id_to_declaration_map.get(id) {
|
|
assert(declaration.id == id);
|
|
return @fieldParentPtr("declaration", declaration);
|
|
}
|
|
};
|
|
|
|
const Function = struct{
|
|
declaration: Function.Declaration,
|
|
entry_block: *BasicBlock,
|
|
stack_slots: PinnedArray(*LocalSymbol) = .{},
|
|
scope: Function.Scope,
|
|
arguments: PinnedArray(*ArgumentSymbol) = .{},
|
|
|
|
const Attributes = struct{
|
|
};
|
|
|
|
const Attribute = enum{
|
|
cc,
|
|
|
|
pub const Mask = std.EnumSet(Function.Attribute);
|
|
};
|
|
|
|
const Abi = struct{
|
|
original_return_type: *Type,
|
|
original_argument_types: []const *Type,
|
|
abi_return_type: *Type,
|
|
abi_argument_types: []const *Type,
|
|
return_type_abi: Function.Abi.Information,
|
|
argument_types_abi: []const Function.Abi.Information,
|
|
calling_convention: CallingConvention,
|
|
|
|
const Kind = union(enum) {
|
|
ignore,
|
|
direct,
|
|
direct_pair: [2]*Type,
|
|
direct_coerce: *Type,
|
|
direct_coerce_int,
|
|
direct_split_struct_i32,
|
|
expand_coerce,
|
|
indirect: struct {
|
|
type: *Type,
|
|
alignment: u32,
|
|
},
|
|
expand,
|
|
};
|
|
|
|
const Attributes = struct {
|
|
by_reg: bool = false,
|
|
zero_extend: bool = false,
|
|
sign_extend: bool = false,
|
|
realign: bool = false,
|
|
by_value: bool = false,
|
|
};
|
|
|
|
const Information = struct{
|
|
kind: Kind = .direct,
|
|
indices: [2]u16 = .{0, 0},
|
|
attributes: Function.Abi.Attributes = .{},
|
|
|
|
fn clone(abi_info: *const Information, args: struct{
|
|
destination_thread: *Thread,
|
|
source_thread_index: u16,
|
|
}) Information {
|
|
_ = args;
|
|
const kind: Kind = switch (abi_info.kind) {
|
|
.direct => .direct,
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
const indices = abi_info.indices;
|
|
const attributes = abi_info.attributes;
|
|
return .{
|
|
.kind = kind,
|
|
.indices = indices,
|
|
.attributes = attributes,
|
|
};
|
|
}
|
|
};
|
|
};
|
|
|
|
const Declaration = struct {
|
|
attributes: Attributes = .{},
|
|
global_symbol: GlobalSymbol,
|
|
|
|
|
|
fn get_function_type(declaration: *Function.Declaration) *Type.Function {
|
|
const ty = declaration.global_symbol.type;
|
|
const function_type = ty.get_payload(.function);
|
|
return function_type;
|
|
}
|
|
|
|
fn clone(declaration: Function.Declaration, destination_thread: *Thread) *Function.Declaration{
|
|
assert(declaration.global_symbol.value.sema.resolved);
|
|
|
|
const source_thread_index = declaration.global_symbol.value.sema.thread;
|
|
const source_thread = &instance.threads[source_thread_index];
|
|
assert(source_thread != destination_thread);
|
|
|
|
const type_cloned = declaration.global_symbol.type.clone(.{
|
|
.destination_thread = destination_thread,
|
|
.source_thread_index = source_thread_index,
|
|
});
|
|
assert(type_cloned.sema.thread == destination_thread.get_index());
|
|
|
|
const result = destination_thread.external_functions.append(.{
|
|
.global_symbol = .{
|
|
.type = type_cloned,
|
|
.pointer_type = get_typed_pointer(destination_thread, .{
|
|
.pointee = type_cloned,
|
|
}),
|
|
.attributes = declaration.global_symbol.attributes,
|
|
.global_declaration = declaration.global_symbol.global_declaration,
|
|
.alignment = declaration.global_symbol.alignment,
|
|
.value = declaration.global_symbol.value,
|
|
.id = declaration.global_symbol.id,
|
|
},
|
|
});
|
|
|
|
result.global_symbol.attributes.@"export" = false;
|
|
result.global_symbol.attributes.@"extern" = true;
|
|
result.global_symbol.value.sema.thread = destination_thread.get_index();
|
|
result.global_symbol.value.llvm = null;
|
|
return result;
|
|
}
|
|
};
|
|
|
|
const Scope = struct {
|
|
scope: compiler.Scope,
|
|
|
|
pub fn lookup_declaration(scope: *Function.Scope, parent: bool) ?*compiler.Declaration {
|
|
_ = scope;
|
|
_ = parent;
|
|
unreachable;
|
|
}
|
|
};
|
|
};
|
|
|
|
const ConstantInt = struct{
|
|
value: Value,
|
|
n: u64,
|
|
type: *Type,
|
|
};
|
|
|
|
const ConstantArray = struct{
|
|
value: Value,
|
|
values: []const *Value,
|
|
type: *Type,
|
|
};
|
|
|
|
const ConstantStruct = struct{
|
|
value: Value,
|
|
values: []const *Value,
|
|
type: *Type,
|
|
};
|
|
|
|
const Undefined = struct{
|
|
value: Value,
|
|
type: *Type,
|
|
};
|
|
|
|
const ConstantBitfield = struct{
|
|
value: Value,
|
|
n: u64,
|
|
type: *Type,
|
|
};
|
|
|
|
const Instruction = struct{
|
|
value: Value,
|
|
basic_block: ?*BasicBlock = null,
|
|
scope: *Scope,
|
|
line: u32,
|
|
column: u32,
|
|
id: Id,
|
|
|
|
const Id = enum{
|
|
abi_argument,
|
|
abi_indirect_argument,
|
|
argument_storage,
|
|
branch,
|
|
call,
|
|
cast,
|
|
debug_argument,
|
|
debug_local,
|
|
extract_value,
|
|
get_element_pointer,
|
|
insert_value,
|
|
integer_binary_operation,
|
|
integer_compare,
|
|
jump,
|
|
leading_zeroes,
|
|
load,
|
|
local_symbol,
|
|
memcpy,
|
|
phi,
|
|
ret,
|
|
ret_void,
|
|
store,
|
|
trailing_zeroes,
|
|
trap,
|
|
@"unreachable",
|
|
};
|
|
|
|
const id_to_instruction_map = std.EnumArray(Id, type).init(.{
|
|
.abi_argument = AbiArgument,
|
|
.abi_indirect_argument = ArgumentSymbol,
|
|
.argument_storage = ArgumentSymbol,
|
|
.branch = Branch,
|
|
.call = Call,
|
|
.cast = Cast,
|
|
.debug_argument = DebugArgument,
|
|
.debug_local = DebugLocal,
|
|
.extract_value = ExtractValue,
|
|
.get_element_pointer = GEP,
|
|
.insert_value = InsertValue,
|
|
.integer_binary_operation = IntegerBinaryOperation,
|
|
.integer_compare = IntegerCompare,
|
|
.jump = Jump,
|
|
.leading_zeroes = LeadingZeroes,
|
|
.local_symbol = LocalSymbol,
|
|
.load = Load,
|
|
.memcpy = Memcpy,
|
|
.phi = Phi,
|
|
.ret = Return,
|
|
.ret_void = Instruction,
|
|
.store = Store,
|
|
.trailing_zeroes = TrailingZeroes,
|
|
.trap = Instruction,
|
|
.@"unreachable" = Instruction,
|
|
});
|
|
|
|
fn get_payload(instruction: *Instruction, comptime id: Id) *id_to_instruction_map.get(id) {
|
|
assert(instruction.id == id);
|
|
return @fieldParentPtr("instruction", instruction);
|
|
}
|
|
};
|
|
|
|
const AbiArgument = struct{
|
|
instruction: Instruction,
|
|
index: u32,
|
|
};
|
|
|
|
const ExtractValue = struct{
|
|
instruction: Instruction,
|
|
aggregate: *Value,
|
|
index: u32,
|
|
type: *Type,
|
|
};
|
|
|
|
const InsertValue = struct{
|
|
instruction: Instruction,
|
|
aggregate: *Value,
|
|
value: *Value,
|
|
index: u32,
|
|
type: *Type,
|
|
};
|
|
|
|
const GEP = struct {
|
|
instruction: Instruction,
|
|
pointer: *Value,
|
|
index: *Value,
|
|
aggregate_type: *Type,
|
|
type: *Type,
|
|
is_struct: bool,
|
|
};
|
|
|
|
const IntegerBinaryOperation = struct {
|
|
instruction: Instruction,
|
|
left: *Value,
|
|
right: *Value,
|
|
type: *Type,
|
|
id: Id,
|
|
|
|
const Id = enum{
|
|
add,
|
|
sub,
|
|
mul,
|
|
udiv,
|
|
sdiv,
|
|
@"and",
|
|
@"or",
|
|
@"xor",
|
|
shift_left,
|
|
arithmetic_shift_right,
|
|
logical_shift_right,
|
|
};
|
|
};
|
|
|
|
const IntegerCompare = struct {
|
|
instruction: Instruction,
|
|
left: *Value,
|
|
right: *Value,
|
|
id: Id,
|
|
|
|
const Id = enum{
|
|
unsigned_greater,
|
|
unsigned_greater_equal,
|
|
signed_greater,
|
|
signed_greater_equal,
|
|
unsigned_less,
|
|
unsigned_less_equal,
|
|
signed_less,
|
|
signed_less_equal,
|
|
equal,
|
|
not_equal,
|
|
not_zero,
|
|
};
|
|
};
|
|
|
|
const Branch = struct {
|
|
instruction: Instruction,
|
|
condition: *Value,
|
|
taken: *BasicBlock,
|
|
not_taken: *BasicBlock,
|
|
};
|
|
|
|
const Jump = struct {
|
|
instruction: Instruction,
|
|
basic_block: *BasicBlock,
|
|
};
|
|
|
|
const Call = struct{
|
|
instruction: Instruction,
|
|
callable: *Value,
|
|
arguments: []const *Value,
|
|
calling_convention: CallingConvention,
|
|
|
|
fn get_function_type(call: *Call) *Type.Function{
|
|
switch (call.callable.sema.id) {
|
|
.global_symbol => {
|
|
const global_symbol = call.callable.get_payload(.global_symbol);
|
|
switch (global_symbol.id) {
|
|
.function_definition, .function_declaration => {
|
|
const function_declaration = global_symbol.get_payload(.function_declaration);
|
|
const function_type = function_declaration.get_function_type();
|
|
return function_type;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
},
|
|
.instruction => {
|
|
const callable_instruction = call.callable.get_payload(.instruction);
|
|
_ = callable_instruction; // autofix
|
|
const callable_type = call.callable.get_type();
|
|
switch (callable_type.sema.id) {
|
|
.typed_pointer => {
|
|
const typed_pointer = callable_type.get_payload(.typed_pointer);
|
|
switch (typed_pointer.descriptor.pointee.sema.id) {
|
|
.function => {
|
|
const function_type = typed_pointer.descriptor.pointee.get_payload(.function);
|
|
return function_type;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
}
|
|
};
|
|
|
|
const Cast = struct{
|
|
instruction: Instruction,
|
|
value: *Value,
|
|
type: *Type,
|
|
id: Id,
|
|
|
|
const Id = enum{
|
|
bitfield_from_int,
|
|
int_from_bitfield,
|
|
int_from_pointer,
|
|
truncate,
|
|
};
|
|
};
|
|
|
|
const Load = struct {
|
|
instruction: Instruction,
|
|
value: *Value,
|
|
type: *Type,
|
|
alignment: u32,
|
|
is_volatile: bool,
|
|
};
|
|
|
|
const Store = struct {
|
|
instruction: Instruction,
|
|
destination: *Value,
|
|
source: *Value,
|
|
alignment: u32,
|
|
is_volatile: bool,
|
|
};
|
|
|
|
const Phi = struct {
|
|
instruction: Instruction,
|
|
type: *Type,
|
|
nodes: PinnedArray(Node) = .{},
|
|
|
|
const Node = struct {
|
|
value: *Value,
|
|
basic_block: *BasicBlock,
|
|
};
|
|
};
|
|
|
|
const Return = struct{
|
|
instruction: Instruction,
|
|
value: *Value,
|
|
};
|
|
|
|
const Import = struct {
|
|
global_declaration: GlobalDeclaration,
|
|
hash: u32,
|
|
resolved: bool = false,
|
|
files: PinnedArray(*File) = .{},
|
|
};
|
|
|
|
const LocalBlock = struct {
|
|
scope: Scope,
|
|
terminated: bool = false,
|
|
};
|
|
|
|
fn get_power_of_two_byte_count_from_bit_count(bit_count: u32) u32 {
|
|
if (bit_count == 0) unreachable;
|
|
if (bit_count <= 8) return 1;
|
|
if (bit_count <= 16) return 2;
|
|
if (bit_count <= 32) return 4;
|
|
if (bit_count <= 64) return 8;
|
|
unreachable;
|
|
}
|
|
|
|
const LeadingZeroes = struct{
|
|
instruction: Instruction,
|
|
value: *Value,
|
|
};
|
|
|
|
const TrailingZeroes = struct{
|
|
instruction: Instruction,
|
|
value: *Value,
|
|
};
|
|
|
|
const DebugLocal = struct{
|
|
instruction: Instruction,
|
|
local: *LocalSymbol,
|
|
};
|
|
|
|
const DebugArgument = struct{
|
|
instruction: Instruction,
|
|
argument: *ArgumentSymbol,
|
|
};
|
|
|
|
const Thread = struct{
|
|
arena: *Arena = undefined,
|
|
functions: PinnedArray(Function) = .{},
|
|
external_functions: PinnedArray(Function.Declaration) = .{},
|
|
identifiers: PinnedHashMap(u32, []const u8) = .{},
|
|
constant_ints: PinnedArray(ConstantInt) = .{},
|
|
constant_arrays: PinnedArray(ConstantArray) = .{},
|
|
constant_structs: PinnedArray(ConstantStruct) = .{},
|
|
constant_bitfields: PinnedArray(ConstantBitfield) = .{},
|
|
basic_blocks: PinnedArray(BasicBlock) = .{},
|
|
task_system: TaskSystem = .{},
|
|
debug_info_file_map: PinnedHashMap(u32, LLVMFile) = .{},
|
|
branches: PinnedArray(Branch) = .{},
|
|
jumps: PinnedArray(Jump) = .{},
|
|
calls: PinnedArray(Call) = .{},
|
|
integer_binary_operations: PinnedArray(IntegerBinaryOperation) = .{},
|
|
integer_compares: PinnedArray(IntegerCompare) = .{},
|
|
loads: PinnedArray(Load) = .{},
|
|
stores: PinnedArray(Store) = .{},
|
|
phis: PinnedArray(Phi) = .{},
|
|
returns: PinnedArray(Return) = .{},
|
|
geps: PinnedArray(GEP) = .{},
|
|
lazy_expressions: PinnedArray(LazyExpression) = .{},
|
|
local_lazy_expressions: PinnedArray(LocalLazyExpression) = .{},
|
|
imports: PinnedArray(Import) = .{},
|
|
local_blocks: PinnedArray(LocalBlock) = .{},
|
|
local_symbols: PinnedArray(LocalSymbol) = .{},
|
|
argument_symbols: PinnedArray(ArgumentSymbol) = .{},
|
|
global_variables: PinnedArray(GlobalVariable) = .{},
|
|
abi_arguments: PinnedArray(AbiArgument) = .{},
|
|
standalone_instructions: PinnedArray(Instruction) = .{},
|
|
leading_zeroes: PinnedArray(LeadingZeroes) = .{},
|
|
trailing_zeroes: PinnedArray(TrailingZeroes) = .{},
|
|
casts: PinnedArray(Cast) = .{},
|
|
memcopies: PinnedArray(Memcpy) = .{},
|
|
undefined_values: PinnedArray(Undefined) = .{},
|
|
insert_values: PinnedArray(InsertValue) = .{},
|
|
extract_values: PinnedArray(ExtractValue) = .{},
|
|
debug_arguments: PinnedArray(DebugArgument) = .{},
|
|
debug_locals: PinnedArray(DebugLocal) = .{},
|
|
function_types: PinnedArray(Type.Function) = .{},
|
|
array_type_map: PinnedHashMap(Type.Array.Descriptor, *Type) = .{},
|
|
typed_pointer_type_map: PinnedHashMap(Type.TypedPointer.Descriptor, *Type) = .{},
|
|
array_types: PinnedArray(Type.Array) = .{},
|
|
typed_pointer_types: PinnedArray(Type.TypedPointer) = .{},
|
|
structs: PinnedArray(Type.Struct) = .{},
|
|
anonymous_structs: PinnedArray(Type.AnonymousStruct) = .{},
|
|
two_struct_map: PinnedHashMap([2]*Type, *Type) = .{},
|
|
fields: PinnedArray(Type.AggregateField) = .{},
|
|
bitfields: PinnedArray(Type.Bitfield) = .{},
|
|
cloned_types: PinnedHashMap(*Type, *Type) = .{},
|
|
analyzed_file_count: u32 = 0,
|
|
assigned_file_count: u32 = 0,
|
|
llvm: struct {
|
|
context: *LLVM.Context,
|
|
module: *LLVM.Module,
|
|
attributes: LLVM.Attributes,
|
|
target_machine: *LLVM.Target.Machine,
|
|
object: ?[]const u8 = null,
|
|
intrinsic_ids: std.EnumArray(LLVMIntrinsic, LLVM.Value.IntrinsicID),
|
|
fixed_intrinsic_functions: std.EnumArray(LLVMFixedIntrinsic, *LLVM.Value.Constant.Function),
|
|
intrinsic_id_map: PinnedHashMap([]const u8, LLVM.Value.IntrinsicID) = .{},
|
|
intrinsic_function_map: PinnedHashMap(LLVMIntrinsic.Parameters, *LLVM.Value.Constant.Function) = .{},
|
|
} = undefined,
|
|
integers: [128]Type.Integer = blk: {
|
|
var integers: [128]Type.Integer = undefined;
|
|
for ([_]Type.Integer.Signedness{.unsigned, .signed }) |signedness| {
|
|
for (1..64 + 1) |bit_count| {
|
|
const integer_type_index = @intFromEnum(signedness) * @as(usize, 64) + bit_count - 1;
|
|
const byte_count = get_power_of_two_byte_count_from_bit_count(bit_count);
|
|
integers[integer_type_index] = .{
|
|
.type = .{
|
|
.sema = .{
|
|
.thread = undefined,
|
|
.id = .integer,
|
|
.resolved = true,
|
|
},
|
|
.size = byte_count,
|
|
.bit_size = bit_count,
|
|
.alignment = byte_count,
|
|
},
|
|
.signedness = signedness,
|
|
};
|
|
}
|
|
}
|
|
break :blk integers;
|
|
},
|
|
void: Type = .{
|
|
.sema = .{
|
|
.thread = undefined,
|
|
.id = .void,
|
|
.resolved = true,
|
|
},
|
|
.size = 0,
|
|
.bit_size = 0,
|
|
.alignment = 0,
|
|
},
|
|
noreturn: Type = .{
|
|
.sema = .{
|
|
.thread = undefined,
|
|
.id = .noreturn,
|
|
.resolved = true,
|
|
},
|
|
.size = 0,
|
|
.bit_size = 0,
|
|
.alignment = 0,
|
|
},
|
|
opaque_pointer: Type = .{
|
|
.sema = .{
|
|
.thread = undefined,
|
|
.id = .opaque_pointer,
|
|
.resolved = true,
|
|
},
|
|
.bit_size = 64,
|
|
.size = 8,
|
|
.alignment = 8,
|
|
},
|
|
discard_count: u64 = 0,
|
|
handle: std.Thread = undefined,
|
|
generate_debug_information: bool = true,
|
|
|
|
fn add_thread_work(thread: *Thread, job: Job) void {
|
|
@atomicStore(@TypeOf(thread.task_system.state), &thread.task_system.state, .running, .seq_cst);
|
|
assert(@atomicLoad(@TypeOf(thread.task_system.program_state), &thread.task_system.program_state, .seq_cst) != .none);
|
|
thread.task_system.job.queue_job(job);
|
|
}
|
|
|
|
fn add_control_work(thread: *Thread, job: Job) void {
|
|
thread.task_system.ask.queue_job(job);
|
|
}
|
|
|
|
fn get_worker_job(thread: *Thread) ?Job {
|
|
if (thread.task_system.job.get_next_job()) |job| {
|
|
// std.debug.print("[WORKER] Thread #{} getting job {s}\n", .{thread.get_index(), @tagName(job.id)});
|
|
return job;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
fn get_control_job(thread: *Thread) ?Job {
|
|
if (thread.task_system.ask.get_next_job()) |job| {
|
|
// std.debug.print("[CONTROL] Getting job {s} from thread #{}\n", .{@tagName(job.id), thread.get_index()});
|
|
return job;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
pub fn get_index(thread: *Thread) u16 {
|
|
const index = @divExact(@intFromPtr(thread) - @intFromPtr(instance.threads.ptr), @sizeOf(Thread));
|
|
return @intCast(index);
|
|
}
|
|
};
|
|
|
|
const LLVMFixedIntrinsic = enum{
|
|
trap,
|
|
};
|
|
const LLVMIntrinsic = enum{
|
|
leading_zeroes,
|
|
trailing_zeroes,
|
|
|
|
const Parameters = struct{
|
|
id: LLVM.Value.IntrinsicID,
|
|
types: []const *LLVM.Type,
|
|
};
|
|
};
|
|
|
|
const LLVMFile = struct {
|
|
file: *LLVM.DebugInfo.File,
|
|
compile_unit: *LLVM.DebugInfo.CompileUnit,
|
|
builder: *LLVM.DebugInfo.Builder,
|
|
};
|
|
|
|
const Job = packed struct(u64) {
|
|
offset: u32 = 0,
|
|
count: u24 = 0,
|
|
id: Id,
|
|
|
|
const Id = enum(u8){
|
|
analyze_file,
|
|
notify_file_resolved,
|
|
notify_analysis_complete,
|
|
llvm_generate_ir,
|
|
llvm_notify_ir_done,
|
|
llvm_optimize,
|
|
llvm_emit_object,
|
|
llvm_notify_object_done,
|
|
compile_c_source_file,
|
|
};
|
|
};
|
|
|
|
const TaskSystem = struct{
|
|
job: JobQueue = .{},
|
|
ask: JobQueue = .{},
|
|
program_state: ProgramState = .none,
|
|
state: ThreadState = .idle,
|
|
|
|
const ProgramState = enum{
|
|
none,
|
|
c_source_file,
|
|
c_source_file_done,
|
|
analysis,
|
|
analysis_resolution,
|
|
llvm_generate_ir,
|
|
llvm_emit_object,
|
|
llvm_finished_object,
|
|
};
|
|
|
|
const ThreadState = enum{
|
|
idle,
|
|
running,
|
|
};
|
|
};
|
|
|
|
const JobQueue = struct{
|
|
entries: [job_entry_count]Job align(cache_line_size) = [1]Job{@bitCast(@as(u64, 0))} ** job_entry_count,
|
|
queuer: struct {
|
|
to_do: u64 = 0,
|
|
next_write: u64 = 0,
|
|
} = .{},
|
|
worker: struct {
|
|
completed: u64 = 0,
|
|
next_read: u64 = 0,
|
|
} = .{},
|
|
reserved: [padding_byte_count]u8 = [1]u8{0} ** padding_byte_count,
|
|
|
|
const job_entry_count = 64;
|
|
const valuable_size = job_entry_count * @sizeOf(Job) + 4 * @sizeOf(u64);
|
|
const real_size = std.mem.alignForward(usize, valuable_size, cache_line_size);
|
|
const padding_byte_count = real_size - valuable_size;
|
|
|
|
comptime {
|
|
assert(@sizeOf(JobQueue) % cache_line_size == 0);
|
|
}
|
|
|
|
fn queue_job(job_queue: *JobQueue, job: Job) void {
|
|
// std.debug.print("[0x{x}] Queueing job '{s}'\n", .{@intFromPtr(job_queue) & 0xfff, @tagName(job.id)});
|
|
const index = job_queue.queuer.next_write;
|
|
if (weak_memory_model) @fence(.seq_cst);
|
|
assert(index + 1 != @atomicLoad(@TypeOf(job_queue.worker.next_read), &job_queue.worker.next_read, .seq_cst));
|
|
if (weak_memory_model) @fence(.seq_cst);
|
|
const ptr = &job_queue.entries[index];
|
|
//if (job.id == .analyze_file and job.count == 0 and job.offset == 0) unreachable;
|
|
// std.debug.print("Before W 0x{x} - 0x{x}\n", .{@intFromPtr(ptr), job.offset});
|
|
ptr.* = job;
|
|
if (weak_memory_model) @fence(.seq_cst);
|
|
// std.debug.print("After W 0x{x}\n", .{@intFromPtr(ptr)});
|
|
job_queue.queuer.to_do += 1;
|
|
if (weak_memory_model) @fence(.seq_cst);
|
|
job_queue.queuer.next_write = index + 1;
|
|
if (weak_memory_model) @fence(.seq_cst);
|
|
}
|
|
|
|
fn get_next_job(job_queue: *JobQueue) ?Job{
|
|
const index = job_queue.worker.next_read;
|
|
if (weak_memory_model) @fence(.seq_cst);
|
|
const nw = @atomicLoad(@TypeOf(job_queue.queuer.next_write), &job_queue.queuer.next_write, .seq_cst);
|
|
if (weak_memory_model) @fence(.seq_cst);
|
|
if (index != nw) {
|
|
if (weak_memory_model) @fence(.seq_cst);
|
|
job_queue.worker.next_read += 1;
|
|
if (weak_memory_model) @fence(.seq_cst);
|
|
const job_ptr = &job_queue.entries[index];
|
|
if (weak_memory_model) @fence(.seq_cst);
|
|
const job = job_ptr.*;
|
|
if (weak_memory_model) @fence(.seq_cst);
|
|
// std.debug.print("[0x{x}] Getting job #{} (0x{x} -\n{}\n) (nw: {})\n", .{@intFromPtr(job_queue) & 0xfff, index, @intFromPtr(job), job.*, nw});
|
|
//if (job.id == .analyze_file and job.count == 0 and job.offset == 0) unreachable;
|
|
return job;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
fn complete_job(job_queue: *JobQueue) void {
|
|
job_queue.worker.completed += 1;
|
|
}
|
|
};
|
|
|
|
const Instance = struct{
|
|
files: PinnedArray(File) = .{},
|
|
file_paths: PinnedArray(u32) = .{},
|
|
file_mutex: std.Thread.Mutex = .{},
|
|
units: PinnedArray(Unit) = .{},
|
|
arena: *Arena = undefined,
|
|
threads: []Thread = undefined,
|
|
paths: struct {
|
|
cwd: []const u8,
|
|
executable: []const u8,
|
|
executable_directory: []const u8,
|
|
} = .{
|
|
.cwd = &.{},
|
|
.executable = &.{},
|
|
.executable_directory = &.{},
|
|
},
|
|
|
|
fn path_from_cwd(i: *Instance, arena: *Arena, relative_path: []const u8) []const u8 {
|
|
return arena.join(&.{i.paths.cwd, "/", relative_path}) catch unreachable;
|
|
}
|
|
|
|
fn path_from_compiler(i: *Instance, arena: *Arena, relative_path: []const u8) []const u8 {
|
|
return arena.join(&.{i.paths.executable_directory, "/", relative_path}) catch unreachable;
|
|
}
|
|
};
|
|
|
|
const File = struct{
|
|
global_declaration: GlobalDeclaration,
|
|
scope: File.Scope,
|
|
source_code: []const u8,
|
|
path: []const u8,
|
|
functions: Range = .{
|
|
.start = 0,
|
|
.end = 0,
|
|
},
|
|
state: State = .queued,
|
|
thread: u32 = 0,
|
|
interested_threads: PinnedArray(u32) = .{},
|
|
interested_files: PinnedArray(*File) = .{},
|
|
imports: PinnedArray(*Import) = .{},
|
|
values_per_import: PinnedArray(PinnedArray(*Value)) = .{},
|
|
resolved_import_count: u32 = 0,
|
|
local_lazy_expressions: PinnedArray(*LocalLazyExpression) = .{},
|
|
|
|
pub fn get_index(file: *File) u32 {
|
|
return instance.files.get_index(file);
|
|
}
|
|
|
|
pub fn get_directory_path(file: *const File) []const u8 {
|
|
return std.fs.path.dirname(file.path) orelse unreachable;
|
|
}
|
|
|
|
const State = enum{
|
|
queued,
|
|
analyzing,
|
|
};
|
|
|
|
const Scope = struct {
|
|
scope: compiler.Scope,
|
|
};
|
|
};
|
|
|
|
var instance = Instance{};
|
|
const do_codegen = true;
|
|
const codegen_backend = CodegenBackend.llvm;
|
|
|
|
const CodegenBackend = union(enum){
|
|
llvm: struct {
|
|
split_object_per_thread: bool,
|
|
},
|
|
};
|
|
|
|
fn add_file(file_absolute_path: []const u8, interested_threads: []const u32) u32 {
|
|
instance.file_mutex.lock();
|
|
defer instance.file_mutex.unlock();
|
|
|
|
const hash = hash_bytes(file_absolute_path);
|
|
const new_file = instance.files.add_one();
|
|
_ = instance.file_paths.append(hash);
|
|
const new_file_index = instance.files.get_index(new_file);
|
|
new_file.* = .{
|
|
.global_declaration = .{
|
|
.declaration = .{
|
|
.name = std.math.maxInt(u32),
|
|
.id = .global,
|
|
.line = 1,
|
|
.column = 1,
|
|
.scope = &new_file.scope.scope,
|
|
},
|
|
.id = .file,
|
|
},
|
|
.scope = .{
|
|
.scope = .{
|
|
.id = .file,
|
|
.parent = null,
|
|
.line = 1,
|
|
.column = 1,
|
|
.file = new_file_index,
|
|
},
|
|
},
|
|
.source_code = &.{},
|
|
.path = file_absolute_path,
|
|
};
|
|
|
|
new_file.interested_threads.append_slice(interested_threads);
|
|
|
|
return new_file_index;
|
|
}
|
|
const Arch = enum {
|
|
x86_64,
|
|
aarch64,
|
|
};
|
|
|
|
const Os = enum {
|
|
linux,
|
|
macos,
|
|
windows,
|
|
};
|
|
|
|
const Abi = enum {
|
|
none,
|
|
gnu,
|
|
musl,
|
|
};
|
|
|
|
const Optimization = enum {
|
|
none,
|
|
debug_prefer_fast,
|
|
debug_prefer_size,
|
|
lightly_optimize_for_speed,
|
|
optimize_for_speed,
|
|
optimize_for_size,
|
|
aggressively_optimize_for_speed,
|
|
aggressively_optimize_for_size,
|
|
};
|
|
|
|
fn error_insufficient_arguments_command(command: []const u8) noreturn {
|
|
@setCold(true);
|
|
write("Command '");
|
|
write(command);
|
|
write("' requires at least one argument\n");
|
|
fail();
|
|
}
|
|
|
|
fn error_unterminated_argument(argument: []const u8) noreturn {
|
|
@setCold(true);
|
|
write("Argument '");
|
|
write(argument);
|
|
write("' must be terminated\n");
|
|
fail();
|
|
}
|
|
|
|
const Target = struct {
|
|
arch: Arch,
|
|
os: Os,
|
|
abi: Abi,
|
|
};
|
|
|
|
const Unit = struct {
|
|
descriptor: Descriptor,
|
|
|
|
const Descriptor = struct {
|
|
main_source_file_path: []const u8,
|
|
executable_path: []const u8,
|
|
object_path: []const u8,
|
|
c_source_files: []const []const u8,
|
|
c_object_files: []const []const u8,
|
|
target: Target,
|
|
optimization: Optimization,
|
|
generate_debug_information: bool,
|
|
link_libc: bool,
|
|
link_libcpp: bool,
|
|
codegen_backend: CodegenBackend,
|
|
};
|
|
|
|
fn compile(descriptor: Descriptor) *Unit {
|
|
const unit = instance.units.add_one();
|
|
unit.* = .{
|
|
.descriptor = descriptor,
|
|
};
|
|
|
|
if (descriptor.c_source_files.len > 0) {
|
|
LLVM.initializeAll();
|
|
} else {
|
|
switch (unit.descriptor.target.arch) {
|
|
inline else => |a| {
|
|
const arch = @field(LLVM, @tagName(a));
|
|
arch.initializeTarget();
|
|
arch.initializeTargetInfo();
|
|
arch.initializeTargetMC();
|
|
arch.initializeAsmPrinter();
|
|
arch.initializeAsmParser();
|
|
},
|
|
}
|
|
}
|
|
|
|
var last_assigned_thread_index: u32 = 0;
|
|
var c_objects = PinnedArray([]const u8){};
|
|
|
|
for (descriptor.c_source_files) |source_file| {
|
|
const extension_start = last_byte(source_file, '.') orelse fail();
|
|
const name = std.fs.path.basename(source_file[0..extension_start]);
|
|
const object_path = instance.arena.join(&.{"nat/o/", name, ".o"}) catch unreachable;
|
|
_ = c_objects.append(object_path);
|
|
}
|
|
|
|
unit.descriptor.c_object_files = c_objects.slice();
|
|
|
|
const main_source_file_absolute = instance.path_from_cwd(instance.arena, unit.descriptor.main_source_file_path);
|
|
const new_file_index = add_file(main_source_file_absolute, &.{});
|
|
instance.threads[last_assigned_thread_index].task_system.program_state = .analysis;
|
|
instance.threads[last_assigned_thread_index].add_thread_work(Job{
|
|
.offset = new_file_index,
|
|
.count = 1,
|
|
.id = .analyze_file,
|
|
});
|
|
last_assigned_thread_index += 1;
|
|
|
|
for (descriptor.c_source_files, 0..) |_, index| {
|
|
const thread_index = last_assigned_thread_index % instance.threads.len;
|
|
const thread = &instance.threads[thread_index];
|
|
thread.task_system.program_state = .analysis;
|
|
thread.add_thread_work(Job{
|
|
.offset = @intCast(index),
|
|
.count = 1,
|
|
.id = .compile_c_source_file,
|
|
});
|
|
last_assigned_thread_index += 1;
|
|
}
|
|
|
|
control_thread(unit, last_assigned_thread_index);
|
|
|
|
return unit;
|
|
}
|
|
};
|
|
|
|
fn control_thread(unit: *Unit, lati: u32) void {
|
|
var last_assigned_thread_index: u32 = lati;
|
|
var first_ir_done = false;
|
|
var total_is_done: bool = false;
|
|
var iterations_without_work_done: u32 = 0;
|
|
|
|
while (!total_is_done) {
|
|
total_is_done = first_ir_done;
|
|
|
|
var task_done_this_iteration: u32 = 0;
|
|
|
|
for (instance.threads, 0..) |*thread, i| {
|
|
const completed = @atomicLoad(u64, &thread.task_system.job.worker.completed, .seq_cst);
|
|
// INFO: No need to do an atomic load here since it's only this thread writing to the value
|
|
const program_state = thread.task_system.program_state;
|
|
const to_do = thread.task_system.job.queuer.to_do;
|
|
total_is_done = total_is_done and completed == to_do and if (@intFromEnum(program_state) >= @intFromEnum(TaskSystem.ProgramState.analysis) and (thread.functions.length > 0 or thread.global_variables.length > 0)) program_state == .llvm_finished_object else true;
|
|
|
|
var previous_job: Job = undefined;
|
|
while (thread.get_control_job()) |job| {
|
|
assert(!(previous_job.id == job.id and previous_job.offset == job.offset and previous_job.count == job.count));
|
|
switch (job.id) {
|
|
.analyze_file => {
|
|
const analyze_file_path_hash = job.offset;
|
|
const interested_file_index = job.count;
|
|
// std.debug.print("[CONTROL] Trying to retrieve file path hash (0x{x}) interested file index: {} in thread #{}\n", .{analyze_file_path_hash, interested_file_index, thread.get_index()});
|
|
assert(analyze_file_path_hash != 0);
|
|
|
|
for (instance.file_paths.slice()) |file_path_hash| {
|
|
if (analyze_file_path_hash == file_path_hash) {
|
|
fail();
|
|
}
|
|
} else {
|
|
const thread_index = last_assigned_thread_index % instance.threads.len;
|
|
last_assigned_thread_index += 1;
|
|
const file_absolute_path = thread.identifiers.get(analyze_file_path_hash).?;
|
|
const interested_thread_index: u32 = @intCast(i);
|
|
const file_index = add_file(file_absolute_path, &.{interested_thread_index});
|
|
_ = instance.files.get_unchecked(file_index).interested_files.append(&instance.files.pointer[interested_file_index]);
|
|
const assigned_thread = &instance.threads[thread_index];
|
|
|
|
assigned_thread.task_system.program_state = .analysis;
|
|
assigned_thread.add_thread_work(Job{
|
|
.offset = file_index,
|
|
.id = .analyze_file,
|
|
.count = 1,
|
|
});
|
|
}
|
|
},
|
|
.notify_file_resolved => {
|
|
const file_index = job.offset;
|
|
const thread_index = job.count;
|
|
const destination_thread = &instance.threads[thread_index];
|
|
const file = instance.files.get(@enumFromInt(file_index));
|
|
const file_path_hash = hash_bytes(file.path);
|
|
|
|
destination_thread.add_thread_work(.{
|
|
.id = .notify_file_resolved,
|
|
.count = @intCast(file_index),
|
|
.offset = file_path_hash,
|
|
});
|
|
},
|
|
.notify_analysis_complete => {
|
|
thread.add_thread_work(.{
|
|
.id = .llvm_generate_ir,
|
|
});
|
|
},
|
|
.llvm_notify_ir_done => {
|
|
thread.add_thread_work(.{
|
|
.id = .llvm_emit_object,
|
|
});
|
|
},
|
|
.llvm_notify_object_done => {
|
|
thread.task_system.program_state = .llvm_finished_object;
|
|
first_ir_done = true;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
|
|
thread.task_system.ask.complete_job();
|
|
previous_job = job;
|
|
task_done_this_iteration += 1;
|
|
}
|
|
}
|
|
|
|
total_is_done = total_is_done and task_done_this_iteration == 0;
|
|
iterations_without_work_done += @intFromBool(task_done_this_iteration == 0);
|
|
|
|
if (configuration.sleep_on_thread_hot_loops) {
|
|
if (iterations_without_work_done > 5) {
|
|
std.time.sleep(100);
|
|
}
|
|
} else {
|
|
std.atomic.spinLoopHint();
|
|
}
|
|
}
|
|
|
|
var objects = PinnedArray([]const u8){};
|
|
for (instance.threads) |*thread| {
|
|
if (thread.llvm.object) |object| {
|
|
_ = objects.append(object);
|
|
}
|
|
}
|
|
|
|
for (unit.descriptor.c_object_files) |object_path| {
|
|
_ = objects.append(object_path);
|
|
}
|
|
|
|
// for (instance.threads) |*thread| {
|
|
// std.debug.print("Thread #{}: {s}\n", .{thread.get_index(), @tagName(thread.task_system.program_state)});
|
|
// }
|
|
|
|
assert(objects.length > 0);
|
|
|
|
link(.{
|
|
.output_file_path = unit.descriptor.executable_path,
|
|
.extra_arguments = &.{},
|
|
.objects = objects.const_slice(),
|
|
.libraries = &.{},
|
|
.link_libc = true,
|
|
.link_libcpp = false,
|
|
});
|
|
}
|
|
|
|
fn command_exe(arguments: []const []const u8) void {
|
|
if (arguments.len == 0) {
|
|
error_insufficient_arguments_command("exe");
|
|
}
|
|
// TODO: make these mutable
|
|
const arch: Arch = switch (builtin.cpu.arch) {
|
|
.aarch64 => .aarch64,
|
|
.x86_64 => .x86_64,
|
|
else => unreachable,
|
|
};
|
|
const os: Os = switch (builtin.os.tag) {
|
|
.linux => .linux,
|
|
.macos => .macos,
|
|
.windows => .windows,
|
|
else => unreachable,
|
|
};
|
|
const abi: Abi = switch (builtin.os.tag) {
|
|
.linux => .gnu,
|
|
.macos => .none,
|
|
.windows => .gnu,
|
|
else => unreachable,
|
|
};
|
|
|
|
|
|
var maybe_executable_path: ?[]const u8 = null;
|
|
var maybe_executable_name: ?[]const u8 = null;
|
|
var maybe_main_source_file_path: ?[]const u8 = null;
|
|
|
|
var c_source_files = PinnedArray([]const u8){};
|
|
|
|
var optimization = Optimization.none;
|
|
var generate_debug_information = true;
|
|
var link_libc = true;
|
|
const link_libcpp = false;
|
|
|
|
var i: usize = 0;
|
|
while (i < arguments.len) : (i += 1) {
|
|
const current_argument = arguments[i];
|
|
if (byte_equal(current_argument, "-o")) {
|
|
if (i + 1 != arguments.len) {
|
|
maybe_executable_path = arguments[i + 1];
|
|
assert(maybe_executable_path.?.len != 0);
|
|
i += 1;
|
|
} else {
|
|
error_unterminated_argument(current_argument);
|
|
}
|
|
} else if (byte_equal(current_argument, "-link_libc")) {
|
|
if (i + 1 != arguments.len) {
|
|
i += 1;
|
|
|
|
const arg = arguments[i];
|
|
if (byte_equal(arg, "true")) {
|
|
link_libc = true;
|
|
} else if (byte_equal(arg, "false")) {
|
|
link_libc = false;
|
|
} else {
|
|
unreachable;
|
|
}
|
|
} else {
|
|
error_unterminated_argument(current_argument);
|
|
}
|
|
} else if (byte_equal(current_argument, "-main_source_file")) {
|
|
if (i + 1 != arguments.len) {
|
|
i += 1;
|
|
|
|
const arg = arguments[i];
|
|
maybe_main_source_file_path = arg;
|
|
} else {
|
|
error_unterminated_argument(current_argument);
|
|
}
|
|
} else if (byte_equal(current_argument, "-name")) {
|
|
if (i + 1 != arguments.len) {
|
|
i += 1;
|
|
|
|
const arg = arguments[i];
|
|
maybe_executable_name = arg;
|
|
} else {
|
|
error_unterminated_argument(current_argument);
|
|
}
|
|
} else if (byte_equal(current_argument, "-c_source_files")) {
|
|
if (i + 1 != arguments.len) {
|
|
i += 1;
|
|
|
|
c_source_files.append_slice(arguments[i..]);
|
|
i = arguments.len;
|
|
} else {
|
|
error_unterminated_argument(current_argument);
|
|
}
|
|
} else if (byte_equal(current_argument, "-optimize")) {
|
|
if (i + 1 != arguments.len) {
|
|
i += 1;
|
|
|
|
const optimize_string = arguments[i];
|
|
optimization = library.enumFromString(Optimization, optimize_string) orelse unreachable;
|
|
} else {
|
|
error_unterminated_argument(current_argument);
|
|
}
|
|
} else if (byte_equal(current_argument, "-debug")) {
|
|
if (i + 1 != arguments.len) {
|
|
i += 1;
|
|
|
|
const debug_string = arguments[i];
|
|
generate_debug_information = if (byte_equal(debug_string, "true")) true else if (byte_equal(debug_string, "false")) false else unreachable;
|
|
} else {
|
|
error_unterminated_argument(current_argument);
|
|
}
|
|
} else if (byte_equal(current_argument, "-c_source_files_start")) {
|
|
i += 1;
|
|
var sentinel = false;
|
|
while (i < arguments.len) : (i += 1) {
|
|
const arg = arguments[i];
|
|
if (byte_equal(arg, "-c_source_files_end")) {
|
|
sentinel = true;
|
|
break;
|
|
}
|
|
|
|
_ = c_source_files.append(arg);
|
|
}
|
|
|
|
if (!sentinel) {
|
|
fail_message("No sentinel for C source files arguments");
|
|
}
|
|
} else {
|
|
@panic(current_argument);
|
|
// std.debug.panic("Unrecognized argument: {s}", .{current_argument});
|
|
}
|
|
}
|
|
|
|
const main_source_file_path = maybe_main_source_file_path orelse fail_message("Main source file must be specified with -main_source_file");
|
|
// TODO: undo this incongruency
|
|
const executable_name = if (maybe_executable_name) |executable_name| executable_name else std.fs.path.basename(main_source_file_path[0..main_source_file_path.len - "/main.nat".len]);
|
|
const executable_path = maybe_executable_path orelse blk: {
|
|
assert(executable_name.len > 0);
|
|
const result = instance.arena.join(&.{"nat/", executable_name }) catch unreachable;
|
|
break :blk result;
|
|
};
|
|
|
|
const object_path = instance.arena.join(&.{"nat/o/", executable_name, ".o"}) catch unreachable;
|
|
|
|
_ = Unit.compile(.{
|
|
.target = .{
|
|
.arch = arch,
|
|
.os = os,
|
|
.abi = abi,
|
|
},
|
|
.link_libc = link_libc,
|
|
.link_libcpp = link_libcpp,
|
|
.main_source_file_path = main_source_file_path,
|
|
.object_path = object_path,
|
|
.executable_path = executable_path,
|
|
.c_source_files = c_source_files.slice(),
|
|
.c_object_files = &.{},
|
|
.optimization = optimization,
|
|
.generate_debug_information = generate_debug_information,
|
|
.codegen_backend = .{
|
|
.llvm = .{
|
|
.split_object_per_thread = true,
|
|
},
|
|
},
|
|
});
|
|
}
|
|
|
|
pub fn main() void {
|
|
instance.arena = library.Arena.init(4 * 1024 * 1024) catch unreachable;
|
|
const executable_path = library.self_exe_path(instance.arena) catch unreachable;
|
|
const executable_directory = std.fs.path.dirname(executable_path).?;
|
|
std.fs.cwd().makePath("nat") catch |err| switch (err) {
|
|
else => @panic(@errorName(err)),
|
|
};
|
|
std.fs.cwd().makePath("nat/o") catch |err| switch (err) {
|
|
else => @panic(@errorName(err)),
|
|
};
|
|
instance.paths = .{
|
|
.cwd = library.realpath(instance.arena, std.fs.cwd(), ".") catch unreachable,
|
|
.executable = executable_path,
|
|
.executable_directory = executable_directory,
|
|
};
|
|
const thread_count = std.Thread.getCpuCount() catch unreachable;
|
|
const cpu_count = &cpu_count_buffer[0];
|
|
instance.arena.align_forward(@alignOf(Thread));
|
|
instance.threads = instance.arena.new_array(Thread, thread_count - 1) catch unreachable;
|
|
cpu_count.* = @intCast(thread_count - 2);
|
|
for (instance.threads) |*thread| {
|
|
thread.* = .{};
|
|
}
|
|
|
|
const thread_index = cpu_count.*;
|
|
instance.threads[thread_index].handle = std.Thread.spawn(.{}, worker_thread, .{thread_index, cpu_count}) catch unreachable;
|
|
|
|
var arg_iterator = std.process.args();
|
|
var argument_buffer = PinnedArray([]const u8){};
|
|
|
|
while (arg_iterator.next()) |arg| {
|
|
_ = argument_buffer.append(arg);
|
|
}
|
|
|
|
const arguments = argument_buffer.const_slice();
|
|
if (arguments.len < 2) {
|
|
fail_message("Insufficient number of arguments");
|
|
}
|
|
|
|
const command = arguments[1];
|
|
const command_arguments = arguments[2..];
|
|
|
|
if (byte_equal(command, "exe")) {
|
|
command_exe(command_arguments);
|
|
} else if (byte_equal(command, "clang") or byte_equal(command, "-cc1") or byte_equal(command, "-cc1as")) {
|
|
fail_message("TODO: clang");
|
|
} else if (byte_equal(command, "cc")) {
|
|
fail_message("TODO: clang");
|
|
} else if (byte_equal(command, "c++")) {
|
|
fail_message("TODO: clang");
|
|
} else {
|
|
fail_message("Unrecognized command");
|
|
}
|
|
}
|
|
|
|
const LinkerOptions = struct {
|
|
output_file_path: []const u8,
|
|
extra_arguments: []const []const u8,
|
|
objects: []const []const u8,
|
|
libraries: []const []const u8,
|
|
link_libc: bool,
|
|
link_libcpp: bool,
|
|
};
|
|
|
|
pub fn link(options: LinkerOptions) void {
|
|
var argv = PinnedArray([]const u8){};
|
|
const driver_program = switch (builtin.os.tag) {
|
|
.windows => "lld-link",
|
|
.linux => "ld.lld",
|
|
.macos => "ld64.lld",
|
|
else => @compileError("OS not supported"),
|
|
};
|
|
_ = argv.append(driver_program);
|
|
_ = argv.append("--error-limit=0");
|
|
|
|
switch (builtin.cpu.arch) {
|
|
.aarch64 => switch (builtin.os.tag) {
|
|
.linux => {
|
|
_ = argv.append("-znow");
|
|
_ = argv.append_slice(&.{ "-m", "aarch64linux" });
|
|
},
|
|
else => {},
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
// const output_path = out_path orelse "a.out";
|
|
_ = argv.append("-o");
|
|
_ = argv.append(options.output_file_path);
|
|
|
|
argv.append_slice(options.extra_arguments);
|
|
|
|
for (options.objects) |object| {
|
|
_ = argv.append(object);
|
|
}
|
|
|
|
const ci = configuration.ci;
|
|
switch (builtin.os.tag) {
|
|
.macos => {
|
|
_ = argv.append("-dynamic");
|
|
argv.append_slice(&.{ "-platform_version", "macos", "13.4.1", "13.3" });
|
|
_ = argv.append("-arch");
|
|
_ = argv.append(switch (builtin.cpu.arch) {
|
|
.aarch64 => "arm64",
|
|
else => |t| @panic(@tagName(t)),
|
|
});
|
|
|
|
argv.append_slice(&.{ "-syslibroot", "/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk" });
|
|
|
|
if (!library.ends_with_slice(options.output_file_path, ".dylib")) {
|
|
argv.append_slice(&.{ "-e", "_main" });
|
|
}
|
|
|
|
_ = argv.append("-lSystem");
|
|
|
|
if (options.link_libcpp) {
|
|
_ = argv.append("-L/Library/Developer/CommandLineTools/SDKs/MacOSX13.3.sdk/usr/lib");
|
|
_ = argv.append("-lc++");
|
|
}
|
|
},
|
|
.linux => {
|
|
if (ci) {
|
|
if (options.link_libcpp) {
|
|
assert(options.link_libc);
|
|
_ = argv.append("/lib/x86_64-linux-gnu/libstdc++.so.6");
|
|
}
|
|
|
|
if (options.link_libc) {
|
|
_ = argv.append("/lib/x86_64-linux-gnu/crt1.o");
|
|
_ = argv.append("/lib/x86_64-linux-gnu/crti.o");
|
|
argv.append_slice(&.{ "-L", "/lib/x86_64-linux-gnu" });
|
|
argv.append_slice(&.{ "-dynamic-linker", "/lib64/ld-linux-x86-64.so.2" });
|
|
_ = argv.append("--as-needed");
|
|
_ = argv.append("-lm");
|
|
_ = argv.append("-lpthread");
|
|
_ = argv.append("-lc");
|
|
_ = argv.append("-ldl");
|
|
_ = argv.append("-lrt");
|
|
_ = argv.append("-lutil");
|
|
_ = argv.append("/lib/x86_64-linux-gnu/crtn.o");
|
|
}
|
|
} else {
|
|
if (options.link_libcpp) {
|
|
assert(options.link_libc);
|
|
_ = argv.append("/usr/lib64/libstdc++.so.6");
|
|
}
|
|
|
|
if (options.link_libc) {
|
|
_ = argv.append("/usr/lib64/crt1.o");
|
|
_ = argv.append("/usr/lib64/crti.o");
|
|
argv.append_slice(&.{ "-L", "/usr/lib64" });
|
|
|
|
_ = argv.append("-dynamic-linker");
|
|
switch (builtin.cpu.arch) {
|
|
.x86_64 => _ = argv.append("/lib64/ld-linux-x86-64.so.2"),
|
|
.aarch64 => _ = argv.append("/lib/ld-linux-aarch64.so.1"),
|
|
else => unreachable,
|
|
}
|
|
|
|
_ = argv.append("--as-needed");
|
|
_ = argv.append("-lm");
|
|
_ = argv.append("-lpthread");
|
|
_ = argv.append("-lc");
|
|
_ = argv.append("-ldl");
|
|
_ = argv.append("-lrt");
|
|
_ = argv.append("-lutil");
|
|
|
|
_ = argv.append("/usr/lib64/crtn.o");
|
|
}
|
|
}
|
|
},
|
|
.windows => {},
|
|
else => @compileError("OS not supported"),
|
|
}
|
|
|
|
for (options.libraries) |lib| {
|
|
_ = argv.append(instance.arena.join(&.{ "-l", lib }) catch unreachable);
|
|
}
|
|
|
|
const argv_zero_terminated = library.argument_copy_zero_terminated(instance.arena, argv.const_slice()) catch unreachable;
|
|
|
|
var stdout_ptr: [*]const u8 = undefined;
|
|
var stdout_len: usize = 0;
|
|
var stderr_ptr: [*]const u8 = undefined;
|
|
var stderr_len: usize = 0;
|
|
const result = switch (builtin.os.tag) {
|
|
.linux => NativityLLDLinkELF(argv_zero_terminated.ptr, argv_zero_terminated.len, &stdout_ptr, &stdout_len, &stderr_ptr, &stderr_len),
|
|
.macos => NativityLLDLinkMachO(argv_zero_terminated.ptr, argv_zero_terminated.len, &stdout_ptr, &stdout_len, &stderr_ptr, &stderr_len),
|
|
.windows => NativityLLDLinkCOFF(argv_zero_terminated.ptr, argv_zero_terminated.len, &stdout_ptr, &stdout_len, &stderr_ptr, &stderr_len),
|
|
else => @compileError("OS not supported"),
|
|
};
|
|
|
|
if (!result) {
|
|
const stdout = stdout_ptr[0..stdout_len];
|
|
const stderr = stderr_ptr[0..stderr_len];
|
|
for (argv.const_slice()) |arg| {
|
|
write(arg);
|
|
write(" ");
|
|
}
|
|
write("\n");
|
|
if (stdout.len > 0) {
|
|
write(stdout);
|
|
write("\n");
|
|
}
|
|
|
|
if (stderr.len > 0) {
|
|
write(stderr);
|
|
write("\n");
|
|
}
|
|
|
|
@panic("Linking with LLD failed");
|
|
}
|
|
}
|
|
|
|
extern fn NativityLLDLinkELF(argument_ptr: [*:null]?[*:0]u8, argument_count: usize, stdout_ptr: *[*]const u8, stdout_len: *usize, stderr_ptr: *[*]const u8, stderr_len: *usize) bool;
|
|
extern fn NativityLLDLinkCOFF(argument_ptr: [*:null]?[*:0]u8, argument_count: usize, stdout_ptr: *[*]const u8, stdout_len: *usize, stderr_ptr: *[*]const u8, stderr_len: *usize) bool;
|
|
extern fn NativityLLDLinkMachO(argument_ptr: [*:null]?[*:0]u8, argument_count: usize, stdout_ptr: *[*]const u8, stdout_len: *usize, stderr_ptr: *[*]const u8, stderr_len: *usize) bool;
|
|
extern fn NativityLLDLinkWasm(argument_ptr: [*:null]?[*:0]u8, argument_count: usize, stdout_ptr: *[*]const u8, stdout_len: *usize, stderr_ptr: *[*]const u8, stderr_len: *usize) bool;
|
|
|
|
fn intern_identifier(pool: *PinnedHashMap(u32, []const u8), identifier: []const u8) u32 {
|
|
const start_index = @intFromBool(identifier[0] == '"');
|
|
const end_index = identifier.len - start_index;
|
|
const hash = hash_bytes(identifier[start_index..end_index]);
|
|
pool.put(hash, identifier);
|
|
|
|
return hash;
|
|
}
|
|
|
|
const CallingConvention = enum{
|
|
c,
|
|
custom,
|
|
};
|
|
|
|
const Analyzer = struct{
|
|
current_basic_block: *BasicBlock,
|
|
current_function: *Function,
|
|
current_scope: *Scope,
|
|
exit_blocks: PinnedArray(*BasicBlock) = .{},
|
|
loops: PinnedArray(LoopData) = .{},
|
|
return_block: ?*BasicBlock = null,
|
|
return_phi: ?*Phi = null,
|
|
return_pointer: ?*Instruction = null,
|
|
|
|
fn append_instruction(analyzer: *Analyzer, instruction: *Instruction) void {
|
|
assert(!analyzer.current_basic_block.is_terminated);
|
|
assert(instruction.basic_block == null);
|
|
instruction.basic_block = analyzer.current_basic_block;
|
|
_ = analyzer.current_basic_block.instructions.append(instruction);
|
|
}
|
|
};
|
|
|
|
const LoopData = struct {
|
|
break_block: *BasicBlock,
|
|
continue_block: *BasicBlock,
|
|
};
|
|
|
|
const brace_open = '{';
|
|
const brace_close = '}';
|
|
|
|
const pointer_token = '*';
|
|
|
|
const cache_line_size = switch (builtin.os.tag) {
|
|
.macos => 128,
|
|
else => 64,
|
|
};
|
|
|
|
var cpu_count_buffer = [1]u32{0} ** @divExact(cache_line_size, @sizeOf(u32));
|
|
|
|
const address_space = 0;
|
|
|
|
fn try_end_analyzing_file(file: *File) void {
|
|
_ = file; // autofix
|
|
}
|
|
|
|
fn worker_thread(thread_index: u32, cpu_count: *u32) void {
|
|
while (true) {
|
|
const local_cpu_count = cpu_count.*;
|
|
if (local_cpu_count == 0) {
|
|
break;
|
|
}
|
|
|
|
if (@cmpxchgWeak(u32, cpu_count, local_cpu_count, local_cpu_count - 1, .seq_cst, .seq_cst) == null) {
|
|
const new_thread_index = local_cpu_count - 1;
|
|
instance.threads[thread_index].handle = std.Thread.spawn(.{}, worker_thread, .{new_thread_index, cpu_count}) catch unreachable;
|
|
}
|
|
}
|
|
|
|
const thread = &instance.threads[thread_index];
|
|
thread.arena = Arena.init(4 * 1024 * 1024) catch unreachable;
|
|
|
|
for (&thread.integers) |*integer| {
|
|
integer.type.sema.thread = @intCast(thread_index);
|
|
}
|
|
thread.opaque_pointer.sema.thread = @intCast(thread_index);
|
|
thread.void.sema.thread = @intCast(thread_index);
|
|
thread.noreturn.sema.thread = @intCast(thread_index);
|
|
|
|
while (true) {
|
|
while (thread.get_worker_job()) |job| {
|
|
const c = thread.task_system.job.worker.completed;
|
|
switch (job.id) {
|
|
.analyze_file => {
|
|
thread.assigned_file_count += 1;
|
|
const file_index = job.offset;
|
|
const file = &instance.files.slice()[file_index];
|
|
file.state = .analyzing;
|
|
file.source_code = library.read_file(thread.arena, std.fs.cwd(), file.path);
|
|
file.thread = thread_index;
|
|
analyze_file(thread, file_index);
|
|
},
|
|
.notify_file_resolved => {
|
|
const file_index = job.count;
|
|
const file = &instance.files.pointer[file_index];
|
|
|
|
if (thread == &instance.threads[file.thread]) {
|
|
fail_message("Threads match!");
|
|
} else {
|
|
const file_path_hash = job.offset;
|
|
for (file.interested_files.slice()) |interested_file| {
|
|
if (interested_file.thread == thread.get_index()) {
|
|
assert(interested_file.resolved_import_count != interested_file.imports.length);
|
|
for (interested_file.imports.slice(), 0..) |import, i| {
|
|
if (import.hash == file_path_hash) {
|
|
const values_per_import = interested_file.values_per_import.get(@enumFromInt(i));
|
|
for (values_per_import.slice()) |value| {
|
|
assert(value.sema.thread == thread.get_index());
|
|
assert(!value.sema.resolved);
|
|
if (!value.sema.resolved) {
|
|
switch (value.sema.id) {
|
|
.instruction => {
|
|
const instruction = value.get_payload(.instruction);
|
|
switch (instruction.id) {
|
|
.call => {
|
|
const call: *Call = instruction.get_payload(.call);
|
|
assert(!call.callable.sema.resolved);
|
|
|
|
switch (call.callable.sema.id) {
|
|
.lazy_expression => {
|
|
const lazy_expression = call.callable.get_payload(.lazy_expression);
|
|
const names = lazy_expression.names();
|
|
assert(names.len > 0);
|
|
|
|
switch (lazy_expression.u) {
|
|
.static => |*static| {
|
|
_ = static; // autofix
|
|
},
|
|
.dynamic => unreachable,
|
|
}
|
|
|
|
const declaration_reference = lazy_expression.u.static.outsider;
|
|
switch (declaration_reference.*.id) {
|
|
.file => {
|
|
assert(names.len == 1);
|
|
const file_declaration = declaration_reference.*.get_payload(.file);
|
|
assert(file_declaration == file);
|
|
|
|
if (file.scope.scope.declarations.get(names[0])) |callable_declaration| {
|
|
const global_declaration = callable_declaration.get_payload(.global);
|
|
switch (global_declaration.id) {
|
|
.global_symbol => {
|
|
const global_symbol = global_declaration.to_symbol();
|
|
switch (global_symbol.id) {
|
|
.function_definition => {
|
|
const function_definition = global_symbol.get_payload(.function_definition);
|
|
const external_fn = function_definition.declaration.clone(thread);
|
|
|
|
call.callable = &external_fn.global_symbol.value;
|
|
value.sema.resolved = true;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
} else {
|
|
unreachable;
|
|
}
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
},
|
|
.lazy_expression => {
|
|
const lazy_expression = value.get_payload(.lazy_expression);
|
|
assert(lazy_expression.u == .static);
|
|
for (lazy_expression.u.static.names) |n| {
|
|
assert(n == 0);
|
|
}
|
|
const declaration_reference = lazy_expression.u.static.outsider;
|
|
|
|
switch (declaration_reference.*.id) {
|
|
.unresolved_import => {
|
|
declaration_reference.* = &file.global_declaration;
|
|
value.sema.resolved = true;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
interested_file.resolved_import_count += 1;
|
|
|
|
try_resolve_file(thread, interested_file);
|
|
}
|
|
}
|
|
}
|
|
},
|
|
.llvm_generate_ir => {
|
|
if (thread.functions.length > 0 or thread.global_variables.length > 0) {
|
|
const context = LLVM.Context.create();
|
|
const module_name: []const u8 = "thread";
|
|
const module = LLVM.Module.create(module_name.ptr, module_name.len, context);
|
|
const builder = LLVM.Builder.create(context);
|
|
const attributes = LLVM.Attributes{
|
|
.naked = context.getAttributeFromEnum(.Naked, 0),
|
|
.noreturn = context.getAttributeFromEnum(.NoReturn, 0),
|
|
.nounwind = context.getAttributeFromEnum(.NoUnwind, 0),
|
|
.inreg = context.getAttributeFromEnum(.InReg, 0),
|
|
.@"noalias" = context.getAttributeFromEnum(.NoAlias, 0),
|
|
.sign_extend = context.getAttributeFromEnum(.SExt, 0),
|
|
.zero_extend = context.getAttributeFromEnum(.ZExt, 0),
|
|
};
|
|
|
|
const target_triple = switch (builtin.os.tag) {
|
|
.linux => switch (builtin.cpu.arch) {
|
|
.aarch64 => "aarch64-linux-none",
|
|
.x86_64 => "x86_64-unknown-linux-gnu",
|
|
else => @compileError("CPU not supported"),
|
|
},
|
|
.macos => "aarch64-apple-macosx-none",
|
|
.windows => "x86_64-windows-gnu",
|
|
else => @compileError("OS not supported"),
|
|
};
|
|
const cpu = builtin.cpu.model.llvm_name.?;
|
|
|
|
var features = PinnedArray(u8){
|
|
.pointer = @constCast(""),
|
|
};
|
|
|
|
const temp_use_native_features = true;
|
|
if (temp_use_native_features) {
|
|
const feature_list = builtin.cpu.arch.allFeaturesList();
|
|
if (feature_list.len > 0) {
|
|
for (feature_list, 0..) |feature, index_usize| {
|
|
const index = @as(std.Target.Cpu.Feature.Set.Index, @intCast(index_usize));
|
|
const is_enabled = builtin.cpu.features.isEnabled(index);
|
|
|
|
if (feature.llvm_name) |llvm_name| {
|
|
const plus_or_minus = "-+"[@intFromBool(is_enabled)];
|
|
_ = features.append(plus_or_minus);
|
|
features.append_slice(llvm_name);
|
|
features.append_slice(",");
|
|
}
|
|
}
|
|
|
|
assert(std.mem.endsWith(u8, features.slice(), ","));
|
|
features.length -= 1;
|
|
}
|
|
}
|
|
|
|
const target = blk: {
|
|
var error_message: [*]const u8 = undefined;
|
|
var error_message_len: usize = 0;
|
|
|
|
const optional_target = LLVM.bindings.NativityLLVMGetTarget(target_triple.ptr, target_triple.len, &error_message, &error_message_len);
|
|
const target = optional_target orelse {
|
|
fail_message(error_message[0..error_message_len]);
|
|
};
|
|
break :blk target;
|
|
};
|
|
const jit = false;
|
|
const code_model: LLVM.CodeModel = undefined;
|
|
const is_code_model_present = false;
|
|
|
|
// TODO:
|
|
const codegen_optimization_level: LLVM.CodegenOptimizationLevel = switch (Optimization.none) {
|
|
.none => .none,
|
|
.debug_prefer_fast, .debug_prefer_size => .none,
|
|
.lightly_optimize_for_speed => .less,
|
|
.optimize_for_speed, .optimize_for_size => .default,
|
|
.aggressively_optimize_for_speed, .aggressively_optimize_for_size => .aggressive,
|
|
};
|
|
const target_machine = target.createTargetMachine(target_triple.ptr, target_triple.len, cpu.ptr, cpu.len, features.pointer, features.length, LLVM.RelocationModel.static, code_model, is_code_model_present, codegen_optimization_level, jit);
|
|
|
|
module.setTargetMachineDataLayout(target_machine);
|
|
module.setTargetTriple(target_triple.ptr, target_triple.len);
|
|
|
|
|
|
thread.llvm = .{
|
|
.context = context,
|
|
.module = module,
|
|
.attributes = attributes,
|
|
.target_machine = target_machine,
|
|
.intrinsic_ids = @TypeOf(thread.llvm.intrinsic_ids).init(.{
|
|
.leading_zeroes = llvm_get_intrinsic_id("llvm.ctlz"),
|
|
.trailing_zeroes = llvm_get_intrinsic_id("llvm.cttz"),
|
|
}),
|
|
.fixed_intrinsic_functions = @TypeOf(thread.llvm.fixed_intrinsic_functions).init(.{
|
|
.trap = llvm_get_intrinsic_function(thread, .{
|
|
.id = llvm_get_intrinsic_id("llvm.trap"),
|
|
.types = &.{},
|
|
}),
|
|
}),
|
|
};
|
|
|
|
for (thread.external_functions.slice()) |*nat_function| {
|
|
llvm_emit_function_declaration(thread, nat_function);
|
|
}
|
|
|
|
for (thread.functions.slice()) |*nat_function| {
|
|
assert(nat_function.declaration.global_symbol.id == .function_definition);
|
|
llvm_emit_function_declaration(thread, &nat_function.declaration);
|
|
}
|
|
|
|
for (thread.global_variables.slice()) |*nat_global| {
|
|
const global_type = llvm_get_type(thread, nat_global.global_symbol.type);
|
|
const linkage: LLVM.Linkage = switch (nat_global.global_symbol.attributes.@"export") {
|
|
true => .@"extern",
|
|
false => .internal,
|
|
};
|
|
const constant = switch (nat_global.global_symbol.attributes.mutability) {
|
|
.@"var" => false,
|
|
.@"const" => true,
|
|
};
|
|
const initializer = llvm_get_value(thread, nat_global.initial_value).toConstant() orelse unreachable;
|
|
const thread_local_mode = LLVM.ThreadLocalMode.not_thread_local;
|
|
const externally_initialized = false;
|
|
const name = thread.identifiers.get(nat_global.global_symbol.global_declaration.declaration.name).?;
|
|
const global_variable = module.addGlobalVariable(global_type, constant, linkage, initializer, name.ptr, name.len, null, thread_local_mode, address_space, externally_initialized);
|
|
global_variable.toGlobalObject().setAlignment(nat_global.global_symbol.alignment);
|
|
nat_global.global_symbol.value.llvm = global_variable.toValue();
|
|
|
|
if (thread.generate_debug_information) {
|
|
const file_index = nat_global.global_symbol.global_declaration.declaration.scope.file;
|
|
const file_struct = llvm_get_file(thread, file_index);
|
|
const file = file_struct.file;
|
|
const scope = file.toScope();
|
|
|
|
const debug_type = llvm_get_debug_type(thread, file_struct.builder, nat_global.global_symbol.type);
|
|
const is_local_to_unit = !nat_global.global_symbol.attributes.@"export";
|
|
const is_defined = !nat_global.global_symbol.attributes.@"extern";
|
|
const expression = null;
|
|
const declaration = null;
|
|
const template_parameters = null;
|
|
const alignment = 0;
|
|
const line = nat_global.global_symbol.global_declaration.declaration.line;
|
|
const debug_global_variable = file_struct.builder.createGlobalVariableExpression(scope, name.ptr, name.len, name.ptr, name.len, file, line, debug_type, is_local_to_unit, is_defined, expression, declaration, template_parameters, alignment);
|
|
global_variable.addDebugInfo(debug_global_variable);
|
|
}
|
|
}
|
|
|
|
for (thread.functions.slice()) |*nat_function| {
|
|
const function = nat_function.declaration.global_symbol.value.llvm.?.toFunction() orelse unreachable;
|
|
const file_index = nat_function.declaration.global_symbol.global_declaration.declaration.scope.file;
|
|
var basic_block_command_buffer = BasicBlock.CommandList{};
|
|
var emit_allocas = true;
|
|
{
|
|
const nat_entry_basic_block = nat_function.entry_block;
|
|
assert(nat_entry_basic_block.predecessors.length == 0);
|
|
const entry_block_name = "entry";
|
|
const entry_block = thread.llvm.context.createBasicBlock(entry_block_name, entry_block_name.len, function, null);
|
|
nat_entry_basic_block.value.llvm = entry_block.toValue();
|
|
|
|
basic_block_command_buffer.append(&nat_entry_basic_block.command_node);
|
|
}
|
|
|
|
var phis = PinnedArray(*Phi){};
|
|
var llvm_phi_nodes = PinnedArray(*LLVM.Value.Instruction.PhiNode){};
|
|
|
|
while (basic_block_command_buffer.len != 0) {
|
|
const basic_block_node = basic_block_command_buffer.first orelse unreachable;
|
|
const basic_block: *BasicBlock = @fieldParentPtr("command_node", basic_block_node);
|
|
const llvm_basic_block = basic_block.get_llvm();
|
|
builder.setInsertPoint(llvm_basic_block);
|
|
|
|
var last_block = basic_block_node;
|
|
|
|
if (emit_allocas) {
|
|
for (nat_function.arguments.slice(), nat_function.declaration.get_function_type().abi.argument_types_abi) |argument, abi| {
|
|
_ = abi; // autofix
|
|
switch (argument.instruction.id) {
|
|
.argument_storage => {
|
|
const alloca_type = llvm_get_type(thread, argument.type);
|
|
argument.instruction.value.llvm = builder.createAlloca(alloca_type, address_space, null, "", "".len, argument.alignment).toValue();
|
|
},
|
|
.abi_indirect_argument => {
|
|
const llvm_argument = function.getArgument(argument.index);
|
|
argument.instruction.value.llvm = llvm_argument.toValue();
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
}
|
|
|
|
for (nat_function.stack_slots.slice()) |local_slot| {
|
|
const alloca_type = llvm_get_type(thread, local_slot.type);
|
|
local_slot.instruction.value.llvm = builder.createAlloca(alloca_type, address_space, null, "", "".len, local_slot.alignment).toValue();
|
|
}
|
|
|
|
emit_allocas = false;
|
|
}
|
|
|
|
for (basic_block.instructions.slice()) |instruction| {
|
|
if (thread.generate_debug_information) {
|
|
if (instruction.line != 0) {
|
|
const scope = llvm_get_scope(thread, instruction.scope);
|
|
builder.setCurrentDebugLocation(context, instruction.line, instruction.column, scope, function);
|
|
} else {
|
|
builder.clearCurrentDebugLocation();
|
|
}
|
|
}
|
|
|
|
const value: *LLVM.Value = switch (instruction.id) {
|
|
.abi_argument => block: {
|
|
const abi_argument = instruction.get_payload(.abi_argument);
|
|
const llvm_argument = function.getArgument(abi_argument.index);
|
|
break :block llvm_argument.toValue();
|
|
},
|
|
.debug_argument => block: {
|
|
assert(thread.generate_debug_information);
|
|
const debug_argument = instruction.get_payload(.debug_argument);
|
|
const argument_symbol = debug_argument.argument;
|
|
const name_hash = argument_symbol.argument_declaration.declaration.name;
|
|
assert(name_hash != 0);
|
|
const name = thread.identifiers.get(name_hash).?;
|
|
const file_struct = llvm_get_file(thread, file_index);
|
|
const scope = llvm_get_scope(thread, instruction.scope);
|
|
|
|
const debug_declaration_type = llvm_get_debug_type(thread, file_struct.builder, argument_symbol.type);
|
|
const always_preserve = true;
|
|
const flags = LLVM.DebugInfo.Node.Flags{
|
|
.visibility = .none,
|
|
.forward_declaration = false,
|
|
.apple_block = false,
|
|
.block_by_ref_struct = false,
|
|
.virtual = false,
|
|
.artificial = false,
|
|
.explicit = false,
|
|
.prototyped = false,
|
|
.objective_c_class_complete = false,
|
|
.object_pointer = false,
|
|
.vector = false,
|
|
.static_member = false,
|
|
.lvalue_reference = false,
|
|
.rvalue_reference = false,
|
|
.reserved = false,
|
|
.inheritance = .none,
|
|
.introduced_virtual = false,
|
|
.bit_field = false,
|
|
.no_return = false,
|
|
.type_pass_by_value = false,
|
|
.type_pass_by_reference = false,
|
|
.enum_class = false,
|
|
.thunk = false,
|
|
.non_trivial = false,
|
|
.big_endian = false,
|
|
.little_endian = false,
|
|
.all_calls_described = false,
|
|
};
|
|
const argument_index = argument_symbol.index + 1;
|
|
const line = argument_symbol.argument_declaration.declaration.line;
|
|
const column = argument_symbol.argument_declaration.declaration.column;
|
|
const debug_parameter_variable = file_struct.builder.createParameterVariable(scope, name.ptr, name.len, argument_index, file_struct.file, line, debug_declaration_type, always_preserve, flags);
|
|
const argument_alloca = argument_symbol.instruction.value.llvm.?;
|
|
|
|
const insert_declare = file_struct.builder.insertDeclare(argument_alloca, debug_parameter_variable, context, line, column, function.getSubprogram().toLocalScope().toScope(), builder.getInsertBlock());
|
|
break :block insert_declare.toValue();
|
|
},
|
|
.debug_local => block: {
|
|
assert(thread.generate_debug_information);
|
|
const file = llvm_get_file(thread, file_index);
|
|
const debug_local = instruction.get_payload(.debug_local);
|
|
const local_symbol = debug_local.local;
|
|
const debug_declaration_type = llvm_get_debug_type(thread, file.builder, local_symbol.type);
|
|
const always_preserve = true;
|
|
const flags = LLVM.DebugInfo.Node.Flags{
|
|
.visibility = .none,
|
|
.forward_declaration = false,
|
|
.apple_block = false,
|
|
.block_by_ref_struct = false,
|
|
.virtual = false,
|
|
.artificial = false,
|
|
.explicit = false,
|
|
.prototyped = false,
|
|
.objective_c_class_complete = false,
|
|
.object_pointer = false,
|
|
.vector = false,
|
|
.static_member = false,
|
|
.lvalue_reference = false,
|
|
.rvalue_reference = false,
|
|
.reserved = false,
|
|
.inheritance = .none,
|
|
.introduced_virtual = false,
|
|
.bit_field = false,
|
|
.no_return = false,
|
|
.type_pass_by_value = false,
|
|
.type_pass_by_reference = false,
|
|
.enum_class = false,
|
|
.thunk = false,
|
|
.non_trivial = false,
|
|
.big_endian = false,
|
|
.little_endian = false,
|
|
.all_calls_described = false,
|
|
};
|
|
|
|
const alignment = 0;
|
|
const declaration_name = thread.identifiers.get(local_symbol.local_declaration.declaration.name).?;
|
|
const line = local_symbol.local_declaration.declaration.line;
|
|
const column = local_symbol.local_declaration.declaration.column;
|
|
const scope = llvm_get_scope(thread, local_symbol.local_declaration.declaration.scope);
|
|
const debug_local_variable = file.builder.createAutoVariable(scope, declaration_name.ptr, declaration_name.len, file.file, line, debug_declaration_type, always_preserve, flags, alignment);
|
|
|
|
const insert_declare = file.builder.insertDeclare(local_symbol.instruction.value.llvm.?, debug_local_variable, context, line, column, (function.getSubprogram()).toLocalScope().toScope(), builder.getInsertBlock());
|
|
break :block insert_declare.toValue();
|
|
},
|
|
.store => block: {
|
|
const store = instruction.get_payload(.store);
|
|
const destination = llvm_get_value(thread, store.destination);
|
|
const source = llvm_get_value(thread, store.source);
|
|
const store_instruction = builder.createStore(source, destination, store.is_volatile, store.alignment);
|
|
builder.setInstructionDebugLocation(@ptrCast(store_instruction));
|
|
break :block store_instruction.toValue();
|
|
},
|
|
.load => block: {
|
|
const load = instruction.get_payload(.load);
|
|
const load_value = llvm_get_value(thread, load.value);
|
|
const load_type = llvm_get_type(thread, load.type);
|
|
// TODO: precise alignment
|
|
const load_instruction = builder.createLoad(load_type, load_value, load.is_volatile, "", "".len, load.alignment);
|
|
builder.setInstructionDebugLocation(@ptrCast(load_instruction));
|
|
break :block load_instruction.toValue();
|
|
},
|
|
.ret => block: {
|
|
const return_instruction = instruction.get_payload(.ret);
|
|
const return_value = llvm_get_value(thread, return_instruction.value);
|
|
const ret = builder.createRet(return_value);
|
|
break :block ret.toValue();
|
|
},
|
|
.ret_void => block: {
|
|
const ret = builder.createRet(null);
|
|
break :block ret.toValue();
|
|
},
|
|
.integer_binary_operation => block: {
|
|
const integer_binary_operation = instruction.get_payload(.integer_binary_operation);
|
|
const left = llvm_get_value(thread, integer_binary_operation.left);
|
|
const right = llvm_get_value(thread, integer_binary_operation.right);
|
|
const integer_type = integer_binary_operation.type.get_payload(.integer);
|
|
const no_unsigned_wrapping = integer_type.signedness == .unsigned;
|
|
const no_signed_wrapping = integer_type.signedness == .signed;
|
|
const name = "";
|
|
const is_exact = false;
|
|
break :block switch (integer_binary_operation.id) {
|
|
.add => builder.createAdd(left, right, name, name.len, no_unsigned_wrapping, no_signed_wrapping),
|
|
.sub => builder.createSub(left, right, name, name.len, no_unsigned_wrapping, no_signed_wrapping),
|
|
.mul => builder.createMultiply(left, right, name, name.len, no_unsigned_wrapping, no_signed_wrapping),
|
|
.udiv => builder.createUDiv(left, right, name, name.len, is_exact),
|
|
.sdiv => builder.createSDiv(left, right, name, name.len, is_exact),
|
|
.@"and" => builder.createAnd(left, right, name, name.len),
|
|
.@"or" => builder.createOr(left, right, name, name.len),
|
|
.@"xor" => builder.createXor(left, right, name, name.len),
|
|
.shift_left => builder.createShiftLeft(left, right, name, name.len, no_unsigned_wrapping, no_signed_wrapping),
|
|
.arithmetic_shift_right => builder.createArithmeticShiftRight(left, right, name, name.len, is_exact),
|
|
.logical_shift_right => builder.createLogicalShiftRight(left, right, name, name.len, is_exact),
|
|
};
|
|
},
|
|
.call => block: {
|
|
const call = instruction.get_payload(.call);
|
|
const nat_function_type = call.get_function_type();
|
|
const function_type = llvm_get_type(thread, &nat_function_type.type);
|
|
const callee = llvm_get_value(thread, call.callable);
|
|
|
|
var arguments = std.BoundedArray(*LLVM.Value, 512){};
|
|
for (call.arguments) |argument| {
|
|
const llvm_argument = llvm_get_value(thread, argument);
|
|
_ = arguments.appendAssumeCapacity(llvm_argument);
|
|
}
|
|
|
|
|
|
const args = arguments.constSlice();
|
|
const call_i = builder.createCall(function_type.toFunction() orelse unreachable, callee, args.ptr, args.len, "", "".len, null);
|
|
|
|
var parameter_attribute_sets = std.BoundedArray(*const LLVM.Attribute.Set, 512){};
|
|
const function_attributes = llvm_emit_function_attributes(thread, nat_function_type, ¶meter_attribute_sets);
|
|
call_i.setAttributes(thread.llvm.context, function_attributes.function_attributes, function_attributes.return_attribute_set, function_attributes.parameter_attribute_sets.ptr, function_attributes.parameter_attribute_sets.len);
|
|
|
|
const calling_convention = calling_convention_map.get(nat_function_type.abi.calling_convention);
|
|
call_i.setCallingConvention(calling_convention);
|
|
|
|
break :block call_i.toValue();
|
|
},
|
|
.integer_compare => block: {
|
|
const compare = instruction.get_payload(.integer_compare);
|
|
const type_a = compare.left.get_type();
|
|
const type_b = compare.right.get_type();
|
|
assert(type_a == type_b);
|
|
const left = llvm_get_value(thread, compare.left);
|
|
if (compare.id == .not_zero) {
|
|
const is_not_null = builder.createIsNotNull(left, "", "".len);
|
|
break :block is_not_null;
|
|
} else {
|
|
const right = llvm_get_value(thread, compare.right);
|
|
const name = "";
|
|
const comparison: LLVM.Value.Instruction.ICmp.Kind = switch (compare.id) {
|
|
.equal => .eq,
|
|
.not_equal => .ne,
|
|
.not_zero => unreachable,
|
|
.unsigned_greater_equal => .uge,
|
|
.unsigned_greater => .ugt,
|
|
.signed_greater_equal => .sge,
|
|
.signed_greater => .sgt,
|
|
.unsigned_less_equal => .ule,
|
|
.unsigned_less => .ult,
|
|
.signed_less_equal => .sle,
|
|
.signed_less => .slt,
|
|
};
|
|
|
|
const cmp = builder.createICmp(comparison, left, right, name, name.len);
|
|
break :block cmp;
|
|
}
|
|
},
|
|
.branch => block: {
|
|
const branch = instruction.get_payload(.branch);
|
|
basic_block_command_buffer.insertAfter(last_block, &branch.taken.command_node);
|
|
basic_block_command_buffer.insertAfter(&branch.taken.command_node, &branch.not_taken.command_node);
|
|
last_block = &branch.not_taken.command_node;
|
|
|
|
const taken = thread.llvm.context.createBasicBlock("", "".len, function, null);
|
|
branch.taken.value.llvm = taken.toValue();
|
|
assert(taken.toValue().toBasicBlock() != null);
|
|
const not_taken = thread.llvm.context.createBasicBlock("", "".len, function, null);
|
|
assert(not_taken.toValue().toBasicBlock() != null);
|
|
branch.not_taken.value.llvm = not_taken.toValue();
|
|
|
|
const condition = llvm_get_value(thread, branch.condition);
|
|
const branch_weights = null;
|
|
const unpredictable = null;
|
|
const br = builder.createConditionalBranch(condition, taken, not_taken, branch_weights, unpredictable);
|
|
break :block br.toValue();
|
|
},
|
|
.jump => block: {
|
|
const jump = instruction.get_payload(.jump);
|
|
const target_block = jump.basic_block;
|
|
assert(target_block.value.sema.thread == thread.get_index());
|
|
const llvm_target_block = if (target_block.value.llvm) |llvm| llvm.toBasicBlock() orelse unreachable else bb: {
|
|
const block = thread.llvm.context.createBasicBlock("", "".len, function, null);
|
|
assert(block.toValue().toBasicBlock() != null);
|
|
target_block.value.llvm = block.toValue();
|
|
basic_block_command_buffer.insertAfter(last_block, &target_block.command_node);
|
|
last_block = &target_block.command_node;
|
|
break :bb block;
|
|
};
|
|
const br = builder.createBranch(llvm_target_block);
|
|
break :block br.toValue();
|
|
},
|
|
.phi => block: {
|
|
const phi = instruction.get_payload(.phi);
|
|
const phi_type = llvm_get_type(thread, phi.type);
|
|
const reserved_value_count = phi.nodes.length;
|
|
const phi_node = builder.createPhi(phi_type, reserved_value_count, "", "".len);
|
|
_ = phis.append(phi);
|
|
_ = llvm_phi_nodes.append(phi_node);
|
|
break :block phi_node.toValue();
|
|
},
|
|
.@"unreachable" => block: {
|
|
const ur = builder.createUnreachable();
|
|
break :block ur.toValue();
|
|
},
|
|
.trap => block: {
|
|
const args: []const *LLVM.Value = &.{};
|
|
const intrinsic = thread.llvm.fixed_intrinsic_functions.get(.trap);
|
|
const call_i = builder.createCall(intrinsic.getType(), intrinsic.toValue(), args.ptr, args.len, "", "".len, null);
|
|
break :block call_i.toValue();
|
|
},
|
|
.leading_zeroes => block: {
|
|
const leading_zeroes = instruction.get_payload(.leading_zeroes);
|
|
const v = llvm_get_value(thread, leading_zeroes.value);
|
|
const v_type = v.getType();
|
|
const lz_id = thread.llvm.intrinsic_ids.get(.leading_zeroes);
|
|
const parameters = LLVMIntrinsic.Parameters{
|
|
.id = lz_id,
|
|
.types = &.{v_type},
|
|
};
|
|
const intrinsic_function = llvm_get_intrinsic_function(thread, parameters);
|
|
const intrinsic_function_type = intrinsic_function.getType();
|
|
const is_poison = context.getConstantInt(1, 0, false);
|
|
const args: []const *LLVM.Value = &.{v, is_poison.toValue()};
|
|
const call_i = builder.createCall(intrinsic_function_type, intrinsic_function.toValue(), args.ptr, args.len, "", "".len, null);
|
|
break :block call_i.toValue();
|
|
},
|
|
.trailing_zeroes => block: {
|
|
const trailing_zeroes = instruction.get_payload(.trailing_zeroes);
|
|
const v = llvm_get_value(thread, trailing_zeroes.value);
|
|
const v_type = v.getType();
|
|
const tz_id = thread.llvm.intrinsic_ids.get(.trailing_zeroes);
|
|
const parameters = LLVMIntrinsic.Parameters{
|
|
.id = tz_id,
|
|
.types = &.{v_type},
|
|
};
|
|
const intrinsic_function = llvm_get_intrinsic_function(thread, parameters);
|
|
const intrinsic_function_type = intrinsic_function.getType();
|
|
const is_poison = context.getConstantInt(1, 0, false);
|
|
const args: []const *LLVM.Value = &.{v, is_poison.toValue()};
|
|
const call_i = builder.createCall(intrinsic_function_type, intrinsic_function.toValue(), args.ptr, args.len, "", "".len, null);
|
|
break :block call_i.toValue();
|
|
},
|
|
.get_element_pointer => block: {
|
|
const gep = instruction.get_payload(.get_element_pointer);
|
|
const aggregate_type = llvm_get_type(thread, gep.aggregate_type);
|
|
const pointer = llvm_get_value(thread, gep.pointer);
|
|
const in_bounds = true;
|
|
const index = llvm_get_value(thread, gep.index);
|
|
const struct_index = context.getConstantInt(@bitSizeOf(u32), 0, false);
|
|
const index_buffer = [2]*LLVM.Value{ struct_index.toValue(), index };
|
|
const indices = index_buffer[@intFromBool(!gep.is_struct)..];
|
|
const get_element_pointer = builder.createGEP(aggregate_type, pointer, indices.ptr, indices.len, "".ptr, "".len, in_bounds);
|
|
break :block get_element_pointer;
|
|
},
|
|
.cast => block: {
|
|
const cast = instruction.get_payload(.cast);
|
|
const cast_value = llvm_get_value(thread, cast.value);
|
|
const v = switch (cast.id) {
|
|
.int_from_bitfield => cast_value,
|
|
.truncate, .int_from_pointer => |cast_id| b: {
|
|
const cast_type = llvm_get_type(thread, cast.type);
|
|
const cast_i = builder.createCast(switch (cast_id) {
|
|
.truncate => .truncate,
|
|
.int_from_pointer => .pointer_to_int,
|
|
else => |t| @panic(@tagName(t)),
|
|
}, cast_value, cast_type, "", "".len);
|
|
break :b cast_i;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
break :block v;
|
|
},
|
|
.memcpy => block: {
|
|
const memcpy = instruction.get_payload(.memcpy);
|
|
const destination = llvm_get_value(thread, memcpy.destination);
|
|
const source = llvm_get_value(thread, memcpy.source);
|
|
const memcopy = builder.createMemcpy(destination, memcpy.destination_alignment, source, memcpy.source_alignment, memcpy.size, memcpy.is_volatile);
|
|
break :block memcopy.toValue();
|
|
},
|
|
.extract_value => block: {
|
|
const extract_value = instruction.get_payload(.extract_value);
|
|
const aggregate = llvm_get_value(thread, extract_value.aggregate);
|
|
const indices = [1]c_uint{extract_value.index};
|
|
const i = builder.createExtractValue(aggregate, &indices, indices.len, "", "".len);
|
|
break :block i;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
|
|
instruction.value.llvm = value;
|
|
}
|
|
|
|
_ = basic_block_command_buffer.popFirst();
|
|
}
|
|
|
|
for (phis.const_slice(), llvm_phi_nodes.const_slice()) |phi, llvm_phi| {
|
|
for (phi.nodes.const_slice()) |phi_node| {
|
|
const llvm_value = llvm_get_value(thread, phi_node.value);
|
|
const llvm_basic_block = phi_node.basic_block.get_llvm();
|
|
llvm_phi.addIncoming(llvm_value, llvm_basic_block);
|
|
}
|
|
}
|
|
|
|
if (thread.generate_debug_information) {
|
|
const llvm_file = thread.debug_info_file_map.get_pointer(file_index).?;
|
|
const subprogram = function.getSubprogram();
|
|
llvm_file.builder.finalizeSubprogram(subprogram, function);
|
|
}
|
|
|
|
const verify_function = false;
|
|
if (verify_function) {
|
|
var message: []const u8 = undefined;
|
|
const verification_success = function.verify(&message.ptr, &message.len);
|
|
if (!verification_success) {
|
|
var function_msg: []const u8 = undefined;
|
|
function.toString(&function_msg.ptr, &function_msg.len);
|
|
write(function_msg);
|
|
write("\n");
|
|
fail_message(message);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (thread.generate_debug_information) {
|
|
for (thread.debug_info_file_map.values()) |v| {
|
|
v.builder.finalize();
|
|
}
|
|
}
|
|
|
|
const verify_module = true;
|
|
const print_module_at_failure = true;
|
|
const print_module = false;
|
|
|
|
if (verify_module) {
|
|
var verification_message: []const u8 = undefined;
|
|
const verification_success = thread.llvm.module.verify(&verification_message.ptr, &verification_message.len);
|
|
if (!verification_success) {
|
|
if (print_module_at_failure) {
|
|
var module_content: []const u8 = undefined;
|
|
thread.llvm.module.toString(&module_content.ptr, &module_content.len);
|
|
write(module_content);
|
|
write("\n");
|
|
}
|
|
|
|
fail_message(verification_message);
|
|
}
|
|
}
|
|
|
|
if (print_module) {
|
|
var module_content: []const u8 = undefined;
|
|
thread.llvm.module.toString(&module_content.ptr, &module_content.len);
|
|
write(module_content);
|
|
write("\n");
|
|
}
|
|
|
|
thread.add_control_work(.{
|
|
.id = .llvm_notify_ir_done,
|
|
});
|
|
}
|
|
},
|
|
.llvm_emit_object => {
|
|
const timestamp = std.time.nanoTimestamp();
|
|
const thread_object = std.fmt.allocPrint(std.heap.page_allocator, "nat/o/{s}_thread{}_{}.o", .{std.fs.path.basename(std.fs.path.dirname(instance.files.get(@enumFromInt(0)).path).?), thread.get_index(), timestamp}) catch unreachable;
|
|
thread.llvm.object = thread_object;
|
|
const disable_verify = false;
|
|
const result = thread.llvm.module.addPassesToEmitFile(thread.llvm.target_machine, thread_object.ptr, thread_object.len, LLVM.CodeGenFileType.object, disable_verify);
|
|
if (!result) {
|
|
@panic("can't generate machine code");
|
|
}
|
|
|
|
thread.add_control_work(.{
|
|
.id = .llvm_notify_object_done,
|
|
});
|
|
// std.debug.print("Thread #{} emitted object and notified\n", .{thread_index});
|
|
},
|
|
.compile_c_source_file => {
|
|
// TODO: FIXME
|
|
const unit = instance.units.get_unchecked(0);
|
|
const c_source_file_index = job.offset;
|
|
const source_file = unit.descriptor.c_source_files[c_source_file_index];
|
|
const object_path = unit.descriptor.c_object_files[c_source_file_index];
|
|
compile_c_source_files(thread, &.{ "-c", source_file, "-o", object_path, "-std=c99"});
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
|
|
thread.task_system.job.complete_job();
|
|
assert(thread.task_system.job.worker.completed == c + 1);
|
|
}
|
|
|
|
if (configuration.sleep_on_thread_hot_loops) {
|
|
std.time.sleep(1000);
|
|
} else {
|
|
std.atomic.spinLoopHint();
|
|
}
|
|
}
|
|
}
|
|
|
|
const CSourceFileCompilationInvoker = enum{
|
|
nat,
|
|
external,
|
|
};
|
|
|
|
fn compile_c_source_files(thread: *Thread, arguments: []const []const u8) void {
|
|
var argument_index: usize = 0;
|
|
_ = &argument_index;
|
|
const Mode = enum {
|
|
object,
|
|
link,
|
|
};
|
|
var out_path: ?[]const u8 = null;
|
|
var out_mode: ?Mode = null;
|
|
const Extension = enum {
|
|
c,
|
|
cpp,
|
|
assembly,
|
|
object,
|
|
static_library,
|
|
shared_library,
|
|
};
|
|
const CSourceFile = struct {
|
|
path: []const u8,
|
|
extension: Extension,
|
|
};
|
|
const DebugInfo = enum {
|
|
yes,
|
|
no,
|
|
};
|
|
const LinkArch = enum {
|
|
arm64,
|
|
};
|
|
var debug_info: ?DebugInfo = null;
|
|
var stack_protector: ?bool = null;
|
|
var link_arch: ?LinkArch = null;
|
|
|
|
var cc_argv = std.BoundedArray([]const u8, 4096){};
|
|
var ld_argv = std.BoundedArray([]const u8, 4096){};
|
|
var c_source_files = std.BoundedArray(CSourceFile, 4096){};
|
|
var link_objects = std.BoundedArray([]const u8, 4096){};
|
|
var link_libraries = std.BoundedArray([]const u8, 4096){};
|
|
|
|
while (argument_index < arguments.len) {
|
|
const argument = arguments[argument_index];
|
|
|
|
if (argument[0] != '-') {
|
|
if (last_byte(argument, '.')) |dot_index| {
|
|
const extension_string = argument[dot_index..];
|
|
const extension: Extension =
|
|
if (byte_equal(extension_string, ".c")) .c else if (byte_equal(extension_string, ".cpp") or byte_equal(extension_string, ".cxx") or byte_equal(extension_string, ".cc")) .cpp else if (byte_equal(extension_string, ".S")) .assembly else if (byte_equal(extension_string, ".o")) .object else if (byte_equal(extension_string, ".a")) .static_library else if (byte_equal(extension_string, ".so") or
|
|
byte_equal(extension_string, ".dll") or
|
|
byte_equal(extension_string, ".dylib") or
|
|
byte_equal(extension_string, ".tbd")) .shared_library else {
|
|
write(argument);
|
|
write("\n");
|
|
@panic("Unable to recognize extension for the file above");
|
|
};
|
|
switch (extension) {
|
|
.c, .cpp, .assembly => {
|
|
c_source_files.appendAssumeCapacity(.{
|
|
.path = argument,
|
|
.extension = extension,
|
|
});
|
|
},
|
|
.object, .static_library, .shared_library => {
|
|
link_objects.appendAssumeCapacity(argument);
|
|
},
|
|
}
|
|
} else {
|
|
write(argument);
|
|
write("\n");
|
|
@panic("Positional argument without extension");
|
|
}
|
|
} else if (byte_equal(argument, "-c")) {
|
|
out_mode = .object;
|
|
} else if (byte_equal(argument, "-o")) {
|
|
argument_index += 1;
|
|
out_path = arguments[argument_index];
|
|
} else if (byte_equal(argument, "-g")) {
|
|
debug_info = .yes;
|
|
} else if (byte_equal(argument, "-fno-stack-protector")) {
|
|
stack_protector = false;
|
|
} else if (byte_equal(argument, "-arch")) {
|
|
argument_index += 1;
|
|
const arch_argument = arguments[argument_index];
|
|
if (byte_equal(arch_argument, "arm64")) {
|
|
link_arch = .arm64;
|
|
cc_argv.appendAssumeCapacity("-arch");
|
|
cc_argv.appendAssumeCapacity("arm64");
|
|
} else {
|
|
unreachable;
|
|
}
|
|
} else if (byte_equal(argument, "-bundle")) {
|
|
ld_argv.appendAssumeCapacity(argument);
|
|
} else if (byte_equal(argument, "-pthread")) {
|
|
cc_argv.appendAssumeCapacity(argument);
|
|
} else if (byte_equal(argument, "-fPIC")) {
|
|
cc_argv.appendAssumeCapacity(argument);
|
|
} else if (byte_equal(argument, "-MD")) {
|
|
cc_argv.appendAssumeCapacity(argument);
|
|
} else if (byte_equal(argument, "-MT")) {
|
|
cc_argv.appendAssumeCapacity(argument);
|
|
argument_index += 1;
|
|
const arg = arguments[argument_index];
|
|
cc_argv.appendAssumeCapacity(arg);
|
|
} else if (byte_equal(argument, "-MF")) {
|
|
cc_argv.appendAssumeCapacity(argument);
|
|
argument_index += 1;
|
|
const arg = arguments[argument_index];
|
|
cc_argv.appendAssumeCapacity(arg);
|
|
} else if (byte_equal(argument, "-isysroot")) {
|
|
cc_argv.appendAssumeCapacity(argument);
|
|
argument_index += 1;
|
|
const arg = arguments[argument_index];
|
|
cc_argv.appendAssumeCapacity(arg);
|
|
} else if (byte_equal(argument, "-isystem")) {
|
|
cc_argv.appendAssumeCapacity(argument);
|
|
argument_index += 1;
|
|
const arg = arguments[argument_index];
|
|
cc_argv.appendAssumeCapacity(arg);
|
|
} else if (byte_equal(argument, "-h")) {
|
|
cc_argv.appendAssumeCapacity(argument);
|
|
} else if (byte_equal(argument, "-framework")) {
|
|
ld_argv.appendAssumeCapacity(argument);
|
|
argument_index += 1;
|
|
const framework = arguments[argument_index];
|
|
ld_argv.appendAssumeCapacity(framework);
|
|
} else if (byte_equal(argument, "--coverage")) {
|
|
cc_argv.appendAssumeCapacity(argument);
|
|
} else if (byte_equal(argument, "-pedantic")) {
|
|
cc_argv.appendAssumeCapacity(argument);
|
|
} else if (byte_equal(argument, "-pedantic-errors")) {
|
|
cc_argv.appendAssumeCapacity(argument);
|
|
} else if (byte_equal(argument, "-?")) {
|
|
cc_argv.appendAssumeCapacity(argument);
|
|
} else if (byte_equal(argument, "-v")) {
|
|
cc_argv.appendAssumeCapacity(argument);
|
|
} else if (byte_equal(argument, "-V")) {
|
|
cc_argv.appendAssumeCapacity(argument);
|
|
} else if (byte_equal(argument, "--version")) {
|
|
cc_argv.appendAssumeCapacity(argument);
|
|
} else if (byte_equal(argument, "-version")) {
|
|
cc_argv.appendAssumeCapacity(argument);
|
|
} else if (byte_equal(argument, "-qversion")) {
|
|
cc_argv.appendAssumeCapacity(argument);
|
|
} else if (byte_equal(argument, "-print-resource-dir")) {
|
|
cc_argv.appendAssumeCapacity(argument);
|
|
} else if (byte_equal(argument, "-shared")) {
|
|
ld_argv.appendAssumeCapacity(argument);
|
|
} else if (byte_equal(argument, "-compatibility_version")) {
|
|
ld_argv.appendAssumeCapacity(argument);
|
|
argument_index += 1;
|
|
const arg = arguments[argument_index];
|
|
ld_argv.appendAssumeCapacity(arg);
|
|
} else if (byte_equal(argument, "-current_version")) {
|
|
ld_argv.appendAssumeCapacity(argument);
|
|
argument_index += 1;
|
|
const arg = arguments[argument_index];
|
|
ld_argv.appendAssumeCapacity(arg);
|
|
} else if (byte_equal(argument, "-install_name")) {
|
|
ld_argv.appendAssumeCapacity(argument);
|
|
argument_index += 1;
|
|
const arg = arguments[argument_index];
|
|
ld_argv.appendAssumeCapacity(arg);
|
|
} else if (starts_with_slice(argument, "-f")) {
|
|
cc_argv.appendAssumeCapacity(argument);
|
|
} else if (starts_with_slice(argument, "-wd")) {
|
|
cc_argv.appendAssumeCapacity(argument);
|
|
} else if (starts_with_slice(argument, "-D")) {
|
|
cc_argv.appendAssumeCapacity(argument);
|
|
} else if (starts_with_slice(argument, "-I")) {
|
|
cc_argv.appendAssumeCapacity(argument);
|
|
} else if (starts_with_slice(argument, "-W")) {
|
|
cc_argv.appendAssumeCapacity(argument);
|
|
} else if (starts_with_slice(argument, "-l")) {
|
|
link_libraries.appendAssumeCapacity(argument[2..]);
|
|
} else if (starts_with_slice(argument, "-O")) {
|
|
cc_argv.appendAssumeCapacity(argument);
|
|
} else if (starts_with_slice(argument, "-std=")) {
|
|
cc_argv.appendAssumeCapacity(argument);
|
|
} else if (starts_with_slice(argument, "-rdynamic")) {
|
|
ld_argv.appendAssumeCapacity("-export_dynamic");
|
|
} else if (starts_with_slice(argument, "-dynamiclib")) {
|
|
ld_argv.appendAssumeCapacity("-dylib");
|
|
} else if (starts_with_slice(argument, "-Wl,")) {
|
|
const wl_arg = argument["-Wl,".len..];
|
|
if (first_byte(wl_arg, ',')) |comma_index| {
|
|
const key = wl_arg[0..comma_index];
|
|
const value = wl_arg[comma_index + 1 ..];
|
|
ld_argv.appendAssumeCapacity(key);
|
|
ld_argv.appendAssumeCapacity(value);
|
|
} else {
|
|
ld_argv.appendAssumeCapacity(wl_arg);
|
|
}
|
|
} else if (starts_with_slice(argument, "-m")) {
|
|
cc_argv.appendAssumeCapacity(argument);
|
|
} else {
|
|
fail_term("Unhandled argument", argument);
|
|
}
|
|
|
|
argument_index += 1;
|
|
}
|
|
|
|
const link_libcpp = true;
|
|
const mode = out_mode orelse .link;
|
|
|
|
var argv = std.BoundedArray([]const u8, 4096){};
|
|
if (c_source_files.len > 0) {
|
|
for (c_source_files.slice()) |c_source_file| {
|
|
argv.appendAssumeCapacity(instance.paths.executable);
|
|
argv.appendAssumeCapacity("clang");
|
|
argv.appendAssumeCapacity("--no-default-config");
|
|
|
|
argv.appendAssumeCapacity(c_source_file.path);
|
|
|
|
if (c_source_file.extension == .cpp) {
|
|
argv.appendAssumeCapacity("-nostdinc++");
|
|
}
|
|
|
|
const caret = true;
|
|
if (!caret) {
|
|
argv.appendAssumeCapacity("-fno-caret-diagnostics");
|
|
}
|
|
|
|
const function_sections = false;
|
|
if (function_sections) {
|
|
argv.appendAssumeCapacity("-ffunction-sections");
|
|
}
|
|
|
|
const data_sections = false;
|
|
if (data_sections) {
|
|
argv.appendAssumeCapacity("-fdata-sections");
|
|
}
|
|
|
|
const use_builtin = true;
|
|
if (!use_builtin) {
|
|
argv.appendAssumeCapacity("-fno-builtin");
|
|
}
|
|
|
|
if (link_libcpp) {
|
|
// include paths
|
|
|
|
}
|
|
|
|
const link_libc = c_source_file.extension == .c;
|
|
if (link_libc) {}
|
|
|
|
const link_libunwind = false;
|
|
if (link_libunwind) {
|
|
unreachable;
|
|
}
|
|
|
|
var target_triple_buffer = std.BoundedArray(u8, 512){};
|
|
const target_triple = blk: {
|
|
// Emit target
|
|
switch (@import("builtin").target.cpu.arch) {
|
|
.x86_64 => {
|
|
target_triple_buffer.appendSliceAssumeCapacity("x86_64-");
|
|
},
|
|
.aarch64 => {
|
|
target_triple_buffer.appendSliceAssumeCapacity("aarch64-");
|
|
},
|
|
else => @compileError("Architecture not supported"),
|
|
}
|
|
|
|
if (@import("builtin").target.cpu.arch == .aarch64 and @import("builtin").target.os.tag == .macos) {
|
|
target_triple_buffer.appendSliceAssumeCapacity("apple-");
|
|
} else {
|
|
target_triple_buffer.appendSliceAssumeCapacity("pc-");
|
|
}
|
|
|
|
switch (@import("builtin").target.os.tag) {
|
|
.linux => {
|
|
target_triple_buffer.appendSliceAssumeCapacity("linux-");
|
|
},
|
|
.macos => {
|
|
target_triple_buffer.appendSliceAssumeCapacity("macos-");
|
|
},
|
|
.windows => {
|
|
target_triple_buffer.appendSliceAssumeCapacity("windows-");
|
|
},
|
|
else => @compileError("OS not supported"),
|
|
}
|
|
|
|
switch (@import("builtin").target.abi) {
|
|
.musl => {
|
|
target_triple_buffer.appendSliceAssumeCapacity("musl");
|
|
},
|
|
.gnu => {
|
|
target_triple_buffer.appendSliceAssumeCapacity("gnu");
|
|
},
|
|
.none => {
|
|
target_triple_buffer.appendSliceAssumeCapacity("unknown");
|
|
},
|
|
else => @compileError("OS not supported"),
|
|
}
|
|
|
|
break :blk target_triple_buffer.slice();
|
|
};
|
|
argv.appendSliceAssumeCapacity(&.{ "-target", target_triple });
|
|
|
|
const object_path = switch (mode) {
|
|
.object => out_path.?,
|
|
.link => thread.arena.join(&.{ if (out_path) |op| op else "a.o", ".o" }) catch unreachable,
|
|
};
|
|
|
|
link_objects.appendAssumeCapacity(object_path);
|
|
|
|
switch (c_source_file.extension) {
|
|
.c, .cpp => {
|
|
argv.appendAssumeCapacity("-nostdinc");
|
|
argv.appendAssumeCapacity("-fno-spell-checking");
|
|
|
|
const lto = false;
|
|
if (lto) {
|
|
argv.appendAssumeCapacity("-flto");
|
|
}
|
|
|
|
const mm = false;
|
|
if (mm) {
|
|
argv.appendAssumeCapacity("-ObjC++");
|
|
}
|
|
|
|
const libc_framework_dirs: []const []const u8 = switch (@import("builtin").os.tag) {
|
|
.macos => &.{"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/System/Library/Frameworks"},
|
|
else => &.{},
|
|
};
|
|
for (libc_framework_dirs) |framework_dir| {
|
|
argv.appendSliceAssumeCapacity(&.{ "-iframework", framework_dir });
|
|
}
|
|
|
|
const framework_dirs = &[_][]const u8{};
|
|
for (framework_dirs) |framework_dir| {
|
|
argv.appendSliceAssumeCapacity(&.{ "-F", framework_dir });
|
|
}
|
|
|
|
// TODO: c headers dir
|
|
|
|
const libc_include_dirs: []const []const u8 = switch (@import("builtin").os.tag) {
|
|
.macos => &.{
|
|
"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/c++/v1",
|
|
"/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/lib/clang/15.0.0/include",
|
|
"/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include",
|
|
},
|
|
.linux => switch (@import("builtin").abi) {
|
|
.gnu => if (@import("configuration").ci) &.{
|
|
"/usr/include/c++/11",
|
|
"/usr/include/x86_64-linux-gnu/c++/11",
|
|
"/usr/lib/clang/17/include",
|
|
"/usr/include",
|
|
"/usr/include/x86_64-linux-gnu",
|
|
} else switch (@import("builtin").cpu.arch) {
|
|
.x86_64 => &.{
|
|
"/usr/include/c++/14",
|
|
"/usr/include/c++/14/x86_64-pc-linux-gnu",
|
|
"/usr/lib/clang/17/include",
|
|
"/usr/include",
|
|
"/usr/include/linux",
|
|
},
|
|
.aarch64 => &.{
|
|
"/usr/include/c++/14",
|
|
"/usr/include/c++/14/aarch64-redhat-linux",
|
|
"/usr/lib/clang/18/include",
|
|
"/usr/include",
|
|
"/usr/include/linux",
|
|
},
|
|
else => unreachable,
|
|
},
|
|
else => unreachable, //@compileError("ABI not supported"),
|
|
},
|
|
.windows => &.{},
|
|
else => @compileError("OS not supported"),
|
|
};
|
|
|
|
for (libc_include_dirs) |include_dir| {
|
|
argv.appendSliceAssumeCapacity(&.{ "-isystem", include_dir });
|
|
}
|
|
|
|
// TODO: cpu model
|
|
// TODO: cpu features
|
|
// TODO: code model
|
|
// TODO: OS-specific flags
|
|
// TODO: sanitize flags
|
|
// const red_zone = true;
|
|
// if (red_zone) {
|
|
// argv.appendAssumeCapacity("-mred-zone");
|
|
// } else {
|
|
// unreachable;
|
|
// }
|
|
|
|
const omit_frame_pointer = false;
|
|
if (omit_frame_pointer) {
|
|
argv.appendAssumeCapacity("-fomit-frame-pointer");
|
|
} else {
|
|
argv.appendAssumeCapacity("-fno-omit-frame-pointer");
|
|
}
|
|
|
|
if (stack_protector orelse false) {
|
|
argv.appendAssumeCapacity("-fstack-protector-strong");
|
|
} else {
|
|
argv.appendAssumeCapacity("-fno-stack-protector");
|
|
}
|
|
|
|
const is_debug = true;
|
|
if (is_debug) {
|
|
argv.appendAssumeCapacity("-D_DEBUG");
|
|
argv.appendAssumeCapacity("-O0");
|
|
} else {
|
|
unreachable;
|
|
}
|
|
|
|
const pic = false;
|
|
if (pic) {
|
|
argv.appendAssumeCapacity("-fPIC");
|
|
}
|
|
|
|
const unwind_tables = false;
|
|
if (unwind_tables) {
|
|
argv.appendAssumeCapacity("-funwind-tables");
|
|
} else {
|
|
argv.appendAssumeCapacity("-fno-unwind-tables");
|
|
}
|
|
},
|
|
.assembly => {
|
|
// TODO:
|
|
},
|
|
.object, .static_library, .shared_library => unreachable,
|
|
}
|
|
|
|
const has_debug_info = true;
|
|
if (has_debug_info) {
|
|
argv.appendAssumeCapacity("-g");
|
|
} else {
|
|
unreachable;
|
|
}
|
|
|
|
// TODO: machine ABI
|
|
const freestanding = false;
|
|
if (freestanding) {
|
|
argv.appendAssumeCapacity("-ffrestanding");
|
|
}
|
|
|
|
// TODO: native system include paths
|
|
// TODO: global cc argv
|
|
|
|
argv.appendSliceAssumeCapacity(cc_argv.slice());
|
|
|
|
// TODO: extra flags
|
|
// TODO: cache exempt flags
|
|
argv.appendSliceAssumeCapacity(&.{ "-c", "-o", object_path });
|
|
// TODO: emit ASM/LLVM IR
|
|
|
|
const debug_clang_args = false;
|
|
if (debug_clang_args) {
|
|
std.debug.print("Argv: {s}\n", .{argv.slice()});
|
|
}
|
|
|
|
clang_main(thread.arena, argv.slice());
|
|
}
|
|
} else if (link_objects.len == 0) {
|
|
unreachable;
|
|
// argv.appendAssumeCapacity(context.executable_absolute_path);
|
|
// argv.appendAssumeCapacity("clang");
|
|
// argv.appendAssumeCapacity("--no-default-config");
|
|
// argv.appendSliceAssumeCapacity(cc_argv.slice());
|
|
// const result = try clangMain(context.arena, argv.slice());
|
|
// if (result != 0) {
|
|
// unreachable;
|
|
// }
|
|
// return;
|
|
}
|
|
|
|
// if (mode == .link) {
|
|
// unreachable;
|
|
// // assert(link_objects.len > 0);
|
|
// // try linker.link(context, .{
|
|
// // .backend = .lld,
|
|
// // .output_file_path = out_path orelse "a.out",
|
|
// // .objects = link_objects.slice(),
|
|
// // .libraries = link_libraries.slice(),
|
|
// // .extra_arguments = ld_argv.slice(),
|
|
// // .link_libc = true,
|
|
// // .link_libcpp = link_libcpp,
|
|
// // });
|
|
// }
|
|
}
|
|
|
|
extern "c" fn nat_clang_main(argc: c_int, argv: [*:null]?[*:0]u8) c_int;
|
|
fn clang_main(arena: *Arena, arguments: []const []const u8) void {
|
|
const argv = library.argument_copy_zero_terminated(arena, arguments) catch unreachable;
|
|
const exit_code = nat_clang_main(@as(c_int, @intCast(arguments.len)), argv.ptr);
|
|
if (exit_code != 0) {
|
|
@breakpoint();
|
|
std.posix.exit(@intCast(exit_code));
|
|
}
|
|
}
|
|
|
|
fn llvm_emit_parameter_attributes(thread: *Thread, abi: Function.Abi.Information, is_return: bool) *const LLVM.Attribute.Set{
|
|
var attributes = std.BoundedArray(*LLVM.Attribute, 64){};
|
|
if (abi.attributes.zero_extend) {
|
|
attributes.appendAssumeCapacity(thread.llvm.attributes.zero_extend);
|
|
}
|
|
if (abi.attributes.sign_extend) {
|
|
attributes.appendAssumeCapacity(thread.llvm.attributes.sign_extend);
|
|
}
|
|
if (abi.attributes.by_reg) {
|
|
attributes.appendAssumeCapacity(thread.llvm.attributes.inreg);
|
|
}
|
|
|
|
switch (abi.kind) {
|
|
.ignore => {
|
|
assert(is_return);
|
|
},
|
|
.direct, .direct_pair, .direct_coerce => {},
|
|
.indirect => |indirect| {
|
|
const indirect_type = llvm_get_type(thread, indirect.type);
|
|
if (is_return) {
|
|
const sret = thread.llvm.context.getAttributeFromType(.StructRet, indirect_type);
|
|
attributes.appendAssumeCapacity(sret);
|
|
attributes.appendAssumeCapacity(thread.llvm.attributes.@"noalias");
|
|
// TODO: alignment
|
|
} else {
|
|
if (abi.attributes.by_value) {
|
|
const byval = thread.llvm.context.getAttributeFromType(.ByVal, indirect_type);
|
|
attributes.appendAssumeCapacity(byval);
|
|
}
|
|
//TODO: alignment
|
|
}
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
|
|
const attribute_set = thread.llvm.context.getAttributeSet(&attributes.buffer, attributes.len);
|
|
return attribute_set;
|
|
}
|
|
|
|
const LLVMFunctionAttributes = struct {
|
|
function_attributes: *const LLVM.Attribute.Set,
|
|
return_attribute_set: *const LLVM.Attribute.Set,
|
|
parameter_attribute_sets: []const *const LLVM.Attribute.Set,
|
|
};
|
|
|
|
fn llvm_emit_function_attributes(thread: *Thread, function_type: *Type.Function, parameter_attribute_sets: *std.BoundedArray(*const LLVM.Attribute.Set, 512)) LLVMFunctionAttributes {
|
|
const function_attributes = blk: {
|
|
var function_attributes = std.BoundedArray(*LLVM.Attribute, 256){};
|
|
function_attributes.appendAssumeCapacity(thread.llvm.attributes.nounwind);
|
|
|
|
switch (function_type.abi.original_return_type.sema.id) {
|
|
.noreturn => {
|
|
function_attributes.appendAssumeCapacity(thread.llvm.attributes.noreturn);
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
// const naked = false;
|
|
// if (naked) {
|
|
// function_attributes.appendAssumeCapacity(thread.llvm.attributes.naked);
|
|
// }
|
|
|
|
const function_attribute_set = thread.llvm.context.getAttributeSet(&function_attributes.buffer, function_attributes.len);
|
|
break :blk function_attribute_set;
|
|
};
|
|
|
|
const return_attribute_set = blk: {
|
|
const attribute_set = llvm_emit_parameter_attributes(thread, function_type.abi.return_type_abi, true);
|
|
break :blk switch (function_type.abi.return_type_abi.kind) {
|
|
.indirect => b: {
|
|
parameter_attribute_sets.appendAssumeCapacity(attribute_set);
|
|
break :b thread.llvm.context.getAttributeSet(null, 0);
|
|
},
|
|
else => attribute_set,
|
|
};
|
|
};
|
|
|
|
for (function_type.abi.argument_types_abi) |abi| {
|
|
const attribute_set = llvm_emit_parameter_attributes(thread, abi, false);
|
|
parameter_attribute_sets.appendAssumeCapacity(attribute_set);
|
|
}
|
|
|
|
return .{
|
|
.function_attributes = function_attributes,
|
|
.return_attribute_set = return_attribute_set,
|
|
.parameter_attribute_sets = parameter_attribute_sets.constSlice(),
|
|
};
|
|
}
|
|
|
|
fn llvm_get_intrinsic_id(intrinsic: []const u8) LLVM.Value.IntrinsicID{
|
|
const intrinsic_id = LLVM.lookupIntrinsic(intrinsic.ptr, intrinsic.len);
|
|
assert(intrinsic_id != .none);
|
|
return intrinsic_id;
|
|
}
|
|
|
|
fn llvm_get_intrinsic_function_from_map(thread: *Thread, parameters: LLVMIntrinsic.Parameters) *LLVM.Value.Constant.Function{
|
|
if (thread.llvm.intrinsic_function_map.get(parameters)) |llvm| return llvm else {
|
|
const intrinsic_function = llvm_get_intrinsic_function(thread, parameters);
|
|
thread.llvm.intrinsic_function_map.put_no_clobber(parameters, intrinsic_function);
|
|
return intrinsic_function;
|
|
}
|
|
}
|
|
|
|
fn llvm_get_intrinsic_function(thread: *Thread, parameters: LLVMIntrinsic.Parameters) *LLVM.Value.Constant.Function{
|
|
const intrinsic_function = thread.llvm.module.getIntrinsicDeclaration(parameters.id, parameters.types.ptr, parameters.types.len);
|
|
return intrinsic_function;
|
|
}
|
|
|
|
fn llvm_get_value(thread: *Thread, value: *Value) *LLVM.Value {
|
|
if (value.llvm) |llvm| {
|
|
assert(value.sema.thread == thread.get_index());
|
|
if (llvm.getContext() != thread.llvm.context) {
|
|
std.debug.print("Value was assigned to thread #{} ", .{thread.get_index()});
|
|
const thread_index = for (instance.threads, 0..) |*t, i| {
|
|
if (t.functions.length > 0) {
|
|
if (t.llvm.context == llvm.getContext()) {
|
|
break i;
|
|
}
|
|
}
|
|
} else unreachable;
|
|
std.debug.print("but context from which it was generated belongs to thread #{}\n", .{thread_index});
|
|
@panic("internal error");
|
|
}
|
|
return llvm;
|
|
} else {
|
|
const value_id = value.sema.id;
|
|
const llvm_value: *LLVM.Value = switch (value_id) {
|
|
.constant_int => b: {
|
|
const constant_int = value.get_payload(.constant_int);
|
|
const integer_type = switch (constant_int.type.sema.id) {
|
|
.integer => i: {
|
|
const integer_type = constant_int.type.get_payload(.integer);
|
|
break :i integer_type;
|
|
},
|
|
.bitfield => bf: {
|
|
const bitfield_type = constant_int.type.get_payload(.bitfield);
|
|
const integer_type = bitfield_type.backing_type.get_payload(.integer);
|
|
break :bf integer_type;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
const result = thread.llvm.context.getConstantInt(@intCast(integer_type.type.bit_size), constant_int.n, @intFromEnum(integer_type.signedness) != 0);
|
|
break :b result.toValue();
|
|
},
|
|
.constant_array => b: {
|
|
const constant_array = value.get_payload(.constant_array);
|
|
const array_type = llvm_get_type(thread, constant_array.type);
|
|
var values = PinnedArray(*LLVM.Value.Constant){};
|
|
for (constant_array.values) |v| {
|
|
const val = llvm_get_value(thread, v);
|
|
_ = values.append(val.toConstant() orelse unreachable);
|
|
}
|
|
const result = array_type.toArray().?.getConstant(values.pointer, values.length);
|
|
break :b result.toValue();
|
|
},
|
|
.constant_struct => b: {
|
|
const constant_struct = value.get_payload(.constant_struct);
|
|
const struct_type = llvm_get_type(thread, constant_struct.type);
|
|
var values = PinnedArray(*LLVM.Value.Constant){};
|
|
for (constant_struct.values) |v| {
|
|
const val = llvm_get_value(thread, v);
|
|
_ = values.append(val.toConstant() orelse unreachable);
|
|
}
|
|
const result = struct_type.toStruct().?.getConstant(values.pointer, values.length);
|
|
break :b result.toValue();
|
|
},
|
|
.constant_bitfield => b: {
|
|
const constant_bitfield = value.get_payload(.constant_bitfield);
|
|
const bitfield_type = constant_bitfield.type.get_payload(.bitfield);
|
|
const bitfield_backing_type = bitfield_type.backing_type.get_payload(.integer);
|
|
const result = thread.llvm.context.getConstantInt(@intCast(bitfield_backing_type.type.bit_size), constant_bitfield.n, @intFromEnum(bitfield_backing_type.signedness) != 0);
|
|
break :b result.toValue();
|
|
},
|
|
.undefined => b: {
|
|
const undef = value.get_payload(.undefined);
|
|
const ty = llvm_get_type(thread, undef.type);
|
|
const poison = ty.getPoison();
|
|
break :b poison.toValue();
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
|
|
value.llvm = llvm_value;
|
|
|
|
return llvm_value;
|
|
}
|
|
}
|
|
|
|
fn llvm_get_debug_type(thread: *Thread, builder: *LLVM.DebugInfo.Builder, ty: *Type) *LLVM.DebugInfo.Type {
|
|
if (ty.llvm_debug) |llvm| return llvm else {
|
|
const llvm_debug_type = switch (ty.sema.id) {
|
|
.integer => block: {
|
|
const integer = ty.get_payload(.integer);
|
|
const dwarf_encoding: LLVM.DebugInfo.AttributeType = switch (integer.signedness) {
|
|
.unsigned => .unsigned,
|
|
.signed => .signed,
|
|
};
|
|
const flags = LLVM.DebugInfo.Node.Flags{
|
|
.visibility = .none,
|
|
.forward_declaration = false,
|
|
.apple_block = false,
|
|
.block_by_ref_struct = false,
|
|
.virtual = false,
|
|
.artificial = false,
|
|
.explicit = false,
|
|
.prototyped = false,
|
|
.objective_c_class_complete = false,
|
|
.object_pointer = false,
|
|
.vector = false,
|
|
.static_member = false,
|
|
.lvalue_reference = false,
|
|
.rvalue_reference = false,
|
|
.reserved = false,
|
|
.inheritance = .none,
|
|
.introduced_virtual = false,
|
|
.bit_field = false,
|
|
.no_return = false,
|
|
.type_pass_by_value = false,
|
|
.type_pass_by_reference = false,
|
|
.enum_class = false,
|
|
.thunk = false,
|
|
.non_trivial = false,
|
|
.big_endian = false,
|
|
.little_endian = false,
|
|
.all_calls_described = false,
|
|
};
|
|
var buffer: [65]u8 = undefined;
|
|
const format = library.format_int(&buffer, integer.type.bit_size, 10, false);
|
|
const slice_ptr = format.ptr - 1;
|
|
const slice = slice_ptr[0 .. format.len + 1];
|
|
slice[0] = switch (integer.signedness) {
|
|
.signed => 's',
|
|
.unsigned => 'u',
|
|
};
|
|
|
|
const name = thread.arena.duplicate_bytes(slice) catch unreachable;
|
|
const integer_type = builder.createBasicType(name.ptr, name.len, integer.type.bit_size, dwarf_encoding, flags);
|
|
break :block integer_type;
|
|
},
|
|
.array => block: {
|
|
const array = ty.get_payload(.array);
|
|
const bitsize = array.type.size * 8;
|
|
const element_type = llvm_get_debug_type(thread, builder, array.descriptor.element_type);
|
|
const array_type = builder.createArrayType(bitsize, array.type.alignment * 8, element_type, array.descriptor.element_count);
|
|
break :block array_type.toType();
|
|
},
|
|
.typed_pointer => block: {
|
|
const typed_pointer = ty.get_payload(.typed_pointer);
|
|
const element_type = llvm_get_debug_type(thread, builder, typed_pointer.descriptor.pointee);
|
|
const alignment = 3;
|
|
const pointer_width = @bitSizeOf(usize);
|
|
// TODO:
|
|
const pointer_type = builder.createPointerType(element_type, pointer_width, alignment, "ptr", "ptr".len);
|
|
break :block pointer_type.toType();
|
|
},
|
|
.@"struct" => block: {
|
|
const nat_struct_type = ty.get_payload(.@"struct");
|
|
const file_struct = llvm_get_file(thread, nat_struct_type.declaration.scope.file);
|
|
const flags = LLVM.DebugInfo.Node.Flags{
|
|
.visibility = .none,
|
|
.forward_declaration = false,
|
|
.apple_block = false,
|
|
.block_by_ref_struct = false,
|
|
.virtual = false,
|
|
.artificial = false,
|
|
.explicit = false,
|
|
.prototyped = false,
|
|
.objective_c_class_complete = false,
|
|
.object_pointer = false,
|
|
.vector = false,
|
|
.static_member = false,
|
|
.lvalue_reference = false,
|
|
.rvalue_reference = false,
|
|
.reserved = false,
|
|
.inheritance = .none,
|
|
.introduced_virtual = false,
|
|
.bit_field = false,
|
|
.no_return = false,
|
|
.type_pass_by_value = false,
|
|
.type_pass_by_reference = false,
|
|
.enum_class = false,
|
|
.thunk = false,
|
|
.non_trivial = false,
|
|
.big_endian = false,
|
|
.little_endian = false,
|
|
.all_calls_described = false,
|
|
};
|
|
const file = file_struct.file;
|
|
const name = thread.identifiers.get(nat_struct_type.declaration.name).?;
|
|
const line = nat_struct_type.declaration.line;
|
|
|
|
const bitsize = nat_struct_type.type.size * 8;
|
|
const alignment = nat_struct_type.type.alignment;
|
|
var member_types = PinnedArray(*LLVM.DebugInfo.Type){};
|
|
|
|
const struct_type = builder.createStructType(file.toScope(), name.ptr, name.len, file, line, bitsize, alignment, flags, null, member_types.pointer, member_types.length, null);
|
|
ty.llvm_debug = struct_type.toType();
|
|
|
|
for (nat_struct_type.fields) |field| {
|
|
const field_type = llvm_get_debug_type(thread, builder, field.type);
|
|
const field_name = thread.identifiers.get(field.name).?;
|
|
const field_bitsize = field.type.size * 8;
|
|
const field_alignment = field.type.alignment * 8;
|
|
const field_offset = field.member_offset * 8;
|
|
const member_type = builder.createMemberType(file.toScope(), field_name.ptr, field_name.len, file, field.line, field_bitsize, field_alignment, field_offset, flags, field_type);
|
|
_ = member_types.append(member_type.toType());
|
|
}
|
|
|
|
builder.replaceCompositeTypes(struct_type, member_types.pointer, member_types.length);
|
|
break :block struct_type.toType();
|
|
},
|
|
.bitfield => block: {
|
|
const nat_bitfield_type = ty.get_payload(.bitfield);
|
|
const file_struct = llvm_get_file(thread, nat_bitfield_type.declaration.scope.file);
|
|
const flags = LLVM.DebugInfo.Node.Flags{
|
|
.visibility = .none,
|
|
.forward_declaration = false,
|
|
.apple_block = false,
|
|
.block_by_ref_struct = false,
|
|
.virtual = false,
|
|
.artificial = false,
|
|
.explicit = false,
|
|
.prototyped = false,
|
|
.objective_c_class_complete = false,
|
|
.object_pointer = false,
|
|
.vector = false,
|
|
.static_member = false,
|
|
.lvalue_reference = false,
|
|
.rvalue_reference = false,
|
|
.reserved = false,
|
|
.inheritance = .none,
|
|
.introduced_virtual = false,
|
|
.bit_field = false,
|
|
.no_return = false,
|
|
.type_pass_by_value = false,
|
|
.type_pass_by_reference = false,
|
|
.enum_class = false,
|
|
.thunk = false,
|
|
.non_trivial = false,
|
|
.big_endian = false,
|
|
.little_endian = false,
|
|
.all_calls_described = false,
|
|
};
|
|
const file = file_struct.file;
|
|
const name = thread.identifiers.get(nat_bitfield_type.declaration.name).?;
|
|
const line = nat_bitfield_type.declaration.line;
|
|
|
|
const bitsize = nat_bitfield_type.type.size * 8;
|
|
const alignment = nat_bitfield_type.type.alignment;
|
|
var member_types = PinnedArray(*LLVM.DebugInfo.Type){};
|
|
|
|
const struct_type = builder.createStructType(file.toScope(), name.ptr, name.len, file, line, bitsize, alignment, flags, null, member_types.pointer, member_types.length, null);
|
|
ty.llvm_debug = struct_type.toType();
|
|
|
|
const nat_backing_type = &thread.integers[nat_bitfield_type.type.bit_size - 1];
|
|
const backing_type = llvm_get_debug_type(thread, builder, &nat_backing_type.type);
|
|
|
|
for (nat_bitfield_type.fields) |field| {
|
|
const field_name = thread.identifiers.get(field.name).?;
|
|
const field_bitsize = field.type.bit_size;
|
|
const field_offset = field.member_offset;
|
|
const member_flags = LLVM.DebugInfo.Node.Flags{
|
|
.visibility = .none,
|
|
.forward_declaration = false,
|
|
.apple_block = false,
|
|
.block_by_ref_struct = false,
|
|
.virtual = false,
|
|
.artificial = false,
|
|
.explicit = false,
|
|
.prototyped = false,
|
|
.objective_c_class_complete = false,
|
|
.object_pointer = false,
|
|
.vector = false,
|
|
.static_member = false,
|
|
.lvalue_reference = false,
|
|
.rvalue_reference = false,
|
|
.reserved = false,
|
|
.inheritance = .none,
|
|
.introduced_virtual = false,
|
|
.bit_field = true,
|
|
.no_return = false,
|
|
.type_pass_by_value = false,
|
|
.type_pass_by_reference = false,
|
|
.enum_class = false,
|
|
.thunk = false,
|
|
.non_trivial = false,
|
|
.big_endian = false,
|
|
.little_endian = false,
|
|
.all_calls_described = false,
|
|
};
|
|
const member_type = builder.createBitfieldMemberType(file.toScope(), field_name.ptr, field_name.len, file, field.line, field_bitsize, field_offset, 0, member_flags, backing_type);
|
|
_ = member_types.append(member_type.toType());
|
|
}
|
|
|
|
builder.replaceCompositeTypes(struct_type, member_types.pointer, member_types.length);
|
|
break :block struct_type.toType();
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
|
|
ty.llvm_debug = llvm_debug_type;
|
|
|
|
return llvm_debug_type;
|
|
}
|
|
}
|
|
|
|
fn llvm_get_type(thread: *Thread, ty: *Type) *LLVM.Type {
|
|
if (ty.llvm) |llvm| {
|
|
assert(ty.sema.thread == thread.get_index());
|
|
assert(llvm.getContext() == thread.llvm.context);
|
|
return llvm;
|
|
} else {
|
|
const llvm_type: *LLVM.Type = switch (ty.sema.id) {
|
|
.void => b: {
|
|
const void_type = thread.llvm.context.getVoidType();
|
|
break :b void_type;
|
|
},
|
|
.integer => b: {
|
|
const integer_type = thread.llvm.context.getIntegerType(@intCast(ty.bit_size));
|
|
break :b integer_type.toType();
|
|
},
|
|
.array => b: {
|
|
const array_ty = ty.get_payload(.array);
|
|
const element_type = llvm_get_type(thread, array_ty.descriptor.element_type);
|
|
const array_type = LLVM.Type.Array.get(element_type, array_ty.descriptor.element_count);
|
|
break :b array_type.toType();
|
|
},
|
|
.opaque_pointer, .typed_pointer => b: {
|
|
const pointer_type = thread.llvm.context.getPointerType(address_space);
|
|
break :b pointer_type.toType();
|
|
},
|
|
.function => b: {
|
|
const nat_function_type = ty.get_payload(.function);
|
|
const return_type = llvm_get_type(thread, nat_function_type.abi.abi_return_type);
|
|
var argument_types = PinnedArray(*LLVM.Type){};
|
|
_ = &argument_types;
|
|
for (nat_function_type.abi.abi_argument_types) |argument_type| {
|
|
const llvm_arg_type = llvm_get_type(thread, argument_type);
|
|
_ = argument_types.append(llvm_arg_type);
|
|
}
|
|
const is_var_args = false;
|
|
const function_type = LLVM.getFunctionType(return_type, argument_types.pointer, argument_types.length, is_var_args);
|
|
break :b function_type.toType();
|
|
},
|
|
.@"struct" => b: {
|
|
const nat_struct_type = ty.get_payload(.@"struct");
|
|
var struct_types = PinnedArray(*LLVM.Type){};
|
|
for (nat_struct_type.fields) |field| {
|
|
const field_type = llvm_get_type(thread, field.type);
|
|
_ = struct_types.append(field_type);
|
|
}
|
|
|
|
const types = struct_types.const_slice();
|
|
const is_packed = false;
|
|
const name = thread.identifiers.get(nat_struct_type.declaration.name).?;
|
|
const struct_type = thread.llvm.context.createStructType(types.ptr, types.len, name.ptr, name.len, is_packed);
|
|
break :b struct_type.toType();
|
|
},
|
|
.anonymous_struct => b: {
|
|
const nat_struct_type = ty.get_payload(.anonymous_struct);
|
|
var struct_types = PinnedArray(*LLVM.Type){};
|
|
for (nat_struct_type.fields) |field| {
|
|
const field_type = llvm_get_type(thread, field.type);
|
|
_ = struct_types.append(field_type);
|
|
}
|
|
|
|
const types = struct_types.const_slice();
|
|
const is_packed = false;
|
|
const struct_type = thread.llvm.context.getStructType(types.ptr, types.len, is_packed);
|
|
break :b struct_type.toType();
|
|
},
|
|
.bitfield => b: {
|
|
const nat_bitfield_type = ty.get_payload(.bitfield);
|
|
const backing_type = llvm_get_type(thread, nat_bitfield_type.backing_type);
|
|
break :b backing_type;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
|
|
ty.llvm = llvm_type;
|
|
|
|
return llvm_type;
|
|
}
|
|
}
|
|
|
|
fn llvm_get_file(thread: *Thread, file_index: u32) *LLVMFile {
|
|
assert(thread.generate_debug_information);
|
|
if (thread.debug_info_file_map.get_pointer(file_index)) |llvm| return llvm else {
|
|
const builder = thread.llvm.module.createDebugInfoBuilder();
|
|
const file = &instance.files.slice()[file_index];
|
|
const filename = std.fs.path.basename(file.path);
|
|
const directory = file.path[0..file.path.len - filename.len];
|
|
const llvm_file = builder.createFile(filename.ptr, filename.len, directory.ptr, directory.len);
|
|
const producer = "nativity";
|
|
const is_optimized = false;
|
|
const flags = "";
|
|
const runtime_version = 0;
|
|
const splitname = "";
|
|
const DWOId = 0;
|
|
const debug_info_kind = LLVM.DebugInfo.CompileUnit.EmissionKind.full_debug;
|
|
const split_debug_inlining = true;
|
|
const debug_info_for_profiling = false;
|
|
const name_table_kind = LLVM.DebugInfo.CompileUnit.NameTableKind.default;
|
|
const ranges_base_address = false;
|
|
const sysroot = "";
|
|
const sdk = "";
|
|
const compile_unit = builder.createCompileUnit(LLVM.DebugInfo.Language.c11, llvm_file, producer, producer.len, is_optimized, flags, flags.len, runtime_version, splitname, splitname.len, debug_info_kind, DWOId, split_debug_inlining, debug_info_for_profiling, name_table_kind, ranges_base_address, sysroot, sysroot.len, sdk, sdk.len);
|
|
|
|
thread.debug_info_file_map.put_no_clobber(file_index, .{
|
|
.file = llvm_file,
|
|
.compile_unit = compile_unit,
|
|
.builder = builder,
|
|
});
|
|
|
|
return thread.debug_info_file_map.get_pointer(file_index).?;
|
|
}
|
|
}
|
|
|
|
fn llvm_get_scope(thread: *Thread, scope: *Scope) *LLVM.DebugInfo.Scope {
|
|
assert(scope.id != .file);
|
|
|
|
if (scope.llvm) |llvm| {
|
|
return llvm;
|
|
} else {
|
|
const llvm_scope = switch (scope.id) {
|
|
.local => block: {
|
|
const local_block: *LocalBlock = @fieldParentPtr("scope", scope);
|
|
const file = llvm_get_file(thread, local_block.scope.file);
|
|
const parent_scope = llvm_get_scope(thread, scope.parent.?);
|
|
const lexical_block = file.builder.createLexicalBlock(parent_scope, file.file, scope.line, scope.column);
|
|
break :block lexical_block.toScope();
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
scope.llvm = llvm_scope;
|
|
return llvm_scope;
|
|
}
|
|
}
|
|
|
|
const calling_convention_map = std.EnumArray(CallingConvention, LLVM.Value.Constant.Function.CallingConvention).init(.{
|
|
.c = .C,
|
|
.custom = .Fast,
|
|
});
|
|
|
|
fn llvm_emit_function_declaration(thread: *Thread, nat_function: *Function.Declaration) void {
|
|
assert(nat_function.global_symbol.value.llvm == null);
|
|
const function_name = thread.identifiers.get(nat_function.global_symbol.global_declaration.declaration.name) orelse unreachable;
|
|
const nat_function_type = nat_function.get_function_type();
|
|
const function_type = llvm_get_type(thread, &nat_function_type.type);
|
|
const is_extern_function = nat_function.global_symbol.attributes.@"extern";
|
|
const export_or_extern = nat_function.global_symbol.attributes.@"export" or is_extern_function;
|
|
const linkage: LLVM.Linkage = switch (export_or_extern) {
|
|
true => .@"extern",
|
|
false => .internal,
|
|
};
|
|
const function = thread.llvm.module.createFunction(function_type.toFunction() orelse unreachable, linkage, address_space, function_name.ptr, function_name.len);
|
|
|
|
var parameter_attribute_sets = std.BoundedArray(*const LLVM.Attribute.Set, 512){};
|
|
const function_attributes = llvm_emit_function_attributes(thread, nat_function_type, ¶meter_attribute_sets);
|
|
function.setAttributes(thread.llvm.context, function_attributes.function_attributes, function_attributes.return_attribute_set, function_attributes.parameter_attribute_sets.ptr, function_attributes.parameter_attribute_sets.len);
|
|
|
|
const calling_convention = calling_convention_map.get(nat_function_type.abi.calling_convention);
|
|
function.setCallingConvention(calling_convention);
|
|
|
|
if (thread.generate_debug_information) {
|
|
const file_index = nat_function.global_symbol.global_declaration.declaration.scope.file;
|
|
const llvm_file = llvm_get_file(thread, file_index);
|
|
// TODO: emit original arguments
|
|
var debug_argument_types = PinnedArray(*LLVM.DebugInfo.Type){};
|
|
for (nat_function.get_function_type().abi.original_argument_types) |argument_type| {
|
|
const arg_type = llvm_get_debug_type(thread, llvm_file.builder, argument_type);
|
|
_ = debug_argument_types.append(arg_type);
|
|
}
|
|
|
|
const subroutine_type_flags = LLVM.DebugInfo.Node.Flags{
|
|
.visibility = .none,
|
|
.forward_declaration = is_extern_function,
|
|
.apple_block = false,
|
|
.block_by_ref_struct = false,
|
|
.virtual = false,
|
|
.artificial = false,
|
|
.explicit = false,
|
|
.prototyped = false,
|
|
.objective_c_class_complete = false,
|
|
.object_pointer = false,
|
|
.vector = false,
|
|
.static_member = false,
|
|
.lvalue_reference = false,
|
|
.rvalue_reference = false,
|
|
.reserved = false,
|
|
.inheritance = .none,
|
|
.introduced_virtual = false,
|
|
.bit_field = false,
|
|
.no_return = false,
|
|
.type_pass_by_value = false,
|
|
.type_pass_by_reference = false,
|
|
.enum_class = false,
|
|
.thunk = false,
|
|
.non_trivial = false,
|
|
.big_endian = false,
|
|
.little_endian = false,
|
|
.all_calls_described = false,
|
|
};
|
|
const subroutine_type_calling_convention = LLVM.DebugInfo.CallingConvention.none;
|
|
const subroutine_type = llvm_file.builder.createSubroutineType(debug_argument_types.pointer, debug_argument_types.length, subroutine_type_flags, subroutine_type_calling_convention);
|
|
const subprogram_flags = LLVM.DebugInfo.Subprogram.Flags{
|
|
.virtuality = .none,
|
|
.local_to_unit = !export_or_extern,
|
|
.definition = !is_extern_function,
|
|
.optimized = false,
|
|
.pure = false,
|
|
.elemental = false,
|
|
.recursive = false,
|
|
.main_subprogram = false,
|
|
.deleted = false,
|
|
.object_c_direct = false,
|
|
};
|
|
const subprogram_declaration = null;
|
|
const file = llvm_file.file;
|
|
const scope = file.toScope();
|
|
const line = nat_function.global_symbol.global_declaration.declaration.line;
|
|
const scope_line = line + 1;
|
|
|
|
const subprogram = llvm_file.builder.createFunction(scope, function_name.ptr, function_name.len, function_name.ptr, function_name.len, file, line, subroutine_type, scope_line, subroutine_type_flags, subprogram_flags, subprogram_declaration);
|
|
nat_function.global_symbol.type.llvm_debug = subroutine_type.toType();
|
|
function.setSubprogram(subprogram);
|
|
if (nat_function.global_symbol.id == .function_definition) {
|
|
const function_definition = nat_function.global_symbol.get_payload(.function_definition);
|
|
function_definition.scope.scope.llvm = subprogram.toLocalScope().toScope();
|
|
}
|
|
}
|
|
|
|
nat_function.global_symbol.value.llvm = function.toValue();
|
|
}
|
|
|
|
fn create_constant_int(thread: *Thread, args: struct {
|
|
n: u64,
|
|
type: *Type,
|
|
resolved: bool = true,
|
|
}) *ConstantInt {
|
|
const constant_int = thread.constant_ints.append(.{
|
|
.value = .{
|
|
.sema = .{
|
|
.thread = thread.get_index(),
|
|
.resolved = args.resolved,
|
|
.id = .constant_int,
|
|
},
|
|
},
|
|
.n = args.n,
|
|
.type = args.type,
|
|
});
|
|
return constant_int;
|
|
}
|
|
|
|
fn create_basic_block(thread: *Thread) *BasicBlock {
|
|
const block = thread.basic_blocks.append(.{
|
|
.value = .{
|
|
.sema = .{
|
|
.thread = thread.get_index(),
|
|
.resolved = true,
|
|
.id = .basic_block,
|
|
},
|
|
},
|
|
});
|
|
return block;
|
|
}
|
|
|
|
fn emit_gep(thread: *Thread, analyzer: *Analyzer, args: struct{
|
|
pointer: *Value,
|
|
index: *Value,
|
|
type: *Type,
|
|
aggregate_type: *Type,
|
|
is_struct: bool,
|
|
line: u32,
|
|
column: u32,
|
|
scope: *Scope,
|
|
}) *GEP{
|
|
const gep = thread.geps.append(.{
|
|
.instruction = new_instruction(thread, .{
|
|
.scope = args.scope,
|
|
.line = args.line,
|
|
.column = args.column,
|
|
.id = .get_element_pointer,
|
|
}),
|
|
.pointer = args.pointer,
|
|
.index = args.index,
|
|
.type = args.type,
|
|
.aggregate_type = args.aggregate_type,
|
|
.is_struct = args.is_struct,
|
|
});
|
|
analyzer.append_instruction(&gep.instruction);
|
|
return gep;
|
|
}
|
|
|
|
fn emit_extract_value(thread: *Thread, analyzer: *Analyzer, args: struct{
|
|
aggregate: *Value,
|
|
index: u32,
|
|
type: *Type,
|
|
line: u32,
|
|
column: u32,
|
|
scope: *Scope,
|
|
}) *ExtractValue{
|
|
const extract_value = thread.extract_values.append(.{
|
|
.instruction = new_instruction(thread, .{
|
|
.scope = args.scope,
|
|
.line = args.line,
|
|
.column = args.column,
|
|
.id = .extract_value,
|
|
}),
|
|
.aggregate = args.aggregate,
|
|
.index = args.index,
|
|
.type = args.type,
|
|
});
|
|
analyzer.append_instruction(&extract_value.instruction);
|
|
return extract_value;
|
|
}
|
|
|
|
fn emit_insert_value(thread: *Thread, analyzer: *Analyzer, args: struct{
|
|
aggregate: *Value,
|
|
value: *Value,
|
|
index: u32,
|
|
type: *Type,
|
|
line: u32,
|
|
column: u32,
|
|
scope: *Scope,
|
|
}) *InsertValue{
|
|
const insert_value = thread.insert_values.append(.{
|
|
.instruction = new_instruction(thread, .{
|
|
.scope = args.scope,
|
|
.line = args.line,
|
|
.column = args.column,
|
|
.id = .insert_value,
|
|
}),
|
|
.value = args.value,
|
|
.aggregate = args.aggregate,
|
|
.index = args.index,
|
|
.type = args.type,
|
|
});
|
|
analyzer.append_instruction(&insert_value.instruction);
|
|
return insert_value;
|
|
}
|
|
|
|
fn emit_ret_void(thread: *Thread, analyzer: *Analyzer, args: RawEmitArgs) void {
|
|
const return_expression = thread.standalone_instructions.append(new_instruction(thread, .{
|
|
.id = .ret_void,
|
|
.line = args.line,
|
|
.column = args.column,
|
|
.scope = args.scope,
|
|
}));
|
|
|
|
analyzer.append_instruction(return_expression);
|
|
analyzer.current_basic_block.is_terminated = true;
|
|
}
|
|
|
|
fn emit_direct_coerce(analyzer: *Analyzer, thread: *Thread, args: struct{
|
|
coerced_type: *Type,
|
|
original_value: *Value,
|
|
}) *Value {
|
|
const source_type = args.original_value.get_type();
|
|
const local = emit_local_symbol(analyzer, thread, .{
|
|
.type = source_type,
|
|
.name = 0,
|
|
.initial_value = args.original_value,
|
|
.line = 0,
|
|
.column = 0,
|
|
});
|
|
|
|
const target_type = args.coerced_type;
|
|
const target_size = args.coerced_type.size;
|
|
const target_alignment = args.coerced_type.alignment;
|
|
const source_size = source_type.size;
|
|
const source_alignment = source_type.alignment;
|
|
const target_is_scalable_vector_type = false;
|
|
const source_is_scalable_vector_type = false;
|
|
if (source_size >= target_size and !source_is_scalable_vector_type and !target_is_scalable_vector_type) {
|
|
const load = emit_load(analyzer, thread, .{
|
|
.value = &local.instruction.value,
|
|
.type = target_type,
|
|
.scope = analyzer.current_scope,
|
|
.line = 0,
|
|
.column = 0,
|
|
});
|
|
return &load.instruction.value;
|
|
} else {
|
|
const alignment = @max(target_alignment, source_alignment);
|
|
const temporal = emit_local_symbol(analyzer, thread, .{
|
|
.name = 0,
|
|
.initial_value = null,
|
|
.type = args.coerced_type,
|
|
.line = 0,
|
|
.column = 0,
|
|
});
|
|
emit_memcpy(analyzer, thread, .{
|
|
.destination = &temporal.instruction.value,
|
|
.source = &local.instruction.value,
|
|
.destination_alignment = .{
|
|
.alignment = alignment,
|
|
},
|
|
.source_alignment = .{
|
|
.alignment = source_alignment,
|
|
},
|
|
.size = source_size,
|
|
.line = 0,
|
|
.column = 0,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
|
|
const load = emit_load(analyzer, thread, .{
|
|
.value = &temporal.instruction.value,
|
|
.type = args.coerced_type,
|
|
.line = 0,
|
|
.column = 0,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
return &load.instruction.value;
|
|
}
|
|
}
|
|
|
|
fn emit_return(thread: *Thread, analyzer: *Analyzer, args: struct {
|
|
return_value: *Value,
|
|
scope: *Scope,
|
|
line: u32,
|
|
column: u32,
|
|
}) void {
|
|
const function = analyzer.current_function;
|
|
const function_type = function.declaration.get_function_type();
|
|
const abi_value: *Value = switch (function_type.abi.return_type_abi.kind) {
|
|
.ignore => unreachable,
|
|
.direct => args.return_value,
|
|
// TODO:
|
|
.direct_coerce => |coerced_type| emit_direct_coerce(analyzer, thread, .{
|
|
.original_value = args.return_value,
|
|
.coerced_type = coerced_type,
|
|
}),
|
|
.direct_pair => |pair| b: {
|
|
const pair_struct_type = get_anonymous_two_field_struct(thread, pair);
|
|
assert(pair_struct_type == function_type.abi.abi_return_type);
|
|
|
|
const return_value_type = args.return_value.get_type();
|
|
if (pair_struct_type == return_value_type) {
|
|
unreachable;
|
|
} else {
|
|
const local = emit_local_symbol(analyzer, thread, .{
|
|
.type = return_value_type,
|
|
.name = 0,
|
|
.initial_value = args.return_value,
|
|
.line = 0,
|
|
.column = 0,
|
|
});
|
|
const source_is_scalable_vector_type = false;
|
|
const target_is_scalable_vector_type = false;
|
|
if (return_value_type.size >= pair_struct_type.size and !source_is_scalable_vector_type and !target_is_scalable_vector_type) {
|
|
const load = emit_load(analyzer, thread, .{
|
|
.value = &local.instruction.value,
|
|
.type = pair_struct_type,
|
|
.line = 0,
|
|
.column = 0,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
break :b &load.instruction.value;
|
|
} else {
|
|
const alignment = @max(return_value_type.alignment, pair_struct_type.alignment);
|
|
const temporal = emit_local_symbol(analyzer, thread, .{
|
|
.name = 0,
|
|
.initial_value = null,
|
|
.type = pair_struct_type,
|
|
.alignment = alignment,
|
|
.line = 0,
|
|
.column = 0,
|
|
});
|
|
emit_memcpy(analyzer, thread, .{
|
|
.destination = &temporal.instruction.value,
|
|
.destination_alignment = .{ .alignment = alignment },
|
|
.source = &local.instruction.value,
|
|
.source_alignment = .{ .alignment = local.alignment },
|
|
.size = local.type.size,
|
|
.line = 0,
|
|
.column = 0,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
|
|
const load = emit_load(analyzer, thread, .{
|
|
.value = &temporal.instruction.value,
|
|
.type = pair_struct_type,
|
|
.line = 0,
|
|
.column = 0,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
break :b &load.instruction.value;
|
|
}
|
|
}
|
|
},
|
|
.indirect => {
|
|
const return_pointer = analyzer.return_pointer orelse unreachable;
|
|
emit_store(analyzer, thread, .{
|
|
.destination = &return_pointer.value,
|
|
.source = args.return_value,
|
|
.alignment = function_type.abi.original_return_type.alignment,
|
|
.line = args.line,
|
|
.column = args.column,
|
|
.scope = args.scope,
|
|
});
|
|
emit_ret_void(thread, analyzer, .{
|
|
.line = args.line,
|
|
.column = args.column,
|
|
.scope = args.scope,
|
|
});
|
|
return;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
|
|
const return_expression = thread.returns.append(.{
|
|
.instruction = new_instruction(thread, .{
|
|
.id = .ret,
|
|
.line = args.line,
|
|
.column = args.column,
|
|
.scope = args.scope,
|
|
}),
|
|
.value = abi_value,
|
|
});
|
|
analyzer.append_instruction(&return_expression.instruction);
|
|
analyzer.current_basic_block.is_terminated = true;
|
|
}
|
|
|
|
pub fn analyze_local_block(thread: *Thread, analyzer: *Analyzer, parser: *Parser, file: *File) *LocalBlock {
|
|
const src = file.source_code;
|
|
const function = analyzer.current_function;
|
|
const block_line = parser.get_debug_line();
|
|
const block_column = parser.get_debug_column();
|
|
parser.expect_character(src, brace_open);
|
|
const local_block = thread.local_blocks.append(.{
|
|
.scope = .{
|
|
.id = .local,
|
|
.parent = analyzer.current_scope,
|
|
.line = block_line,
|
|
.column = @intCast(block_column),
|
|
.file = file.scope.scope.file,
|
|
},
|
|
});
|
|
analyzer.current_scope = &local_block.scope;
|
|
parser.skip_space(src);
|
|
|
|
while (true) {
|
|
parser.skip_space(src);
|
|
|
|
const statement_start_ch_index = parser.i;
|
|
const statement_start_ch = src[statement_start_ch_index];
|
|
|
|
if (statement_start_ch == brace_close) {
|
|
break;
|
|
}
|
|
|
|
const debug_line = parser.get_debug_line();
|
|
const debug_column = parser.get_debug_column();
|
|
|
|
switch (statement_start_ch) {
|
|
// Local variable
|
|
'>' => {
|
|
parser.i += 1;
|
|
|
|
parser.skip_space(src);
|
|
|
|
const local_name = parser.parse_identifier(thread, src);
|
|
if (analyzer.current_scope.get_declaration(local_name)) |lookup_result| {
|
|
_ = lookup_result;
|
|
fail_message("Existing declaration with the same name");
|
|
}
|
|
|
|
const has_local_attributes = src[parser.i] == '[';
|
|
parser.i += @intFromBool(has_local_attributes);
|
|
|
|
if (has_local_attributes) {
|
|
fail_message("TODO: local attributes");
|
|
}
|
|
|
|
parser.skip_space(src);
|
|
|
|
const LocalResult = struct {
|
|
initial_value: *Value,
|
|
type: *Type,
|
|
};
|
|
|
|
const result: LocalResult = switch (src[parser.i]) {
|
|
':' => block: {
|
|
parser.i += 1;
|
|
|
|
parser.skip_space(src);
|
|
|
|
const local_type = parser.parse_type_expression(thread, file, analyzer.current_scope);
|
|
|
|
parser.skip_space(src);
|
|
parser.expect_character(src, '=');
|
|
|
|
parser.skip_space(src);
|
|
|
|
const local_initial_value = parser.parse_expression(analyzer, thread, file, local_type, .right);
|
|
|
|
break :block .{
|
|
.initial_value = local_initial_value,
|
|
.type = local_type,
|
|
};
|
|
},
|
|
'=' => block: {
|
|
parser.i += 1;
|
|
|
|
parser.skip_space(src);
|
|
|
|
const local_initial_value = parser.parse_expression(analyzer, thread, file, null, .right);
|
|
|
|
const local_type = local_initial_value.get_type();
|
|
|
|
break :block .{
|
|
.initial_value = local_initial_value,
|
|
.type = local_type,
|
|
};
|
|
},
|
|
else => fail(),
|
|
};
|
|
|
|
parser.skip_space(src);
|
|
|
|
parser.expect_character(src, ';');
|
|
|
|
_ = emit_local_symbol(analyzer, thread, .{
|
|
.name = local_name,
|
|
.initial_value = result.initial_value,
|
|
.type = result.type,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
});
|
|
},
|
|
'b' => {
|
|
const identifier = parser.parse_raw_identifier(src);
|
|
const break_kw = "break";
|
|
if (byte_equal(identifier, break_kw)) {
|
|
parser.skip_space(src);
|
|
|
|
parser.expect_character(src, ';');
|
|
|
|
if (analyzer.loops.length == 0) {
|
|
fail_message("break when no loop");
|
|
}
|
|
|
|
const inner_loop = analyzer.loops.const_slice()[analyzer.loops.length - 1];
|
|
_ = emit_jump(analyzer, thread, .{
|
|
.basic_block = inner_loop.break_block,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
local_block.terminated = true;
|
|
} else {
|
|
parser.i = statement_start_ch_index;
|
|
}
|
|
},
|
|
'c' => {
|
|
const identifier = parser.parse_raw_identifier(src);
|
|
const continue_kw = "continue";
|
|
if (byte_equal(identifier, continue_kw)) {
|
|
parser.skip_space(src);
|
|
|
|
parser.expect_character(src, ';');
|
|
|
|
if (analyzer.loops.length == 0) {
|
|
fail_message("continue when no loop");
|
|
}
|
|
|
|
const inner_loop = analyzer.loops.const_slice()[analyzer.loops.length - 1];
|
|
_ = emit_jump(analyzer, thread, .{
|
|
.basic_block = inner_loop.continue_block,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
local_block.terminated = true;
|
|
} else {
|
|
parser.i = statement_start_ch_index;
|
|
}
|
|
},
|
|
'i' => {
|
|
if (src[parser.i + 1] == 'f') {
|
|
const if_block = parser.parse_if_expression(analyzer, thread, file);
|
|
local_block.terminated = local_block.terminated or if_block.terminated;
|
|
}
|
|
},
|
|
'l' => {
|
|
const identifier = parser.parse_raw_identifier(src);
|
|
|
|
const loop_text = "loop";
|
|
if (byte_equal(identifier, loop_text)) {
|
|
parser.skip_space(src);
|
|
|
|
const loop_header_block = create_basic_block(thread);
|
|
const loop_body_block = create_basic_block(thread);
|
|
const loop_exit_block = create_basic_block(thread);
|
|
_ = emit_jump(analyzer, thread, .{
|
|
.basic_block = loop_header_block,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
analyzer.current_basic_block = loop_header_block;
|
|
|
|
if (src[parser.i] == '(') {
|
|
const condition = parser.parse_condition(analyzer, thread, file);
|
|
|
|
_ = emit_branch(analyzer, thread, .{
|
|
.condition = condition,
|
|
.taken = loop_body_block,
|
|
.not_taken = loop_exit_block,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
} else {
|
|
_ = emit_jump(analyzer, thread, .{
|
|
.basic_block = loop_body_block,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
}
|
|
|
|
parser.skip_space(src);
|
|
|
|
analyzer.current_basic_block = loop_body_block;
|
|
_ = analyzer.loops.append(.{
|
|
.continue_block = loop_header_block,
|
|
.break_block = loop_exit_block,
|
|
});
|
|
|
|
switch (src[parser.i]) {
|
|
brace_open => {
|
|
const loop_block = analyze_local_block(thread, analyzer, parser, file);
|
|
if (!loop_block.terminated) {
|
|
_ = emit_jump(analyzer, thread, .{
|
|
.basic_block = loop_header_block,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
}
|
|
},
|
|
else => unreachable,
|
|
}
|
|
|
|
analyzer.current_basic_block = loop_exit_block;
|
|
analyzer.loops.length -= 1;
|
|
} else {
|
|
parser.i = statement_start_ch_index;
|
|
}
|
|
},
|
|
'r' => {
|
|
const identifier = parser.parse_raw_identifier(src);
|
|
|
|
if (byte_equal(identifier, "return")) {
|
|
parser.skip_space(src);
|
|
|
|
const function_type = function.declaration.get_function_type();
|
|
if (!function_type.abi.original_return_type.sema.resolved) {
|
|
fail_message("Return type not resolved");
|
|
}
|
|
const return_value = parser.parse_expression(analyzer, thread, file, function_type.abi.original_return_type, .right);
|
|
parser.expect_character(src, ';');
|
|
|
|
if (analyzer.return_block) |return_block| {
|
|
const return_phi = analyzer.return_phi.?;
|
|
_ = return_phi.nodes.append(.{
|
|
.value = return_value,
|
|
.basic_block = analyzer.current_basic_block,
|
|
});
|
|
assert(analyzer.current_basic_block != return_block);
|
|
|
|
_ = emit_jump(analyzer, thread, .{
|
|
.basic_block = return_block,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
} else if (analyzer.exit_blocks.length > 0) {
|
|
const return_phi = thread.phis.append(.{
|
|
.instruction = new_instruction(thread, .{
|
|
.id = .phi,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
}),
|
|
.type = function_type.abi.original_return_type,
|
|
});
|
|
analyzer.return_phi = return_phi;
|
|
const return_block = create_basic_block(thread);
|
|
analyzer.return_block = return_block;
|
|
_ = return_phi.nodes.append(.{
|
|
.value = return_value,
|
|
.basic_block = analyzer.current_basic_block,
|
|
});
|
|
|
|
_ = emit_jump(analyzer, thread, .{
|
|
.basic_block = return_block,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
} else {
|
|
emit_return(thread, analyzer, .{
|
|
.return_value = return_value,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
}
|
|
local_block.terminated = true;
|
|
} else {
|
|
parser.i = statement_start_ch_index;
|
|
}
|
|
},
|
|
'#' => {
|
|
const intrinsic = parser.parse_intrinsic(analyzer, thread, file, &thread.void);
|
|
assert(intrinsic == null);
|
|
parser.skip_space(src);
|
|
parser.expect_character(src, ';');
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
if (statement_start_ch_index == parser.i) {
|
|
const left = parser.parse_single_expression(analyzer, thread, file, null, .left);
|
|
parser.skip_space(src);
|
|
|
|
const operation_index = parser.i;
|
|
const operation_ch = src[operation_index];
|
|
var is_binary_operation = false;
|
|
switch (operation_ch) {
|
|
'+', '-', '*', '/', '=' => {
|
|
if (operation_ch != '=') {
|
|
if (src[parser.i + 1] == '=') {
|
|
parser.i += 2;
|
|
|
|
is_binary_operation = true;
|
|
} else {
|
|
unreachable;
|
|
}
|
|
} else {
|
|
parser.i += 1;
|
|
}
|
|
|
|
parser.skip_space(src);
|
|
|
|
const expected_right_type = switch (left.sema.id) {
|
|
.instruction => b: {
|
|
const instruction = left.get_payload(.instruction);
|
|
switch (instruction.id) {
|
|
.load => {
|
|
const load = instruction.get_payload(.load);
|
|
switch (load.value.sema.id) {
|
|
.instruction => {
|
|
const load_instruction = load.value.get_payload(.instruction);
|
|
switch (load_instruction.id) {
|
|
.local_symbol => {
|
|
const local_symbol = load_instruction.get_payload(.local_symbol);
|
|
if (local_symbol.type.sema.id == .typed_pointer) {
|
|
const pointer_type = local_symbol.type.get_payload(.typed_pointer);
|
|
break :b pointer_type.descriptor.pointee;
|
|
} else if (local_symbol.type.sema.id == .opaque_pointer) {
|
|
unreachable;
|
|
} else {
|
|
break :b local_symbol.type;
|
|
}
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
},
|
|
.local_symbol => {
|
|
const local_symbol = instruction.get_payload(.local_symbol);
|
|
break :b local_symbol.type;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
},
|
|
.global_symbol => b: {
|
|
const global_symbol = left.get_payload(.global_symbol);
|
|
const global_type = global_symbol.type;
|
|
break :b global_type;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
|
|
const source = switch (is_binary_operation) {
|
|
false => parser.parse_expression(analyzer, thread, file, expected_right_type, .right),
|
|
true => block: {
|
|
const left_load = emit_load(analyzer, thread, .{
|
|
.value = left,
|
|
.type = expected_right_type,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
|
|
const right = parser.parse_expression(analyzer, thread, file, expected_right_type, .right);
|
|
|
|
const binary_operation_id: IntegerBinaryOperation.Id = switch (operation_ch) {
|
|
'+' => .add,
|
|
else => unreachable,
|
|
};
|
|
const binary_operation = emit_integer_binary_operation(analyzer, thread, .{
|
|
.id = binary_operation_id,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
.left = &left_load.instruction.value,
|
|
.right = right,
|
|
.type = expected_right_type,
|
|
});
|
|
break :block &binary_operation.instruction.value;
|
|
},
|
|
};
|
|
parser.skip_space(src);
|
|
|
|
parser.expect_character(src, ';');
|
|
emit_store(analyzer, thread, .{
|
|
.destination = left,
|
|
.source = source,
|
|
.alignment = expected_right_type.alignment,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
},
|
|
';' => {
|
|
switch (left.sema.id) {
|
|
.instruction => {
|
|
const instruction = left.get_payload(.instruction);
|
|
switch (instruction.id) {
|
|
.call => parser.i += 1,
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
},
|
|
else => @panic((src.ptr + parser.i)[0..1]),
|
|
}
|
|
}
|
|
}
|
|
|
|
analyzer.current_scope = analyzer.current_scope.parent.?;
|
|
|
|
parser.expect_character(src, brace_close);
|
|
|
|
return local_block;
|
|
}
|
|
|
|
const Aarch64 = struct{
|
|
};
|
|
|
|
const SystemV = struct{
|
|
const RegisterCount = struct{
|
|
gp_registers: u32,
|
|
sse_registers: u32,
|
|
};
|
|
const Class = enum {
|
|
no_class,
|
|
memory,
|
|
integer,
|
|
sse,
|
|
sseup,
|
|
|
|
fn merge(accumulator: Class, field: Class) Class {
|
|
assert(accumulator != .memory);
|
|
if (accumulator == field) {
|
|
return accumulator;
|
|
} else {
|
|
var a = accumulator;
|
|
var f = field;
|
|
if (@intFromEnum(accumulator) > @intFromEnum(field)) {
|
|
a = field;
|
|
f = accumulator;
|
|
}
|
|
|
|
return switch (a) {
|
|
.no_class => f,
|
|
.memory => .memory,
|
|
.integer => .integer,
|
|
.sse, .sseup => .sse,
|
|
};
|
|
}
|
|
}
|
|
};
|
|
|
|
fn classify(ty: *Type, base_offset: u64) [2]Class {
|
|
var result: [2]Class = undefined;
|
|
const is_memory = base_offset >= 8;
|
|
const current_index = @intFromBool(is_memory);
|
|
const not_current_index = @intFromBool(!is_memory);
|
|
assert(current_index != not_current_index);
|
|
result[current_index] = .memory;
|
|
result[not_current_index] = .no_class;
|
|
|
|
switch (ty.sema.id) {
|
|
.void, .noreturn => result[current_index] = .no_class,
|
|
.bitfield => result[current_index] = .integer,
|
|
.integer => {
|
|
const integer_index = ty.get_integer_index();
|
|
switch (integer_index) {
|
|
8 - 1, 16 - 1, 32 - 1, 64 - 1,
|
|
64 + 8 - 1, 64 + 16 - 1, 64 + 32 - 1, 64 + 64 - 1,
|
|
=> result[current_index] = .integer,
|
|
else => unreachable,
|
|
}
|
|
},
|
|
.typed_pointer => result[current_index] = .integer,
|
|
.@"struct" => {
|
|
if (ty.size <= 64) {
|
|
const has_variable_array = false;
|
|
if (!has_variable_array) {
|
|
const struct_type = ty.get_payload(.@"struct");
|
|
result[current_index] = .no_class;
|
|
const is_union = false;
|
|
var member_offset: u32 = 0;
|
|
for (struct_type.fields) |field| {
|
|
const offset = base_offset + member_offset;
|
|
const member_size = field.type.size;
|
|
const member_alignment = field.type.alignment;
|
|
member_offset = @intCast(library.align_forward(member_offset + member_size, ty.alignment));
|
|
const native_vector_size = 16;
|
|
if (ty.size > 16 and ((!is_union and ty.size != member_size) or ty.size > native_vector_size)) {
|
|
result[0] = .memory;
|
|
const r = classify_post_merge(ty.size, result);
|
|
return r;
|
|
}
|
|
|
|
if (offset % member_alignment != 0) {
|
|
result[0] = .memory;
|
|
const r = classify_post_merge(ty.size, result);
|
|
return r;
|
|
}
|
|
|
|
const member_classes = classify(field.type, offset);
|
|
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.size, result);
|
|
result = final;
|
|
}
|
|
}
|
|
},
|
|
.array => {
|
|
if (ty.size <= 64) {
|
|
if (base_offset % ty.alignment == 0) {
|
|
const array_type = ty.get_payload(.array);
|
|
result[current_index] = .no_class;
|
|
|
|
const vector_size = 16;
|
|
if (ty.size > 16 and (ty.size != array_type.descriptor.element_type.size or ty.size > vector_size)) {
|
|
unreachable;
|
|
} else {
|
|
var offset = base_offset;
|
|
|
|
for (0..array_type.descriptor.element_count) |_| {
|
|
const element_classes = classify(array_type.descriptor.element_type, offset);
|
|
offset += array_type.descriptor.element_type.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.size, result);
|
|
assert(final_result[1] != .sseup or final_result[0] != .sse);
|
|
result = final_result;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
fn classify_post_merge(size: u64, classes: [2]Class) [2]Class{
|
|
if (classes[1] == .memory) {
|
|
return .{ .memory, .memory };
|
|
} else if (size > 16 and (classes[0] != .sse or classes[1] != .sseup)) {
|
|
return .{ .memory, classes[1] };
|
|
} else if (classes[1] == .sseup and classes[0] != .sse and classes[0] != .sseup) {
|
|
return .{ classes[0], .sse };
|
|
} else {
|
|
return classes;
|
|
}
|
|
}
|
|
|
|
fn get_int_type_at_offset(ty: *Type, offset: u32, source_type: *Type, source_offset: u32) *Type {
|
|
switch (ty.sema.id) {
|
|
.bitfield => {
|
|
const bitfield = ty.get_payload(.bitfield);
|
|
return get_int_type_at_offset(bitfield.backing_type, offset, if (source_type == ty) bitfield.backing_type else source_type, source_offset);
|
|
},
|
|
.integer => {
|
|
const integer_index = ty.get_integer_index();
|
|
switch (integer_index) {
|
|
64 - 1, 64 + 64 - 1 => return ty,
|
|
8 - 1, 16 - 1, 32 - 1, 64 + 8 - 1, 64 + 16 - 1, 64 + 32 - 1 => {
|
|
if (offset != 0) unreachable;
|
|
const start = source_offset + ty.size;
|
|
const end = source_offset + 8;
|
|
if (contains_no_user_data(source_type, start, end)) {
|
|
return ty;
|
|
}
|
|
},
|
|
else => unreachable,
|
|
}
|
|
},
|
|
.typed_pointer => return if (offset == 0) ty else unreachable,
|
|
.@"struct" => {
|
|
if (get_member_at_offset(ty, offset)) |field| {
|
|
return get_int_type_at_offset(field.type, @intCast(offset - field.member_offset), source_type, source_offset);
|
|
}
|
|
unreachable;
|
|
},
|
|
.array => {
|
|
const array_type = ty.get_payload(.array);
|
|
const element_type = array_type.descriptor.element_type;
|
|
const element_size = element_type.size;
|
|
const element_offset = (offset / element_size) * element_size;
|
|
return get_int_type_at_offset(element_type, @intCast(offset - element_offset), source_type, source_offset);
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
|
|
|
|
if (source_type.size - source_offset > 8) {
|
|
return &instance.threads[ty.sema.thread].integers[63].type;
|
|
} else {
|
|
const byte_count = source_type.size - source_offset;
|
|
const bit_count = byte_count * 8;
|
|
return &instance.threads[ty.sema.thread].integers[bit_count - 1].type;
|
|
}
|
|
|
|
unreachable;
|
|
}
|
|
|
|
fn get_member_at_offset(ty: *Type, offset: u32) ?*Type.AggregateField{
|
|
if (ty.size <= offset) {
|
|
return null;
|
|
}
|
|
|
|
var offset_it: u32 = 0;
|
|
var last_match: ?*Type.AggregateField = null;
|
|
|
|
const struct_type = ty.get_payload(.@"struct");
|
|
for (struct_type.fields) |field| {
|
|
if (offset_it > offset) {
|
|
break;
|
|
}
|
|
|
|
last_match = field;
|
|
offset_it = @intCast(library.align_forward(offset_it + field.type.size, ty.alignment));
|
|
}
|
|
|
|
assert(last_match != null);
|
|
return last_match;
|
|
}
|
|
|
|
fn contains_no_user_data(ty: *Type, start: u64, end: u64) bool {
|
|
if (ty.size <= start) {
|
|
return true;
|
|
}
|
|
|
|
switch (ty.sema.id) {
|
|
.@"struct" => {
|
|
const struct_type = ty.get_payload(.@"struct");
|
|
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.size;
|
|
}
|
|
|
|
return true;
|
|
},
|
|
.array => {
|
|
const array_type = ty.get_payload(.array);
|
|
for (0..array_type.descriptor.element_count) |i| {
|
|
const offset = i * array_type.descriptor.element_type.size;
|
|
if (offset >= end) break;
|
|
const element_start = if (offset < start) start - offset else 0;
|
|
if (!contains_no_user_data(array_type.descriptor.element_type, element_start, end - offset)) return false;
|
|
}
|
|
|
|
return true;
|
|
},
|
|
.anonymous_struct => unreachable,
|
|
else => return false,
|
|
}
|
|
}
|
|
|
|
fn get_argument_pair(types: [2]*Type) Function.Abi.Information{
|
|
const low_size = types[0].size;
|
|
const high_alignment = types[1].alignment;
|
|
const high_start = library.align_forward(low_size, high_alignment);
|
|
assert(high_start == 8);
|
|
return .{
|
|
.kind = .{
|
|
.direct_pair = types,
|
|
},
|
|
};
|
|
}
|
|
|
|
fn indirect_argument(ty: *Type, free_integer_registers: u32) Function.Abi.Information{
|
|
const is_illegal_vector = false;
|
|
if (!ty.is_aggregate() and !is_illegal_vector) {
|
|
if (ty.sema.id == .integer and ty.bit_size < 32) {
|
|
unreachable;
|
|
} else {
|
|
return .{
|
|
.kind = .direct,
|
|
};
|
|
}
|
|
} else {
|
|
if (free_integer_registers == 0) {
|
|
if (ty.alignment <= 8 and ty.size <= 8) {
|
|
unreachable;
|
|
}
|
|
}
|
|
|
|
if (ty.alignment < 8) {
|
|
return .{
|
|
.kind = .{
|
|
.indirect = .{
|
|
.type = ty,
|
|
.alignment = 8,
|
|
},
|
|
},
|
|
.attributes = .{
|
|
.realign = true,
|
|
.by_value = true,
|
|
},
|
|
};
|
|
} else {
|
|
return .{
|
|
.kind = .{
|
|
.indirect = .{
|
|
.type = ty,
|
|
.alignment = ty.alignment,
|
|
},
|
|
},
|
|
.attributes = .{
|
|
.by_value = true,
|
|
},
|
|
};
|
|
}
|
|
}
|
|
unreachable;
|
|
}
|
|
|
|
fn indirect_return(ty: *Type) Function.Abi.Information{
|
|
if (ty.is_aggregate()) {
|
|
return .{
|
|
.kind = .{
|
|
.indirect = .{
|
|
.type = ty,
|
|
.alignment = ty.alignment,
|
|
},
|
|
},
|
|
};
|
|
} else {
|
|
unreachable;
|
|
}
|
|
}
|
|
};
|
|
|
|
fn get_declaration_value(analyzer: *Analyzer, thread: *Thread, declaration: *Declaration, maybe_type: ?*Type, side: Side) *Value {
|
|
var declaration_type: *Type = undefined;
|
|
const declaration_value = switch (declaration.id) {
|
|
.local => block: {
|
|
const local_declaration = declaration.get_payload(.local);
|
|
const local_symbol = local_declaration.to_symbol();
|
|
declaration_type = local_symbol.type;
|
|
break :block &local_symbol.instruction.value;
|
|
},
|
|
.argument => block: {
|
|
const argument_declaration = declaration.get_payload(.argument);
|
|
const argument_symbol = argument_declaration.to_symbol();
|
|
declaration_type = argument_symbol.type;
|
|
break :block &argument_symbol.instruction.value;
|
|
},
|
|
.global => block: {
|
|
const global_declaration = declaration.get_payload(.global);
|
|
const global_symbol = global_declaration.to_symbol();
|
|
const global_type = global_symbol.get_type();
|
|
declaration_type = global_type;
|
|
break :block &global_symbol.value;
|
|
},
|
|
};
|
|
|
|
if (maybe_type) |ty| {
|
|
switch (typecheck(ty, declaration_type)) {
|
|
.success => {},
|
|
}
|
|
}
|
|
|
|
return switch (side) {
|
|
.left => declaration_value,
|
|
.right => block: {
|
|
const load = emit_load(analyzer, thread, .{
|
|
.value = declaration_value,
|
|
.type = declaration_type,
|
|
});
|
|
break :block &load.instruction.value;
|
|
},
|
|
};
|
|
}
|
|
|
|
pub fn analyze_file(thread: *Thread, file_index: u32) void {
|
|
const file = instance.files.get(@enumFromInt(file_index));
|
|
const src = file.source_code;
|
|
|
|
if (src.len > std.math.maxInt(u32)) {
|
|
fail_message("File too big");
|
|
}
|
|
|
|
file.functions.start = @intCast(thread.functions.length);
|
|
|
|
var parser = Parser{};
|
|
|
|
while (true) {
|
|
parser.skip_space(src);
|
|
|
|
if (parser.i >= src.len) {
|
|
break;
|
|
}
|
|
|
|
const declaration_start_i = parser.i;
|
|
const declaration_start_ch = src[declaration_start_i];
|
|
const declaration_line = parser.get_debug_line();
|
|
const declaration_column = parser.get_debug_column();
|
|
|
|
switch (declaration_start_ch) {
|
|
'>' => {
|
|
parser.i += 1;
|
|
parser.skip_space(src);
|
|
const global_name = parser.parse_identifier(thread, src);
|
|
|
|
if (global_name == 0) {
|
|
fail_message("discard identifier '_' cannot be used as a global variable name");
|
|
}
|
|
|
|
if (file.scope.scope.get_global_declaration(global_name)) |existing_global| {
|
|
_ = existing_global; // autofix
|
|
fail();
|
|
}
|
|
|
|
parser.skip_space(src);
|
|
|
|
parser.expect_character(src, ':');
|
|
|
|
parser.skip_space(src);
|
|
|
|
const global_type = parser.parse_type_expression(thread, file, &file.scope.scope);
|
|
|
|
parser.skip_space(src);
|
|
|
|
parser.expect_character(src, '=');
|
|
|
|
parser.skip_space(src);
|
|
|
|
const global_initial_value = parser.parse_constant_expression(thread, file, global_type);
|
|
|
|
parser.expect_character(src, ';');
|
|
|
|
const global_variable = thread.global_variables.append(.{
|
|
.global_symbol = .{
|
|
.global_declaration = .{
|
|
.declaration = .{
|
|
.id = .global,
|
|
.name = global_name,
|
|
.line = declaration_line,
|
|
.column = declaration_column,
|
|
.scope = &file.scope.scope,
|
|
},
|
|
.id = .global_symbol,
|
|
},
|
|
.value = .{
|
|
.sema = .{
|
|
.thread = thread.get_index(),
|
|
.resolved = global_type.sema.resolved and global_initial_value.sema.resolved,
|
|
.id = .global_symbol,
|
|
},
|
|
},
|
|
.alignment = global_type.alignment,
|
|
.id = .global_variable,
|
|
.type = global_type,
|
|
.pointer_type = get_typed_pointer(thread, .{
|
|
.pointee = global_type,
|
|
}),
|
|
},
|
|
.initial_value = global_initial_value,
|
|
});
|
|
|
|
file.scope.scope.declarations.put_no_clobber(global_name, &global_variable.global_symbol.global_declaration.declaration);
|
|
},
|
|
'b' => {
|
|
const identifier = parser.parse_raw_identifier(src);
|
|
if (byte_equal(identifier, "bitfield")) {
|
|
parser.expect_character(src, '(');
|
|
const backing_type = parser.parse_type_expression(thread, file, &file.scope.scope);
|
|
parser.expect_character(src, ')');
|
|
|
|
switch (backing_type.sema.id) {
|
|
.integer => {
|
|
if (backing_type.bit_size > 64) {
|
|
fail();
|
|
}
|
|
|
|
if (backing_type.bit_size % 8 != 0) {
|
|
fail();
|
|
}
|
|
|
|
parser.skip_space(src);
|
|
|
|
const bitfield_name = parser.parse_identifier(thread, src);
|
|
const bitfield_type = thread.bitfields.append(.{
|
|
.type = .{
|
|
.sema = .{
|
|
.id = .bitfield,
|
|
.thread = thread.get_index(),
|
|
.resolved = true,
|
|
},
|
|
.size = backing_type.size,
|
|
.alignment = backing_type.alignment,
|
|
.bit_size = backing_type.bit_size,
|
|
},
|
|
.declaration = .{
|
|
.name = bitfield_name,
|
|
.id = .bitfield,
|
|
.line = declaration_line,
|
|
.column = declaration_column,
|
|
.scope = &file.scope.scope,
|
|
},
|
|
.fields = &.{},
|
|
.backing_type = backing_type,
|
|
});
|
|
file.scope.scope.declarations.put_no_clobber(bitfield_name, &bitfield_type.declaration);
|
|
|
|
parser.skip_space(src);
|
|
|
|
parser.expect_character(src, brace_open);
|
|
|
|
var fields = PinnedArray(*Type.AggregateField){};
|
|
var total_bit_count: u64 = 0;
|
|
while (parser.parse_field(thread, file)) |field_data| {
|
|
const field_bit_offset = total_bit_count;
|
|
const field_bit_count = field_data.type.bit_size;
|
|
if (field_bit_count == 0) {
|
|
fail();
|
|
}
|
|
total_bit_count += field_bit_count;
|
|
const field = thread.fields.append(.{
|
|
.type = field_data.type,
|
|
.parent = &bitfield_type.type,
|
|
.name = field_data.name,
|
|
.index = fields.length,
|
|
.line = field_data.line,
|
|
.column = field_data.column,
|
|
.member_offset = field_bit_offset,
|
|
});
|
|
_ = fields.append(field);
|
|
}
|
|
|
|
parser.i += 1;
|
|
|
|
if (total_bit_count != backing_type.bit_size) {
|
|
fail();
|
|
}
|
|
|
|
bitfield_type.fields = fields.const_slice();
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
} else {
|
|
fail();
|
|
}
|
|
},
|
|
'f' => {
|
|
if (src[parser.i + 1] == 'n') {
|
|
parser.i += 2;
|
|
parser.skip_space(src);
|
|
|
|
// This variable lives in the stack as mere data collector,
|
|
// so it will be consumed by other data structure later when
|
|
// it is certain if this declaration is an external function or
|
|
// a function definition
|
|
var function_declaration_data = Function.Declaration{
|
|
.global_symbol = .{
|
|
.global_declaration = .{
|
|
.declaration = .{
|
|
.name = std.math.maxInt(u32),
|
|
.id = .global,
|
|
.line = declaration_line,
|
|
.column = declaration_column,
|
|
.scope = &file.scope.scope,
|
|
},
|
|
.id = .global_symbol,
|
|
},
|
|
.alignment = 1,
|
|
.value = .{
|
|
.sema = .{
|
|
.thread = thread.get_index(),
|
|
.resolved = true, // TODO: is this correct?
|
|
.id = .global_symbol,
|
|
},
|
|
},
|
|
.id = .function_definition,
|
|
.type = undefined,
|
|
.pointer_type = undefined,
|
|
},
|
|
};
|
|
|
|
const has_function_attributes = src[parser.i] == '[';
|
|
parser.i += @intFromBool(has_function_attributes);
|
|
|
|
var calling_convention = CallingConvention.custom;
|
|
|
|
if (has_function_attributes) {
|
|
var attribute_mask = Function.Attribute.Mask.initEmpty();
|
|
|
|
while (true) {
|
|
parser.skip_space(src);
|
|
|
|
if (src[parser.i] == ']') break;
|
|
|
|
const attribute_identifier = parser.parse_raw_identifier(src);
|
|
b: inline for (@typeInfo(Function.Attribute).Enum.fields) |fa_field| {
|
|
if (byte_equal(fa_field.name, attribute_identifier)) {
|
|
const function_attribute = @field(Function.Attribute, fa_field.name);
|
|
if (attribute_mask.contains(function_attribute)) {
|
|
fail();
|
|
}
|
|
|
|
attribute_mask.setPresent(function_attribute, true);
|
|
|
|
switch (function_attribute) {
|
|
.cc => {
|
|
parser.skip_space(src);
|
|
|
|
parser.expect_character(src, '(');
|
|
|
|
parser.skip_space(src);
|
|
|
|
parser.expect_character(src, '.');
|
|
|
|
const cc_identifier = parser.parse_raw_identifier(src);
|
|
|
|
parser.skip_space(src);
|
|
|
|
parser.expect_character(src, ')');
|
|
|
|
inline for (@typeInfo(CallingConvention).Enum.fields) |cc_field| {
|
|
if (byte_equal(cc_field.name, cc_identifier)) {
|
|
calling_convention = @field(CallingConvention, cc_field.name);
|
|
break :b;
|
|
}
|
|
} else {
|
|
fail();
|
|
}
|
|
},
|
|
}
|
|
}
|
|
} else {
|
|
fail();
|
|
}
|
|
|
|
parser.skip_space(src);
|
|
|
|
const after_ch = src[parser.i];
|
|
switch (after_ch) {
|
|
']' => {},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
parser.i += 1;
|
|
|
|
parser.skip_space(src);
|
|
}
|
|
|
|
function_declaration_data.global_symbol.global_declaration.declaration.name = parser.parse_identifier(thread, src);
|
|
|
|
parser.skip_space(src);
|
|
|
|
const has_global_attributes = src[parser.i] == '[';
|
|
parser.i += @intFromBool(has_global_attributes);
|
|
|
|
if (has_global_attributes) {
|
|
var attribute_mask = GlobalSymbol.Attribute.Mask.initEmpty();
|
|
|
|
while (true) {
|
|
parser.skip_space(src);
|
|
|
|
if (src[parser.i] == ']') break;
|
|
|
|
const attribute_identifier = parser.parse_raw_identifier(src);
|
|
inline for (@typeInfo(GlobalSymbol.Attribute).Enum.fields) |fa_field| {
|
|
if (byte_equal(fa_field.name, attribute_identifier)) {
|
|
const global_attribute = @field(GlobalSymbol.Attribute, fa_field.name);
|
|
if (attribute_mask.contains(global_attribute)) {
|
|
fail();
|
|
}
|
|
|
|
attribute_mask.setPresent(global_attribute, true);
|
|
|
|
switch (global_attribute) {
|
|
.@"export" => {
|
|
function_declaration_data.global_symbol.attributes.@"export" = true;
|
|
},
|
|
.@"extern" => {
|
|
function_declaration_data.global_symbol.attributes.@"extern" = true;
|
|
},
|
|
}
|
|
|
|
const after_ch =src[parser.i];
|
|
switch (after_ch) {
|
|
']' => {},
|
|
else => unreachable,
|
|
}
|
|
|
|
break;
|
|
}
|
|
} else {
|
|
fail();
|
|
}
|
|
|
|
parser.skip_space(src);
|
|
|
|
const after_ch = src[parser.i];
|
|
switch (after_ch) {
|
|
']' => {},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
parser.i += 1;
|
|
|
|
parser.skip_space(src);
|
|
}
|
|
|
|
if (function_declaration_data.global_symbol.attributes.@"export" and function_declaration_data.global_symbol.attributes.@"extern") {
|
|
fail();
|
|
}
|
|
|
|
const split_modules = true;
|
|
if (split_modules and !function_declaration_data.global_symbol.attributes.@"extern") {
|
|
function_declaration_data.global_symbol.attributes.@"export" = true;
|
|
}
|
|
|
|
parser.expect_character(src, '(');
|
|
|
|
const ArgumentData = struct{
|
|
type: *Type,
|
|
name: u32,
|
|
line: u32,
|
|
column: u32,
|
|
};
|
|
|
|
var original_arguments = PinnedArray(ArgumentData){};
|
|
var original_argument_types = PinnedArray(*Type){};
|
|
var fully_resolved = true;
|
|
|
|
while (true) {
|
|
parser.skip_space(src);
|
|
|
|
if (src[parser.i] == ')') break;
|
|
|
|
const argument_line = parser.get_debug_line();
|
|
const argument_column = parser.get_debug_column();
|
|
|
|
const argument_name = parser.parse_identifier(thread, src);
|
|
|
|
parser.skip_space(src);
|
|
|
|
parser.expect_character(src, ':');
|
|
|
|
parser.skip_space(src);
|
|
|
|
const argument_type = parser.parse_type_expression(thread, file, &file.scope.scope);
|
|
fully_resolved = fully_resolved and argument_type.sema.resolved;
|
|
_ = original_arguments.append(.{
|
|
.type = argument_type,
|
|
.name = argument_name,
|
|
.line = argument_line,
|
|
.column = argument_column,
|
|
});
|
|
_ = original_argument_types.append(argument_type);
|
|
|
|
parser.skip_space(src);
|
|
|
|
switch (src[parser.i]) {
|
|
',' => parser.i += 1,
|
|
')' => {},
|
|
else => fail(),
|
|
}
|
|
}
|
|
|
|
parser.expect_character(src, ')');
|
|
|
|
parser.skip_space(src);
|
|
|
|
const original_return_type = parser.parse_type_expression(thread, file, &file.scope.scope);
|
|
fully_resolved = fully_resolved and original_return_type.sema.resolved;
|
|
|
|
const function_abi: Function.Abi = if (fully_resolved) switch (calling_convention) {
|
|
.c => abi: {
|
|
var argument_type_abis = PinnedArray(Function.Abi.Information){};
|
|
const return_type_abi: Function.Abi.Information = switch (builtin.cpu.arch) {
|
|
.x86_64 => block: {
|
|
switch (builtin.os.tag) {
|
|
.linux => {
|
|
const return_type_abi: Function.Abi.Information = rta: {
|
|
const type_classes = SystemV.classify(original_return_type, 0);
|
|
assert(type_classes[1] != .memory or type_classes[0] == .memory);
|
|
assert(type_classes[1] != .sseup or type_classes[0] == .sse);
|
|
|
|
const result_type = switch (type_classes[0]) {
|
|
.no_class => switch (type_classes[1]) {
|
|
.no_class => break :rta .{
|
|
.kind = .ignore,
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
},
|
|
.integer => b: {
|
|
const result_type = SystemV.get_int_type_at_offset(original_return_type, 0, original_return_type, 0);
|
|
if (type_classes[1] == .no_class and original_return_type.bit_size < 32) {
|
|
const signed = switch (original_return_type.sema.id) {
|
|
.integer => @intFromEnum(original_return_type.get_payload(.integer).signedness) != 0,
|
|
.bitfield => false,
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
|
|
break :rta .{
|
|
.kind = .{
|
|
.direct_coerce = original_return_type,
|
|
},
|
|
.attributes = .{
|
|
.sign_extend = signed,
|
|
.zero_extend = !signed,
|
|
},
|
|
};
|
|
}
|
|
break :b result_type;
|
|
},
|
|
.memory => break :rta SystemV.indirect_return(original_return_type),
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
const high_part: ?*Type = switch (type_classes[1]) {
|
|
.no_class, .memory => null,
|
|
.integer => b: {
|
|
assert(type_classes[0] != .no_class);
|
|
const high_part = SystemV.get_int_type_at_offset(original_return_type, 8, original_return_type, 8);
|
|
break :b high_part;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
|
|
if (high_part) |hp| {
|
|
break :rta SystemV.get_argument_pair(.{ result_type, hp });
|
|
} else {
|
|
// TODO
|
|
const is_type = true;
|
|
if (is_type) {
|
|
if (result_type == original_return_type) {
|
|
break :rta Function.Abi.Information{
|
|
.kind = .direct,
|
|
};
|
|
} else {
|
|
break :rta Function.Abi.Information{
|
|
.kind = .{
|
|
.direct_coerce = result_type,
|
|
},
|
|
};
|
|
}
|
|
} else {
|
|
unreachable;
|
|
}
|
|
}
|
|
};
|
|
var available_registers = SystemV.RegisterCount{
|
|
.gp_registers = 6,
|
|
.sse_registers = 8,
|
|
};
|
|
|
|
if (return_type_abi.kind == .indirect) {
|
|
available_registers.gp_registers -= 1;
|
|
}
|
|
|
|
const return_by_reference = false;
|
|
if (return_by_reference) {
|
|
unreachable;
|
|
}
|
|
|
|
for (original_argument_types.const_slice()) |original_argument_type| {
|
|
var needed_registers = SystemV.RegisterCount{
|
|
.gp_registers = 0,
|
|
.sse_registers = 0,
|
|
};
|
|
const argument_type_abi_classification: Function.Abi.Information = ata: {
|
|
const type_classes = SystemV.classify(original_argument_type, 0);
|
|
assert(type_classes[1] != .memory or type_classes[0] == .memory);
|
|
assert(type_classes[1] != .sseup or type_classes[0] == .sse);
|
|
|
|
_ = &needed_registers; // autofix
|
|
|
|
const result_type = switch (type_classes[0]) {
|
|
.integer => b: {
|
|
needed_registers.gp_registers += 1;
|
|
const result_type = SystemV.get_int_type_at_offset(original_argument_type, 0, original_argument_type, 0);
|
|
if (type_classes[1] == .no_class and original_argument_type.bit_size < 32) {
|
|
const signed = switch (original_argument_type.sema.id) {
|
|
.integer => @intFromEnum(original_argument_type.get_payload(.integer).signedness) != 0,
|
|
.bitfield => false,
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
|
|
break :ata .{
|
|
.kind = .{
|
|
.direct_coerce = original_argument_type,
|
|
},
|
|
.attributes = .{
|
|
.sign_extend = signed,
|
|
.zero_extend = !signed,
|
|
},
|
|
};
|
|
}
|
|
break :b result_type;
|
|
},
|
|
.memory => break :ata SystemV.indirect_argument(original_argument_type, available_registers.gp_registers),
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
const high_part: ?*Type = switch (type_classes[1]) {
|
|
.no_class, .memory => null,
|
|
.integer => b: {
|
|
assert(type_classes[0] != .no_class);
|
|
needed_registers.gp_registers += 1;
|
|
const high_part = SystemV.get_int_type_at_offset(original_argument_type, 8, original_argument_type, 8);
|
|
break :b high_part;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
|
|
if (high_part) |hp| {
|
|
break :ata SystemV.get_argument_pair(.{ result_type, hp });
|
|
} else {
|
|
// TODO
|
|
const is_type = true;
|
|
if (is_type) {
|
|
if (result_type == original_argument_type) {
|
|
break :ata Function.Abi.Information{
|
|
.kind = .direct,
|
|
};
|
|
} else if (result_type.sema.id == .integer and original_argument_type.sema.id == .integer and original_argument_type.size == result_type.size) {
|
|
unreachable;
|
|
} else {
|
|
break :ata Function.Abi.Information{
|
|
.kind = .{
|
|
.direct_coerce = result_type,
|
|
},
|
|
};
|
|
}
|
|
}
|
|
unreachable;
|
|
}
|
|
};
|
|
const argument_type_abi = if (available_registers.sse_registers < needed_registers.sse_registers or available_registers.gp_registers < needed_registers.gp_registers) b: {
|
|
break :b SystemV.indirect_argument(original_argument_type, available_registers.gp_registers);
|
|
} else b: {
|
|
available_registers.gp_registers -= needed_registers.gp_registers;
|
|
available_registers.sse_registers -= needed_registers.sse_registers;
|
|
break :b argument_type_abi_classification;
|
|
};
|
|
|
|
_ = argument_type_abis.append(argument_type_abi);
|
|
}
|
|
|
|
break :block return_type_abi;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
},
|
|
.aarch64 => block: {
|
|
const return_type_abi: Function.Abi.Information = blk: {
|
|
if (original_return_type.returns_nothing()) {
|
|
break :blk .{
|
|
.kind = .ignore,
|
|
};
|
|
}
|
|
|
|
const size = original_return_type.size;
|
|
const alignment = original_return_type.alignment;
|
|
|
|
const is_vector = false;
|
|
if (is_vector and size > 16) {
|
|
unreachable;
|
|
}
|
|
|
|
if (!original_return_type.is_aggregate()) {
|
|
const extend = builtin.os.tag.isDarwin() and switch (original_return_type.sema.id) {
|
|
.integer => original_return_type.bit_size < 32,
|
|
.bitfield => original_return_type.bit_size < 32,
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
|
|
if (extend) {
|
|
const signed = switch (original_return_type.sema.id) {
|
|
else => |t| @panic(@tagName(t)),
|
|
.bitfield => @intFromEnum(original_return_type.get_payload(.bitfield).backing_type.get_payload(.integer).signedness) != 0,
|
|
.integer => @intFromEnum(original_return_type.get_payload(.integer).signedness) != 0,
|
|
.typed_pointer => false,
|
|
};
|
|
|
|
break :blk Function.Abi.Information{
|
|
.kind = .direct,
|
|
.attributes = .{
|
|
.zero_extend = !signed,
|
|
.sign_extend = signed,
|
|
},
|
|
};
|
|
} else break :blk .{
|
|
.kind = .direct,
|
|
};
|
|
} else {
|
|
assert(size > 0);
|
|
const is_variadic = false;
|
|
const is_aarch64_32 = false;
|
|
const maybe_homogeneous_aggregate = original_return_type.get_homogeneous_aggregate();
|
|
if (maybe_homogeneous_aggregate != null and !(is_aarch64_32 and is_variadic)) {
|
|
unreachable;
|
|
} else if (size <= 16) {
|
|
if (size <= 8 and builtin.cpu.arch.endian() == .little) {
|
|
break :blk .{
|
|
.kind = .{
|
|
.direct_coerce = &thread.integers[size * 8 - 1].type,
|
|
},
|
|
};
|
|
} else {
|
|
const aligned_size = library.align_forward(size, 8);
|
|
if (alignment < 16 and aligned_size == 16) {
|
|
break :blk .{
|
|
.kind = .{
|
|
.direct_coerce = get_array_type(thread, .{
|
|
.element_type = &thread.integers[63].type,
|
|
.element_count = 2,
|
|
}),
|
|
},
|
|
};
|
|
} else {
|
|
unreachable;
|
|
}
|
|
unreachable;
|
|
}
|
|
} else {
|
|
assert(alignment > 0);
|
|
break :blk .{
|
|
.kind = .{
|
|
.indirect = .{
|
|
.type = original_return_type,
|
|
.alignment = alignment,
|
|
},
|
|
},
|
|
.attributes = .{
|
|
.by_value = true,
|
|
},
|
|
};
|
|
}
|
|
}
|
|
};
|
|
|
|
for (original_argument_types.const_slice()) |argument_type| {
|
|
const argument_type_abi: Function.Abi.Information = blk: {
|
|
if (argument_type.returns_nothing()) {
|
|
break :blk .{
|
|
.kind = .ignore,
|
|
};
|
|
}
|
|
|
|
// TODO:
|
|
const is_illegal_vector = false;
|
|
if (is_illegal_vector) {
|
|
unreachable;
|
|
}
|
|
|
|
if (!argument_type.is_aggregate()) {
|
|
const extend = builtin.os.tag.isDarwin() and switch (argument_type.sema.id) {
|
|
else => |t| @panic(@tagName(t)),
|
|
.bitfield => argument_type.bit_size < 32,
|
|
.integer => argument_type.bit_size < 32,
|
|
.typed_pointer => false,
|
|
};
|
|
|
|
if (extend) {
|
|
const signed = switch (argument_type.sema.id) {
|
|
else => |t| @panic(@tagName(t)),
|
|
.bitfield => @intFromEnum(argument_type.get_payload(.bitfield).backing_type.get_payload(.integer).signedness) != 0,
|
|
.integer => @intFromEnum(argument_type.get_payload(.integer).signedness) != 0,
|
|
.typed_pointer => false,
|
|
};
|
|
|
|
break :blk Function.Abi.Information{
|
|
.kind = .direct,
|
|
.attributes = .{
|
|
.zero_extend = !signed,
|
|
.sign_extend = signed,
|
|
},
|
|
};
|
|
} else break :blk .{
|
|
.kind = .direct,
|
|
};
|
|
} else {
|
|
assert(argument_type.size > 0);
|
|
|
|
if (argument_type.get_homogeneous_aggregate()) |homogeneous_aggregate| {
|
|
_ = homogeneous_aggregate; // autofix
|
|
unreachable;
|
|
} else if (argument_type.size <= 16) {
|
|
const base_alignment = argument_type.alignment;
|
|
const is_aapcs = false;
|
|
const alignment = switch (is_aapcs) {
|
|
true => if (base_alignment < 16) 8 else 16,
|
|
false => @max(base_alignment, 8),
|
|
};
|
|
assert(alignment == 8 or alignment == 16);
|
|
const aligned_size = library.align_forward(argument_type.size, alignment);
|
|
if (alignment == 16) {
|
|
unreachable;
|
|
} else {
|
|
const element_count = @divExact(aligned_size, alignment);
|
|
if (element_count > 1) {
|
|
break :blk .{
|
|
.kind = .{
|
|
.direct_coerce = get_array_type(thread, .{
|
|
.element_type = &thread.integers[63].type,
|
|
.element_count = element_count,
|
|
}),
|
|
}
|
|
};
|
|
} else break :blk .{
|
|
.kind = .{
|
|
.direct_coerce = &thread.integers[63].type,
|
|
},
|
|
};
|
|
}
|
|
} else {
|
|
const alignment = argument_type.alignment;
|
|
assert(alignment > 0);
|
|
|
|
break :blk .{
|
|
.kind = .{
|
|
.indirect = .{
|
|
.type = argument_type,
|
|
.alignment = alignment,
|
|
},
|
|
},
|
|
};
|
|
}
|
|
}
|
|
};
|
|
_ = argument_type_abis.append(argument_type_abi);
|
|
}
|
|
|
|
break :block return_type_abi;
|
|
},
|
|
else => fail_message("ABI not supported"),
|
|
};
|
|
|
|
var abi_argument_types = PinnedArray(*Type){};
|
|
const abi_return_type = switch (return_type_abi.kind) {
|
|
.ignore, .direct => original_return_type,
|
|
.direct_coerce => |coerced_type| coerced_type,
|
|
.indirect => |indirect| b: {
|
|
_ = abi_argument_types.append(get_typed_pointer(thread, .{
|
|
.pointee = indirect.type,
|
|
}));
|
|
break :b &thread.void;
|
|
},
|
|
.direct_pair => |pair| get_anonymous_two_field_struct(thread, pair),
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
|
|
for (argument_type_abis.slice(), original_argument_types.const_slice()) |*argument_abi, original_argument_type| {
|
|
const start: u16 = @intCast(abi_argument_types.length);
|
|
switch (argument_abi.kind) {
|
|
.direct => _ = abi_argument_types.append(original_argument_type),
|
|
.direct_coerce => |coerced_type| _ = abi_argument_types.append(coerced_type),
|
|
.direct_pair => |pair| {
|
|
_ = abi_argument_types.append(pair[0]);
|
|
_ = abi_argument_types.append(pair[1]);
|
|
},
|
|
.indirect => |indirect| _ = abi_argument_types.append(get_typed_pointer(thread, .{
|
|
.pointee = indirect.type,
|
|
})),
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
|
|
const end: u16 = @intCast(abi_argument_types.length);
|
|
argument_abi.indices = .{start, end};
|
|
}
|
|
|
|
break :abi Function.Abi{
|
|
.original_return_type = original_return_type,
|
|
.original_argument_types = original_argument_types.const_slice(),
|
|
.abi_return_type = abi_return_type,
|
|
.abi_argument_types = abi_argument_types.const_slice(),
|
|
.return_type_abi = return_type_abi,
|
|
.argument_types_abi = argument_type_abis.const_slice(),
|
|
.calling_convention = calling_convention,
|
|
};
|
|
},
|
|
.custom => custom: {
|
|
break :custom Function.Abi{
|
|
.original_return_type = original_return_type,
|
|
.original_argument_types = original_argument_types.const_slice(),
|
|
.abi_return_type = original_return_type,
|
|
.abi_argument_types = original_argument_types.const_slice(),
|
|
.return_type_abi = .{
|
|
.kind = .direct,
|
|
},
|
|
.argument_types_abi = blk: {
|
|
var argument_abis = PinnedArray(Function.Abi.Information){};
|
|
for (0..original_argument_types.length) |i| {
|
|
_ = argument_abis.append(.{
|
|
.indices = .{@intCast(i), @intCast(i + 1) },
|
|
.kind = .direct,
|
|
});
|
|
}
|
|
|
|
break :blk argument_abis.const_slice();
|
|
},
|
|
.calling_convention = calling_convention,
|
|
};
|
|
},
|
|
} else {
|
|
unreachable;
|
|
};
|
|
|
|
const function_type = thread.function_types.append(.{
|
|
.type = .{
|
|
.sema = .{
|
|
.id = .function,
|
|
.resolved = true,
|
|
.thread = thread.get_index(),
|
|
},
|
|
.size = 0,
|
|
.alignment = 0,
|
|
.bit_size = 0,
|
|
},
|
|
.abi = function_abi,
|
|
});
|
|
|
|
function_declaration_data.global_symbol.type = &function_type.type;
|
|
function_declaration_data.global_symbol.pointer_type = get_typed_pointer(thread, .{
|
|
.pointee = function_declaration_data.global_symbol.type,
|
|
});
|
|
|
|
parser.skip_space(src);
|
|
|
|
switch (src[parser.i]) {
|
|
brace_open => {
|
|
if (function_declaration_data.global_symbol.attributes.@"extern") {
|
|
fail();
|
|
}
|
|
|
|
|
|
const function = thread.functions.add_one();
|
|
const entry_block = create_basic_block(thread);
|
|
function.* = .{
|
|
.declaration = function_declaration_data,
|
|
.scope = .{
|
|
.scope = .{
|
|
.id = .function,
|
|
.parent = &file.scope.scope,
|
|
.line = declaration_line + 1,
|
|
.column = declaration_column + 1,
|
|
.file = file_index,
|
|
},
|
|
},
|
|
.entry_block = entry_block,
|
|
};
|
|
file.scope.scope.declarations.put_no_clobber(function.declaration.global_symbol.global_declaration.declaration.name, &function.declaration.global_symbol.global_declaration.declaration);
|
|
var analyzer = Analyzer{
|
|
.current_function = function,
|
|
.current_basic_block = entry_block,
|
|
.current_scope = &function.scope.scope,
|
|
};
|
|
analyzer.current_scope = &analyzer.current_function.scope.scope;
|
|
|
|
switch (function_type.abi.return_type_abi.kind) {
|
|
.indirect => |indirect| {
|
|
_ = indirect; // autofix
|
|
const abi_argument = thread.abi_arguments.append(.{
|
|
.instruction = new_instruction(thread, .{
|
|
.scope = analyzer.current_scope,
|
|
.line = 0,
|
|
.column = 0,
|
|
.id = .abi_argument,
|
|
}),
|
|
.index = 0,
|
|
});
|
|
|
|
analyzer.append_instruction(&abi_argument.instruction);
|
|
analyzer.return_pointer = &abi_argument.instruction;
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
if (original_arguments.length > 0) {
|
|
// var runtime_parameter_count: u64 = 0;
|
|
for (original_arguments.const_slice(), function_abi.argument_types_abi, 0..) |argument, argument_abi, argument_index| {
|
|
if (analyzer.current_scope.declarations.get(argument.name) != null) {
|
|
fail_message("A declaration already exists with such name");
|
|
}
|
|
|
|
var argument_abi_instructions = std.BoundedArray(*Instruction, 12){};
|
|
|
|
const argument_abi_count = argument_abi.indices[1] - argument_abi.indices[0];
|
|
const argument_symbol = if (argument_abi.kind == .indirect) blk: {
|
|
assert(argument_abi_count == 1);
|
|
const argument_symbol = emit_argument_symbol(&analyzer, thread, .{
|
|
.type = argument.type,
|
|
.name = argument.name,
|
|
.line = argument.line,
|
|
.column = argument.column,
|
|
.index = @intCast(argument_index),
|
|
.indirect_argument = argument_abi.indices[0],
|
|
});
|
|
argument_symbol.instruction.id = .abi_indirect_argument;
|
|
break :blk argument_symbol;
|
|
} else blk: {
|
|
for (0..argument_abi_count) |abi_argument_index| {
|
|
const abi_argument = thread.abi_arguments.append(.{
|
|
.instruction = new_instruction(thread, .{
|
|
.scope = analyzer.current_scope,
|
|
.line = 0,
|
|
.column = 0,
|
|
.id = .abi_argument,
|
|
}),
|
|
.index = @intCast(abi_argument_index + argument_abi.indices[0]),
|
|
});
|
|
analyzer.append_instruction(&abi_argument.instruction);
|
|
argument_abi_instructions.appendAssumeCapacity(&abi_argument.instruction);
|
|
}
|
|
const LowerKind = union(enum) {
|
|
direct,
|
|
direct_pair: [2]*Type,
|
|
direct_coerce: *Type,
|
|
indirect,
|
|
};
|
|
const lower_kind: LowerKind = switch (argument_abi.kind) {
|
|
.direct => .direct,
|
|
.direct_coerce => |coerced_type| if (argument.type == coerced_type) .direct else .{ .direct_coerce = coerced_type },
|
|
.direct_pair => |pair| .{ .direct_pair = pair },
|
|
.indirect => .indirect,
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
|
|
const argument_symbol = switch (lower_kind) {
|
|
.indirect => unreachable,
|
|
.direct => block: {
|
|
assert(argument_abi_count == 1);
|
|
const argument_symbol = emit_argument_symbol(&analyzer, thread, .{
|
|
.type = argument.type,
|
|
.name = argument.name,
|
|
.line = argument.line,
|
|
.column = argument.column,
|
|
.index = @intCast(argument_index),
|
|
});
|
|
_ = emit_store(&analyzer, thread, .{
|
|
.destination = &argument_symbol.instruction.value,
|
|
.source = &argument_abi_instructions.slice()[0].value,
|
|
.alignment = argument.type.alignment,
|
|
.line = 0,
|
|
.column = 0,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
break :block argument_symbol;
|
|
},
|
|
.direct_coerce => |coerced_type| block: {
|
|
assert(coerced_type != argument.type);
|
|
assert(argument_abi_count == 1);
|
|
const argument_symbol = emit_argument_symbol(&analyzer, thread, .{
|
|
.type = argument.type,
|
|
.name = argument.name,
|
|
.line = argument.line,
|
|
.column = argument.column,
|
|
.index = @intCast(argument_index),
|
|
});
|
|
|
|
switch (argument.type.sema.id) {
|
|
.@"struct" => {
|
|
// TODO:
|
|
const is_vector = false;
|
|
|
|
if (coerced_type.size <= argument.type.size and !is_vector) {
|
|
_ = emit_store(&analyzer, thread, .{
|
|
.destination = &argument_symbol.instruction.value,
|
|
.source = &argument_abi_instructions.slice()[0].value,
|
|
.alignment = coerced_type.alignment,
|
|
.line = 0,
|
|
.column = 0,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
} else {
|
|
const temporal = emit_local_symbol(&analyzer, thread, .{
|
|
.name = 0,
|
|
.initial_value = &argument_abi_instructions.slice()[0].value,
|
|
.type = coerced_type,
|
|
.line = 0,
|
|
.column = 0,
|
|
});
|
|
emit_memcpy(&analyzer, thread, .{
|
|
.destination = &argument_symbol.instruction.value,
|
|
.source = &temporal.instruction.value,
|
|
.destination_alignment = .{
|
|
.type = argument_symbol.type,
|
|
},
|
|
.source_alignment = .{
|
|
.type = temporal.type,
|
|
},
|
|
.size = argument.type.size,
|
|
.line = 0,
|
|
.column = 0,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
}
|
|
|
|
break :block argument_symbol;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
unreachable;
|
|
},
|
|
.direct_pair => |pair| b: {
|
|
assert(argument_abi_count == 2);
|
|
assert(argument_abi_instructions.len == 2);
|
|
assert(pair[0].sema.id == .integer);
|
|
assert(pair[1].sema.id == .integer);
|
|
const alignments = [2]u32{ pair[0].alignment, pair[1].alignment };
|
|
const sizes = [2]u64{ pair[0].size, pair[1].size };
|
|
const alignment = @max(alignments[0], alignments[1]);
|
|
_ = alignment; // autofix
|
|
const high_aligned_size: u32 = @intCast(library.align_forward(sizes[1], alignments[1]));
|
|
_ = high_aligned_size; // autofix
|
|
const high_offset: u32 = @intCast(library.align_forward(sizes[0], alignments[1]));
|
|
assert(high_offset + sizes[1] <= argument.type.size);
|
|
const argument_symbol = emit_argument_symbol(&analyzer, thread, .{
|
|
.type = argument.type,
|
|
.name = argument.name,
|
|
.line = argument.line,
|
|
.column = argument.column,
|
|
.index = @intCast(argument_index),
|
|
});
|
|
|
|
_ = emit_store(&analyzer, thread, .{
|
|
.destination = &argument_symbol.instruction.value,
|
|
.source = &argument_abi_instructions.slice()[0].value,
|
|
.alignment = pair[0].alignment,
|
|
.line = 0,
|
|
.column = 0,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
|
|
const gep = emit_gep(thread, &analyzer, .{
|
|
.pointer = &argument_symbol.instruction.value,
|
|
.type = pair[1],
|
|
.aggregate_type = pair[0],
|
|
.index = &create_constant_int(thread, .{
|
|
.n = 1,
|
|
.type = &thread.integers[31].type,
|
|
}).value,
|
|
.is_struct = false,
|
|
.line = 0,
|
|
.column = 0,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
|
|
_ = emit_store(&analyzer, thread, .{
|
|
.destination = &gep.instruction.value,
|
|
.source = &argument_abi_instructions.slice()[1].value,
|
|
.alignment = pair[1].alignment,
|
|
.line = 0,
|
|
.column = 0,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
break :b argument_symbol;
|
|
},
|
|
};
|
|
|
|
break :blk argument_symbol;
|
|
};
|
|
|
|
if (argument.name != 0) {
|
|
analyzer.current_scope.declarations.put_no_clobber(argument.name, &argument_symbol.argument_declaration.declaration);
|
|
if (thread.generate_debug_information) {
|
|
emit_debug_argument(&analyzer, thread, .{
|
|
.argument_symbol = argument_symbol,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const result = analyze_local_block(thread, &analyzer, &parser, file);
|
|
_ = result;
|
|
|
|
const current_basic_block = analyzer.current_basic_block;
|
|
if (analyzer.return_phi) |return_phi| {
|
|
analyzer.current_basic_block = analyzer.return_block.?;
|
|
analyzer.append_instruction(&return_phi.instruction);
|
|
emit_return(thread, &analyzer, .{
|
|
.return_value = &return_phi.instruction.value,
|
|
.line = parser.get_debug_line(),
|
|
.column = parser.get_debug_column(),
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
analyzer.current_basic_block = current_basic_block;
|
|
}
|
|
|
|
if (!current_basic_block.is_terminated and (current_basic_block.instructions.length > 0 or current_basic_block.predecessors.length > 0)) {
|
|
if (analyzer.return_block == null) {
|
|
switch (original_return_type.sema.id) {
|
|
.void => {
|
|
emit_ret_void(thread, &analyzer, .{
|
|
.line = parser.get_debug_line(),
|
|
.column = parser.get_debug_column(),
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
} else {
|
|
unreachable;
|
|
}
|
|
}
|
|
},
|
|
';' => {
|
|
parser.i += 1;
|
|
|
|
if (!function_declaration_data.global_symbol.attributes.@"extern") {
|
|
fail();
|
|
}
|
|
function_declaration_data.global_symbol.id = .function_declaration;
|
|
|
|
const function_declaration = thread.external_functions.append(function_declaration_data);
|
|
file.scope.scope.declarations.put_no_clobber(function_declaration.global_symbol.global_declaration.declaration.name, &function_declaration.global_symbol.global_declaration.declaration);
|
|
},
|
|
else => fail_message("Unexpected character to close function declaration"),
|
|
}
|
|
} else {
|
|
fail();
|
|
}
|
|
},
|
|
'i' => {
|
|
const import_keyword = "import";
|
|
if (byte_equal(src[parser.i..][0..import_keyword.len], import_keyword)) {
|
|
parser.i += import_keyword.len;
|
|
|
|
parser.skip_space(src);
|
|
|
|
const string_literal = parser.parse_non_escaped_string_literal_content(src);
|
|
parser.skip_space(src);
|
|
|
|
parser.expect_character(src, ';');
|
|
|
|
const filename = std.fs.path.basename(string_literal);
|
|
const has_right_extension = filename.len > ".nat".len and byte_equal(filename[filename.len - ".nat".len..], ".nat");
|
|
if (!has_right_extension) {
|
|
fail();
|
|
}
|
|
|
|
const filename_without_extension = filename[0..filename.len - ".nat".len];
|
|
const filename_without_extension_hash = hash_bytes(filename_without_extension);
|
|
const directory_path = file.get_directory_path();
|
|
const directory = std.fs.openDirAbsolute(directory_path, .{}) catch unreachable;
|
|
const file_path = library.realpath(thread.arena, directory, string_literal) catch unreachable;
|
|
const file_path_hash = intern_identifier(&thread.identifiers, file_path);
|
|
// std.debug.print("Interning '{s}' (0x{x}) in thread #{}\n", .{file_path, file_path_hash, thread.get_index()});
|
|
|
|
for (thread.imports.slice()) |import| {
|
|
const pending_file_hash = import.hash;
|
|
if (pending_file_hash == file_path_hash) {
|
|
fail();
|
|
}
|
|
} else {
|
|
const import = thread.imports.append(.{
|
|
.global_declaration = .{
|
|
.declaration = .{
|
|
.id = .global,
|
|
.name = std.math.maxInt(u32),
|
|
.line = declaration_line,
|
|
.column = declaration_column,
|
|
.scope = &file.scope.scope,
|
|
},
|
|
.id = .unresolved_import,
|
|
},
|
|
.hash = file_path_hash,
|
|
});
|
|
_ = import.files.append(file);
|
|
_ = file.imports.append(import);
|
|
file.scope.scope.declarations.put_no_clobber(filename_without_extension_hash, &import.global_declaration.declaration);
|
|
const global_declaration_reference: **GlobalDeclaration = @ptrCast(file.scope.scope.declarations.get_pointer(filename_without_extension_hash) orelse unreachable);
|
|
const import_values = file.values_per_import.append(.{});
|
|
const lazy_expression = thread.lazy_expressions.append(LazyExpression.init(global_declaration_reference, thread));
|
|
_ = import_values.append(&lazy_expression.value);
|
|
|
|
thread.add_control_work(.{
|
|
.id = .analyze_file,
|
|
.offset = file_path_hash,
|
|
.count = @intCast(file_index),
|
|
});
|
|
}
|
|
} else {
|
|
fail();
|
|
}
|
|
},
|
|
's' => {
|
|
const lead_identifier = parser.parse_raw_identifier(src);
|
|
if (byte_equal(lead_identifier, "struct")) {
|
|
parser.skip_space(src);
|
|
|
|
const struct_name = parser.parse_identifier(thread, src);
|
|
const struct_type = thread.structs.append(.{
|
|
.type = .{
|
|
.sema = .{
|
|
.id = .@"struct",
|
|
.thread = thread.get_index(),
|
|
.resolved = true,
|
|
},
|
|
.size = 0,
|
|
.alignment = 1,
|
|
.bit_size = 0,
|
|
},
|
|
.declaration = .{
|
|
.name = struct_name,
|
|
.id = .@"struct",
|
|
.line = declaration_line,
|
|
.column = declaration_column,
|
|
.scope = &file.scope.scope,
|
|
},
|
|
.fields = &.{},
|
|
});
|
|
file.scope.scope.declarations.put_no_clobber(struct_name, &struct_type.declaration);
|
|
|
|
parser.skip_space(src);
|
|
|
|
parser.expect_character(src, brace_open);
|
|
|
|
var fields = PinnedArray(*Type.AggregateField){};
|
|
|
|
while (parser.parse_field(thread, file)) |field_data| {
|
|
struct_type.type.alignment = @max(struct_type.type.alignment, field_data.type.alignment);
|
|
const aligned_offset = library.align_forward(struct_type.type.size, field_data.type.alignment);
|
|
const field = thread.fields.append(.{
|
|
.type = field_data.type,
|
|
.parent = &struct_type.type,
|
|
.name = field_data.name,
|
|
.index = fields.length,
|
|
.line = field_data.line,
|
|
.column = field_data.column,
|
|
.member_offset = aligned_offset,
|
|
});
|
|
struct_type.type.size = aligned_offset + field.type.size;
|
|
_ = fields.append(field);
|
|
}
|
|
|
|
parser.i += 1;
|
|
|
|
struct_type.type.size = library.align_forward(struct_type.type.size, struct_type.type.alignment);
|
|
struct_type.type.bit_size = struct_type.type.size * 8;
|
|
struct_type.fields = fields.const_slice();
|
|
} else {
|
|
fail();
|
|
}
|
|
},
|
|
else => fail(),
|
|
}
|
|
}
|
|
|
|
for (file.local_lazy_expressions.slice()) |local_lazy_expression| {
|
|
const name = local_lazy_expression.name;
|
|
if (file.scope.scope.get_global_declaration(name)) |global_declaration| {
|
|
switch (global_declaration.id) {
|
|
.global_symbol => {
|
|
const global_symbol = global_declaration.to_symbol();
|
|
switch (global_symbol.id) {
|
|
.function_definition => {
|
|
const function_definition = global_symbol.get_payload(.function_definition);
|
|
for (local_lazy_expression.values.slice()) |value| {
|
|
switch (value.sema.id) {
|
|
.instruction => {
|
|
const instruction = value.get_payload(.instruction);
|
|
switch (instruction.id) {
|
|
.call => {
|
|
const call = instruction.get_payload(.call);
|
|
call.callable = &function_definition.declaration.global_symbol.value;
|
|
call.instruction.value.sema.resolved = true;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
}
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
} else {
|
|
fail_term("Unable to find lazy expression", thread.identifiers.get(name).?);
|
|
}
|
|
}
|
|
|
|
try_resolve_file(thread, file);
|
|
}
|
|
|
|
fn try_resolve_file(thread: *Thread, file: *File) void {
|
|
if (file.imports.length == file.resolved_import_count) {
|
|
thread.analyzed_file_count += 1;
|
|
|
|
if (thread.analyzed_file_count == thread.assigned_file_count) {
|
|
if (@atomicLoad(@TypeOf(thread.task_system.job.queuer.to_do), &thread.task_system.job.queuer.to_do, .seq_cst) == thread.task_system.job.worker.completed + 1) {
|
|
thread.add_control_work(.{
|
|
.id = .notify_analysis_complete,
|
|
});
|
|
} else {
|
|
unreachable;
|
|
}
|
|
}
|
|
|
|
for (file.interested_threads.slice()) |ti| {
|
|
thread.add_control_work(.{
|
|
.id = .notify_file_resolved,
|
|
.offset = file.get_index(),
|
|
.count = @intCast(ti),
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
const TypecheckResult = enum{
|
|
success,
|
|
};
|
|
|
|
fn typecheck(expected: *Type, have: *Type) TypecheckResult {
|
|
if (expected == have) {
|
|
return TypecheckResult.success;
|
|
} else {
|
|
fail();
|
|
}
|
|
}
|
|
|
|
fn emit_integer_binary_operation(analyzer: *Analyzer, thread: *Thread, args: struct{
|
|
left: *Value,
|
|
right: *Value,
|
|
type: *Type,
|
|
id: IntegerBinaryOperation.Id,
|
|
scope: *Scope,
|
|
line: u32,
|
|
column: u32,
|
|
}) *IntegerBinaryOperation{
|
|
const integer_binary_operation = thread.integer_binary_operations.append(.{
|
|
.instruction = new_instruction(thread, .{
|
|
.id = .integer_binary_operation,
|
|
.line = args.line,
|
|
.column = args.column,
|
|
.scope = analyzer.current_scope,
|
|
}),
|
|
.left = args.left,
|
|
.right = args.right,
|
|
.type = args.type,
|
|
.id = args.id,
|
|
});
|
|
analyzer.append_instruction(&integer_binary_operation.instruction);
|
|
|
|
return integer_binary_operation;
|
|
}
|
|
|
|
fn emit_cast(analyzer: *Analyzer, thread: *Thread, args: struct{
|
|
value: *Value,
|
|
type: *Type,
|
|
id: Cast.Id,
|
|
scope: *Scope,
|
|
line: u32,
|
|
column: u32,
|
|
}) *Cast{
|
|
const cast = thread.casts.append(.{
|
|
.instruction = new_instruction(thread, .{
|
|
.id = .cast,
|
|
.line = args.line,
|
|
.column = args.column,
|
|
.scope = analyzer.current_scope,
|
|
}),
|
|
.value = args.value,
|
|
.type = args.type,
|
|
.id = args.id,
|
|
});
|
|
analyzer.append_instruction(&cast.instruction);
|
|
|
|
return cast;
|
|
}
|
|
|
|
fn emit_load(analyzer: *Analyzer, thread: *Thread, args: struct {
|
|
value: *Value,
|
|
type: *Type,
|
|
scope: *Scope,
|
|
line: u32,
|
|
column: u32,
|
|
is_volatile: bool = false,
|
|
}) *Load {
|
|
const load = thread.loads.append(.{
|
|
.instruction = new_instruction(thread, .{
|
|
.id = .load,
|
|
.line = args.line,
|
|
.column = args.column,
|
|
.scope = args.scope,
|
|
}),
|
|
.value = args.value,
|
|
.type = args.type,
|
|
.alignment = args.type.alignment,
|
|
.is_volatile = args.is_volatile,
|
|
});
|
|
|
|
analyzer.append_instruction(&load.instruction);
|
|
|
|
return load;
|
|
}
|
|
|
|
fn emit_argument_symbol(analyzer: *Analyzer, thread: *Thread, args: struct{
|
|
type: *Type,
|
|
name: u32,
|
|
line: u32,
|
|
column: u32,
|
|
index: u32,
|
|
indirect_argument: ?u32 = null,
|
|
}) *ArgumentSymbol{
|
|
const argument_symbol = thread.argument_symbols.append(.{
|
|
.argument_declaration = .{
|
|
.declaration = .{
|
|
.id = .argument,
|
|
.name = args.name,
|
|
.line = args.line,
|
|
.column = args.column,
|
|
.scope = analyzer.current_scope,
|
|
},
|
|
},
|
|
.type = args.type,
|
|
.pointer_type = get_typed_pointer(thread, .{
|
|
.pointee = args.type,
|
|
}),
|
|
.alignment = args.type.alignment,
|
|
.index = if (args.indirect_argument) |i| i else args.index,
|
|
.instruction = new_instruction(thread, .{
|
|
.scope = analyzer.current_scope,
|
|
.id = if (args.indirect_argument) |_| .abi_indirect_argument else .argument_storage,
|
|
.line = args.line,
|
|
.column = args.column,
|
|
}),
|
|
});
|
|
_ = analyzer.current_function.arguments.append(argument_symbol);
|
|
|
|
return argument_symbol;
|
|
}
|
|
|
|
fn emit_debug_argument(analyzer: *Analyzer, thread: *Thread, args: struct {
|
|
argument_symbol: *ArgumentSymbol,
|
|
}) void {
|
|
assert(thread.generate_debug_information);
|
|
var i = args.argument_symbol.instruction;
|
|
i.id = .debug_argument;
|
|
const debug_argument = thread.debug_arguments.append(.{
|
|
.argument = args.argument_symbol,
|
|
.instruction = i,
|
|
});
|
|
analyzer.append_instruction(&debug_argument.instruction);
|
|
}
|
|
|
|
fn emit_local_symbol(analyzer: *Analyzer, thread: *Thread, args: struct{
|
|
name: u32,
|
|
initial_value: ?*Value,
|
|
type: *Type,
|
|
line: u32,
|
|
column: u32,
|
|
alignment: ?u32 = null,
|
|
}) *LocalSymbol {
|
|
const local_symbol = thread.local_symbols.append(.{
|
|
.local_declaration = .{
|
|
.declaration = .{
|
|
.id = .local,
|
|
.name = args.name,
|
|
.line = args.line,
|
|
.column = args.column,
|
|
.scope = analyzer.current_scope,
|
|
},
|
|
},
|
|
.type = args.type,
|
|
.pointer_type = get_typed_pointer(thread, .{
|
|
.pointee = args.type,
|
|
}),
|
|
.instruction = new_instruction(thread, .{
|
|
.resolved = args.type.sema.resolved and if (args.initial_value) |iv| iv.sema.resolved else true,
|
|
.id = .local_symbol,
|
|
.line = args.line,
|
|
.column = args.column,
|
|
.scope = analyzer.current_scope,
|
|
}),
|
|
.alignment = if (args.alignment) |a| a else args.type.alignment,
|
|
});
|
|
|
|
_ = analyzer.current_function.stack_slots.append(local_symbol);
|
|
|
|
if (args.name != 0) {
|
|
analyzer.current_scope.declarations.put_no_clobber(args.name, &local_symbol.local_declaration.declaration);
|
|
if (thread.generate_debug_information) {
|
|
emit_debug_local(analyzer, thread, .{
|
|
.local_symbol = local_symbol,
|
|
});
|
|
}
|
|
}
|
|
|
|
if (args.initial_value) |initial_value| {
|
|
emit_store(analyzer, thread, .{
|
|
.destination = &local_symbol.instruction.value,
|
|
.source = initial_value,
|
|
.alignment = local_symbol.alignment,
|
|
.line = args.line,
|
|
.column = args.column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
}
|
|
|
|
return local_symbol;
|
|
}
|
|
|
|
fn emit_debug_local(analyzer: *Analyzer, thread: *Thread, args: struct {
|
|
local_symbol: *LocalSymbol,
|
|
}) void {
|
|
assert(thread.generate_debug_information);
|
|
var i = args.local_symbol.instruction;
|
|
i.id = .debug_local;
|
|
const debug_local = thread.debug_locals.append(.{
|
|
.local = args.local_symbol,
|
|
.instruction = i,
|
|
});
|
|
analyzer.append_instruction(&debug_local.instruction);
|
|
}
|
|
|
|
fn emit_store(analyzer: *Analyzer, thread: *Thread, args: struct {
|
|
destination: *Value,
|
|
source: *Value,
|
|
alignment: u32,
|
|
line: u32,
|
|
column: u32,
|
|
scope: *Scope,
|
|
is_volatile: bool = false,
|
|
}) void {
|
|
const store = thread.stores.append(.{
|
|
.instruction = new_instruction(thread, .{
|
|
.id = .store,
|
|
.line = args.line,
|
|
.column = args.column,
|
|
.scope = args.scope,
|
|
}),
|
|
.destination = args.destination,
|
|
.source = args.source,
|
|
.alignment = args.alignment,
|
|
.is_volatile = args.is_volatile,
|
|
});
|
|
|
|
analyzer.append_instruction(&store.instruction);
|
|
}
|
|
|
|
const Memcpy = struct{
|
|
instruction: Instruction,
|
|
destination: *Value,
|
|
destination_alignment: u32,
|
|
source: *Value,
|
|
source_alignment: u32,
|
|
size: u64,
|
|
is_volatile: bool,
|
|
const Alignment = union(enum){
|
|
alignment: u32,
|
|
type: *Type,
|
|
};
|
|
};
|
|
|
|
fn emit_memcpy(analyzer: *Analyzer, thread: *Thread, args: struct{
|
|
destination: *Value,
|
|
destination_alignment: Memcpy.Alignment,
|
|
source: *Value,
|
|
source_alignment: Memcpy.Alignment,
|
|
size: u64,
|
|
is_volatile: bool = false,
|
|
line: u32,
|
|
column: u32,
|
|
scope: *Scope,
|
|
}) void {
|
|
const memcpy = thread.memcopies.append(.{
|
|
.instruction = new_instruction(thread, .{
|
|
.scope = args.scope,
|
|
.line = args.line,
|
|
.column = args.column,
|
|
.id = .memcpy,
|
|
}),
|
|
.destination = args.destination,
|
|
.destination_alignment = switch (args.destination_alignment) {
|
|
.alignment => |a| a,
|
|
.type => |t| t.alignment,
|
|
},
|
|
.source = args.source,
|
|
.source_alignment = switch (args.source_alignment) {
|
|
.alignment => |a| a,
|
|
.type => |t| t.alignment,
|
|
},
|
|
.size = args.size,
|
|
.is_volatile = args.is_volatile,
|
|
});
|
|
analyzer.append_instruction(&memcpy.instruction);
|
|
}
|
|
|
|
const RawEmitArgs = struct{
|
|
line: u32,
|
|
column: u32,
|
|
scope: *Scope,
|
|
};
|
|
|
|
fn emit_unreachable(analyzer: *Analyzer, thread: *Thread, args: RawEmitArgs) void {
|
|
assert(!analyzer.current_basic_block.is_terminated);
|
|
const ur = thread.standalone_instructions.append(new_instruction(thread, .{
|
|
.scope = args.scope,
|
|
.line = args.line,
|
|
.column = args.column,
|
|
.id = .@"unreachable",
|
|
}));
|
|
analyzer.append_instruction(ur);
|
|
analyzer.current_basic_block.is_terminated = true;
|
|
}
|
|
|
|
fn emit_trap(analyzer: *Analyzer, thread: *Thread, args: RawEmitArgs) void {
|
|
assert(!analyzer.current_basic_block.is_terminated);
|
|
const trap = thread.standalone_instructions.append(new_instruction(thread, .{
|
|
.scope = args.scope,
|
|
.line = args.line,
|
|
.column = args.column,
|
|
.id = .@"trap",
|
|
}));
|
|
analyzer.append_instruction(trap);
|
|
|
|
emit_unreachable(analyzer, thread, args);
|
|
}
|
|
|
|
fn new_instruction(thread: *Thread, args: struct {
|
|
scope: *Scope,
|
|
line: u32,
|
|
column: u32,
|
|
id: Instruction.Id,
|
|
resolved: bool = true,
|
|
}) Instruction {
|
|
return Instruction{
|
|
.id = args.id,
|
|
.value = .{
|
|
.sema = .{
|
|
.thread = thread.get_index(),
|
|
.id = .instruction,
|
|
.resolved = args.resolved,
|
|
},
|
|
},
|
|
.scope = args.scope,
|
|
.line = args.line,
|
|
.column = args.column,
|
|
};
|
|
}
|
|
|
|
const JumpEmission = struct {
|
|
jump: *Jump,
|
|
basic_block: *BasicBlock,
|
|
};
|
|
|
|
fn emit_jump(analyzer: *Analyzer, thread: *Thread, args: struct {
|
|
basic_block: *BasicBlock,
|
|
line: u32,
|
|
column: u32,
|
|
scope: *Scope,
|
|
}) JumpEmission {
|
|
assert(!analyzer.current_basic_block.is_terminated);
|
|
const jump = thread.jumps.append(.{
|
|
.instruction = new_instruction(thread, .{
|
|
.id = .jump,
|
|
.line = args.line,
|
|
.column = args.column,
|
|
.scope = args.scope,
|
|
}),
|
|
.basic_block = args.basic_block,
|
|
});
|
|
const original_block = analyzer.current_basic_block;
|
|
analyzer.append_instruction(&jump.instruction);
|
|
analyzer.current_basic_block.is_terminated = true;
|
|
_ = args.basic_block.predecessors.append(analyzer.current_basic_block);
|
|
|
|
return .{
|
|
.jump = jump,
|
|
.basic_block = original_block,
|
|
};
|
|
}
|
|
|
|
const BranchEmission = struct {
|
|
branch: *Branch,
|
|
basic_block: *BasicBlock,
|
|
// index: u32,
|
|
};
|
|
|
|
fn emit_branch(analyzer: *Analyzer, thread: *Thread, args: struct {
|
|
condition: *Value,
|
|
taken: *BasicBlock,
|
|
not_taken: *BasicBlock,
|
|
line: u32,
|
|
column: u32,
|
|
scope: *Scope,
|
|
}) BranchEmission {
|
|
assert(!analyzer.current_basic_block.is_terminated);
|
|
const branch = thread.branches.append(.{
|
|
.instruction = new_instruction(thread, .{
|
|
.id = .branch,
|
|
.line = args.line,
|
|
.column = args.column,
|
|
.scope = args.scope,
|
|
}),
|
|
.condition = args.condition,
|
|
.taken = args.taken,
|
|
.not_taken = args.not_taken,
|
|
});
|
|
const original_block = analyzer.current_basic_block;
|
|
analyzer.append_instruction(&branch.instruction);
|
|
analyzer.current_basic_block.is_terminated = true;
|
|
_ = args.taken.predecessors.append(analyzer.current_basic_block);
|
|
_ = args.not_taken.predecessors.append(analyzer.current_basic_block);
|
|
|
|
return .{
|
|
.branch = branch,
|
|
.basic_block = original_block,
|
|
};
|
|
}
|
|
|
|
fn emit_condition(analyzer: *Analyzer, thread: *Thread, args: struct {
|
|
condition: *Value,
|
|
line: u32,
|
|
column: u32,
|
|
scope: *Scope,
|
|
}) *Value {
|
|
const condition_type = args.condition.get_type();
|
|
const compare = switch (condition_type.sema.id) {
|
|
.integer => int: {
|
|
if (condition_type.bit_size == 1) {
|
|
break :int args.condition;
|
|
} else {
|
|
const zero = create_constant_int(thread, .{
|
|
.n = 0,
|
|
.type = condition_type,
|
|
});
|
|
|
|
const compare = thread.integer_compares.append(.{
|
|
.instruction = new_instruction(thread, .{
|
|
.line = args.line,
|
|
.column = args.column,
|
|
.scope = args.scope,
|
|
.id = .integer_compare,
|
|
}),
|
|
.left = args.condition,
|
|
.right = &zero.value,
|
|
.id = .not_zero,
|
|
});
|
|
analyzer.append_instruction(&compare.instruction);
|
|
|
|
break :int &compare.instruction.value;
|
|
}
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
|
|
return compare;
|
|
}
|
|
|
|
fn get_typed_pointer(thread: *Thread, descriptor: Type.TypedPointer.Descriptor) *Type {
|
|
assert(descriptor.pointee.sema.resolved);
|
|
if (thread.typed_pointer_type_map.get(descriptor)) |result| return result else {
|
|
const typed_pointer_type = thread.typed_pointer_types.append(.{
|
|
.type = .{
|
|
.sema = .{
|
|
.thread = thread.get_index(),
|
|
.id = .typed_pointer,
|
|
.resolved = true,
|
|
},
|
|
.size = 8,
|
|
.alignment = 8,
|
|
.bit_size = 64,
|
|
},
|
|
.descriptor = descriptor,
|
|
});
|
|
|
|
thread.typed_pointer_type_map.put_no_clobber(descriptor, &typed_pointer_type.type);
|
|
return &typed_pointer_type.type;
|
|
}
|
|
}
|
|
|
|
fn get_anonymous_two_field_struct(thread: *Thread, types: [2]*Type) *Type {
|
|
if (thread.two_struct_map.get(types)) |result| return result else {
|
|
const anonymous_struct = thread.anonymous_structs.add_one();
|
|
const first_field = thread.fields.append(.{
|
|
.type = types[0],
|
|
.parent = &anonymous_struct.type,
|
|
.member_offset = 0,
|
|
.name = 0,
|
|
.index = 0,
|
|
.line = 0,
|
|
.column = 0,
|
|
});
|
|
const second_field = thread.fields.append(.{
|
|
.type = types[1],
|
|
.parent = &anonymous_struct.type,
|
|
.member_offset = types[0].alignment,
|
|
.name = 0,
|
|
.index = 1,
|
|
.line = 0,
|
|
.column = 0,
|
|
});
|
|
const fields = thread.arena.new_array(*Type.AggregateField, 2) catch unreachable;
|
|
fields[0] = first_field;
|
|
fields[1] = second_field;
|
|
const alignment = @max(types[0].alignment, types[1].alignment);
|
|
const size = library.align_forward(types[0].size + types[1].size, alignment);
|
|
anonymous_struct.* = .{
|
|
.type = .{
|
|
.sema = .{
|
|
.id = .anonymous_struct,
|
|
.thread = thread.get_index(),
|
|
.resolved = true,
|
|
},
|
|
.size = size,
|
|
.alignment = alignment,
|
|
.bit_size = @intCast(size * 8),
|
|
},
|
|
.fields = fields,
|
|
};
|
|
|
|
thread.two_struct_map.put_no_clobber(types, &anonymous_struct.type);
|
|
|
|
return &anonymous_struct.type;
|
|
}
|
|
}
|
|
|
|
fn get_array_type(thread: *Thread, descriptor: Type.Array.Descriptor) *Type {
|
|
assert(descriptor.element_type.sema.resolved);
|
|
if (thread.array_type_map.get(descriptor)) |result| return result else {
|
|
const array_type = thread.array_types.append(.{
|
|
.type = .{
|
|
.sema = .{
|
|
.thread = thread.get_index(),
|
|
.id = .array,
|
|
.resolved = true,
|
|
},
|
|
.size = descriptor.element_type.size * descriptor.element_count,
|
|
.alignment = descriptor.element_type.alignment,
|
|
.bit_size = 0,
|
|
},
|
|
.descriptor = descriptor,
|
|
});
|
|
|
|
thread.array_type_map.put_no_clobber(descriptor, &array_type.type);
|
|
return &array_type.type;
|
|
}
|
|
}
|
|
|
|
pub const LLVM = struct {
|
|
const bindings = @import("backend/llvm_bindings.zig");
|
|
pub const initializeAll = bindings.NativityLLVMInitializeAll;
|
|
pub const x86_64 = struct {
|
|
pub const initializeTarget = bindings.LLVMInitializeX86Target;
|
|
pub const initializeTargetInfo = bindings.LLVMInitializeX86TargetInfo;
|
|
pub const initializeTargetMC = bindings.LLVMInitializeX86TargetMC;
|
|
pub const initializeAsmPrinter = bindings.LLVMInitializeX86AsmPrinter;
|
|
pub const initializeAsmParser = bindings.LLVMInitializeX86AsmParser;
|
|
};
|
|
|
|
pub const aarch64 = struct {
|
|
pub const initializeTarget = bindings.LLVMInitializeAArch64Target;
|
|
pub const initializeTargetInfo = bindings.LLVMInitializeAArch64TargetInfo;
|
|
pub const initializeTargetMC = bindings.LLVMInitializeAArch64TargetMC;
|
|
pub const initializeAsmPrinter = bindings.LLVMInitializeAArch64AsmPrinter;
|
|
pub const initializeAsmParser = bindings.LLVMInitializeAArch64AsmParser;
|
|
};
|
|
|
|
pub const Attributes = struct {
|
|
noreturn: *Attribute,
|
|
naked: *Attribute,
|
|
nounwind: *Attribute,
|
|
inreg: *Attribute,
|
|
@"noalias": *Attribute,
|
|
sign_extend: *Attribute,
|
|
zero_extend: *Attribute,
|
|
};
|
|
|
|
pub const Linkage = enum(c_uint) {
|
|
@"extern" = 0,
|
|
available_external = 1,
|
|
link_once_any = 2,
|
|
link_once_odr = 3,
|
|
weak_any = 4,
|
|
weak_odr = 5,
|
|
appending = 6,
|
|
internal = 7,
|
|
private = 8,
|
|
external_weak = 9,
|
|
common = 10,
|
|
};
|
|
|
|
pub const ThreadLocalMode = enum(c_uint) {
|
|
not_thread_local = 0,
|
|
};
|
|
|
|
const getFunctionType = bindings.NativityLLVMGetFunctionType;
|
|
|
|
pub const Context = opaque {
|
|
const create = bindings.NativityLLVMCreateContext;
|
|
const createBasicBlock = bindings.NativityLLVMCreateBasicBlock;
|
|
const getConstantInt = bindings.NativityLLVMContextGetConstantInt;
|
|
const getConstString = bindings.NativityLLVMContextGetConstString;
|
|
const getVoidType = bindings.NativityLLVMGetVoidType;
|
|
const getIntegerType = bindings.NativityLLVMGetIntegerType;
|
|
const getPointerType = bindings.NativityLLVMGetPointerType;
|
|
const getStructType = bindings.NativityLLVMGetStructType;
|
|
const createStructType = bindings.NativityLLVMCreateStructType;
|
|
const getIntrinsicType = bindings.NativityLLVMContextGetIntrinsicType;
|
|
const getAttributeFromEnum = bindings.NativityLLVMContextGetAttributeFromEnum;
|
|
const getAttributeFromString = bindings.NativityLLVMContextGetAttributeFromString;
|
|
const getAttributeFromType = bindings.NativityLLVMContextGetAttributeFromType;
|
|
const getAttributeSet = bindings.NativityLLVMContextGetAttributeSet;
|
|
};
|
|
|
|
pub const Module = opaque {
|
|
const addGlobalVariable = bindings.NativityLLVMModuleAddGlobalVariable;
|
|
const create = bindings.NativityLLVMCreateModule;
|
|
const getFunction = bindings.NativityLLVMModuleGetFunction;
|
|
const createFunction = bindings.NativityLLVModuleCreateFunction;
|
|
const verify = bindings.NativityLLVMVerifyModule;
|
|
const toString = bindings.NativityLLVMModuleToString;
|
|
const getIntrinsicDeclaration = bindings.NativityLLVMModuleGetIntrinsicDeclaration;
|
|
const createDebugInfoBuilder = bindings.NativityLLVMModuleCreateDebugInfoBuilder;
|
|
const setTargetMachineDataLayout = bindings.NativityLLVMModuleSetTargetMachineDataLayout;
|
|
const setTargetTriple = bindings.NativityLLVMModuleSetTargetTriple;
|
|
const runOptimizationPipeline = bindings.NativityLLVMRunOptimizationPipeline;
|
|
const addPassesToEmitFile = bindings.NativityLLVMModuleAddPassesToEmitFile;
|
|
const link = bindings.NativityLLVMLinkModules;
|
|
};
|
|
|
|
pub const LinkFlags = packed struct(c_uint) {
|
|
override_from_source: bool,
|
|
link_only_needed: bool,
|
|
_: u30 = 0,
|
|
};
|
|
|
|
pub const Builder = opaque {
|
|
const create = bindings.NativityLLVMCreateBuilder;
|
|
const setInsertPoint = bindings.NativityLLVMBuilderSetInsertPoint;
|
|
const createAdd = bindings.NativityLLVMBuilderCreateAdd;
|
|
const createAlloca = bindings.NativityLLVMBuilderCreateAlloca;
|
|
const createAnd = bindings.NativityLLVMBuilderCreateAnd;
|
|
const createOr = bindings.NativityLLVMBuilderCreateOr;
|
|
const createCall = bindings.NativityLLVMBuilderCreateCall;
|
|
const createCast = bindings.NativityLLVMBuilderCreateCast;
|
|
const createBranch = bindings.NativityLLVMBuilderCreateBranch;
|
|
const createConditionalBranch = bindings.NativityLLVMBuilderCreateConditionalBranch;
|
|
const createSwitch = bindings.NativityLLVMBuilderCreateSwitch;
|
|
const createGEP = bindings.NativityLLVMBuilderCreateGEP;
|
|
const createICmp = bindings.NativityLLVMBuilderCreateICmp;
|
|
const createIsNotNull = bindings.NativityLLVMBuilderCreateIsNotNull;
|
|
const createLoad = bindings.NativityLLVMBuilderCreateLoad;
|
|
const createMultiply = bindings.NativityLLVMBuilderCreateMultiply;
|
|
const createRet = bindings.NativityLLVMBuilderCreateRet;
|
|
const createShiftLeft = bindings.NativityLLVMBuilderCreateShiftLeft;
|
|
const createArithmeticShiftRight = bindings.NativityLLVMBuilderCreateArithmeticShiftRight;
|
|
const createLogicalShiftRight = bindings.NativityLLVMBuilderCreateLogicalShiftRight;
|
|
const createStore = bindings.NativityLLVMBuilderCreateStore;
|
|
const createSub = bindings.NativityLLVMBuilderCreateSub;
|
|
const createUnreachable = bindings.NativityLLVMBuilderCreateUnreachable;
|
|
const createXor = bindings.NativityLLVMBuilderCreateXor;
|
|
const createUDiv = bindings.NativityLLVMBuilderCreateUDiv;
|
|
const createSDiv = bindings.NativityLLVMBuilderCreateSDiv;
|
|
const createURem = bindings.NativityLLVMBuilderCreateURem;
|
|
const createSRem = bindings.NativityLLVMBuilderCreateSRem;
|
|
const createExtractValue = bindings.NativityLLVMBuilderCreateExtractValue;
|
|
const createInsertValue = bindings.NativityLLVMBuilderCreateInsertValue;
|
|
const createGlobalString = bindings.NativityLLVMBuilderCreateGlobalString;
|
|
const createGlobalStringPointer = bindings.NativityLLVMBuilderCreateGlobalStringPointer;
|
|
const createPhi = bindings.NativityLLVMBuilderCreatePhi;
|
|
const createMemcpy = bindings.NativityLLVMBuilderCreateMemcpy;
|
|
|
|
const getInsertBlock = bindings.NativityLLVMBuilderGetInsertBlock;
|
|
const isCurrentBlockTerminated = bindings.NativityLLVMBuilderIsCurrentBlockTerminated;
|
|
const setCurrentDebugLocation = bindings.NativityLLVMBuilderSetCurrentDebugLocation;
|
|
const clearCurrentDebugLocation = bindings.NativityLLVMBuilderClearCurrentDebugLocation;
|
|
const setInstructionDebugLocation = bindings.NativityLLVMBuilderSetInstructionDebugLocation;
|
|
};
|
|
|
|
pub const DebugInfo = struct {
|
|
pub const AttributeType = enum(c_uint) {
|
|
address = 0x01,
|
|
boolean = 0x02,
|
|
complex_float = 0x03,
|
|
float = 0x04,
|
|
signed = 0x05,
|
|
signed_char = 0x06,
|
|
unsigned = 0x07,
|
|
unsigned_char = 0x08,
|
|
imaginary_float = 0x09,
|
|
packed_decimal = 0x0a,
|
|
numeric_string = 0x0b,
|
|
edited = 0x0c,
|
|
signed_fixed = 0x0d,
|
|
unsigned_fixed = 0x0e,
|
|
decimal_float = 0x0f,
|
|
UTF = 0x10,
|
|
UCS = 0x11,
|
|
ASCII = 0x12,
|
|
};
|
|
|
|
pub const CallingConvention = enum(c_uint) {
|
|
none = 0,
|
|
normal = 0x01,
|
|
program = 0x02,
|
|
nocall = 0x03,
|
|
pass_by_reference = 0x04,
|
|
pass_by_value = 0x05,
|
|
// Vendor extensions
|
|
GNU_renesas_sh = 0x40,
|
|
GNU_borland_fastcall_i386 = 0x41,
|
|
BORLAND_safecall = 0xb0,
|
|
BORLAND_stdcall = 0xb1,
|
|
BORLAND_pascal = 0xb2,
|
|
BORLAND_msfastcall = 0xb3,
|
|
BORLAND_msreturn = 0xb4,
|
|
BORLAND_thiscall = 0xb5,
|
|
BORLAND_fastcall = 0xb6,
|
|
LLVM_vectorcall = 0xc0,
|
|
LLVM_Win64 = 0xc1,
|
|
LLVM_X86_64SysV = 0xc2,
|
|
LLVM_AAPCS = 0xc3,
|
|
LLVM_AAPCS_VFP = 0xc4,
|
|
LLVM_IntelOclBicc = 0xc5,
|
|
LLVM_SpirFunction = 0xc6,
|
|
LLVM_OpenCLKernel = 0xc7,
|
|
LLVM_Swift = 0xc8,
|
|
LLVM_PreserveMost = 0xc9,
|
|
LLVM_PreserveAll = 0xca,
|
|
LLVM_X86RegCall = 0xcb,
|
|
GDB_IBM_OpenCL = 0xff,
|
|
};
|
|
|
|
pub const Builder = opaque {
|
|
const createCompileUnit = bindings.NativityLLVMDebugInfoBuilderCreateCompileUnit;
|
|
const createFile = bindings.NativityLLVMDebugInfoBuilderCreateFile;
|
|
const createFunction = bindings.NativityLLVMDebugInfoBuilderCreateFunction;
|
|
const createSubroutineType = bindings.NativityLLVMDebugInfoBuilderCreateSubroutineType;
|
|
const createLexicalBlock = bindings.NativityLLVMDebugInfoBuilderCreateLexicalBlock;
|
|
const createParameterVariable = bindings.NativityLLVMDebugInfoBuilderCreateParameterVariable;
|
|
const createAutoVariable = bindings.NativityLLVMDebugInfoBuilderCreateAutoVariable;
|
|
const createGlobalVariableExpression = bindings.NativityLLVMDebugInfoBuilderCreateGlobalVariableExpression;
|
|
const createExpression = bindings.NativityLLVMDebugInfoBuilderCreateExpression;
|
|
const createBasicType = bindings.NativityLLVMDebugInfoBuilderCreateBasicType;
|
|
const createPointerType = bindings.NativityLLVMDebugInfoBuilderCreatePointerType;
|
|
const createStructType = bindings.NativityLLVMDebugInfoBuilderCreateStructType;
|
|
const createArrayType = bindings.NativityLLVMDebugInfoBuilderCreateArrayType;
|
|
const createEnumerationType = bindings.NativityLLVMDebugInfoBuilderCreateEnumerationType;
|
|
const createEnumerator = bindings.NativityLLVMDebugInfoBuilderCreateEnumerator;
|
|
const createReplaceableCompositeType = bindings.NativityLLVMDebugInfoBuilderCreateReplaceableCompositeType;
|
|
const createMemberType = bindings.NativityLLVMDebugInfoBuilderCreateMemberType;
|
|
const createBitfieldMemberType = bindings.NativityLLVMDebugInfoBuilderCreateBitfieldMemberType;
|
|
const insertDeclare = bindings.NativityLLVMDebugInfoBuilderInsertDeclare;
|
|
const finalizeSubprogram = bindings.NativityLLVMDebugInfoBuilderFinalizeSubprogram;
|
|
const finalize = bindings.NativityLLVMDebugInfoBuilderFinalize;
|
|
const replaceCompositeTypes = bindings.NativityLLVMDebugInfoBuilderCompositeTypeReplaceTypes;
|
|
};
|
|
|
|
pub const CompileUnit = opaque {
|
|
fn toScope(this: *@This()) *LLVM.DebugInfo.Scope {
|
|
return @ptrCast(this);
|
|
}
|
|
|
|
pub const EmissionKind = enum(c_uint) {
|
|
no_debug = 0,
|
|
full_debug = 1,
|
|
line_tables_only = 2,
|
|
debug_directives_only = 3,
|
|
};
|
|
|
|
pub const NameTableKind = enum(c_uint) {
|
|
default = 0,
|
|
gnu = 1,
|
|
none = 2,
|
|
};
|
|
};
|
|
|
|
pub const Expression = opaque {};
|
|
|
|
pub const GlobalVariableExpression = opaque {};
|
|
|
|
pub const LocalVariable = opaque {};
|
|
pub const LexicalBlock = opaque {
|
|
fn toScope(this: *@This()) *LLVM.DebugInfo.Scope {
|
|
return @ptrCast(this);
|
|
}
|
|
};
|
|
|
|
pub const Node = opaque {
|
|
pub const Flags = packed struct(u32) {
|
|
visibility: Visibility,
|
|
forward_declaration: bool,
|
|
apple_block: bool,
|
|
block_by_ref_struct: bool,
|
|
virtual: bool,
|
|
artificial: bool,
|
|
explicit: bool,
|
|
prototyped: bool,
|
|
objective_c_class_complete: bool,
|
|
object_pointer: bool,
|
|
vector: bool,
|
|
static_member: bool,
|
|
lvalue_reference: bool,
|
|
rvalue_reference: bool,
|
|
reserved: bool = false,
|
|
inheritance: Inheritance,
|
|
introduced_virtual: bool,
|
|
bit_field: bool,
|
|
no_return: bool,
|
|
type_pass_by_value: bool,
|
|
type_pass_by_reference: bool,
|
|
enum_class: bool,
|
|
thunk: bool,
|
|
non_trivial: bool,
|
|
big_endian: bool,
|
|
little_endian: bool,
|
|
all_calls_described: bool,
|
|
_: u3 = 0,
|
|
|
|
const Visibility = enum(u2) {
|
|
none = 0,
|
|
private = 1,
|
|
protected = 2,
|
|
public = 3,
|
|
};
|
|
const Inheritance = enum(u2) {
|
|
none = 0,
|
|
single = 1,
|
|
multiple = 2,
|
|
virtual = 3,
|
|
};
|
|
};
|
|
};
|
|
|
|
pub const File = opaque {
|
|
fn toScope(this: *@This()) *LLVM.DebugInfo.Scope {
|
|
return @ptrCast(this);
|
|
}
|
|
};
|
|
|
|
pub const Language = enum(c_uint) {
|
|
c = 0x02,
|
|
c11 = 0x1d,
|
|
};
|
|
|
|
pub const Scope = opaque {
|
|
const toSubprogram = bindings.NativityLLVMDebugInfoScopeToSubprogram;
|
|
};
|
|
|
|
pub const LocalScope = opaque {
|
|
fn toScope(this: *@This()) *LLVM.DebugInfo.Scope {
|
|
return @ptrCast(this);
|
|
}
|
|
};
|
|
pub const Subprogram = opaque {
|
|
const getFile = bindings.NativityLLVMDebugInfoSubprogramGetFile;
|
|
const getArgumentType = bindings.NativityLLVMDebugInfoSubprogramGetArgumentType;
|
|
fn toLocalScope(this: *@This()) *LocalScope {
|
|
return @ptrCast(this);
|
|
}
|
|
|
|
pub const Flags = packed struct(u32) {
|
|
virtuality: Virtuality,
|
|
local_to_unit: bool,
|
|
definition: bool,
|
|
optimized: bool,
|
|
pure: bool,
|
|
elemental: bool,
|
|
recursive: bool,
|
|
main_subprogram: bool,
|
|
deleted: bool,
|
|
reserved: bool = false,
|
|
object_c_direct: bool,
|
|
_: u20 = 0,
|
|
|
|
const Virtuality = enum(u2) {
|
|
none = 0,
|
|
virtual = 1,
|
|
pure_virtual = 2,
|
|
};
|
|
};
|
|
};
|
|
|
|
pub const Type = opaque {
|
|
const isResolved = bindings.NativityLLLVMDITypeIsResolved;
|
|
fn toScope(this: *@This()) *LLVM.DebugInfo.Scope {
|
|
return @ptrCast(this);
|
|
}
|
|
|
|
pub const Derived = opaque {
|
|
fn toType(this: *@This()) *LLVM.DebugInfo.Type {
|
|
return @ptrCast(this);
|
|
}
|
|
};
|
|
|
|
pub const Composite = opaque {
|
|
fn toType(this: *@This()) *LLVM.DebugInfo.Type {
|
|
return @ptrCast(this);
|
|
}
|
|
};
|
|
|
|
pub const Enumerator = opaque {};
|
|
pub const Subroutine = opaque {
|
|
fn toType(this: *@This()) *LLVM.DebugInfo.Type {
|
|
return @ptrCast(this);
|
|
}
|
|
};
|
|
};
|
|
};
|
|
|
|
pub const FloatAbi = enum(c_uint) {
|
|
default = 0,
|
|
soft = 1,
|
|
hard = 2,
|
|
};
|
|
|
|
pub const FloatOperationFusionMode = enum(c_uint) {
|
|
fast = 0,
|
|
standard = 1,
|
|
strict = 2,
|
|
};
|
|
|
|
pub const JumpTableType = enum(c_uint) {
|
|
single = 0,
|
|
arity = 1,
|
|
simplified = 2,
|
|
full = 3,
|
|
};
|
|
|
|
pub const ThreadModel = enum(c_uint) {
|
|
posix = 0,
|
|
single = 1,
|
|
};
|
|
|
|
pub const BasicBlockSection = enum(c_uint) {
|
|
all = 0,
|
|
list = 1,
|
|
labels = 2,
|
|
preset = 3,
|
|
none = 4,
|
|
};
|
|
|
|
pub const EAbi = enum(c_uint) {
|
|
unknown = 0,
|
|
default = 1,
|
|
eabi4 = 2,
|
|
eabi5 = 3,
|
|
gnu = 4,
|
|
};
|
|
|
|
pub const DebuggerKind = enum(c_uint) {
|
|
default = 0,
|
|
gdb = 1,
|
|
lldb = 2,
|
|
sce = 3,
|
|
dbx = 4,
|
|
};
|
|
|
|
pub const GlobalISelAbortMode = enum(c_uint) {
|
|
disable = 0,
|
|
enable = 1,
|
|
disable_with_diagnostic = 2,
|
|
};
|
|
|
|
pub const DebugCompressionType = enum(c_uint) {
|
|
none = 0,
|
|
zlib = 1,
|
|
zstd = 2,
|
|
};
|
|
|
|
pub const RelocationModel = enum(c_uint) {
|
|
static = 0,
|
|
pic = 1,
|
|
dynamic_no_pic = 2,
|
|
ropi = 3,
|
|
rwpi = 4,
|
|
ropi_rwpi = 5,
|
|
};
|
|
|
|
pub const CodeModel = enum(c_uint) {
|
|
tiny = 0,
|
|
small = 1,
|
|
kernel = 2,
|
|
medium = 3,
|
|
large = 4,
|
|
};
|
|
|
|
pub const PicLevel = enum(c_uint) {
|
|
not_pic = 0,
|
|
small_pic = 1,
|
|
big_pic = 2,
|
|
};
|
|
|
|
pub const PieLevel = enum(c_uint) {
|
|
default = 0,
|
|
small = 1,
|
|
large = 2,
|
|
};
|
|
|
|
pub const TlsModel = enum(c_uint) {
|
|
general_dynamic = 0,
|
|
local_dynamic = 1,
|
|
initial_exec = 2,
|
|
local_exec = 3,
|
|
};
|
|
|
|
pub const CodegenOptimizationLevel = enum(c_int) {
|
|
none = 0,
|
|
less = 1,
|
|
default = 2,
|
|
aggressive = 3,
|
|
};
|
|
|
|
pub const OptimizationLevel = extern struct {
|
|
speed_level: c_uint,
|
|
size_level: c_uint,
|
|
};
|
|
|
|
pub const FramePointerKind = enum(c_uint) {
|
|
none = 0,
|
|
non_leaf = 1,
|
|
all = 2,
|
|
};
|
|
|
|
pub const CodeGenFileType = enum(c_uint) {
|
|
assembly = 0,
|
|
object = 1,
|
|
null = 2,
|
|
};
|
|
|
|
pub const Target = opaque {
|
|
const createTargetMachine = bindings.NativityLLVMTargetCreateTargetMachine;
|
|
|
|
pub const Machine = opaque {};
|
|
|
|
// This is a non-LLVM struct
|
|
const Options = extern struct {
|
|
bin_utils_version: struct { i32, i32 },
|
|
fp_math: extern struct {
|
|
unsafe: bool,
|
|
no_infs: bool,
|
|
no_nans: bool,
|
|
no_traping: bool,
|
|
no_signed_zeroes: bool,
|
|
approx_func: bool,
|
|
enable_aix_extended_altivec_abi: bool,
|
|
honor_sign_dependent_rounding: bool,
|
|
},
|
|
no_zeroes_in_bss: bool,
|
|
guaranteed_tail_call_optimization: bool,
|
|
stack_symbol_ordering: bool,
|
|
enable_fast_isel: bool,
|
|
enable_global_isel: bool,
|
|
global_isel_abort_mode: GlobalISelAbortMode,
|
|
use_init_array: bool,
|
|
disable_integrated_assembler: bool,
|
|
debug_compression_type: DebugCompressionType,
|
|
relax_elf_relocations: bool,
|
|
function_sections: bool,
|
|
data_sections: bool,
|
|
ignore_xcoff_visibility: bool,
|
|
xcoff_traceback_table: bool,
|
|
unique_section_names: bool,
|
|
unique_basic_block_section_names: bool,
|
|
trap_unreachable: bool,
|
|
no_trap_after_noreturn: bool,
|
|
tls_size: u8,
|
|
emulated_tls: bool,
|
|
enable_ipra: bool,
|
|
emit_stack_size_section: bool,
|
|
enable_machine_outliner: bool,
|
|
enable_machine_function_splitter: bool,
|
|
support_default_outlining: bool,
|
|
emit_address_significance_table: bool,
|
|
bb_sections: BasicBlockSection,
|
|
emit_call_site_info: bool,
|
|
support_debug_entry_values: bool,
|
|
enable_debug_entry_values: bool,
|
|
value_tracking_variable_locations: bool,
|
|
force_dwarf_frame_section: bool,
|
|
xray_function_index: bool,
|
|
debug_strict_dwarf: bool,
|
|
hotpatch: bool,
|
|
ppc_gen_scalar_mass_entries: bool,
|
|
jmc_instrument: bool,
|
|
cfi_fixup: bool,
|
|
loop_alignment: u32 = 0,
|
|
float_abi_type: FloatAbi,
|
|
fp_operation_fusion: FloatOperationFusionMode,
|
|
thread_model: ThreadModel,
|
|
eabi_version: EAbi,
|
|
debugger_tuning: DebuggerKind,
|
|
};
|
|
};
|
|
|
|
const lookupIntrinsic = bindings.NativityLLVMLookupIntrinsic;
|
|
const newPhiNode = bindings.NativityLLVMCreatePhiNode;
|
|
|
|
pub const Metadata = opaque {
|
|
pub const Node = opaque {};
|
|
pub const Tuple = opaque {};
|
|
};
|
|
|
|
pub const Attribute = opaque {
|
|
pub const Set = opaque {};
|
|
pub const Id = enum(u32) {
|
|
AllocAlign = 1,
|
|
AllocatedPointer = 2,
|
|
AlwaysInline = 3,
|
|
Builtin = 4,
|
|
Cold = 5,
|
|
Convergent = 6,
|
|
CoroDestroyOnlyWhenComplete = 7,
|
|
DeadOnUnwind = 8,
|
|
DisableSanitizerInstrumentation = 9,
|
|
FnRetThunkExtern = 10,
|
|
Hot = 11,
|
|
ImmArg = 12,
|
|
InReg = 13,
|
|
InlineHint = 14,
|
|
JumpTable = 15,
|
|
MinSize = 16,
|
|
MustProgress = 17,
|
|
Naked = 18,
|
|
Nest = 19,
|
|
NoAlias = 20,
|
|
NoBuiltin = 21,
|
|
NoCallback = 22,
|
|
NoCapture = 23,
|
|
NoCfCheck = 24,
|
|
NoDuplicate = 25,
|
|
NoFree = 26,
|
|
NoImplicitFloat = 27,
|
|
NoInline = 28,
|
|
NoMerge = 29,
|
|
NoProfile = 30,
|
|
NoRecurse = 31,
|
|
NoRedZone = 32,
|
|
NoReturn = 33,
|
|
NoSanitizeBounds = 34,
|
|
NoSanitizeCoverage = 35,
|
|
NoSync = 36,
|
|
NoUndef = 37,
|
|
NoUnwind = 38,
|
|
NonLazyBind = 39,
|
|
NonNull = 40,
|
|
NullPointerIsValid = 41,
|
|
OptForFuzzing = 42,
|
|
OptimizeForDebugging = 43,
|
|
OptimizeForSize = 44,
|
|
OptimizeNone = 45,
|
|
PresplitCoroutine = 46,
|
|
ReadNone = 47,
|
|
ReadOnly = 48,
|
|
Returned = 49,
|
|
ReturnsTwice = 50,
|
|
SExt = 51,
|
|
SafeStack = 52,
|
|
SanitizeAddress = 53,
|
|
SanitizeHWAddress = 54,
|
|
SanitizeMemTag = 55,
|
|
SanitizeMemory = 56,
|
|
SanitizeThread = 57,
|
|
ShadowCallStack = 58,
|
|
SkipProfile = 59,
|
|
Speculatable = 60,
|
|
SpeculativeLoadHardening = 61,
|
|
StackProtect = 62,
|
|
StackProtectReq = 63,
|
|
StackProtectStrong = 64,
|
|
StrictFP = 65,
|
|
SwiftAsync = 66,
|
|
SwiftError = 67,
|
|
SwiftSelf = 68,
|
|
WillReturn = 69,
|
|
Writable = 70,
|
|
WriteOnly = 71,
|
|
ZExt = 72,
|
|
ByRef = 73,
|
|
ByVal = 74,
|
|
ElementType = 75,
|
|
InAlloca = 76,
|
|
Preallocated = 77,
|
|
StructRet = 78,
|
|
Alignment = 79,
|
|
AllocKind = 80,
|
|
AllocSize = 81,
|
|
Dereferenceable = 82,
|
|
DereferenceableOrNull = 83,
|
|
Memory = 84,
|
|
NoFPClass = 85,
|
|
StackAlignment = 86,
|
|
UWTable = 87,
|
|
VScaleRange = 88,
|
|
};
|
|
};
|
|
|
|
pub const Type = opaque {
|
|
const compare = bindings.NativityLLVMCompareTypes;
|
|
const toStruct = bindings.NativityLLVMTypeToStruct;
|
|
const toFunction = bindings.NativityLLVMTypeToFunction;
|
|
const toArray = bindings.NativityLLVMTypeToArray;
|
|
const toPointer = bindings.NativityLLVMTypeToPointer;
|
|
const isPointer = bindings.NativityLLVMTypeIsPointer;
|
|
const isInteger = bindings.NativityLLVMTypeIsInteger;
|
|
const isVoid = bindings.NativityLLVMTypeIsVoid;
|
|
const assertEqual = bindings.NativityLLVMTypeAssertEqual;
|
|
const getPoison = bindings.NativityLLVMGetPoisonValue;
|
|
const getContext = bindings.NativityLLVMTypeGetContext;
|
|
|
|
pub const Array = opaque {
|
|
fn toType(integer: *@This()) *LLVM.Type {
|
|
return @ptrCast(integer);
|
|
}
|
|
const get = bindings.NativityLLVMGetArrayType;
|
|
const getConstant = bindings.NativityLLVMGetConstantArray;
|
|
const getElementType = bindings.NativityLLVMArrayTypeGetElementType;
|
|
};
|
|
|
|
pub const Integer = opaque {
|
|
fn toType(integer: *@This()) *LLVM.Type {
|
|
return @ptrCast(integer);
|
|
}
|
|
};
|
|
|
|
pub const Function = opaque {
|
|
fn toType(integer: *@This()) *LLVM.Type {
|
|
return @ptrCast(integer);
|
|
}
|
|
|
|
const getArgumentType = bindings.NativityLLVMFunctionTypeGetArgumentType;
|
|
const getReturnType = bindings.NativityLLVMFunctionTypeGetReturnType;
|
|
};
|
|
|
|
pub const Pointer = opaque {
|
|
fn toType(integer: *@This()) *LLVM.Type {
|
|
return @ptrCast(integer);
|
|
}
|
|
|
|
const getNull = bindings.NativityLLVMPointerTypeGetNull;
|
|
};
|
|
|
|
pub const Struct = opaque {
|
|
const getConstant = bindings.NativityLLVMGetConstantStruct;
|
|
fn toType(integer: *@This()) *LLVM.Type {
|
|
return @ptrCast(integer);
|
|
}
|
|
};
|
|
|
|
pub const Error = error{
|
|
void,
|
|
function,
|
|
integer,
|
|
pointer,
|
|
@"struct",
|
|
intrinsic,
|
|
array,
|
|
};
|
|
|
|
};
|
|
|
|
pub const Value = opaque {
|
|
const setName = bindings.NativityLLVMValueSetName;
|
|
const getType = bindings.NativityLLVMValueGetType;
|
|
const getContext = bindings.NativityLLVMValueGetContext;
|
|
const toConstant = bindings.NativityLLVMValueToConstant;
|
|
const toFunction = bindings.NativityLLVMValueToFunction;
|
|
const toAlloca = bindings.NativityLLVMValueToAlloca;
|
|
const toBasicBlock = bindings.NativityLLVMValueToBasicBlock;
|
|
const toString = bindings.NativityLLVMValueToString;
|
|
|
|
pub const IntrinsicID = enum(u32) {
|
|
none = 0,
|
|
_,
|
|
};
|
|
|
|
pub const BasicBlock = opaque {
|
|
const remove = bindings.NativityLLVMBasicBlockRemoveFromParent;
|
|
fn toValue(this: *@This()) *LLVM.Value {
|
|
return @ptrCast(this);
|
|
}
|
|
};
|
|
|
|
pub const Argument = opaque {
|
|
const getIndex = bindings.NativityLLVMArgumentGetIndex;
|
|
fn toValue(this: *@This()) *LLVM.Value {
|
|
return @ptrCast(this);
|
|
}
|
|
};
|
|
|
|
pub const Instruction = opaque {
|
|
fn toValue(this: *@This()) *LLVM.Value {
|
|
return @ptrCast(this);
|
|
}
|
|
|
|
pub const Alloca = opaque {
|
|
fn toValue(this: *@This()) *LLVM.Value {
|
|
return @ptrCast(this);
|
|
}
|
|
|
|
const getAllocatedType = bindings.NativityLLVMAllocatGetAllocatedType;
|
|
};
|
|
|
|
pub const Branch = opaque {
|
|
fn toValue(this: *@This()) *LLVM.Value {
|
|
return @ptrCast(this);
|
|
}
|
|
};
|
|
|
|
pub const Call = opaque {
|
|
const setCallingConvention = bindings.NativityLLVMCallSetCallingConvention;
|
|
const setAttributes = bindings.NativityLLVMCallSetAttributes;
|
|
fn toValue(this: *@This()) *LLVM.Value {
|
|
return @ptrCast(this);
|
|
}
|
|
};
|
|
|
|
pub const Cast = opaque {
|
|
pub const Type = enum(c_uint) {
|
|
truncate = 38,
|
|
zero_extend = 39,
|
|
sign_extend = 40,
|
|
float_to_unsigned_integer = 41,
|
|
float_to_signed_integer = 42,
|
|
unsigned_integer_to_float = 43,
|
|
signed_integer_to_float = 44,
|
|
float_truncate = 45,
|
|
float_extend = 46,
|
|
pointer_to_int = 47,
|
|
int_to_pointer = 48,
|
|
bitcast = 49,
|
|
address_space_cast = 50,
|
|
};
|
|
|
|
fn toValue(this: *@This()) *LLVM.Value {
|
|
return @ptrCast(this);
|
|
}
|
|
};
|
|
|
|
pub const ICmp = opaque {
|
|
fn toValue(this: *@This()) *LLVM.Value {
|
|
return @ptrCast(this);
|
|
}
|
|
|
|
pub const Kind = enum(c_uint) {
|
|
eq = 32, // equal
|
|
ne = 33, // not equal
|
|
ugt = 34, // unsigned greater than
|
|
uge = 35, // unsigned greater or equal
|
|
ult = 36, // unsigned less than
|
|
ule = 37, // unsigned less or equal
|
|
sgt = 38, // signed greater than
|
|
sge = 39, // signed greater or equal
|
|
slt = 40, // signed less than
|
|
sle = 41, // signed less or equal
|
|
};
|
|
};
|
|
|
|
pub const Load = opaque {
|
|
fn toValue(this: *@This()) *LLVM.Value {
|
|
return @ptrCast(this);
|
|
}
|
|
};
|
|
|
|
pub const PhiNode = opaque {
|
|
pub const addIncoming = bindings.NativityLLVMPhiAddIncoming;
|
|
|
|
fn toValue(this: *@This()) *LLVM.Value {
|
|
return @ptrCast(this);
|
|
}
|
|
};
|
|
|
|
pub const Store = opaque {
|
|
fn toValue(this: *@This()) *LLVM.Value {
|
|
return @ptrCast(this);
|
|
}
|
|
};
|
|
|
|
pub const Switch = opaque {
|
|
fn toValue(this: *@This()) *LLVM.Value {
|
|
return @ptrCast(this);
|
|
}
|
|
};
|
|
|
|
pub const Ret = opaque {
|
|
fn toValue(this: *@This()) *LLVM.Value {
|
|
return @ptrCast(this);
|
|
}
|
|
};
|
|
|
|
pub const Unreachable = opaque {
|
|
fn toValue(this: *@This()) *LLVM.Value {
|
|
return @ptrCast(this);
|
|
}
|
|
};
|
|
|
|
pub const Error = error{
|
|
add,
|
|
alloca,
|
|
@"and",
|
|
arithmetic_shift_right,
|
|
call,
|
|
cast,
|
|
conditional_branch,
|
|
extract_value,
|
|
gep,
|
|
icmp,
|
|
insert_value,
|
|
load,
|
|
logical_shift_right,
|
|
multiply,
|
|
@"or",
|
|
ret,
|
|
sdiv,
|
|
shift_left,
|
|
store,
|
|
udiv,
|
|
@"unreachable",
|
|
xor,
|
|
};
|
|
};
|
|
|
|
pub const Constant = opaque {
|
|
pub const Function = opaque {
|
|
const getArgument = bindings.NativityLLVMFunctionGetArgument;
|
|
const getArguments = bindings.NativityLLVMFunctionGetArguments;
|
|
const getType = bindings.NativityLLVMFunctionGetType;
|
|
// const addAttributeKey = bindings.NativityLLVMFunctionAddAttributeKey;
|
|
const verify = bindings.NativityLLVMVerifyFunction;
|
|
const toString = bindings.NativityLLVMFunctionToString;
|
|
const setCallingConvention = bindings.NativityLLVMFunctionSetCallingConvention;
|
|
const getCallingConvention = bindings.NativityLLVMFunctionGetCallingConvention;
|
|
const setSubprogram = bindings.NativityLLVMFunctionSetSubprogram;
|
|
const getSubprogram = bindings.NativityLLVMFunctionGetSubprogram;
|
|
const setAttributes = bindings.NativityLLVMFunctionSetAttributes;
|
|
|
|
fn toValue(this: *@This()) *LLVM.Value {
|
|
return @ptrCast(this);
|
|
}
|
|
|
|
fn toConstant(this: *@This()) *Constant {
|
|
return @ptrCast(this);
|
|
}
|
|
|
|
pub const CallingConvention = enum(c_uint) {
|
|
/// The default llvm calling convention, compatible with C. This convention
|
|
/// is the only one that supports varargs calls. As with typical C calling
|
|
/// conventions, the callee/caller have to tolerate certain amounts of
|
|
/// prototype mismatch.
|
|
C = 0,
|
|
|
|
// Generic LLVM calling conventions. None of these support varargs calls,
|
|
// and all assume that the caller and callee prototype exactly match.
|
|
|
|
/// Attempts to make calls as fast as possible (e.g. by passing things in
|
|
/// registers).
|
|
Fast = 8,
|
|
|
|
/// Attempts to make code in the caller as efficient as possible under the
|
|
/// assumption that the call is not commonly executed. As such, these calls
|
|
/// often preserve all registers so that the call does not break any live
|
|
/// ranges in the caller side.
|
|
Cold = 9,
|
|
|
|
/// Used by the Glasgow Haskell Compiler (GHC).
|
|
GHC = 10,
|
|
|
|
/// Used by the High-Performance Erlang Compiler (HiPE).
|
|
HiPE = 11,
|
|
|
|
/// Used for stack based JavaScript calls
|
|
WebKit_JS = 12,
|
|
|
|
/// Used for dynamic register based calls (e.g. stackmap and patchpoint
|
|
/// intrinsics).
|
|
AnyReg = 13,
|
|
|
|
/// Used for runtime calls that preserves most registers.
|
|
PreserveMost = 14,
|
|
|
|
/// Used for runtime calls that preserves (almost) all registers.
|
|
PreserveAll = 15,
|
|
|
|
/// Calling convention for Swift.
|
|
Swift = 16,
|
|
|
|
/// Used for access functions.
|
|
CXX_FAST_TLS = 17,
|
|
|
|
/// Attemps to make calls as fast as possible while guaranteeing that tail
|
|
/// call optimization can always be performed.
|
|
Tail = 18,
|
|
|
|
/// Special calling convention on Windows for calling the Control Guard
|
|
/// Check ICall funtion. The function takes exactly one argument (address of
|
|
/// the target function) passed in the first argument register, and has no
|
|
/// return value. All register values are preserved.
|
|
CFGuard_Check = 19,
|
|
|
|
/// This follows the Swift calling convention in how arguments are passed
|
|
/// but guarantees tail calls will be made by making the callee clean up
|
|
/// their stack.
|
|
SwiftTail = 20,
|
|
|
|
/// This is the start of the target-specific calling conventions, e.g.
|
|
/// fastcall and thiscall on X86.
|
|
// FirstTargetCC = 64,
|
|
|
|
/// stdcall is mostly used by the Win32 API. It is basically the same as the
|
|
/// C convention with the difference in that the callee is responsible for
|
|
/// popping the arguments from the stack.
|
|
X86_StdCall = 64,
|
|
|
|
/// 'fast' analog of X86_StdCall. Passes first two arguments in ECX:EDX
|
|
/// registers, others - via stack. Callee is responsible for stack cleaning.
|
|
X86_FastCall = 65,
|
|
|
|
/// ARM Procedure Calling Standard (obsolete, but still used on some
|
|
/// targets).
|
|
ARM_APCS = 66,
|
|
|
|
/// ARM Architecture Procedure Calling Standard calling convention (aka
|
|
/// EABI). Soft float variant.
|
|
ARM_AAPCS = 67,
|
|
|
|
/// Same as ARM_AAPCS, but uses hard floating point ABI.
|
|
ARM_AAPCS_VFP = 68,
|
|
|
|
/// Used for MSP430 interrupt routines.
|
|
MSP430_INTR = 69,
|
|
|
|
/// Similar to X86_StdCall. Passes first argument in ECX, others via stack.
|
|
/// Callee is responsible for stack cleaning. MSVC uses this by default for
|
|
/// methods in its ABI.
|
|
X86_ThisCall = 70,
|
|
|
|
/// Call to a PTX kernel. Passes all arguments in parameter space.
|
|
PTX_Kernel = 71,
|
|
|
|
/// Call to a PTX device function. Passes all arguments in register or
|
|
/// parameter space.
|
|
PTX_Device = 72,
|
|
|
|
/// Used for SPIR non-kernel device functions. No lowering or expansion of
|
|
/// arguments. Structures are passed as a pointer to a struct with the
|
|
/// byval attribute. Functions can only call SPIR_FUNC and SPIR_KERNEL
|
|
/// functions. Functions can only have zero or one return values. Variable
|
|
/// arguments are not allowed, except for printf. How arguments/return
|
|
/// values are lowered are not specified. Functions are only visible to the
|
|
/// devices.
|
|
SPIR_FUNC = 75,
|
|
|
|
/// Used for SPIR kernel functions. Inherits the restrictions of SPIR_FUNC,
|
|
/// except it cannot have non-void return values, it cannot have variable
|
|
/// arguments, it can also be called by the host or it is externally
|
|
/// visible.
|
|
SPIR_KERNEL = 76,
|
|
|
|
/// Used for Intel OpenCL built-ins.
|
|
Intel_OCL_BI = 77,
|
|
|
|
/// The C convention as specified in the x86-64 supplement to the System V
|
|
/// ABI, used on most non-Windows systems.
|
|
X86_64_SysV = 78,
|
|
|
|
/// The C convention as implemented on Windows/x86-64 and AArch64. It
|
|
/// differs from the more common \c X86_64_SysV convention in a number of
|
|
/// ways, most notably in that XMM registers used to pass arguments are
|
|
/// shadowed by GPRs, and vice versa. On AArch64, this is identical to the
|
|
/// normal C (AAPCS) calling convention for normal functions, but floats are
|
|
/// passed in integer registers to variadic functions.
|
|
Win64 = 79,
|
|
|
|
/// MSVC calling convention that passes vectors and vector aggregates in SSE
|
|
/// registers.
|
|
X86_VectorCall = 80,
|
|
|
|
/// Used by HipHop Virtual Machine (HHVM) to perform calls to and from
|
|
/// translation cache, and for calling PHP functions. HHVM calling
|
|
/// convention supports tail/sibling call elimination.
|
|
HHVM = 81,
|
|
|
|
/// HHVM calling convention for invoking C/C++ helpers.
|
|
HHVM_C = 82,
|
|
|
|
/// x86 hardware interrupt context. Callee may take one or two parameters,
|
|
/// where the 1st represents a pointer to hardware context frame and the 2nd
|
|
/// represents hardware error code, the presence of the later depends on the
|
|
/// interrupt vector taken. Valid for both 32- and 64-bit subtargets.
|
|
X86_INTR = 83,
|
|
|
|
/// Used for AVR interrupt routines.
|
|
AVR_INTR = 84,
|
|
|
|
/// Used for AVR signal routines.
|
|
AVR_SIGNAL = 85,
|
|
|
|
/// Used for special AVR rtlib functions which have an "optimized"
|
|
/// convention to preserve registers.
|
|
AVR_BUILTIN = 86,
|
|
|
|
/// Used for Mesa vertex shaders, or AMDPAL last shader stage before
|
|
/// rasterization (vertex shader if tessellation and geometry are not in
|
|
/// use, or otherwise copy shader if one is needed).
|
|
AMDGPU_VS = 87,
|
|
|
|
/// Used for Mesa/AMDPAL geometry shaders.
|
|
AMDGPU_GS = 88,
|
|
|
|
/// Used for Mesa/AMDPAL pixel shaders.
|
|
AMDGPU_PS = 89,
|
|
|
|
/// Used for Mesa/AMDPAL compute shaders.
|
|
AMDGPU_CS = 90,
|
|
|
|
/// Used for AMDGPU code object kernels.
|
|
AMDGPU_KERNEL = 91,
|
|
|
|
/// Register calling convention used for parameters transfer optimization
|
|
X86_RegCall = 92,
|
|
|
|
/// Used for Mesa/AMDPAL hull shaders (= tessellation control shaders).
|
|
AMDGPU_HS = 93,
|
|
|
|
/// Used for special MSP430 rtlib functions which have an "optimized"
|
|
/// convention using additional registers.
|
|
MSP430_BUILTIN = 94,
|
|
|
|
/// Used for AMDPAL vertex shader if tessellation is in use.
|
|
AMDGPU_LS = 95,
|
|
|
|
/// Used for AMDPAL shader stage before geometry shader if geometry is in
|
|
/// use. So either the domain (= tessellation evaluation) shader if
|
|
/// tessellation is in use, or otherwise the vertex shader.
|
|
AMDGPU_ES = 96,
|
|
|
|
/// Used between AArch64 Advanced SIMD functions
|
|
AArch64_VectorCall = 97,
|
|
|
|
/// Used between AArch64 SVE functions
|
|
AArch64_SVE_VectorCall = 98,
|
|
|
|
/// For emscripten __invoke_* functions. The first argument is required to
|
|
/// be the function ptr being indirectly called. The remainder matches the
|
|
/// regular calling convention.
|
|
WASM_EmscriptenInvoke = 99,
|
|
|
|
/// Used for AMD graphics targets.
|
|
AMDGPU_Gfx = 100,
|
|
|
|
/// Used for M68k interrupt routines.
|
|
M68k_INTR = 101,
|
|
|
|
/// Preserve X0-X13, X19-X29, SP, Z0-Z31, P0-P15.
|
|
AArch64_SME_ABI_Support_Routines_PreserveMost_From_X0 = 102,
|
|
|
|
/// Preserve X2-X15, X19-X29, SP, Z0-Z31, P0-P15.
|
|
AArch64_SME_ABI_Support_Routines_PreserveMost_From_X2 = 103,
|
|
|
|
/// The highest possible ID. Must be some 2^k - 1.
|
|
MaxID = 1023,
|
|
};
|
|
};
|
|
|
|
pub const Int = opaque {
|
|
fn toValue(this: *@This()) *LLVM.Value {
|
|
return @ptrCast(this);
|
|
}
|
|
fn toConstant(this: *@This()) *Constant {
|
|
return @ptrCast(this);
|
|
}
|
|
};
|
|
|
|
pub const GlobalObject = opaque{
|
|
const setAlignment = bindings.NativityLLVMGlobalObjectSetAlignment;
|
|
};
|
|
|
|
pub const GlobalVariable = opaque {
|
|
pub const setInitializer = bindings.NativityLLVMGlobalVariableSetInitializer;
|
|
pub const addDebugInfo = bindings.NativityLLVMDebugInfoGlobalVariableAddDebugInfo;
|
|
|
|
fn toValue(this: *@This()) *LLVM.Value {
|
|
return @ptrCast(this);
|
|
}
|
|
fn toConstant(this: *@This()) *Constant {
|
|
return @ptrCast(this);
|
|
}
|
|
|
|
fn toGlobalObject(this: *@This()) *LLVM.Value.Constant.GlobalObject{
|
|
return @ptrCast(this);
|
|
}
|
|
};
|
|
|
|
pub const PointerNull = opaque {
|
|
fn toValue(this: *@This()) *LLVM.Value {
|
|
return @ptrCast(this);
|
|
}
|
|
fn toConstant(this: *@This()) *Constant {
|
|
return @ptrCast(this);
|
|
}
|
|
};
|
|
|
|
pub const Undefined = opaque {
|
|
fn toConstant(this: *@This()) *Constant {
|
|
return @ptrCast(this);
|
|
}
|
|
fn toValue(this: *@This()) *LLVM.Value {
|
|
return @ptrCast(this);
|
|
}
|
|
};
|
|
|
|
pub const Poison = opaque {
|
|
fn toConstant(this: *@This()) *Constant {
|
|
return @ptrCast(this);
|
|
}
|
|
fn toValue(this: *@This()) *LLVM.Value {
|
|
return @ptrCast(this);
|
|
}
|
|
};
|
|
|
|
fn toValue(this: *@This()) *LLVM.Value {
|
|
return @ptrCast(this);
|
|
}
|
|
|
|
const toInt = bindings.NativityLLVMConstantToInt;
|
|
};
|
|
|
|
pub const InlineAssembly = opaque {
|
|
pub const Dialect = enum(c_uint) {
|
|
@"at&t",
|
|
intel,
|
|
};
|
|
const get = bindings.NativityLLVMGetInlineAssembly;
|
|
fn toValue(this: *@This()) *LLVM.Value {
|
|
return @ptrCast(this);
|
|
}
|
|
};
|
|
|
|
pub const Error = error{
|
|
constant_struct,
|
|
constant_int,
|
|
constant_array,
|
|
inline_assembly,
|
|
global_variable,
|
|
intrinsic,
|
|
};
|
|
};
|
|
};
|
|
|
|
pub fn panic(message: []const u8, stack_trace: ?*std.builtin.StackTrace, return_address: ?usize) noreturn {
|
|
@setCold(true);
|
|
const print_stack_trace = configuration.print_stack_trace;
|
|
switch (print_stack_trace) {
|
|
true => @call(.always_inline, std.builtin.default_panic, .{ message, stack_trace, return_address }),
|
|
false => {
|
|
compiler.write("\nPANIC: ");
|
|
compiler.write(message);
|
|
compiler.write("\n");
|
|
fail();
|
|
},
|
|
}
|
|
}
|