nativity/bootstrap/compiler.zig
David Gonzalez Martin fff62dd396 Implement bitfield
2024-06-08 23:43:48 -06:00

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);
},
}
}