7857 lines
322 KiB
Zig
7857 lines
322 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 hash_bytes = library.my_hash;
|
|
const byte_equal = library.byte_equal;
|
|
const Atomic = std.atomic.Value;
|
|
|
|
const weak_memory_model = switch (builtin.cpu.arch) {
|
|
.aarch64 => true,
|
|
.x86_64 => false,
|
|
else => @compileError("Error: unknown arch"),
|
|
};
|
|
|
|
fn exit(exit_code: u8) noreturn {
|
|
@setCold(true);
|
|
// if (builtin.mode == .Debug) {
|
|
if (exit_code != 0) @breakpoint();
|
|
// }
|
|
std.posix.exit(exit_code);
|
|
}
|
|
|
|
fn is_space(ch: u8) bool {
|
|
const is_whitespace = ch == ' ';
|
|
const is_tab = ch == '\t';
|
|
const is_line_feed = ch == '\n';
|
|
const is_carry_return = ch == '\r';
|
|
const result = (is_whitespace or is_tab) or (is_line_feed or is_carry_return);
|
|
return result;
|
|
}
|
|
|
|
pub fn write(string: []const u8) void {
|
|
std.io.getStdOut().writeAll(string) catch unreachable;
|
|
}
|
|
|
|
fn exit_with_error(string: []const u8) noreturn {
|
|
@setCold(true);
|
|
write("error: ");
|
|
write(string);
|
|
write("\n");
|
|
exit(1);
|
|
}
|
|
|
|
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,
|
|
alignment: u32,
|
|
value: Value,
|
|
id: GlobalSymbol.Id,
|
|
|
|
pub fn get_type(global_symbol: *GlobalSymbol) *Type {
|
|
return global_symbol.type;
|
|
}
|
|
|
|
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,
|
|
value: Value,
|
|
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,
|
|
appointee_type: ?*Type = null,
|
|
instruction: Instruction,
|
|
alignment: u32,
|
|
initial_value: *Value,
|
|
|
|
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 skip_space(parser: *Parser, file: []const u8) void {
|
|
const original_i = parser.i;
|
|
|
|
if (original_i == file.len or !is_space(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)) {
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
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,
|
|
'=' => {
|
|
exit_with_error("TODO: field default value");
|
|
},
|
|
else => exit(1),
|
|
}
|
|
|
|
return ParseFieldData{
|
|
.type = field_type,
|
|
.name = field_name,
|
|
.line = field_line,
|
|
.column = field_column,
|
|
};
|
|
}
|
|
|
|
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 {
|
|
exit_with_error("Unknown intrinsic");
|
|
};
|
|
|
|
switch (intrinsic_id) {
|
|
.assert => {
|
|
const condition = parser.parse_condition(analyzer, thread, file);
|
|
const assert_true_block = create_basic_block(thread);
|
|
const assert_false_block = create_basic_block(thread);
|
|
_ = emit_branch(analyzer, thread, .{
|
|
.condition = condition,
|
|
.taken = assert_true_block,
|
|
.not_taken = assert_false_block,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
analyzer.current_basic_block = assert_false_block;
|
|
emit_unreachable(analyzer, thread, .{
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
analyzer.current_basic_block = assert_true_block;
|
|
|
|
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 exit(1);
|
|
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) {
|
|
exit(1);
|
|
}
|
|
|
|
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 {
|
|
exit(1);
|
|
}
|
|
},
|
|
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 {
|
|
exit(1);
|
|
}
|
|
},
|
|
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;
|
|
},
|
|
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] != '"') {
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
const identifier = file[identifier_start..parser.i];
|
|
return identifier;
|
|
}
|
|
} else {
|
|
exit(1);
|
|
}
|
|
} else {
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
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))) {
|
|
exit(1);
|
|
}
|
|
const hash = intern_identifier(&thread.identifiers, identifier);
|
|
return hash;
|
|
}
|
|
|
|
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) {
|
|
exit(1);
|
|
}
|
|
|
|
while (src[parser.i] != '"') : (parser.i += 1) {
|
|
if (src[parser.i] == '\\') exit(1);
|
|
}
|
|
|
|
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) {
|
|
exit(1);
|
|
}
|
|
} else {
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
pub fn parse_hex(slice: []const u8) u64 {
|
|
var i = slice.len;
|
|
var integer: u64 = 0;
|
|
var factor: u64 = 1;
|
|
|
|
while (i > 0) {
|
|
i -= 1;
|
|
const ch = slice[i];
|
|
switch (ch) {
|
|
'0'...'9' => {
|
|
const int = ch - '0';
|
|
const extra = int * factor;
|
|
integer += extra;
|
|
factor *= 16;
|
|
},
|
|
'a'...'f' => {
|
|
const int = ch - 'a' + 10;
|
|
const extra = int * factor;
|
|
integer += extra;
|
|
factor *= 16;
|
|
},
|
|
'A'...'F' => {
|
|
const int = ch - 'A' + 10;
|
|
const extra = int * factor;
|
|
integer += extra;
|
|
factor *= 16;
|
|
},
|
|
else => exit(1),
|
|
}
|
|
}
|
|
|
|
return integer;
|
|
}
|
|
|
|
fn parse_type_expression(parser: *Parser, thread: *Thread, file: *File, current_scope: *Scope) *Type {
|
|
const src = file.source_code;
|
|
const starting_ch = src[parser.i];
|
|
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 integer_start = is_start_s or is_start_u;
|
|
const is_number_type_start = integer_start or float_start;
|
|
|
|
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 => exit(1),
|
|
};
|
|
parser.i += decimal_digit_count;
|
|
|
|
const index = bit_count - 1 + @intFromEnum(signedness) * @as(usize, 64);
|
|
const result = &thread.integers[index];
|
|
assert(result.bit_count == bit_count);
|
|
assert(result.signedness == signedness);
|
|
return &result.type;
|
|
} else if (float_start) {
|
|
exit(1);
|
|
} else {
|
|
unreachable;
|
|
}
|
|
} else {
|
|
exit(1);
|
|
}
|
|
} 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)),
|
|
}
|
|
unreachable;
|
|
}
|
|
|
|
exit_with_error("Unrecognized type expression");
|
|
}
|
|
|
|
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) 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;
|
|
},
|
|
}
|
|
exit(1);
|
|
} else if (is_valid_after_zero) {
|
|
parser.i += 1;
|
|
const constant_int = create_constant_int(thread, .{
|
|
.n = 0,
|
|
.type = ty,
|
|
});
|
|
return constant_int;
|
|
} else {
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
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,
|
|
};
|
|
|
|
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;
|
|
}
|
|
|
|
parser.expect_character(src, '.');
|
|
const name = parser.parse_identifier(thread, src);
|
|
for (names_initialized) |initialized_name| {
|
|
if (initialized_name == name) {
|
|
exit(1);
|
|
}
|
|
}
|
|
const field_index = for (fields) |field| {
|
|
if (field.name == name) {
|
|
break field.index;
|
|
}
|
|
} else {
|
|
exit(1);
|
|
};
|
|
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 => exit(1),
|
|
}
|
|
|
|
return ParseFieldInitialization{
|
|
.value = field_value,
|
|
.name = name,
|
|
.index = field_index,
|
|
};
|
|
}
|
|
|
|
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,
|
|
};
|
|
|
|
const unary: Unary = switch (src[parser.i]) {
|
|
'A'...'Z', 'a'...'z', '_' => Unary.none,
|
|
'0'...'9' => Unary.none,
|
|
'~' => 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 exit(1);
|
|
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;
|
|
|
|
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);
|
|
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 {
|
|
exit_with_error("TODO: runtime struct initialization");
|
|
}
|
|
} else {
|
|
exit(1);
|
|
}
|
|
},
|
|
.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 int_ty = constant_int.type.get_payload(.integer);
|
|
const field_bit_count = int_ty.bit_count;
|
|
const field_value = constant_int.n;
|
|
const offset_value = field_value << @as(u6, @intCast(bit_offset));
|
|
result |= offset_value;
|
|
bit_offset += 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
|
|
// const constant_bitfield = thread.constant_bitfields.append(.{
|
|
// .value = .{
|
|
// .sema = .{
|
|
// .thread = thread.get_index(),
|
|
// .resolved = true,
|
|
// .id = .constant_bitfield,
|
|
// },
|
|
// },
|
|
// .n = result,
|
|
// .type = ty,
|
|
// });
|
|
// return &constant_bitfield.value;
|
|
} else {
|
|
unreachable;
|
|
}
|
|
} else {
|
|
exit_with_error("TODO: runtime struct initialization");
|
|
}
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
},
|
|
'[' => {
|
|
// This is an array expression
|
|
parser.i += 1;
|
|
|
|
const ty = maybe_type orelse exit(1);
|
|
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) {
|
|
exit(1);
|
|
}
|
|
|
|
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) {
|
|
assert(unary == .none);
|
|
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 if (is_alpha_start) {
|
|
var resolved = true;
|
|
const identifier = parser.parse_identifier(thread, src);
|
|
|
|
if (analyzer.current_scope.get_declaration(identifier)) |lookup_result| {
|
|
switch (src[parser.i]) {
|
|
'(' => {
|
|
parser.i += 1;
|
|
parser.skip_space(src);
|
|
|
|
const FunctionCallData = struct{
|
|
type: *Type.Function,
|
|
value: *Value,
|
|
};
|
|
const declaration = lookup_result.declaration.*;
|
|
const function_call_data: FunctionCallData = switch (declaration.id) {
|
|
.local => local: {
|
|
const local_declaration = declaration.get_payload(.local);
|
|
const local_symbol = local_declaration.to_symbol();
|
|
break :local switch (local_symbol.type.sema.id) {
|
|
.pointer => p: {
|
|
const appointee_type = local_symbol.appointee_type.?;
|
|
break :p switch (appointee_type.sema.id) {
|
|
.function => f: {
|
|
const function_type = appointee_type.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 :f .{
|
|
.type = function_type,
|
|
.value = &load.instruction.value,
|
|
};
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
},
|
|
.global => g: {
|
|
const global_declaration = lookup_result.declaration.*.get_payload(.global);
|
|
break :g switch (global_declaration.id) {
|
|
.global_symbol => gs: {
|
|
const global_symbol = global_declaration.to_symbol();
|
|
break :gs switch (global_symbol.id) {
|
|
.function_definition => f: {
|
|
const function_definition = global_symbol.get_payload(.function_definition);
|
|
const function_type = function_definition.declaration.get_type();
|
|
break :f .{
|
|
.type = function_type,
|
|
.value = &function_definition.declaration.global_symbol.value,
|
|
};
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
},
|
|
.argument => unreachable,
|
|
.@"struct" => unreachable,
|
|
.bitfield => unreachable,
|
|
};
|
|
|
|
const function_type = function_call_data.type;
|
|
const function_value = function_call_data.value;
|
|
const declaration_argument_count = function_type.argument_types.len;
|
|
var argument_values = PinnedArray(*Value){};
|
|
while (true) {
|
|
parser.skip_space(src);
|
|
|
|
if (src[parser.i] == ')') {
|
|
break;
|
|
}
|
|
|
|
const argument_index = argument_values.length;
|
|
if (argument_index >= declaration_argument_count) {
|
|
exit(1);
|
|
}
|
|
const expected_argument_type = function_type.argument_types[argument_index];
|
|
const passed_argument_value = parser.parse_expression(analyzer, thread, file, expected_argument_type, .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,
|
|
}),
|
|
.callable = function_value,
|
|
.arguments = argument_values.const_slice(),
|
|
});
|
|
analyzer.append_instruction(&call.instruction);
|
|
return &call.instruction.value;
|
|
},
|
|
'.' => {
|
|
switch (lookup_result.declaration.*.id) {
|
|
.global => {
|
|
const global_declaration = lookup_result.declaration.*.get_payload(.global);
|
|
switch (global_declaration.id) {
|
|
.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 = &.{},
|
|
});
|
|
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)),
|
|
}
|
|
},
|
|
.local => {
|
|
parser.i += 1;
|
|
|
|
const local_declaration = lookup_result.declaration.*.get_payload(.local);
|
|
const local_symbol = local_declaration.to_symbol();
|
|
|
|
const result = parser.parse_field_access(analyzer, thread, file, maybe_type, side, &local_symbol.instruction.value, local_symbol.type, debug_line, debug_column);
|
|
return result;
|
|
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
},
|
|
'[' => {
|
|
parser.i += 1;
|
|
|
|
parser.skip_space(src);
|
|
|
|
const declaration_type = switch (lookup_result.declaration.*.id) {
|
|
.local => block: {
|
|
const local_declaration = lookup_result.declaration.*.get_payload(.local);
|
|
const local_symbol = local_declaration.to_symbol();
|
|
break :block local_symbol.type;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
const declaration_value = switch (lookup_result.declaration.*.id) {
|
|
.local => block: {
|
|
const local_declaration = lookup_result.declaration.*.get_payload(.local);
|
|
const local_symbol = local_declaration.to_symbol();
|
|
break :block &local_symbol.instruction.value;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
const declaration_element_type = switch (declaration_type.sema.id) {
|
|
.array => block: {
|
|
const array_type = declaration_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 = thread.geps.append(.{
|
|
.instruction = new_instruction(thread, .{
|
|
.id = .get_element_pointer,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
}),
|
|
.pointer = declaration_value,
|
|
.index = index,
|
|
.type = declaration_element_type,
|
|
.aggregate_type = declaration_type,
|
|
.is_struct = false,
|
|
});
|
|
analyzer.append_instruction(&gep.instruction);
|
|
|
|
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;
|
|
},
|
|
};
|
|
},
|
|
'&' => {
|
|
parser.i += 1;
|
|
|
|
switch (lookup_result.declaration.*.id) {
|
|
.local => {
|
|
const local_declaration = lookup_result.declaration.*.get_payload(.local);
|
|
const local_symbol = local_declaration.to_symbol();
|
|
return &local_symbol.instruction.value;
|
|
},
|
|
.global => {
|
|
const global_declaration = lookup_result.declaration.*.get_payload(.global);
|
|
const global_symbol = global_declaration.to_symbol();
|
|
return &global_symbol.value;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
},
|
|
'@' => {
|
|
parser.i += 1;
|
|
|
|
const local_declaration = lookup_result.declaration.*.get_payload(.local);
|
|
const local_symbol = local_declaration.to_symbol();
|
|
assert(local_symbol.type.sema.id == .pointer);
|
|
assert(local_symbol.appointee_type != null);
|
|
|
|
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,
|
|
});
|
|
|
|
return switch (side) {
|
|
.left => &load.instruction.value,
|
|
.right => block: {
|
|
const pointer_load_type = local_symbol.appointee_type.?;
|
|
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;
|
|
},
|
|
};
|
|
},
|
|
' ', ',', ';', ')' => {
|
|
const declaration_value = switch (lookup_result.declaration.*.id) {
|
|
.local => block: {
|
|
const local_declaration = lookup_result.declaration.*.get_payload(.local);
|
|
const local_symbol = local_declaration.to_symbol();
|
|
|
|
if (maybe_type) |ty| {
|
|
switch (typecheck(ty, local_symbol.type)) {
|
|
.success => {},
|
|
}
|
|
}
|
|
|
|
break :block switch (side) {
|
|
.right => b: {
|
|
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 :b &load.instruction.value;
|
|
},
|
|
.left => &local_symbol.instruction.value,
|
|
};
|
|
},
|
|
.argument => block: {
|
|
const argument_declaration = lookup_result.declaration.*.get_payload(.argument);
|
|
const argument_symbol = argument_declaration.to_symbol();
|
|
if (maybe_type) |ty| {
|
|
switch (typecheck(ty, argument_symbol.type)) {
|
|
.success => {},
|
|
}
|
|
}
|
|
break :block switch (side) {
|
|
.right => b: {
|
|
const load = emit_load(analyzer, thread, .{
|
|
.value = &argument_symbol.instruction.value,
|
|
.type = argument_symbol.type,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
break :b &load.instruction.value;
|
|
},
|
|
.left => &argument_symbol.instruction.value,
|
|
};
|
|
},
|
|
.global => block: {
|
|
const global_declaration = lookup_result.declaration.*.get_payload(.global);
|
|
const global_symbol = global_declaration.to_symbol();
|
|
|
|
const global_type = global_symbol.get_type();
|
|
|
|
if (maybe_type) |ty| {
|
|
switch (typecheck(ty, global_type)) {
|
|
.success => {},
|
|
}
|
|
}
|
|
|
|
break :block switch (side) {
|
|
.right => b: {
|
|
const load = emit_load(analyzer, thread, .{
|
|
.value = &global_symbol.value,
|
|
.type = global_symbol.type,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
break :b &load.instruction.value;
|
|
},
|
|
.left => &global_symbol.value,
|
|
};
|
|
},
|
|
.@"struct" => unreachable,
|
|
.bitfield => unreachable,
|
|
};
|
|
|
|
const declaration_type = declaration_value.get_type();
|
|
|
|
if (unary != .none) assert(side == .right);
|
|
if (side == .left) assert(side == .left);
|
|
|
|
return switch (unary) {
|
|
.none => declaration_value,
|
|
.one_complement => block: {
|
|
const operand = create_constant_int(thread, .{
|
|
.type = declaration_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 = declaration_value,
|
|
.right = &operand.value,
|
|
.id = .xor,
|
|
.type = declaration_type,
|
|
});
|
|
break :block &xor.instruction.value;
|
|
},
|
|
};
|
|
},
|
|
else => exit(1),
|
|
}
|
|
} else {
|
|
write("Unable to find declaration: '");
|
|
const name =thread.identifiers.get(identifier).?;
|
|
write(name);
|
|
write("'\n");
|
|
exit(1);
|
|
}
|
|
} else {
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
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);
|
|
const is_alpha_start = is_alphabetic(starting_ch);
|
|
_ = is_alpha_start; // autofix
|
|
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;
|
|
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 exit(1);
|
|
|
|
const index_value = create_constant_int(thread, .{
|
|
.type = &thread.integers[31].type,
|
|
.n = field_index,
|
|
});
|
|
|
|
const gep = thread.geps.append(.{
|
|
.instruction = new_instruction(thread, .{
|
|
.id = .get_element_pointer,
|
|
.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,
|
|
});
|
|
analyzer.append_instruction(&gep.instruction);
|
|
|
|
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 exit(1);
|
|
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 (src[parser.i] == ';') {
|
|
// break;
|
|
// }
|
|
|
|
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;
|
|
exit(1);
|
|
}
|
|
|
|
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 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,
|
|
};
|
|
|
|
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,
|
|
});
|
|
|
|
fn is_constant(value: *Value) bool {
|
|
return switch (value.sema.id) {
|
|
.constant_int => true,
|
|
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.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 => {
|
|
return &instance.threads[value.sema.thread].pointer;
|
|
},
|
|
.cast => {
|
|
const cast = instruction.get_payload(.cast);
|
|
return cast.type;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
},
|
|
.constant_int => {
|
|
const constant_int = value.get_payload(.constant_int);
|
|
return constant_int.type;
|
|
},
|
|
.global_symbol => {
|
|
return &instance.threads[value.sema.thread].pointer;
|
|
},
|
|
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,
|
|
alignment: u32,
|
|
|
|
const Id = enum(u8){
|
|
unresolved,
|
|
void,
|
|
integer,
|
|
array,
|
|
pointer,
|
|
function,
|
|
@"struct",
|
|
bitfield,
|
|
};
|
|
|
|
const Integer = struct {
|
|
type: Type,
|
|
signedness: Signedness,
|
|
bit_count: u16,
|
|
|
|
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,
|
|
argument_types: []const *Type,
|
|
return_type: *Type,
|
|
};
|
|
|
|
const Struct = struct {
|
|
type: Type,
|
|
declaration: Declaration,
|
|
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 id_to_type_map = std.EnumArray(Id, type).init(.{
|
|
.unresolved = void,
|
|
.void = void,
|
|
.integer = Integer,
|
|
.array = Array,
|
|
.pointer = void,
|
|
.function = Type.Function,
|
|
.@"struct" = Type.Struct,
|
|
.bitfield = Type.Bitfield,
|
|
});
|
|
|
|
fn get_payload(ty: *Type, comptime id: Id) *id_to_type_map.get(id) {
|
|
assert(ty.sema.id == id);
|
|
return @fieldParentPtr("type", ty);
|
|
}
|
|
|
|
fn get_bit_count(ty: *Type) u32 {
|
|
switch (ty.sema.id) {
|
|
.integer => {
|
|
const integer_type = ty.get_payload(.integer);
|
|
return integer_type.bit_count;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
}
|
|
|
|
fn clone(ty: *Type, args: struct{
|
|
destination_thread: *Thread,
|
|
source_thread_index: u16,
|
|
}) *Type {
|
|
assert(ty.sema.resolved);
|
|
assert(ty.sema.thread == args.source_thread_index);
|
|
|
|
switch (ty.sema.id) {
|
|
.function => {
|
|
const source_function_type = ty.get_payload(.function);
|
|
const return_type = source_function_type.return_type.clone(.{
|
|
.destination_thread = args.destination_thread,
|
|
.source_thread_index = args.source_thread_index,
|
|
});
|
|
var argument_types = PinnedArray(*Type){};
|
|
for (source_function_type.argument_types) |argument_type| {
|
|
const new = argument_type.clone(.{
|
|
.destination_thread = args.destination_thread,
|
|
.source_thread_index = args.source_thread_index,
|
|
});
|
|
_ = argument_types.append(new);
|
|
}
|
|
const function_type = args.destination_thread.function_types.append(.{
|
|
.type = source_function_type.type,
|
|
.argument_types = argument_types.const_slice(),
|
|
.return_type = return_type,
|
|
});
|
|
function_type.type.llvm = null;
|
|
function_type.type.sema.thread = args.destination_thread.get_index();
|
|
return &function_type.type;
|
|
},
|
|
.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));
|
|
return &args.destination_thread.integers[index].type;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
}
|
|
};
|
|
|
|
const Keyword = enum{
|
|
@"break",
|
|
@"else",
|
|
@"for",
|
|
@"if",
|
|
@"loop",
|
|
@"orelse",
|
|
};
|
|
|
|
const Intrinsic = enum{
|
|
assert,
|
|
leading_zeroes,
|
|
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);
|
|
return @ptrCast(scope.get_declaration_one_level(name));
|
|
}
|
|
|
|
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) {
|
|
// function_definition,
|
|
// function_declaration,
|
|
file,
|
|
unresolved_import,
|
|
global_symbol,
|
|
// global_variable,
|
|
};
|
|
|
|
const id_to_global_declaration_map = std.EnumArray(Id, type).init(.{
|
|
// .function_definition = Function,
|
|
// .function_declaration = Function.Declaration,
|
|
.file = File,
|
|
.global_symbol = GlobalSymbol,
|
|
// .global_variable = GlobalVariable,
|
|
.unresolved_import = Import,
|
|
});
|
|
|
|
fn get_payload(global_declaration: *GlobalDeclaration, comptime id: Id) *id_to_global_declaration_map.get(id) {
|
|
assert(global_declaration.id == id);
|
|
// Function definition has to be upcast twice
|
|
// if (id == .function_definition) {
|
|
// const global_symbol: *GlobalSymbol = @alignCast(@fieldParentPtr("global_declaration", global_declaration));
|
|
// const function_declaration: *Function.Declaration = @alignCast(@fieldParentPtr("global_symbol", global_symbol));
|
|
// const function_definition: *Function = @alignCast(@fieldParentPtr("declaration", function_declaration));
|
|
// return function_definition;
|
|
// }
|
|
|
|
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{
|
|
calling_convention: CallingConvention = .custom,
|
|
};
|
|
|
|
const Attribute = enum{
|
|
cc,
|
|
|
|
pub const Mask = std.EnumSet(Function.Attribute);
|
|
};
|
|
|
|
const Declaration = struct {
|
|
attributes: Attributes = .{},
|
|
global_symbol: GlobalSymbol,
|
|
|
|
fn get_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 result = destination_thread.external_functions.append(.{
|
|
.global_symbol = .{
|
|
.type = declaration.global_symbol.type.clone(.{
|
|
.destination_thread = destination_thread,
|
|
.source_thread_index = source_thread_index,
|
|
}),
|
|
.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 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{
|
|
argument_storage,
|
|
branch,
|
|
call,
|
|
cast,
|
|
debug_argument,
|
|
debug_local,
|
|
get_element_pointer,
|
|
integer_binary_operation,
|
|
integer_compare,
|
|
jump,
|
|
leading_zeroes,
|
|
load,
|
|
local_symbol,
|
|
phi,
|
|
ret,
|
|
ret_void,
|
|
store,
|
|
trailing_zeroes,
|
|
@"unreachable",
|
|
};
|
|
|
|
const id_to_instruction_map = std.EnumArray(Id, type).init(.{
|
|
.argument_storage = ArgumentSymbol,
|
|
.branch = Branch,
|
|
.call = Call,
|
|
.cast = Cast,
|
|
.debug_argument = DebugArgument,
|
|
.debug_local = DebugLocal,
|
|
.get_element_pointer = GEP,
|
|
.integer_binary_operation = IntegerBinaryOperation,
|
|
.integer_compare = IntegerCompare,
|
|
.jump = Jump,
|
|
.leading_zeroes = LeadingZeroes,
|
|
.local_symbol = LocalSymbol,
|
|
.load = Load,
|
|
.phi = Phi,
|
|
.ret = Return,
|
|
.ret_void = void,
|
|
.store = Store,
|
|
.trailing_zeroes = TrailingZeroes,
|
|
.@"unreachable" = Unreachable,
|
|
});
|
|
|
|
fn get_payload(instruction: *Instruction, comptime id: Id) *id_to_instruction_map.get(id) {
|
|
assert(instruction.id == id);
|
|
return @fieldParentPtr("instruction", instruction);
|
|
}
|
|
};
|
|
|
|
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,
|
|
|
|
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 => {
|
|
const function_declaration = global_symbol.get_payload(.function_declaration);
|
|
const function_type = function_declaration.get_type();
|
|
return function_type;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
},
|
|
.instruction => {
|
|
const callable_instruction = call.callable.get_payload(.instruction);
|
|
switch (callable_instruction.id) {
|
|
.load => {
|
|
const load = callable_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);
|
|
assert(local_symbol.type.sema.id == .pointer);
|
|
const app = local_symbol.appointee_type.?;
|
|
switch (app.sema.id) {
|
|
.function => {
|
|
const function_type = app.get_payload(.function);
|
|
return function_type;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
},
|
|
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{
|
|
int_from_bitfield,
|
|
bitfield_from_int,
|
|
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 Unreachable = struct{
|
|
instruction: Instruction,
|
|
};
|
|
|
|
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) = .{},
|
|
// pending_values_per_file: PinnedArray(PinnedArray(*Value)) = .{},
|
|
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) = .{},
|
|
imports: PinnedArray(Import) = .{},
|
|
local_blocks: PinnedArray(LocalBlock) = .{},
|
|
local_symbols: PinnedArray(LocalSymbol) = .{},
|
|
argument_symbols: PinnedArray(ArgumentSymbol) = .{},
|
|
global_variables: PinnedArray(GlobalVariable) = .{},
|
|
unreachables: PinnedArray(Unreachable) = .{},
|
|
leading_zeroes: PinnedArray(LeadingZeroes) = .{},
|
|
trailing_zeroes: PinnedArray(TrailingZeroes) = .{},
|
|
casts: PinnedArray(Cast) = .{},
|
|
debug_arguments: PinnedArray(DebugArgument) = .{},
|
|
debug_locals: PinnedArray(DebugLocal) = .{},
|
|
function_types: PinnedArray(Type.Function) = .{},
|
|
array_type_map: PinnedHashMap(Type.Array.Descriptor, *Type) = .{},
|
|
array_types: PinnedArray(Type.Array) = .{},
|
|
structs: PinnedArray(Type.Struct) = .{},
|
|
fields: PinnedArray(Type.AggregateField) = .{},
|
|
bitfields: PinnedArray(Type.Bitfield) = .{},
|
|
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),
|
|
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 = .{
|
|
// We can fieldParentPtr to the thread
|
|
.thread = undefined,
|
|
.id = .integer,
|
|
.resolved = true,
|
|
},
|
|
.size = byte_count,
|
|
.alignment = byte_count,
|
|
},
|
|
.bit_count = bit_count,
|
|
.signedness = signedness,
|
|
};
|
|
}
|
|
}
|
|
break :blk integers;
|
|
},
|
|
void: Type = .{
|
|
.sema = .{
|
|
.thread = undefined,
|
|
.id = .void,
|
|
.resolved = true,
|
|
},
|
|
.size = 0,
|
|
.alignment = 0,
|
|
},
|
|
pointer: Type = .{
|
|
.sema = .{
|
|
.thread = undefined,
|
|
.id = .pointer,
|
|
.resolved = true,
|
|
},
|
|
.size = 8,
|
|
.alignment = 8,
|
|
},
|
|
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 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,
|
|
};
|
|
};
|
|
|
|
const TaskSystem = struct{
|
|
job: JobQueue = .{},
|
|
ask: JobQueue = .{},
|
|
program_state: ProgramState = .none,
|
|
state: ThreadState = .idle,
|
|
|
|
const ProgramState = enum{
|
|
none,
|
|
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,
|
|
|
|
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");
|
|
exit(1);
|
|
}
|
|
|
|
fn error_unterminated_argument(argument: []const u8) noreturn {
|
|
@setCold(true);
|
|
write("Argument '");
|
|
write(argument);
|
|
write("' must be terminated\n");
|
|
exit(1);
|
|
}
|
|
|
|
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,
|
|
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,
|
|
};
|
|
|
|
switch (unit.descriptor.target.arch) {
|
|
inline else => |a| {
|
|
const arch = @field(LLVM, @tagName(a));
|
|
arch.initializeTarget();
|
|
arch.initializeTargetInfo();
|
|
arch.initializeTargetMC();
|
|
arch.initializeAsmPrinter();
|
|
arch.initializeAsmParser();
|
|
},
|
|
}
|
|
|
|
|
|
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[0].task_system.program_state = .analysis;
|
|
instance.threads[0].add_thread_work(Job{
|
|
.offset = new_file_index,
|
|
.count = 1,
|
|
.id = .analyze_file,
|
|
});
|
|
control_thread(unit);
|
|
|
|
return unit;
|
|
}
|
|
};
|
|
|
|
fn control_thread(unit: *Unit) void {
|
|
var last_assigned_thread_index: u32 = 1;
|
|
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| {
|
|
// 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;
|
|
total_is_done = total_is_done and if (@intFromEnum(program_state) >= @intFromEnum(TaskSystem.ProgramState.analysis)) 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) {
|
|
exit(1);
|
|
}
|
|
} 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;
|
|
}
|
|
}
|
|
|
|
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 (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 {
|
|
@panic(current_argument);
|
|
// std.debug.panic("Unrecognized argument: {s}", .{current_argument});
|
|
}
|
|
}
|
|
|
|
const main_source_file_path = maybe_main_source_file_path orelse exit_with_error("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,
|
|
.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) {
|
|
exit_with_error("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")) {
|
|
exit_with_error("TODO: clang");
|
|
} else if (byte_equal(command, "cc")) {
|
|
exit_with_error("TODO: clang");
|
|
} else if (byte_equal(command, "c++")) {
|
|
exit_with_error("TODO: clang");
|
|
} else {
|
|
exit_with_error("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,
|
|
|
|
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.pointer.sema.thread = @intCast(thread_index);
|
|
thread.void.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]) {
|
|
exit_with_error("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),
|
|
};
|
|
|
|
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 {
|
|
exit_with_error(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"),
|
|
}),
|
|
};
|
|
|
|
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, null);
|
|
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(), 0..) |argument, argument_index| {
|
|
const alloca_type = llvm_get_type(thread, argument.type);
|
|
argument.instruction.value.llvm = builder.createAlloca(alloca_type, address_space, null, "", "".len, argument.alignment).toValue();
|
|
const llvm_argument = function.getArgument(@intCast(argument_index));
|
|
argument.value.llvm = llvm_argument.toValue();
|
|
}
|
|
|
|
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) {
|
|
.debug_argument => block: {
|
|
assert(thread.generate_debug_information);
|
|
const debug_argument = instruction.get_payload(.debug_argument);
|
|
const argument_symbol = debug_argument.argument;
|
|
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, null);
|
|
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 name = thread.identifiers.get(argument_symbol.argument_declaration.declaration.name).?;
|
|
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, local_symbol.appointee_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();
|
|
},
|
|
.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 function_type = llvm_get_type(thread, &call.get_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);
|
|
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();
|
|
},
|
|
.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 => b: {
|
|
const cast_type = llvm_get_type(thread, cast.type);
|
|
const cast_i = builder.createCast(.truncate, cast_value, cast_type, "", "".len);
|
|
break :b cast_i;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
};
|
|
break :block v;
|
|
},
|
|
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");
|
|
exit_with_error(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");
|
|
}
|
|
|
|
exit_with_error(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});
|
|
},
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
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(thread: *Thread, parameters: LLVMIntrinsic.Parameters) *LLVM.Value.Constant.Function{
|
|
if (thread.llvm.intrinsic_function_map.get(parameters)) |llvm| return llvm else {
|
|
const intrinsic_function = thread.llvm.module.getIntrinsicDeclaration(parameters.id, parameters.types.ptr, parameters.types.len);
|
|
thread.llvm.intrinsic_function_map.put_no_clobber(parameters, intrinsic_function);
|
|
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(integer_type.bit_count, 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(bitfield_backing_type.bit_count, constant_bitfield.n, @intFromEnum(bitfield_backing_type.signedness) != 0);
|
|
break :b result.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, appointee_type: ?*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.bit_count, 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.bit_count, 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, null);
|
|
const array_type = builder.createArrayType(bitsize, array.type.alignment * 8, element_type, array.descriptor.element_count);
|
|
break :block array_type.toType();
|
|
},
|
|
.pointer => block: {
|
|
const element_type = llvm_get_debug_type(thread, builder, appointee_type.?, null);
|
|
const pointer_width = @bitSizeOf(usize);
|
|
const alignment = 3;
|
|
const pointer_type = builder.createPointerType(element_type, pointer_width, alignment, "*dummyptr", "*dummyptr".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, null);
|
|
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.backing_type.get_payload(.integer).bit_count - 1];
|
|
const backing_type = llvm_get_debug_type(thread, builder, &nat_backing_type.type, null);
|
|
|
|
for (nat_bitfield_type.fields) |field| {
|
|
const field_name = thread.identifiers.get(field.name).?;
|
|
const field_bitsize = field.type.get_bit_count();
|
|
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) {
|
|
.integer => b: {
|
|
const int_ty = ty.get_payload(.integer);
|
|
const integer_type = thread.llvm.context.getIntegerType(int_ty.bit_count);
|
|
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();
|
|
},
|
|
.pointer => b: {
|
|
const pointer_type = thread.llvm.context.getPointerType(0);
|
|
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.return_type);
|
|
var argument_types = PinnedArray(*LLVM.Type){};
|
|
_ = &argument_types;
|
|
for (nat_function_type.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();
|
|
},
|
|
.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;
|
|
}
|
|
}
|
|
|
|
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_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);
|
|
|
|
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);
|
|
var debug_argument_types = PinnedArray(*LLVM.DebugInfo.Type){};
|
|
_ = &debug_argument_types;
|
|
// for (nat_function.argument_types) |argument_type| {
|
|
// _ = argument_type; // autofix
|
|
// exit(1);
|
|
// }
|
|
|
|
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 build_return(thread: *Thread, analyzer: *Analyzer, args: struct {
|
|
return_value: *Value,
|
|
scope: *Scope,
|
|
line: u32,
|
|
column: u32,
|
|
}) void {
|
|
const return_expression = thread.returns.append(.{
|
|
.instruction = new_instruction(thread, .{
|
|
.id = .ret,
|
|
.line = args.line,
|
|
.column = args.column,
|
|
.scope = args.scope,
|
|
}),
|
|
.value = args.return_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;
|
|
exit_with_error("Existing declaration with the same name");
|
|
}
|
|
|
|
const has_local_attributes = src[parser.i] == '[';
|
|
parser.i += @intFromBool(has_local_attributes);
|
|
|
|
if (has_local_attributes) {
|
|
exit_with_error("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 => exit(1),
|
|
};
|
|
|
|
parser.skip_space(src);
|
|
|
|
parser.expect_character(src, ';');
|
|
|
|
const local_symbol = thread.local_symbols.append(.{
|
|
.local_declaration = .{
|
|
.declaration = .{
|
|
.id = .local,
|
|
.name = local_name,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
},
|
|
},
|
|
.type = result.type,
|
|
.instruction = new_instruction(thread, .{
|
|
.resolved = result.type.sema.resolved and result.initial_value.sema.resolved,
|
|
.id = .local_symbol,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
}),
|
|
.alignment = result.type.alignment,
|
|
.initial_value = result.initial_value,
|
|
});
|
|
|
|
if (local_symbol.type.sema.id == .pointer) {
|
|
switch (result.initial_value.sema.id) {
|
|
.instruction => {
|
|
const instruction = result.initial_value.get_payload(.instruction);
|
|
switch (instruction.id) {
|
|
.local_symbol => {
|
|
const right_local_symbol = instruction.get_payload(.local_symbol);
|
|
local_symbol.appointee_type = right_local_symbol.type;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
},
|
|
.global_symbol => {
|
|
const global_symbol = result.initial_value.get_payload(.global_symbol);
|
|
local_symbol.appointee_type = global_symbol.type;
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
}
|
|
|
|
_ = analyzer.current_function.stack_slots.append(local_symbol);
|
|
|
|
if (thread.generate_debug_information) {
|
|
emit_debug_local(analyzer, thread, .{
|
|
.local_symbol = local_symbol,
|
|
});
|
|
}
|
|
|
|
emit_store(analyzer, thread, .{
|
|
.destination = &local_symbol.instruction.value,
|
|
.source = result.initial_value,
|
|
.alignment = local_symbol.alignment,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
|
|
analyzer.current_scope.declarations.put_no_clobber(local_name, &local_symbol.local_declaration.declaration);
|
|
},
|
|
'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) {
|
|
exit_with_error("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) {
|
|
exit_with_error("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_type();
|
|
if (function_type.return_type.sema.id != .unresolved) {
|
|
const return_type = function_type.return_type;
|
|
const return_value = parser.parse_expression(analyzer, thread, file, 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 = 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 {
|
|
build_return(thread, analyzer, .{
|
|
.return_value = return_value,
|
|
.line = debug_line,
|
|
.column = debug_column,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
}
|
|
local_block.terminated = true;
|
|
} else {
|
|
exit(1);
|
|
}
|
|
} 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 == .pointer) {
|
|
break :b local_symbol.appointee_type.?;
|
|
} 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);
|
|
assert(local_symbol.appointee_type == null);
|
|
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.get_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,
|
|
});
|
|
},
|
|
else => @panic((src.ptr + parser.i)[0..1]),
|
|
}
|
|
}
|
|
}
|
|
|
|
analyzer.current_scope = analyzer.current_scope.parent.?;
|
|
|
|
parser.expect_character(src, brace_close);
|
|
|
|
return local_block;
|
|
}
|
|
|
|
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)) {
|
|
exit_with_error("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 symbol_identifier_start = parser.i;
|
|
_ = symbol_identifier_start; // autofix
|
|
const global_name = parser.parse_identifier(thread, src);
|
|
|
|
if (file.scope.scope.get_global_declaration(global_name)) |existing_global| {
|
|
_ = existing_global; // autofix
|
|
exit(1);
|
|
}
|
|
|
|
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,
|
|
},
|
|
.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 => {
|
|
const integer_ty = backing_type.get_payload(.integer);
|
|
if (integer_ty.bit_count > 64) {
|
|
exit(1);
|
|
}
|
|
|
|
if (integer_ty.bit_count % 8 != 0) {
|
|
exit(1);
|
|
}
|
|
|
|
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,
|
|
},
|
|
.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: u32 = 0;
|
|
while (parser.parse_field(thread, file)) |field_data| {
|
|
const field_bit_offset = total_bit_count;
|
|
const field_bit_count = field_data.type.get_bit_count();
|
|
total_bit_count += field_bit_count;
|
|
const field = thread.fields.append(.{
|
|
.type = field_data.type,
|
|
.parent = &bitfield_type.type,
|
|
.name = field_data.name,
|
|
.index = thread.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 != integer_ty.bit_count) {
|
|
exit(1);
|
|
}
|
|
|
|
bitfield_type.fields = fields.const_slice();
|
|
},
|
|
else => |t| @panic(@tagName(t)),
|
|
}
|
|
} else {
|
|
exit(1);
|
|
}
|
|
},
|
|
'f' => {
|
|
if (src[parser.i + 1] == 'n') {
|
|
parser.i += 2;
|
|
parser.skip_space(src);
|
|
|
|
const function = thread.functions.add_one();
|
|
const entry_block = create_basic_block(thread);
|
|
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,
|
|
},
|
|
},
|
|
.scope = .{
|
|
.scope = .{
|
|
.id = .function,
|
|
.parent = &file.scope.scope,
|
|
.line = declaration_line + 1,
|
|
.column = declaration_column + 1,
|
|
.file = file_index,
|
|
},
|
|
},
|
|
.entry_block = entry_block,
|
|
};
|
|
|
|
var analyzer = Analyzer{
|
|
.current_function = function,
|
|
.current_basic_block = entry_block,
|
|
.current_scope = &function.scope.scope,
|
|
};
|
|
|
|
const has_function_attributes = src[parser.i] == '[';
|
|
parser.i += @intFromBool(has_function_attributes);
|
|
|
|
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)) {
|
|
exit(1);
|
|
}
|
|
|
|
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)) {
|
|
const calling_convention = @field(CallingConvention, cc_field.name);
|
|
function.declaration.attributes.calling_convention = calling_convention;
|
|
break :b;
|
|
}
|
|
} else {
|
|
exit(1);
|
|
}
|
|
},
|
|
}
|
|
}
|
|
} else {
|
|
exit(1);
|
|
}
|
|
|
|
parser.skip_space(src);
|
|
|
|
const after_ch = src[parser.i];
|
|
switch (after_ch) {
|
|
']' => {},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
parser.i += 1;
|
|
|
|
parser.skip_space(src);
|
|
}
|
|
|
|
function.declaration.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)) {
|
|
exit(1);
|
|
}
|
|
|
|
attribute_mask.setPresent(global_attribute, true);
|
|
|
|
switch (global_attribute) {
|
|
.@"export" => {},
|
|
.@"extern" => {},
|
|
}
|
|
|
|
const after_ch =src[parser.i];
|
|
switch (after_ch) {
|
|
']' => {},
|
|
else => unreachable,
|
|
}
|
|
|
|
break;
|
|
}
|
|
} else {
|
|
exit(1);
|
|
}
|
|
|
|
parser.skip_space(src);
|
|
|
|
const after_ch = src[parser.i];
|
|
switch (after_ch) {
|
|
']' => {},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
parser.i += 1;
|
|
|
|
parser.skip_space(src);
|
|
}
|
|
|
|
file.scope.scope.declarations.put_no_clobber(function.declaration.global_symbol.global_declaration.declaration.name, &function.declaration.global_symbol.global_declaration.declaration);
|
|
|
|
const split_modules = true;
|
|
if (split_modules) {
|
|
function.declaration.global_symbol.attributes.@"export" = true;
|
|
}
|
|
|
|
parser.expect_character(src, '(');
|
|
|
|
const ArgumentData = struct{
|
|
type: *Type,
|
|
name: u32,
|
|
line: u32,
|
|
column: u32,
|
|
};
|
|
var arguments = PinnedArray(ArgumentData){};
|
|
var argument_types = PinnedArray(*Type){};
|
|
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, analyzer.current_scope);
|
|
_ = arguments.append(.{
|
|
.type = argument_type,
|
|
.name = argument_name,
|
|
.line = argument_line,
|
|
.column = argument_column,
|
|
});
|
|
_ = argument_types.append(argument_type);
|
|
|
|
parser.skip_space(src);
|
|
|
|
switch (src[parser.i]) {
|
|
',' => parser.i += 1,
|
|
')' => {},
|
|
else => exit(1),
|
|
}
|
|
}
|
|
|
|
parser.expect_character(src, ')');
|
|
|
|
parser.skip_space(src);
|
|
|
|
const return_type = parser.parse_type_expression(thread, file, analyzer.current_scope);
|
|
|
|
const function_type = thread.function_types.append(.{
|
|
.type = .{
|
|
.sema = .{
|
|
.id = .function,
|
|
.resolved = true,
|
|
.thread = thread.get_index(),
|
|
},
|
|
.size = 0,
|
|
.alignment = 0,
|
|
},
|
|
.argument_types = argument_types.const_slice(),
|
|
.return_type = return_type,
|
|
});
|
|
|
|
function.declaration.global_symbol.type = &function_type.type;
|
|
|
|
parser.skip_space(src);
|
|
|
|
switch (src[parser.i]) {
|
|
brace_open => {
|
|
analyzer.current_scope = &analyzer.current_function.scope.scope;
|
|
|
|
for (arguments.const_slice(), 0..) |argument, i| {
|
|
if (analyzer.current_scope.declarations.get(argument.name) != null) {
|
|
exit_with_error("A declaration already exists with such name");
|
|
}
|
|
|
|
const argument_symbol = thread.argument_symbols.append(.{
|
|
.argument_declaration = .{
|
|
.declaration = .{
|
|
.id = .argument,
|
|
.name = argument.name,
|
|
.line = argument.line,
|
|
.column = argument.column,
|
|
.scope = analyzer.current_scope,
|
|
},
|
|
},
|
|
.type = argument.type,
|
|
.alignment = argument.type.alignment,
|
|
.value = .{
|
|
.sema = .{
|
|
.id = .argument,
|
|
.resolved = true,
|
|
.thread = thread.get_index(),
|
|
},
|
|
},
|
|
.index = @intCast(i),
|
|
.instruction = new_instruction(thread, .{
|
|
.scope = analyzer.current_scope,
|
|
.id = .argument_storage,
|
|
.line = argument.line,
|
|
.column = argument.column,
|
|
}),
|
|
});
|
|
_ = analyzer.current_function.arguments.append(argument_symbol);
|
|
|
|
analyzer.current_scope.declarations.put_no_clobber(argument.name, &argument_symbol.argument_declaration.declaration);
|
|
|
|
emit_store(&analyzer, thread, .{
|
|
.destination = &argument_symbol.instruction.value,
|
|
.source = &argument_symbol.value,
|
|
.alignment = argument.type.alignment,
|
|
.line = 0,
|
|
.column = 0,
|
|
.scope = analyzer.current_scope,
|
|
});
|
|
|
|
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);
|
|
build_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)) {
|
|
unreachable;
|
|
}
|
|
},
|
|
';' => {
|
|
unreachable;
|
|
},
|
|
else => exit_with_error("Unexpected character to close function declaration"),
|
|
}
|
|
} else {
|
|
exit(1);
|
|
}
|
|
},
|
|
'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) {
|
|
exit(1);
|
|
}
|
|
|
|
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) {
|
|
exit(1);
|
|
}
|
|
} 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 {
|
|
exit(1);
|
|
}
|
|
},
|
|
'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,
|
|
},
|
|
.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 = thread.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.fields = fields.const_slice();
|
|
} else {
|
|
exit(1);
|
|
}
|
|
},
|
|
else => exit(1),
|
|
}
|
|
}
|
|
|
|
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 {
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
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_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_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);
|
|
}
|
|
|
|
fn emit_unreachable(analyzer: *Analyzer, thread: *Thread, args: struct {
|
|
line: u32,
|
|
column: u32,
|
|
scope: *Scope,
|
|
}) void {
|
|
assert(!analyzer.current_basic_block.is_terminated);
|
|
const ur = thread.unreachables.append(.{
|
|
.instruction = new_instruction(thread, .{
|
|
.scope = args.scope,
|
|
.line = args.line,
|
|
.column = args.column,
|
|
.id = .@"unreachable",
|
|
}),
|
|
});
|
|
analyzer.append_instruction(&ur.instruction);
|
|
analyzer.current_basic_block.is_terminated = true;
|
|
}
|
|
|
|
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: {
|
|
const integer_ty = condition_type.get_payload(.integer);
|
|
if (integer_ty.bit_count == 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_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,
|
|
},
|
|
.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 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,
|
|
};
|
|
|
|
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,
|
|
DisableSanitizerInstrumentation = 7,
|
|
FnRetThunkExtern = 8,
|
|
Hot = 9,
|
|
ImmArg = 10,
|
|
InReg = 11,
|
|
InlineHint = 12,
|
|
JumpTable = 13,
|
|
MinSize = 14,
|
|
MustProgress = 15,
|
|
Naked = 16,
|
|
Nest = 17,
|
|
NoAlias = 18,
|
|
NoBuiltin = 19,
|
|
NoCallback = 20,
|
|
NoCapture = 21,
|
|
NoCfCheck = 22,
|
|
NoDuplicate = 23,
|
|
NoFree = 24,
|
|
NoImplicitFloat = 25,
|
|
NoInline = 26,
|
|
NoMerge = 27,
|
|
NoProfile = 28,
|
|
NoRecurse = 29,
|
|
NoRedZone = 30,
|
|
NoReturn = 31,
|
|
NoSanitizeBounds = 32,
|
|
NoSanitizeCoverage = 33,
|
|
NoSync = 34,
|
|
NoUndef = 35,
|
|
NoUnwind = 36,
|
|
NonLazyBind = 37,
|
|
NonNull = 38,
|
|
NullPointerIsValid = 39,
|
|
OptForFuzzing = 40,
|
|
OptimizeForSize = 41,
|
|
OptimizeNone = 42,
|
|
PresplitCoroutine = 43,
|
|
ReadNone = 44,
|
|
ReadOnly = 45,
|
|
Returned = 46,
|
|
ReturnsTwice = 47,
|
|
SExt = 48,
|
|
SafeStack = 49,
|
|
SanitizeAddress = 50,
|
|
SanitizeHWAddress = 51,
|
|
SanitizeMemTag = 52,
|
|
SanitizeMemory = 53,
|
|
SanitizeThread = 54,
|
|
ShadowCallStack = 55,
|
|
SkipProfile = 56,
|
|
Speculatable = 57,
|
|
SpeculativeLoadHardening = 58,
|
|
StackProtect = 59,
|
|
StackProtectReq = 60,
|
|
StackProtectStrong = 61,
|
|
StrictFP = 62,
|
|
SwiftAsync = 63,
|
|
SwiftError = 64,
|
|
SwiftSelf = 65,
|
|
WillReturn = 66,
|
|
WriteOnly = 67,
|
|
ZExt = 68,
|
|
ByRef = 69,
|
|
ByVal = 70,
|
|
ElementType = 71,
|
|
InAlloca = 72,
|
|
Preallocated = 73,
|
|
StructRet = 74,
|
|
Alignment = 75,
|
|
AllocKind = 76,
|
|
AllocSize = 77,
|
|
Dereferenceable = 78,
|
|
DereferenceableOrNull = 79,
|
|
Memory = 80,
|
|
StackAlignment = 81,
|
|
UWTable = 82,
|
|
VScaleRange = 83,
|
|
};
|
|
};
|
|
|
|
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");
|
|
exit(1);
|
|
},
|
|
}
|
|
}
|