Support Asahi Linux (aarch64-linux)

This commit is contained in:
David Gonzalez Martin 2024-04-26 22:21:55 -06:00
parent 3c6aaf67d0
commit 128a3ee508
22 changed files with 916 additions and 193 deletions

View File

@ -648,12 +648,22 @@ pub fn compileCSourceFile(context: *const Context, arguments: []const []const u8
"/usr/lib/clang/17/include",
"/usr/include",
"/usr/include/x86_64-linux-gnu",
} else &.{
"/usr/include/c++/13.2.1",
"/usr/include/c++/13.2.1/x86_64-pc-linux-gnu",
"/usr/lib/clang/17/include",
"/usr/include",
"/usr/include/linux",
} else switch (@import("builtin").cpu.arch) {
.x86_64 => &.{
"/usr/include/c++/13.2.1",
"/usr/include/c++/13.2.1/x86_64-pc-linux-gnu",
"/usr/lib/clang/17/include",
"/usr/include",
"/usr/include/linux",
},
.aarch64 => &.{
"/usr/include/c++/13",
"/usr/include/c++/13/aarch64-redhat-linux",
"/usr/lib/clang/17/include",
"/usr/include",
"/usr/include/linux",
},
else => unreachable,
},
else => unreachable, //@compileError("ABI not supported"),
},
@ -2771,28 +2781,25 @@ const Abi = enum {
pub fn buildExecutable(context: *const Context, arguments: []const []const u8, options: ExecutableOptions) !void {
var maybe_executable_path: ?[]const u8 = null;
var maybe_main_package_path: ?[]const u8 = null;
var arch: Arch = undefined;
var os: Os = undefined;
var abi: Abi = undefined;
switch (@import("builtin").os.tag) {
.linux => {
arch = .x86_64;
os = .linux;
abi = .gnu;
},
.macos => {
arch = .aarch64;
os = .macos;
abi = .none;
},
.windows => {
arch = .x86_64;
os = .windows;
abi = .gnu;
},
// TODO: make these mutable
const arch: Arch = switch (@import("builtin").cpu.arch) {
.aarch64 => .aarch64,
.x86_64 => .x86_64,
else => unreachable,
}
};
const os: Os = switch (@import("builtin").os.tag) {
.linux => .linux,
.macos => .macos,
.windows => .windows,
else => unreachable,
};
const abi: Abi = switch (@import("builtin").os.tag) {
.linux => .gnu,
.macos => .none,
.windows => .gnu,
else => unreachable,
};
var maybe_only_parse: ?bool = null;
var link_libc = false;
@ -2982,7 +2989,7 @@ pub fn buildExecutable(context: *const Context, arguments: []const []const u8, o
try unit.compile(context);
}
fn createUnit(context: *const Context, arguments: struct{
fn createUnit(context: *const Context, arguments: struct {
main_package_path: []const u8,
executable_path: []const u8,
object_path: []const u8,
@ -2996,7 +3003,7 @@ fn createUnit(context: *const Context, arguments: struct{
name: []const u8,
is_test: bool,
c_source_files: []const []const u8,
}) !*Unit{
}) !*Unit {
const unit = try context.allocator.create(Unit);
unit.* = .{
.descriptor = .{
@ -4687,86 +4694,89 @@ pub const Builder = struct {
};
},
.@"asm" => {
const architecture = InlineAssembly.x86_64;
switch (unit.descriptor.arch) {
inline else => |arch| {
const architecture = @field(InlineAssembly, @tagName(arch));
assert(argument_node_list.len == 1);
const assembly_block_node = unit.getNode(argument_node_list[0]);
const instruction_node_list = unit.getNodeList(assembly_block_node.left);
var instructions = try context.arena.new_array(InlineAssembly.Instruction.Index, instruction_node_list.len);
instructions.len = 0;
assert(argument_node_list.len == 1);
const assembly_block_node = unit.getNode(argument_node_list[0]);
const instruction_node_list = unit.getNodeList(assembly_block_node.left);
var instructions = try context.arena.new_array(InlineAssembly.Instruction.Index, instruction_node_list.len);
instructions.len = 0;
for (instruction_node_list) |assembly_statement_node_index| {
const assembly_instruction_node = unit.getNode(assembly_statement_node_index);
const assembly_instruction_name_node = unit.getNode(assembly_instruction_node.left);
const instruction_name = unit.getExpectedTokenBytes(assembly_instruction_name_node.token, .identifier);
const instruction = inline for (@typeInfo(architecture.Instruction).Enum.fields) |instruction_enum_field| {
if (byte_equal(instruction_name, instruction_enum_field.name)) {
break @field(architecture.Instruction, instruction_enum_field.name);
}
} else unreachable;
const operand_nodes = unit.getNodeList(assembly_instruction_node.right);
for (instruction_node_list) |assembly_statement_node_index| {
const assembly_instruction_node = unit.getNode(assembly_statement_node_index);
const assembly_instruction_name_node = unit.getNode(assembly_instruction_node.left);
const instruction_name = unit.getExpectedTokenBytes(assembly_instruction_name_node.token, .identifier);
const instruction = inline for (@typeInfo(architecture.Instruction).Enum.fields) |instruction_enum_field| {
if (byte_equal(instruction_name, instruction_enum_field.name)) {
break @field(architecture.Instruction, instruction_enum_field.name);
}
} else unreachable;
const operand_nodes = unit.getNodeList(assembly_instruction_node.right);
var operands = try context.arena.new_array(InlineAssembly.Operand, operand_nodes.len);
operands.len = 0;
var operands = try context.arena.new_array(InlineAssembly.Operand, operand_nodes.len);
operands.len = 0;
for (operand_nodes) |operand_node_index| {
const operand_node = unit.getNode(operand_node_index);
const operand: InlineAssembly.Operand = switch (operand_node.id) {
.assembly_register => blk: {
const register_name = unit.getExpectedTokenBytes(operand_node.token, .identifier);
for (operand_nodes) |operand_node_index| {
const operand_node = unit.getNode(operand_node_index);
const operand: InlineAssembly.Operand = switch (operand_node.id) {
.assembly_register => blk: {
const register_name = unit.getExpectedTokenBytes(operand_node.token, .identifier);
const register = inline for (@typeInfo(architecture.Register).Enum.fields) |register_enum_field| {
if (byte_equal(register_name, register_enum_field.name)) {
break @field(architecture.Register, register_enum_field.name);
}
} else unreachable;
break :blk .{
.register = @intFromEnum(register),
const register = inline for (@typeInfo(architecture.Register).Enum.fields) |register_enum_field| {
if (byte_equal(register_name, register_enum_field.name)) {
break @field(architecture.Register, register_enum_field.name);
}
} else unreachable;
break :blk .{
.register = @intFromEnum(register),
};
},
.number_literal => switch (std.zig.parseNumberLiteral(unit.getExpectedTokenBytes(operand_node.token, .number_literal))) {
.int => |integer| .{
.number_literal = integer,
},
else => |t| @panic(@tagName(t)),
},
.assembly_code_expression => .{
.value = try builder.resolveRuntimeValue(unit, context, Type.Expect.none, operand_node.left, .left),
},
else => |t| @panic(@tagName(t)),
};
const index = operands.len;
operands.len += 1;
operands[index] = operand;
}
const instruction_index = unit.assembly_instructions.append_index(.{
.id = @intFromEnum(instruction),
.operands = operands,
});
const index = instructions.len;
instructions.len += 1;
instructions[index] = instruction_index;
}
const inline_assembly = unit.inline_assembly.append_index(.{
.instructions = instructions,
});
const inline_asm = unit.instructions.append_index(.{
.inline_assembly = inline_assembly,
});
try builder.appendInstruction(unit, inline_asm);
return .{
.value = .{
.runtime = inline_asm,
},
.number_literal => switch (std.zig.parseNumberLiteral(unit.getExpectedTokenBytes(operand_node.token, .number_literal))) {
.int => |integer| .{
.number_literal = integer,
},
else => |t| @panic(@tagName(t)),
},
.assembly_code_expression => .{
.value = try builder.resolveRuntimeValue(unit, context, Type.Expect.none, operand_node.left, .left),
},
else => |t| @panic(@tagName(t)),
// TODO: WARN fix
.type = .noreturn,
};
const index = operands.len;
operands.len += 1;
operands[index] = operand;
}
const instruction_index = unit.assembly_instructions.append_index(.{
.id = @intFromEnum(instruction),
.operands = operands,
});
const index = instructions.len;
instructions.len += 1;
instructions[index] = instruction_index;
}
const inline_assembly = unit.inline_assembly.append_index(.{
.instructions = instructions,
});
const inline_asm = unit.instructions.append_index(.{
.inline_assembly = inline_assembly,
});
try builder.appendInstruction(unit, inline_asm);
return .{
.value = .{
.runtime = inline_asm,
},
// TODO: WARN fix
.type = .noreturn,
};
}
},
.cast => {
assert(argument_node_list.len == 1);
@ -6289,7 +6299,7 @@ pub const Builder = struct {
const pointer_to_global = try unit.getPointerType(.{
.type = global.declaration.type,
.termination = switch (type_expect) {
.none => .none,
.none, .cast => .none,
.type => |type_index| switch (unit.types.get(type_index).*) {
.pointer => |pointer| pointer.termination,
else => .none,
@ -6297,7 +6307,7 @@ pub const Builder = struct {
else => unreachable,
},
.mutability = switch (type_expect) {
.none => .@"var",
.none, .cast => .@"var",
.type => |type_index| switch (unit.types.get(type_index).*) {
.pointer => |pointer| pointer.mutability,
else => .@"var",
@ -10034,6 +10044,73 @@ pub const Builder = struct {
}
},
.character_literal => return try unit.resolve_character_literal(node_index),
.negation => {
assert(node.left != .null);
assert(node.right == .null);
const value = try builder.resolveComptimeValue(unit, context, type_expect, .{}, node.left, null, .right, &.{}, null, &.{});
switch (value) {
.constant_int => |constant_int| switch (type_expect) {
.type => |type_index| {
assert(type_index == value.type);
const expected_type = unit.types.get(type_index);
switch (expected_type.*) {
.integer => |integer| switch (integer.kind) {
.materialized_int => {
assert(integer.signedness == .signed);
var v: i64 = @intCast(constant_int.value);
v = 0 - v;
return .{
.constant_int = .{
.value = @bitCast(v),
},
};
},
else => |t| @panic(@tagName(t)),
},
else => |t| @panic(@tagName(t)),
}
},
else => |t| @panic(@tagName(t)),
},
.comptime_int => |ct_int| switch (type_expect) {
.type => |type_index| switch (unit.types.get(type_index).*) {
.integer => |integer| switch (integer.kind) {
.materialized_int => {
assert(integer.signedness == .signed);
var v = switch (ct_int.signedness) {
.signed => 0 - @as(i64, @intCast(ct_int.value)),
.unsigned => @as(i64, @intCast(ct_int.value)),
};
v = 0 - v;
return .{
.constant_int = .{
.value = @bitCast(v),
},
};
},
else => |t| @panic(@tagName(t)),
},
else => |t| @panic(@tagName(t)),
},
.none => {
return .{
.comptime_int = .{
.value = ct_int.value,
.signedness = switch (ct_int.signedness) {
.unsigned => .signed,
.signed => .unsigned,
},
},
};
},
else => |t| @panic(@tagName(t)),
},
else => |t| @panic(@tagName(t)),
}
},
else => |t| @panic(@tagName(t)),
}
}
@ -12299,6 +12376,7 @@ pub const Builder = struct {
.negation => block: {
assert(node.left != .null);
assert(node.right == .null);
const value = try builder.resolveRuntimeValue(unit, context, type_expect, node.left, .right);
switch (value.value) {
@ -18095,6 +18173,51 @@ pub const InlineAssembly = struct {
rdi,
};
};
pub const aarch64 = struct {
pub const Instruction = enum {
b,
mov,
};
pub const Register = enum {
fp,
lr,
sp,
x0,
x1,
x2,
x3,
x4,
x5,
x6,
x7,
x8,
x9,
x10,
x11,
x12,
x13,
x14,
x15,
x16,
x17,
x18,
x19,
x20,
x21,
x22,
x23,
x24,
x25,
x26,
x27,
x28,
x29,
x30,
x31,
};
};
};
const LogKind = enum {

View File

@ -2674,12 +2674,72 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo
_ = assembly_statements.pop();
}
assembly_statements.appendSliceAssumeCapacity("\n\t");
assembly_statements.appendSliceAssumeCapacity("\n");
}
_ = assembly_statements.pop();
// try constraints.append_slice(context.allocator, ",~{dirflag},~{fpsr},~{flags}");
},
else => |t| @panic(@tagName(t)),
.aarch64 => {
for (assembly_block.instructions) |assembly_instruction_index| {
const instruction = unit.assembly_instructions.get(assembly_instruction_index);
const instruction_id: Compilation.InlineAssembly.aarch64.Instruction = @enumFromInt(instruction.id);
assembly_statements.appendSliceAssumeCapacity(@tagName(instruction_id));
assembly_statements.appendAssumeCapacity(' ');
if (instruction.operands.len > 0) {
for (instruction.operands) |operand| {
switch (operand) {
.register => |register_value| {
const register: Compilation.InlineAssembly.aarch64.Register = @enumFromInt(register_value);
assembly_statements.appendSliceAssumeCapacity(@tagName(register));
},
.number_literal => |literal| {
var buffer: [65]u8 = undefined;
const number_literal = format_int(&buffer, literal, 10, false);
const slice_ptr = number_literal.ptr - 1;
const literal_slice = slice_ptr[0 .. number_literal.len + 1];
literal_slice[0] = '#';
assembly_statements.appendSliceAssumeCapacity(try context.arena.duplicate_bytes(literal_slice));
},
.value => |sema_value| {
if (llvm.llvm_value_map.get(sema_value)) |v| {
_ = v; // autofix
unreachable;
} else {
const value = try llvm.emitLeftValue(unit, context, sema_value);
var buffer: [65]u8 = undefined;
const operand_number = format_int(&buffer, operand_values.len, 16, false);
const slice_ptr = operand_number.ptr - 2;
const operand_slice = slice_ptr[0 .. operand_number.len + 2];
operand_slice[0] = '$';
operand_slice[1] = '{';
var new_buffer: [65]u8 = undefined;
@memcpy(new_buffer[0..operand_slice.len], operand_slice);
new_buffer[operand_slice.len] = '}';
const new_slice = try context.arena.duplicate_bytes(new_buffer[0 .. operand_slice.len + 1]);
assembly_statements.appendSliceAssumeCapacity(new_slice);
operand_values.appendAssumeCapacity(value);
const value_type = value.getType();
operand_types.appendAssumeCapacity(value_type);
constraints.appendAssumeCapacity('X');
}
},
}
assembly_statements.appendSliceAssumeCapacity(", ");
}
_ = assembly_statements.pop();
_ = assembly_statements.pop();
}
assembly_statements.appendSliceAssumeCapacity("\n");
}
_ = assembly_statements.pop();
},
}
const is_var_args = false;
@ -2908,31 +2968,41 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo
const function_type = LLVM.Context.getFunctionType(return_type, syscall_argument_types.ptr, syscall_argument_types.len, is_var_args) orelse unreachable;
var constraints = BoundedArray(u8, 4096){};
const inline_asm = switch (unit.descriptor.arch) {
.x86_64 => blk: {
constraints.appendSliceAssumeCapacity("={rax}");
const syscall_registers = [7][]const u8{ "rax", "rdi", "rsi", "rdx", "r10", "r8", "r9" };
for (syscall_registers[0..syscall_argument_count]) |syscall_register| {
constraints.appendAssumeCapacity(',');
constraints.appendAssumeCapacity('{');
constraints.appendSliceAssumeCapacity(syscall_register);
constraints.appendAssumeCapacity('}');
}
constraints.appendSliceAssumeCapacity(",~{rcx},~{r11},~{memory}");
const assembly = "syscall";
const has_side_effects = true;
const is_align_stack = true;
const can_throw = false;
const inline_assembly = LLVM.Value.InlineAssembly.get(function_type, assembly, assembly.len, &constraints.buffer, constraints.len, has_side_effects, is_align_stack, LLVM.Value.InlineAssembly.Dialect.@"at&t", can_throw) orelse return LLVM.Value.Error.inline_assembly;
break :blk inline_assembly;
},
else => |t| @panic(@tagName(t)),
const syscall_registers: [7][]const u8 = switch (unit.descriptor.arch) {
.x86_64 => .{ "rax", "rdi", "rsi", "rdx", "r10", "r8", "r9" },
.aarch64 => .{ "x8", "x0", "x1", "x2", "x3", "x4", "x5" },
};
const syscall_instruction: []const u8 = switch (unit.descriptor.arch) {
.x86_64 => "syscall",
.aarch64 => "svc #0",
};
const return_constraints: []const u8 = switch (unit.descriptor.arch) {
.x86_64 => "={rax}",
.aarch64 => "={x0}",
};
const call_to_asm = llvm.builder.createCall(function_type, inline_asm.toValue(), syscall_arguments.ptr, syscall_arguments.len, "syscall", "syscall".len, null) orelse return LLVM.Value.Instruction.Error.call;
const clobber_constraints: []const u8 = switch (unit.descriptor.arch) {
.x86_64 => ",~{rcx},~{r11},~{memory}",
.aarch64 => ",~{memory},~{cc}",
};
constraints.appendSliceAssumeCapacity(return_constraints);
for (syscall_registers[0..syscall_argument_count]) |syscall_register| {
constraints.appendAssumeCapacity(',');
constraints.appendAssumeCapacity('{');
constraints.appendSliceAssumeCapacity(syscall_register);
constraints.appendAssumeCapacity('}');
}
constraints.appendSliceAssumeCapacity(clobber_constraints);
const has_side_effects = true;
const is_align_stack = true;
const can_throw = false;
const inline_assembly = LLVM.Value.InlineAssembly.get(function_type, syscall_instruction.ptr, syscall_instruction.len, &constraints.buffer, constraints.len, has_side_effects, is_align_stack, LLVM.Value.InlineAssembly.Dialect.@"at&t", can_throw) orelse return LLVM.Value.Error.inline_assembly;
const call_to_asm = llvm.builder.createCall(function_type, inline_assembly.toValue(), syscall_arguments.ptr, syscall_arguments.len, "syscall", "syscall".len, null) orelse return LLVM.Value.Instruction.Error.call;
try llvm.llvm_instruction_map.put_no_clobber(instruction_index, call_to_asm.toValue());
},
.@"unreachable" => {
@ -3298,7 +3368,10 @@ pub fn codegen(unit: *Compilation.Unit, context: *const Compilation.Context) !vo
// TODO: proper target selection
const target_triple = switch (unit.descriptor.os) {
.linux => "x86_64-linux-none",
.linux => switch (unit.descriptor.arch) {
.aarch64 => "aarch64-linux-none",
.x86_64 => "x86_64-linux-none",
},
.macos => "aarch64-apple-macosx-none",
.windows => "x86_64-windows-gnu",
};

View File

@ -6,6 +6,7 @@ const log = std.log;
const library = @import("../library.zig");
const byte_equal = library.byte_equal;
const enumFromString = library.enumFromString;
const exit_with_error = library.exit_with_error;
const MyAllocator = library.MyAllocator;
const PinnedArray = library.PinnedArray;
@ -505,7 +506,7 @@ pub fn analyze(text: []const u8, token_buffer: *Token.Buffer) !Result {
'A'...'Z', 'a'...'z' => {
while (true) {
switch (text[index]) {
'A'...'Z', 'a'...'z' => index += 1,
'A'...'Z', 'a'...'z', '0'...'9' => index += 1,
else => break,
}
}
@ -559,12 +560,13 @@ pub fn analyze(text: []const u8, token_buffer: *Token.Buffer) !Result {
hex,
bin,
octal,
decimal,
};
const representation: Representation = switch (text[index]) {
'x' => .hex,
else => unreachable,
else => .decimal,
};
index += 1;
index += @intFromBool(representation != .decimal);
switch (representation) {
.hex => {
while (true) {
@ -574,6 +576,20 @@ pub fn analyze(text: []const u8, token_buffer: *Token.Buffer) !Result {
}
}
_ = token_buffer.tokens.append(.{
.id = .number_literal,
.line = line_index,
.offset = start_i,
.length = index - start_i,
});
},
.decimal => {
switch (text[index]) {
'0'...'9' => unreachable,
else => {},
}
_ = token_buffer.tokens.append(.{
.id = .number_literal,
.line = line_index,
@ -590,9 +606,16 @@ pub fn analyze(text: []const u8, token_buffer: *Token.Buffer) !Result {
const ch_fmt = library.format_int(&ch_array, start_ch, 16, false);
try Compilation.write(.panic, ch_fmt);
try Compilation.write(.panic, "\n");
std.posix.exit(0);
exit_with_error();
},
}
const debug_asm_tokens = false;
if (debug_asm_tokens) {
try Compilation.write(.panic, "ASM token: '");
try Compilation.write(.panic, text[start_i..index]);
try Compilation.write(.panic, "'\n");
}
}
_ = token_buffer.tokens.append(.{

View File

@ -239,7 +239,7 @@ pub fn PinnedArrayAdvanced(comptime T: type, comptime MaybeIndex: ?type) type {
array.ensure_capacity(1);
const src = array.slice()[index..];
array.length += 1;
const dst = array.slice()[index + 1..];
const dst = array.slice()[index + 1 ..];
copy_backwards(T, dst, src);
array.slice()[index] = item;
}
@ -707,3 +707,8 @@ pub fn align_forward(value: u64, alignment: u64) u64 {
const mask = alignment - 1;
return (value + mask) & ~mask;
}
pub fn exit_with_error() noreturn {
@breakpoint();
std.posix.exit(1);
}

View File

@ -20,6 +20,17 @@ pub fn link(context: *const Compilation.Context, options: linker.Options) !void
_ = argv.append(driver_program);
_ = argv.append("--error-limit=0");
switch (@import("builtin").cpu.arch) {
.aarch64 => switch (@import("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);
@ -78,14 +89,30 @@ pub fn link(context: *const Compilation.Context, options: linker.Options) !void
} else {
if (options.link_libcpp) {
assert(options.link_libc);
_ = argv.append("/usr/lib/libstdc++.so");
switch (@import("builtin").cpu.arch) {
.x86_64 => _ = argv.append("/usr/lib/libstdc++.so"),
.aarch64 => _ = argv.append("/usr/lib64/libstdc++.so.6"),
else => unreachable,
}
}
if (options.link_libc) {
_ = argv.append("/usr/lib/crt1.o");
_ = argv.append("/usr/lib/crti.o");
argv.append_slice(&.{ "-L", "/usr/lib" });
argv.append_slice(&.{ "-dynamic-linker", "/lib64/ld-linux-x86-64.so.2" });
switch (@import("builtin").cpu.arch) {
.x86_64 => {
_ = argv.append("/usr/lib/crt1.o");
_ = argv.append("/usr/lib/crti.o");
argv.append_slice(&.{ "-L", "/usr/lib" });
argv.append_slice(&.{ "-dynamic-linker", "/lib64/ld-linux-x86-64.so.2" });
},
.aarch64 => {
_ = argv.append("/usr/lib64/crt1.o");
_ = argv.append("/usr/lib64/crti.o");
argv.append_slice(&.{ "-L", "/usr/lib64" });
argv.append_slice(&.{ "-dynamic-linker", "/lib/ld-linux-aarch64.so.1" });
},
else => unreachable,
}
_ = argv.append("--as-needed");
_ = argv.append("-lm");
_ = argv.append("-lpthread");
@ -93,7 +120,16 @@ pub fn link(context: *const Compilation.Context, options: linker.Options) !void
_ = argv.append("-ldl");
_ = argv.append("-lrt");
_ = argv.append("-lutil");
_ = argv.append("/usr/lib/crtn.o");
switch (@import("builtin").cpu.arch) {
.x86_64 => {
_ = argv.append("/usr/lib/crtn.o");
},
.aarch64 => {
_ = argv.append("/usr/lib64/crtn.o");
},
else => unreachable,
}
}
}
},

View File

@ -446,7 +446,7 @@ pub fn build(b: *std.Build) !void {
const result = try std.ChildProcess.run(.{
.allocator = b.allocator,
.argv = &.{
"cc", "--version",
"c++", "--version",
},
.max_output_bytes = 0xffffffffffffff,
});
@ -470,11 +470,27 @@ pub fn build(b: *std.Build) !void {
unreachable;
};
const cxx_include_base = try std.mem.concat(b.allocator, u8, &.{ "/usr/include/c++/", cxx_version });
compiler.addObjectFile(.{ .cwd_relative = "/usr/lib/libstdc++.so" });
const cxx_include_base = switch (@import("builtin").cpu.arch) {
.x86_64 => try std.mem.concat(b.allocator, u8, &.{ "/usr/include/c++/", cxx_version }),
.aarch64 => "/usr/include/c++/13",
else => @compileError("Arch not supported"),
};
compiler.addObjectFile(.{
.cwd_relative = switch (@import("builtin").cpu.arch) {
.aarch64 => "/lib64/libstdc++.so.6",
.x86_64 => "/usr/lib/libstdc++.so",
else => @compileError("Arch not supported"),
},
});
compiler.addIncludePath(.{ .cwd_relative = "/usr/include" });
compiler.addIncludePath(.{ .cwd_relative = cxx_include_base });
compiler.addIncludePath(.{ .cwd_relative = try std.mem.concat(b.allocator, u8, &.{ cxx_include_base, "/x86_64-pc-linux-gnu" }) });
compiler.addIncludePath(.{
.cwd_relative = try std.mem.concat(b.allocator, u8, &.{ cxx_include_base, switch (@import("builtin").cpu.arch) {
.x86_64 => "/x86_64-pc-linux-gnu",
.aarch64 => "/aarch64-redhat-linux",
else => @compileError("Arch not supported"),
} }),
});
compiler.addLibraryPath(.{ .cwd_relative = "/usr/lib" });
}
},

View File

@ -131,7 +131,7 @@ fn runBuildTests(allocator: Allocator, args: struct {
const test_dir_path = "test/build";
const test_names = try collectDirectoryDirEntries(allocator, test_dir_path);
const test_dir_realpath = try std.fs.cwd().realpathAlloc(allocator, test_dir_path);
const compiler_realpath = try std.fs.cwd().realpathAlloc(allocator, args.compiler_path);
try std.posix.chdir(test_dir_realpath);
const total_compilation_count = test_names.len;
@ -142,16 +142,23 @@ fn runBuildTests(allocator: Allocator, args: struct {
var failed_test_count: usize = 0;
const total_test_count = test_names.len;
errdefer {
std.posix.chdir(previous_cwd) catch unreachable;
}
for (test_names) |test_name| {
std.debug.print("{s}... ", .{test_name});
try std.posix.chdir(test_name);
const compile_run = try std.ChildProcess.run(.{
const compile_run = std.ChildProcess.run(.{
.allocator = allocator,
// TODO: delete -main_source_file?
.argv = &.{ compiler_realpath, "build" },
.argv = &.{ args.compiler_path, "build" },
.max_output_bytes = std.math.maxInt(u64),
});
}) catch |err| {
const compilation_success = false;
std.debug.print("[COMPILATION {s}] ", .{if (compilation_success) "\x1b[32mOK\x1b[0m" else "\x1b[31mFAILED\x1b[0m"});
return err;
};
ran_compilation_count += 1;
@ -177,12 +184,20 @@ fn runBuildTests(allocator: Allocator, args: struct {
if (compilation_success and !args.self_hosted) {
const test_path = try std.mem.concat(allocator, u8, &.{ "nat/", test_name });
const test_run = try std.ChildProcess.run(.{
const test_run = std.ChildProcess.run(.{
.allocator = allocator,
// TODO: delete -main_source_file?
.argv = &.{test_path},
.max_output_bytes = std.math.maxInt(u64),
});
}) catch |err| {
const test_success = false;
std.debug.print("[TEST {s}]\n", .{if (test_success) "\x1b[32mOK\x1b[0m" else "\x1b[31mFAILED\x1b[0m"});
std.debug.print("{}\n", .{err});
if (@errorReturnTrace()) |error_trace| {
std.debug.dumpStackTrace(error_trace.*);
}
return err;
};
ran_test_count += 1;
const test_result: TestError!bool = switch (test_run.term) {
.Exited => |exit_code| if (exit_code == 0) true else error.abnormal_exit_code,
@ -213,6 +228,9 @@ fn runBuildTests(allocator: Allocator, args: struct {
std.debug.print("TOTAL TESTS: {}. RAN: {}. FAILED: {}\n", .{ total_test_count, ran_test_count, failed_test_count });
try std.posix.chdir(previous_cwd);
const current_cwd = try std.fs.cwd().realpathAlloc(allocator, ".");
std.debug.assert(std.mem.eql(u8, current_cwd, previous_cwd));
std.debug.print("Hello \n", .{});
if (failed_compilation_count > 0 or failed_test_count > 0) {
return error.fail;
@ -226,11 +244,21 @@ fn runStdTests(allocator: Allocator, args: struct {
var errors = false;
std.debug.print("std... ", .{});
const result = try std.ChildProcess.run(.{
std.debug.print("CWD: {s}\n", .{std.fs.cwd().realpathAlloc(allocator, ".") catch unreachable});
const argv = &.{ args.compiler_path, "test", "-main_source_file", "lib/std/std.nat", "-name", "std" };
const result = std.ChildProcess.run(.{
.allocator = allocator,
.argv = &.{ args.compiler_path, "test", "-main_source_file", "lib/std/std.nat", "-name", "std" },
.argv = argv,
.max_output_bytes = std.math.maxInt(u64),
});
}) catch |err| {
std.debug.print("Error: {}", .{err});
if (@errorReturnTrace()) |error_trace| {
std.debug.dumpStackTrace(error_trace.*);
}
return err;
};
const compilation_result: TestError!bool = switch (result.term) {
.Exited => |exit_code| if (exit_code == 0) true else error.abnormal_exit_code,
.Signal => error.signaled,
@ -290,7 +318,6 @@ fn runCmakeTests(allocator: Allocator, args: struct {
const cc_dir = try std.fs.cwd().openDir(args.dir_path, .{
.iterate = true,
});
const compiler_realpath = try std.fs.cwd().realpathAlloc(allocator, args.compiler_path);
const cc_dir_path = try cc_dir.realpathAlloc(allocator, ".");
try std.posix.chdir(cc_dir_path);
@ -316,9 +343,9 @@ fn runCmakeTests(allocator: Allocator, args: struct {
"-G",
"Ninja",
// "-DCMAKE_VERBOSE_MAKEFILE=On",
try std.mem.concat(allocator, u8, &.{ "-DCMAKE_C_COMPILER=", compiler_realpath, ";cc" }),
try std.mem.concat(allocator, u8, &.{ "-DCMAKE_CXX_COMPILER=", compiler_realpath, ";c++" }),
try std.mem.concat(allocator, u8, &.{ "-DCMAKE_ASM_COMPILER=", compiler_realpath, ";cc" }),
try std.mem.concat(allocator, u8, &.{ "-DCMAKE_C_COMPILER=", args.compiler_path, ";cc" }),
try std.mem.concat(allocator, u8, &.{ "-DCMAKE_CXX_COMPILER=", args.compiler_path, ";c++" }),
try std.mem.concat(allocator, u8, &.{ "-DCMAKE_ASM_COMPILER=", args.compiler_path, ";cc" }),
},
.max_output_bytes = std.math.maxInt(u64),
});
@ -474,19 +501,21 @@ fn run_test_suite(allocator: Allocator, args: struct {
std.debug.print("TESTING {s} COMPILER: {s}...\n=================\n", .{ if (self_hosted) "SELF-HOSTED" else "BOOTSTRAP", args.compiler_path });
var errors = false;
const compiler_path = std.fs.cwd().realpathAlloc(allocator, args.compiler_path) catch unreachable;
runStandalone(allocator, .{
.directory_path = "test/standalone",
.group_name = "STANDALONE",
.is_test = false,
.self_hosted = self_hosted,
.compiler_path = args.compiler_path,
.compiler_path = compiler_path,
}) catch {
errors = true;
};
runBuildTests(allocator, .{
.self_hosted = self_hosted,
.compiler_path = args.compiler_path,
.compiler_path = compiler_path,
}) catch {
errors = true;
};
@ -496,14 +525,14 @@ fn run_test_suite(allocator: Allocator, args: struct {
.group_name = "TEST EXECUTABLE",
.is_test = true,
.self_hosted = self_hosted,
.compiler_path = args.compiler_path,
.compiler_path = compiler_path,
}) catch {
errors = true;
};
runStdTests(allocator, .{
.self_hosted = self_hosted,
.compiler_path = args.compiler_path,
.compiler_path = compiler_path,
}) catch {
errors = true;
};
@ -511,37 +540,41 @@ fn run_test_suite(allocator: Allocator, args: struct {
if (!self_hosted) {
runCmakeTests(allocator, .{
.dir_path = "test/cc",
.compiler_path = args.compiler_path,
.compiler_path = compiler_path,
}) catch {
errors = true;
};
runCmakeTests(allocator, .{
.dir_path = "test/c++",
.compiler_path = args.compiler_path,
.compiler_path = compiler_path,
}) catch {
errors = true;
};
switch (@import("builtin").os.tag) {
.macos => runCmakeTests(allocator, .{
.dir_path = "test/cc_macos",
.compiler_path = args.compiler_path,
}) catch {
errors = true;
},
.windows => {},
.linux => switch (@import("builtin").abi) {
.gnu => runCmakeTests(allocator, .{
.dir_path = "test/cc_linux",
.compiler_path = args.compiler_path,
switch (@import("builtin").cpu.arch) {
.aarch64 => switch (@import("builtin").os.tag) {
.linux => runCmakeTests(allocator, .{
.dir_path = "test/cc_aarch64_linux",
.compiler_path = compiler_path,
}) catch {
errors = true;
},
.musl => {},
else => @compileError("ABI not supported"),
.macos => runCmakeTests(allocator, .{
.dir_path = "test/cc_aarch64_macos",
.compiler_path = compiler_path,
}) catch {
errors = true;
},
else => unreachable,
},
else => @compileError("OS not supported"),
.x86_64 => runCmakeTests(allocator, .{
.dir_path = "test/cc_x86_64",
.compiler_path = compiler_path,
}) catch {
errors = true;
},
else => @compileError("Arch not supported"),
}
}

View File

@ -24,8 +24,7 @@ const unwrap_syscall = system.unwrap_syscall;
const exit = fn(exit_code: s32) noreturn {
switch (current) {
.linux => _ = #syscall(#cast(linux.Syscall.exit_group), #cast(exit_code)),
.macos => system.exit(exit_code),
.linux, .macos => system.exit(exit_code),
.windows => windows.ExitProcess(#cast(exit_code)),
}
}
@ -323,6 +322,7 @@ const duplicate_process = fn () DuplicateProcessError!Process.Id {
const ExecveError = error{
execve_failed,
};
const execute = fn(path: [&:0]const u8, argv: [&:null]const ?[&:0]const u8, env: [&:null]const ?[&:null]const u8) ExecveError!noreturn {
switch (current) {
.linux, .macos => {

View File

@ -3,14 +3,333 @@ const std = #import("std");
const stdin: FileDescriptor = 0;
const stdout: FileDescriptor = 1;
const stderr: FileDescriptor = 2;
const cpu = #import("builtin").cpu;
const Syscall = switch (#import("builtin").cpu) {
const Syscall = switch (cpu) {
.x86_64 => Syscall_x86_64,
.aarch64 => Syscall_aarch64,
else => #error("Architecture not supported"),
};
const current_working_directory_file_descriptor: ssize = -100;
const ProcessId = s32;
const Syscall_aarch64 = enum(usize) {
io_setup = 0,
io_destroy = 1,
io_submit = 2,
io_cancel = 3,
io_getevents = 4,
setxattr = 5,
lsetxattr = 6,
fsetxattr = 7,
getxattr = 8,
lgetxattr = 9,
fgetxattr = 10,
listxattr = 11,
llistxattr = 12,
flistxattr = 13,
removexattr = 14,
lremovexattr = 15,
fremovexattr = 16,
getcwd = 17,
lookup_dcookie = 18,
eventfd2 = 19,
epoll_create1 = 20,
epoll_ctl = 21,
epoll_pwait = 22,
dup = 23,
dup3 = 24,
fcntl = 25,
inotify_init1 = 26,
inotify_add_watch = 27,
inotify_rm_watch = 28,
ioctl = 29,
ioprio_set = 30,
ioprio_get = 31,
flock = 32,
mknodat = 33,
mkdirat = 34,
unlinkat = 35,
symlinkat = 36,
linkat = 37,
renameat = 38,
umount2 = 39,
mount = 40,
pivot_root = 41,
nfsservctl = 42,
statfs = 43,
fstatfs = 44,
truncate = 45,
ftruncate = 46,
fallocate = 47,
faccessat = 48,
chdir = 49,
fchdir = 50,
chroot = 51,
fchmod = 52,
fchmodat = 53,
fchownat = 54,
fchown = 55,
openat = 56,
close = 57,
vhangup = 58,
pipe2 = 59,
quotactl = 60,
getdents64 = 61,
lseek = 62,
read = 63,
write = 64,
readv = 65,
writev = 66,
pread64 = 67,
pwrite64 = 68,
preadv = 69,
pwritev = 70,
sendfile = 71,
pselect6 = 72,
ppoll = 73,
signalfd4 = 74,
vmsplice = 75,
splice = 76,
tee = 77,
readlinkat = 78,
fstatat = 79,
fstat = 80,
sync = 81,
fsync = 82,
fdatasync = 83,
sync_file_range = 84,
timerfd_create = 85,
timerfd_settime = 86,
timerfd_gettime = 87,
utimensat = 88,
acct = 89,
capget = 90,
capset = 91,
personality = 92,
exit = 93,
exit_group = 94,
waitid = 95,
set_tid_address = 96,
unshare = 97,
futex = 98,
set_robust_list = 99,
get_robust_list = 100,
nanosleep = 101,
getitimer = 102,
setitimer = 103,
kexec_load = 104,
init_module = 105,
delete_module = 106,
timer_create = 107,
timer_gettime = 108,
timer_getoverrun = 109,
timer_settime = 110,
timer_delete = 111,
clock_settime = 112,
clock_gettime = 113,
clock_getres = 114,
clock_nanosleep = 115,
syslog = 116,
ptrace = 117,
sched_setparam = 118,
sched_setscheduler = 119,
sched_getscheduler = 120,
sched_getparam = 121,
sched_setaffinity = 122,
sched_getaffinity = 123,
sched_yield = 124,
sched_get_priority_max = 125,
sched_get_priority_min = 126,
sched_rr_get_interval = 127,
restart_syscall = 128,
kill = 129,
tkill = 130,
tgkill = 131,
sigaltstack = 132,
rt_sigsuspend = 133,
rt_sigaction = 134,
rt_sigprocmask = 135,
rt_sigpending = 136,
rt_sigtimedwait = 137,
rt_sigqueueinfo = 138,
rt_sigreturn = 139,
setpriority = 140,
getpriority = 141,
reboot = 142,
setregid = 143,
setgid = 144,
setreuid = 145,
setuid = 146,
setresuid = 147,
getresuid = 148,
setresgid = 149,
getresgid = 150,
setfsuid = 151,
setfsgid = 152,
times = 153,
setpgid = 154,
getpgid = 155,
getsid = 156,
setsid = 157,
getgroups = 158,
setgroups = 159,
uname = 160,
sethostname = 161,
setdomainname = 162,
getrlimit = 163,
setrlimit = 164,
getrusage = 165,
umask = 166,
prctl = 167,
getcpu = 168,
gettimeofday = 169,
settimeofday = 170,
adjtimex = 171,
getpid = 172,
getppid = 173,
getuid = 174,
geteuid = 175,
getgid = 176,
getegid = 177,
gettid = 178,
sysinfo = 179,
mq_open = 180,
mq_unlink = 181,
mq_timedsend = 182,
mq_timedreceive = 183,
mq_notify = 184,
mq_getsetattr = 185,
msgget = 186,
msgctl = 187,
msgrcv = 188,
msgsnd = 189,
semget = 190,
semctl = 191,
semtimedop = 192,
semop = 193,
shmget = 194,
shmctl = 195,
shmat = 196,
shmdt = 197,
socket = 198,
socketpair = 199,
bind = 200,
listen = 201,
accept = 202,
connect = 203,
getsockname = 204,
getpeername = 205,
sendto = 206,
recvfrom = 207,
setsockopt = 208,
getsockopt = 209,
shutdown = 210,
sendmsg = 211,
recvmsg = 212,
readahead = 213,
brk = 214,
munmap = 215,
mremap = 216,
add_key = 217,
request_key = 218,
keyctl = 219,
clone = 220,
execve = 221,
mmap = 222,
fadvise64 = 223,
swapon = 224,
swapoff = 225,
mprotect = 226,
msync = 227,
mlock = 228,
munlock = 229,
mlockall = 230,
munlockall = 231,
mincore = 232,
madvise = 233,
remap_file_pages = 234,
mbind = 235,
get_mempolicy = 236,
set_mempolicy = 237,
migrate_pages = 238,
move_pages = 239,
rt_tgsigqueueinfo = 240,
perf_event_open = 241,
accept4 = 242,
recvmmsg = 243,
wait4 = 260,
prlimit64 = 261,
fanotify_init = 262,
fanotify_mark = 263,
name_to_handle_at = 264,
open_by_handle_at = 265,
clock_adjtime = 266,
syncfs = 267,
setns = 268,
sendmmsg = 269,
process_vm_readv = 270,
process_vm_writev = 271,
kcmp = 272,
finit_module = 273,
sched_setattr = 274,
sched_getattr = 275,
renameat2 = 276,
seccomp = 277,
getrandom = 278,
memfd_create = 279,
bpf = 280,
execveat = 281,
userfaultfd = 282,
membarrier = 283,
mlock2 = 284,
copy_file_range = 285,
preadv2 = 286,
pwritev2 = 287,
pkey_mprotect = 288,
pkey_alloc = 289,
pkey_free = 290,
statx = 291,
io_pgetevents = 292,
rseq = 293,
kexec_file_load = 294,
pidfd_send_signal = 424,
io_uring_setup = 425,
io_uring_enter = 426,
io_uring_register = 427,
open_tree = 428,
move_mount = 429,
fsopen = 430,
fsconfig = 431,
fsmount = 432,
fspick = 433,
pidfd_open = 434,
clone3 = 435,
close_range = 436,
openat2 = 437,
pidfd_getfd = 438,
faccessat2 = 439,
process_madvise = 440,
epoll_pwait2 = 441,
mount_setattr = 442,
quotactl_fd = 443,
landlock_create_ruleset = 444,
landlock_add_rule = 445,
landlock_restrict_self = 446,
memfd_secret = 447,
process_mrelease = 448,
futex_waitv = 449,
set_mempolicy_home_node = 450,
cachestat = 451,
fchmodat2 = 452,
map_shadow_stack = 453,
futex_wake = 454,
futex_wait = 455,
futex_requeue = 456,
};
const Syscall_x86_64 = enum(usize) {
read = 0,
write = 1,
@ -865,6 +1184,11 @@ const get_map_flags = fn(flags: std.os.MapFlags) MapFlags{
};
}
const exit = fn (exit_code: s32) noreturn {
_ = #syscall(#cast(Syscall.exit), #cast(exit_code));
unreachable;
}
const mmap = fn(address: ?[&]u8, length: usize, protection_flags: ProtectionFlags, map_flags: MapFlags, fd: FileDescriptor, offset: u64) usize {
const flat_protection_flags: u32 = #cast(protection_flags);
const flat_map_flags: u32 = #cast(map_flags);
@ -884,13 +1208,70 @@ const munmap = fn(bytes: []const u8) usize {
}
const readlink = fn(file_path: [&:0]const u8, bytes: []u8) usize {
const result = #syscall(#cast(Syscall.readlink), #cast(file_path), #cast(bytes.pointer), bytes.length);
return result;
switch (cpu) {
.x86_64 => {
const result = #syscall(#cast(Syscall.readlink), #cast(file_path), #cast(bytes.pointer), bytes.length);
return result;
},
.aarch64 => {
const result = #syscall(#cast(Syscall.readlinkat), #cast(current_working_directory_file_descriptor), #cast(file_path), #cast(bytes.pointer), bytes.length);
return result;
},
}
}
const SIG = struct {
const BLOCK = 0;
const UNBLOCK = 1;
const SETMASK = 2;
const HUP = 1;
const INT = 2;
const QUIT = 3;
const ILL = 4;
const TRAP = 5;
const ABRT = 6;
const IOT = ABRT;
const BUS = 7;
const FPE = 8;
const KILL = 9;
const USR1 = 10;
const SEGV = 11;
const USR2 = 12;
const PIPE = 13;
const ALRM = 14;
const TERM = 15;
const STKFLT = 16;
const CHLD = 17;
const CONT = 18;
const STOP = 19;
const TSTP = 20;
const TTIN = 21;
const TTOU = 22;
const URG = 23;
const XCPU = 24;
const XFSZ = 25;
const VTALRM = 26;
const PROF = 27;
const WINCH = 28;
const IO = 29;
const POLL = 29;
const PWR = 30;
const SYS = 31;
};
const fork = fn() usize {
const result = #syscall(#cast(Syscall.fork));
return result;
switch (cpu) {
.x86_64 => {
const result = #syscall(#cast(Syscall.fork));
return result;
},
.aarch64 => {
const result = #syscall(#cast(Syscall.clone), SIG.CHLD, 0);
return result;
},
}
}
const execve = fn(path: [&:0]const u8, argv: [&:null]const ?[&:0]const u8, env: [&:null]const ?[&:null]const u8) usize {
@ -910,8 +1291,16 @@ const dup2 = fn(old: FileDescriptor, new: FileDescriptor) usize {
const open = fn(path: [&:0]const u8, flags: OpenFlags, permissions: u32) usize {
const flattened_flags: u32 = #cast(flags);
const result = #syscall(#cast(Syscall.open), #cast(path), flattened_flags, permissions);
return result;
switch (cpu) {
.x86_64 => {
const result = #syscall(#cast(Syscall.open), #cast(path), flattened_flags, permissions);
return result;
},
.aarch64 => {
const result = #syscall(#cast(Syscall.openat), #cast(current_working_directory_file_descriptor), #cast(path), flattened_flags, permissions);
return result;
},
}
}
const openat = fn(directory_file_descriptor: FileDescriptor, path: [&:0]const u8, flags: u32, permissions: u32) usize {

View File

@ -9,12 +9,21 @@ comptime {
}
const _start = fn naked cc(.c) () noreturn {
#asm(`
xor ebp, ebp;
mov rdi, rsp;
and rsp, 0xfffffffffffffff0;
call {start};
`);
switch (builtin.cpu) {
.x86_64 => #asm(`
xor ebp, ebp;
mov rdi, rsp;
and rsp, 0xfffffffffffffff0;
call {start};
`),
.aarch64 => #asm(`
mov fp, 0;
mov lr, 0;
mov x0, sp;
b {start};
`),
else => #error("Architecture not supported"),
}
}
var argument_count: usize = 0;

View File

@ -0,0 +1,4 @@
.global foo
foo:
mov w0, #42
ret

2
test/cc_x86_64/c_asm/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
*.o
build/

View File

@ -0,0 +1,3 @@
cmake_minimum_required(VERSION 3.15)
project(c_asm C ASM)
add_executable(c_asm main.c assembly.S)

View File

@ -0,0 +1,7 @@
extern int foo();
#include <assert.h>
int main()
{
assert(foo() == 42);
return 0;
}