Merge pull request #24497 from ziglang/aro-translate-c

compiler: update aro and translate-c to latest; delete clang translate-c
This commit is contained in:
Andrew Kelley 2025-09-25 14:18:49 -07:00 committed by GitHub
commit 2a88a6a456
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
154 changed files with 43718 additions and 53257 deletions

View File

@ -197,7 +197,6 @@ set(ZIG_CPP_SOURCES
# These are planned to stay even when we are self-hosted.
src/zig_llvm.cpp
src/zig_llvm-ar.cpp
src/zig_clang.cpp
src/zig_clang_driver.cpp
src/zig_clang_cc1_main.cpp
src/zig_clang_cc1as_main.cpp
@ -502,7 +501,6 @@ set(ZIG_STAGE2_SOURCES
lib/std/zig/Server.zig
lib/std/zig/WindowsSdk.zig
lib/std/zig/Zir.zig
lib/std/zig/c_builtins.zig
lib/std/zig/string_literal.zig
lib/std/zig/system.zig
lib/std/zig/system/NativePaths.zig
@ -537,7 +535,6 @@ set(ZIG_STAGE2_SOURCES
src/Value.zig
src/Zcu.zig
src/Zcu/PerThread.zig
src/clang.zig
src/clang_options.zig
src/clang_options_data.zig
src/codegen.zig
@ -641,7 +638,6 @@ set(ZIG_STAGE2_SOURCES
src/register_manager.zig
src/target.zig
src/tracy.zig
src/translate_c.zig
src/libs/wasi_libc.zig
)

View File

@ -90,8 +90,6 @@ pub fn build(b: *std.Build) !void {
const skip_libc = b.option(bool, "skip-libc", "Main test suite skips tests that link libc") orelse false;
const skip_single_threaded = b.option(bool, "skip-single-threaded", "Main test suite skips tests that are single-threaded") orelse false;
const skip_compile_errors = b.option(bool, "skip-compile-errors", "Main test suite skips compile error tests") orelse false;
const skip_translate_c = b.option(bool, "skip-translate-c", "Main test suite skips translate-c tests") orelse false;
const skip_run_translated_c = b.option(bool, "skip-run-translated-c", "Main test suite skips run-translated-c tests") orelse skip_translate_c;
const skip_freebsd = b.option(bool, "skip-freebsd", "Main test suite skips targets with freebsd OS") orelse false;
const skip_netbsd = b.option(bool, "skip-netbsd", "Main test suite skips targets with netbsd OS") orelse false;
const skip_windows = b.option(bool, "skip-windows", "Main test suite skips targets with windows OS") orelse false;
@ -416,7 +414,7 @@ pub fn build(b: *std.Build) !void {
test_step.dependOn(check_fmt);
const test_cases_step = b.step("test-cases", "Run the main compiler test cases");
try tests.addCases(b, test_cases_step, target, .{
try tests.addCases(b, test_cases_step, .{
.test_filters = test_filters,
.test_target_filters = test_target_filters,
.skip_compile_errors = skip_compile_errors,
@ -428,9 +426,6 @@ pub fn build(b: *std.Build) !void {
.skip_linux = skip_linux,
.skip_llvm = skip_llvm,
.skip_libc = skip_libc,
}, .{
.skip_translate_c = skip_translate_c,
.skip_run_translated_c = skip_run_translated_c,
}, .{
.enable_llvm = enable_llvm,
.llvm_has_m68k = llvm_has_m68k,
@ -465,28 +460,6 @@ pub fn build(b: *std.Build) !void {
.max_rss = 4000000000,
}));
if (!skip_translate_c) {
test_modules_step.dependOn(tests.addModuleTests(b, .{
.test_filters = test_filters,
.test_target_filters = test_target_filters,
.test_extra_targets = test_extra_targets,
.root_src = "test/c_import.zig",
.name = "c-import",
.desc = "Run the @cImport tests",
.optimize_modes = optimization_modes,
.include_paths = &.{"test/c_import"},
.skip_single_threaded = true,
.skip_non_native = skip_non_native,
.skip_freebsd = skip_freebsd,
.skip_netbsd = skip_netbsd,
.skip_windows = skip_windows,
.skip_macos = skip_macos,
.skip_linux = skip_linux,
.skip_llvm = skip_llvm,
.skip_libc = skip_libc,
}));
}
test_modules_step.dependOn(tests.addModuleTests(b, .{
.test_filters = test_filters,
.test_target_filters = test_target_filters,
@ -577,7 +550,6 @@ pub fn build(b: *std.Build) !void {
enable_macos_sdk,
enable_ios_sdk,
enable_symlinks_windows,
skip_translate_c,
));
test_step.dependOn(tests.addCAbiTests(b, .{
.test_target_filters = test_target_filters,
@ -732,13 +704,7 @@ fn addCompilerMod(b: *std.Build, options: AddCompilerModOptions) *std.Build.Modu
.root_source_file = b.path("lib/compiler/aro/aro.zig"),
});
const aro_translate_c_mod = b.createModule(.{
.root_source_file = b.path("lib/compiler/aro_translate_c.zig"),
});
aro_translate_c_mod.addImport("aro", aro_mod);
compiler_mod.addImport("aro", aro_mod);
compiler_mod.addImport("aro_translate_c", aro_translate_c_mod);
return compiler_mod;
}
@ -1158,7 +1124,6 @@ fn toNativePathSep(b: *std.Build, s: []const u8) []u8 {
const zig_cpp_sources = [_][]const u8{
// These are planned to stay even when we are self-hosted.
"src/zig_llvm.cpp",
"src/zig_clang.cpp",
"src/zig_llvm-ar.cpp",
"src/zig_clang_driver.cpp",
"src/zig_clang_cc1_main.cpp",

View File

@ -1,26 +0,0 @@
<img src="https://aro.vexu.eu/aro-logo.svg" alt="Aro" width="120px"/>
# Aro
A C compiler with the goal of providing fast compilation and low memory usage with good diagnostics.
Aro is included as an alternative C frontend in the [Zig compiler](https://github.com/ziglang/zig)
for `translate-c` and eventually compiling C files by translating them to Zig first.
Aro is developed in https://github.com/Vexu/arocc and the Zig dependency is
updated from there when needed.
Currently most of standard C is supported up to C23 and as are many of the common
extensions from GNU, MSVC, and Clang
Basic code generation is supported for x86-64 linux and can produce a valid hello world:
```sh-session
$ cat hello.c
extern int printf(const char *restrict fmt, ...);
int main(void) {
printf("Hello, world!\n");
return 0;
}
$ zig build && ./zig-out/bin/arocc hello.c -o hello
$ ./hello
Hello, world!
```

View File

@ -5,12 +5,14 @@ pub const Driver = @import("aro/Driver.zig");
pub const Parser = @import("aro/Parser.zig");
pub const Preprocessor = @import("aro/Preprocessor.zig");
pub const Source = @import("aro/Source.zig");
pub const StringInterner = @import("aro/StringInterner.zig");
pub const target_util = @import("aro/target.zig");
pub const Tokenizer = @import("aro/Tokenizer.zig");
pub const Toolchain = @import("aro/Toolchain.zig");
pub const Tree = @import("aro/Tree.zig");
pub const Type = @import("aro/Type.zig");
pub const TypeMapper = @import("aro/StringInterner.zig").TypeMapper;
pub const target_util = @import("aro/target.zig");
pub const TypeStore = @import("aro/TypeStore.zig");
pub const QualType = TypeStore.QualType;
pub const Type = TypeStore.Type;
pub const Value = @import("aro/Value.zig");
const backend = @import("backend.zig");
@ -18,6 +20,7 @@ pub const Interner = backend.Interner;
pub const Ir = backend.Ir;
pub const Object = backend.Object;
pub const CallingConvention = backend.CallingConvention;
pub const Assembly = backend.Assembly;
pub const version_str = backend.version_str;
pub const version = backend.version;
@ -34,6 +37,5 @@ test {
_ = @import("aro/Preprocessor.zig");
_ = @import("aro/target.zig");
_ = @import("aro/Tokenizer.zig");
_ = @import("aro/toolchains/Linux.zig");
_ = @import("aro/Value.zig");
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,21 +1,23 @@
const std = @import("std");
const Compilation = @import("Compilation.zig");
const Type = @import("Type.zig");
const TypeDescription = @import("Builtins/TypeDescription.zig");
const target_util = @import("target.zig");
const StringId = @import("StringInterner.zig").StringId;
const LangOpts = @import("LangOpts.zig");
const Parser = @import("Parser.zig");
const target_util = @import("target.zig");
const TypeStore = @import("TypeStore.zig");
const QualType = TypeStore.QualType;
const Builder = TypeStore.Builder;
const TypeDescription = @import("Builtins/TypeDescription.zig");
const Properties = @import("Builtins/Properties.zig");
pub const Builtin = @import("Builtins/Builtin.zig").with(Properties);
const Expanded = struct {
ty: Type,
qt: QualType,
builtin: Builtin,
};
const NameToTypeMap = std.StringHashMapUnmanaged(Type);
const NameToTypeMap = std.StringHashMapUnmanaged(QualType);
const Builtins = @This();
@ -25,38 +27,38 @@ pub fn deinit(b: *Builtins, gpa: std.mem.Allocator) void {
b._name_to_type_map.deinit(gpa);
}
fn specForSize(comp: *const Compilation, size_bits: u32) Type.Builder.Specifier {
var ty = Type{ .specifier = .short };
if (ty.sizeof(comp).? * 8 == size_bits) return .short;
fn specForSize(comp: *const Compilation, size_bits: u32) TypeStore.Builder.Specifier {
var qt: QualType = .short;
if (qt.bitSizeof(comp) == size_bits) return .short;
ty.specifier = .int;
if (ty.sizeof(comp).? * 8 == size_bits) return .int;
qt = .int;
if (qt.bitSizeof(comp) == size_bits) return .int;
ty.specifier = .long;
if (ty.sizeof(comp).? * 8 == size_bits) return .long;
qt = .long;
if (qt.bitSizeof(comp) == size_bits) return .long;
ty.specifier = .long_long;
if (ty.sizeof(comp).? * 8 == size_bits) return .long_long;
qt = .long_long;
if (qt.bitSizeof(comp) == size_bits) return .long_long;
unreachable;
}
fn createType(desc: TypeDescription, it: *TypeDescription.TypeIterator, comp: *const Compilation, allocator: std.mem.Allocator) !Type {
var builder: Type.Builder = .{ .error_on_invalid = true };
fn createType(desc: TypeDescription, it: *TypeDescription.TypeIterator, comp: *Compilation) !QualType {
var parser: Parser = undefined;
parser.comp = comp;
var builder: TypeStore.Builder = .{ .parser = &parser, .error_on_invalid = true };
var require_native_int32 = false;
var require_native_int64 = false;
for (desc.prefix) |prefix| {
switch (prefix) {
.L => builder.combine(undefined, .long, 0) catch unreachable,
.LL => {
builder.combine(undefined, .long, 0) catch unreachable;
builder.combine(undefined, .long, 0) catch unreachable;
},
.L => builder.combine(.long, 0) catch unreachable,
.LL => builder.combine(.long_long, 0) catch unreachable,
.LLL => {
switch (builder.specifier) {
.none => builder.specifier = .int128,
.signed => builder.specifier = .sint128,
.unsigned => builder.specifier = .uint128,
switch (builder.type) {
.none => builder.type = .int128,
.signed => builder.type = .sint128,
.unsigned => builder.type = .uint128,
else => unreachable,
}
},
@ -65,239 +67,226 @@ fn createType(desc: TypeDescription, it: *TypeDescription.TypeIterator, comp: *c
.N => {
std.debug.assert(desc.spec == .i);
if (!target_util.isLP64(comp.target)) {
builder.combine(undefined, .long, 0) catch unreachable;
builder.combine(.long, 0) catch unreachable;
}
},
.O => {
builder.combine(undefined, .long, 0) catch unreachable;
builder.combine(.long, 0) catch unreachable;
if (comp.target.os.tag != .opencl) {
builder.combine(undefined, .long, 0) catch unreachable;
builder.combine(.long, 0) catch unreachable;
}
},
.S => builder.combine(undefined, .signed, 0) catch unreachable,
.U => builder.combine(undefined, .unsigned, 0) catch unreachable,
.S => builder.combine(.signed, 0) catch unreachable,
.U => builder.combine(.unsigned, 0) catch unreachable,
.I => {
// Todo: compile-time constant integer
},
}
}
switch (desc.spec) {
.v => builder.combine(undefined, .void, 0) catch unreachable,
.b => builder.combine(undefined, .bool, 0) catch unreachable,
.c => builder.combine(undefined, .char, 0) catch unreachable,
.s => builder.combine(undefined, .short, 0) catch unreachable,
.v => builder.combine(.void, 0) catch unreachable,
.b => builder.combine(.bool, 0) catch unreachable,
.c => builder.combine(.char, 0) catch unreachable,
.s => builder.combine(.short, 0) catch unreachable,
.i => {
if (require_native_int32) {
builder.specifier = specForSize(comp, 32);
builder.type = specForSize(comp, 32);
} else if (require_native_int64) {
builder.specifier = specForSize(comp, 64);
builder.type = specForSize(comp, 64);
} else {
switch (builder.specifier) {
switch (builder.type) {
.int128, .sint128, .uint128 => {},
else => builder.combine(undefined, .int, 0) catch unreachable,
else => builder.combine(.int, 0) catch unreachable,
}
}
},
.h => builder.combine(undefined, .fp16, 0) catch unreachable,
.x => builder.combine(undefined, .float16, 0) catch unreachable,
.h => builder.combine(.fp16, 0) catch unreachable,
.x => builder.combine(.float16, 0) catch unreachable,
.y => {
// Todo: __bf16
return .{ .specifier = .invalid };
return .invalid;
},
.f => builder.combine(undefined, .float, 0) catch unreachable,
.f => builder.combine(.float, 0) catch unreachable,
.d => {
if (builder.specifier == .long_long) {
builder.specifier = .float128;
if (builder.type == .long_long) {
builder.type = .float128;
} else {
builder.combine(undefined, .double, 0) catch unreachable;
builder.combine(.double, 0) catch unreachable;
}
},
.z => {
std.debug.assert(builder.specifier == .none);
builder.specifier = Type.Builder.fromType(comp.types.size);
std.debug.assert(builder.type == .none);
builder.type = Builder.fromType(comp, comp.type_store.size);
},
.w => {
std.debug.assert(builder.specifier == .none);
builder.specifier = Type.Builder.fromType(comp.types.wchar);
std.debug.assert(builder.type == .none);
builder.type = Builder.fromType(comp, comp.type_store.wchar);
},
.F => {
std.debug.assert(builder.specifier == .none);
builder.specifier = Type.Builder.fromType(comp.types.ns_constant_string.ty);
std.debug.assert(builder.type == .none);
builder.type = Builder.fromType(comp, comp.type_store.ns_constant_string);
},
.G => {
// Todo: id
return .{ .specifier = .invalid };
return .invalid;
},
.H => {
// Todo: SEL
return .{ .specifier = .invalid };
return .invalid;
},
.M => {
// Todo: struct objc_super
return .{ .specifier = .invalid };
return .invalid;
},
.a => {
std.debug.assert(builder.specifier == .none);
std.debug.assert(builder.type == .none);
std.debug.assert(desc.suffix.len == 0);
builder.specifier = Type.Builder.fromType(comp.types.va_list);
builder.type = Builder.fromType(comp, comp.type_store.va_list);
},
.A => {
std.debug.assert(builder.specifier == .none);
std.debug.assert(builder.type == .none);
std.debug.assert(desc.suffix.len == 0);
var va_list = comp.types.va_list;
if (va_list.isArray()) va_list.decayArray();
builder.specifier = Type.Builder.fromType(va_list);
var va_list = comp.type_store.va_list;
std.debug.assert(!va_list.is(comp, .array));
builder.type = Builder.fromType(comp, va_list);
},
.V => |element_count| {
std.debug.assert(desc.suffix.len == 0);
const child_desc = it.next().?;
const child_ty = try createType(child_desc, undefined, comp, allocator);
const arr_ty = try allocator.create(Type.Array);
arr_ty.* = .{
const elem_qt = try createType(child_desc, undefined, comp);
const vector_qt = try comp.type_store.put(comp.gpa, .{ .vector = .{
.elem = elem_qt,
.len = element_count,
.elem = child_ty,
};
const vector_ty: Type = .{ .specifier = .vector, .data = .{ .array = arr_ty } };
builder.specifier = Type.Builder.fromType(vector_ty);
} });
builder.type = .{ .other = vector_qt };
},
.q => {
// Todo: scalable vector
return .{ .specifier = .invalid };
return .invalid;
},
.E => {
// Todo: ext_vector (OpenCL vector)
return .{ .specifier = .invalid };
return .invalid;
},
.X => |child| {
builder.combine(undefined, .complex, 0) catch unreachable;
builder.combine(.complex, 0) catch unreachable;
switch (child) {
.float => builder.combine(undefined, .float, 0) catch unreachable,
.double => builder.combine(undefined, .double, 0) catch unreachable,
.float => builder.combine(.float, 0) catch unreachable,
.double => builder.combine(.double, 0) catch unreachable,
.longdouble => {
builder.combine(undefined, .long, 0) catch unreachable;
builder.combine(undefined, .double, 0) catch unreachable;
builder.combine(.long, 0) catch unreachable;
builder.combine(.double, 0) catch unreachable;
},
}
},
.Y => {
std.debug.assert(builder.specifier == .none);
std.debug.assert(builder.type == .none);
std.debug.assert(desc.suffix.len == 0);
builder.specifier = Type.Builder.fromType(comp.types.ptrdiff);
builder.type = Builder.fromType(comp, comp.type_store.ptrdiff);
},
.P => {
std.debug.assert(builder.specifier == .none);
if (comp.types.file.specifier == .invalid) {
return comp.types.file;
std.debug.assert(builder.type == .none);
if (comp.type_store.file.isInvalid()) {
return comp.type_store.file;
}
builder.specifier = Type.Builder.fromType(comp.types.file);
builder.type = Builder.fromType(comp, comp.type_store.file);
},
.J => {
std.debug.assert(builder.specifier == .none);
std.debug.assert(builder.type == .none);
std.debug.assert(desc.suffix.len == 0);
if (comp.types.jmp_buf.specifier == .invalid) {
return comp.types.jmp_buf;
if (comp.type_store.jmp_buf.isInvalid()) {
return comp.type_store.jmp_buf;
}
builder.specifier = Type.Builder.fromType(comp.types.jmp_buf);
builder.type = Builder.fromType(comp, comp.type_store.jmp_buf);
},
.SJ => {
std.debug.assert(builder.specifier == .none);
std.debug.assert(builder.type == .none);
std.debug.assert(desc.suffix.len == 0);
if (comp.types.sigjmp_buf.specifier == .invalid) {
return comp.types.sigjmp_buf;
if (comp.type_store.sigjmp_buf.isInvalid()) {
return comp.type_store.sigjmp_buf;
}
builder.specifier = Type.Builder.fromType(comp.types.sigjmp_buf);
builder.type = Builder.fromType(comp, comp.type_store.sigjmp_buf);
},
.K => {
std.debug.assert(builder.specifier == .none);
if (comp.types.ucontext_t.specifier == .invalid) {
return comp.types.ucontext_t;
std.debug.assert(builder.type == .none);
if (comp.type_store.ucontext_t.isInvalid()) {
return comp.type_store.ucontext_t;
}
builder.specifier = Type.Builder.fromType(comp.types.ucontext_t);
builder.type = Builder.fromType(comp, comp.type_store.ucontext_t);
},
.p => {
std.debug.assert(builder.specifier == .none);
std.debug.assert(builder.type == .none);
std.debug.assert(desc.suffix.len == 0);
builder.specifier = Type.Builder.fromType(comp.types.pid_t);
builder.type = Builder.fromType(comp, comp.type_store.pid_t);
},
.@"!" => return .{ .specifier = .invalid },
.@"!" => return .invalid,
}
for (desc.suffix) |suffix| {
switch (suffix) {
.@"*" => |address_space| {
_ = address_space; // TODO: handle address space
const elem_ty = try allocator.create(Type);
elem_ty.* = builder.finish(undefined) catch unreachable;
const ty = Type{
.specifier = .pointer,
.data = .{ .sub_type = elem_ty },
};
builder.qual = .{};
builder.specifier = Type.Builder.fromType(ty);
const pointer_qt = try comp.type_store.put(comp.gpa, .{ .pointer = .{
.child = builder.finish() catch unreachable,
.decayed = null,
} });
builder.@"const" = null;
builder.@"volatile" = null;
builder.restrict = null;
builder.type = .{ .other = pointer_qt };
},
.C => builder.qual.@"const" = 0,
.D => builder.qual.@"volatile" = 0,
.R => builder.qual.restrict = 0,
.C => builder.@"const" = 0,
.D => builder.@"volatile" = 0,
.R => builder.restrict = 0,
}
}
return builder.finish(undefined) catch unreachable;
return builder.finish() catch unreachable;
}
fn createBuiltin(comp: *const Compilation, builtin: Builtin, type_arena: std.mem.Allocator) !Type {
fn createBuiltin(comp: *Compilation, builtin: Builtin) !QualType {
var it = TypeDescription.TypeIterator.init(builtin.properties.param_str);
const ret_ty_desc = it.next().?;
if (ret_ty_desc.spec == .@"!") {
// Todo: handle target-dependent definition
}
const ret_ty = try createType(ret_ty_desc, &it, comp, type_arena);
const ret_ty = try createType(ret_ty_desc, &it, comp);
var param_count: usize = 0;
var params: [Builtin.max_param_count]Type.Func.Param = undefined;
var params: [Builtin.max_param_count]TypeStore.Type.Func.Param = undefined;
while (it.next()) |desc| : (param_count += 1) {
params[param_count] = .{ .name_tok = 0, .ty = try createType(desc, &it, comp, type_arena), .name = .empty };
params[param_count] = .{ .name_tok = 0, .qt = try createType(desc, &it, comp), .name = .empty, .node = .null };
}
const duped_params = try type_arena.dupe(Type.Func.Param, params[0..param_count]);
const func = try type_arena.create(Type.Func);
func.* = .{
return comp.type_store.put(comp.gpa, .{ .func = .{
.return_type = ret_ty,
.params = duped_params,
};
return .{
.specifier = if (builtin.properties.isVarArgs()) .var_args_func else .func,
.data = .{ .func = func },
};
.kind = if (builtin.properties.isVarArgs()) .variadic else .normal,
.params = params[0..param_count],
} });
}
/// Asserts that the builtin has already been created
pub fn lookup(b: *const Builtins, name: []const u8) Expanded {
const builtin = Builtin.fromName(name).?;
const ty = b._name_to_type_map.get(name).?;
return .{
.builtin = builtin,
.ty = ty,
};
const qt = b._name_to_type_map.get(name).?;
return .{ .builtin = builtin, .qt = qt };
}
pub fn getOrCreate(b: *Builtins, comp: *Compilation, name: []const u8, type_arena: std.mem.Allocator) !?Expanded {
const ty = b._name_to_type_map.get(name) orelse {
pub fn getOrCreate(b: *Builtins, comp: *Compilation, name: []const u8) !?Expanded {
const qt = b._name_to_type_map.get(name) orelse {
const builtin = Builtin.fromName(name) orelse return null;
if (!comp.hasBuiltinFunction(builtin)) return null;
try b._name_to_type_map.ensureUnusedCapacity(comp.gpa, 1);
const ty = try createBuiltin(comp, builtin, type_arena);
b._name_to_type_map.putAssumeCapacity(name, ty);
const qt = try createBuiltin(comp, builtin);
b._name_to_type_map.putAssumeCapacity(name, qt);
return .{
.builtin = builtin,
.ty = ty,
.qt = qt,
};
};
const builtin = Builtin.fromName(name).?;
return .{
.builtin = builtin,
.ty = ty,
};
return .{ .builtin = builtin, .qt = qt };
}
pub const Iterator = struct {
@ -323,12 +312,13 @@ pub const Iterator = struct {
};
test Iterator {
const gpa = std.testing.allocator;
var it = Iterator{};
var seen = std.StringHashMap(Builtin).init(std.testing.allocator);
defer seen.deinit();
var seen: std.StringHashMapUnmanaged(Builtin) = .empty;
defer seen.deinit(gpa);
var arena_state = std.heap.ArenaAllocator.init(std.testing.allocator);
var arena_state = std.heap.ArenaAllocator.init(gpa);
defer arena_state.deinit();
const arena = arena_state.allocator();
@ -344,25 +334,27 @@ test Iterator {
std.debug.print("previous data: {}\n", .{seen.get(entry.name).?});
return error.TestExpectedUniqueEntries;
}
try seen.put(try arena.dupe(u8, entry.name), entry.builtin);
try seen.put(gpa, try arena.dupe(u8, entry.name), entry.builtin);
}
try std.testing.expectEqual(@as(usize, Builtin.data.len), seen.count());
}
test "All builtins" {
var comp = Compilation.init(std.testing.allocator, std.fs.cwd());
defer comp.deinit();
_ = try comp.generateBuiltinMacros(.include_system_defines);
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
var arena_state: std.heap.ArenaAllocator = .init(std.testing.allocator);
defer arena_state.deinit();
const arena = arena_state.allocator();
const type_arena = arena.allocator();
var comp = Compilation.init(std.testing.allocator, arena, undefined, std.fs.cwd());
defer comp.deinit();
try comp.type_store.initNamedTypes(&comp);
comp.type_store.va_list = try comp.type_store.va_list.decay(&comp);
var builtin_it = Iterator{};
while (builtin_it.next()) |entry| {
const name = try type_arena.dupe(u8, entry.name);
if (try comp.builtins.getOrCreate(&comp, name, type_arena)) |func_ty| {
const get_again = (try comp.builtins.getOrCreate(&comp, name, std.testing.failing_allocator)).?;
const name = try arena.dupe(u8, entry.name);
if (try comp.builtins.getOrCreate(&comp, name)) |func_ty| {
const get_again = (try comp.builtins.getOrCreate(&comp, name)).?;
const found_by_lookup = comp.builtins.lookup(name);
try std.testing.expectEqual(func_ty.builtin.tag, get_again.builtin.tag);
try std.testing.expectEqual(func_ty.builtin.tag, found_by_lookup.builtin.tag);
@ -373,19 +365,19 @@ test "All builtins" {
test "Allocation failures" {
const Test = struct {
fn testOne(allocator: std.mem.Allocator) !void {
var comp = Compilation.init(allocator, std.fs.cwd());
var arena_state: std.heap.ArenaAllocator = .init(allocator);
defer arena_state.deinit();
const arena = arena_state.allocator();
var comp = Compilation.init(allocator, arena, undefined, std.fs.cwd());
defer comp.deinit();
_ = try comp.generateBuiltinMacros(.include_system_defines);
var arena = std.heap.ArenaAllocator.init(comp.gpa);
defer arena.deinit();
const type_arena = arena.allocator();
const num_builtins = 40;
var builtin_it = Iterator{};
for (0..num_builtins) |_| {
const entry = builtin_it.next().?;
_ = try comp.builtins.getOrCreate(&comp, entry.name, type_arena);
_ = try comp.builtins.getOrCreate(&comp, entry.name);
}
}
};

File diff suppressed because it is too large Load Diff

View File

@ -5,8 +5,9 @@ const Builtins = @import("../Builtins.zig");
const Builtin = Builtins.Builtin;
const Parser = @import("../Parser.zig");
const Tree = @import("../Tree.zig");
const NodeIndex = Tree.NodeIndex;
const Type = @import("../Type.zig");
const TypeStore = @import("../TypeStore.zig");
const Type = TypeStore.Type;
const QualType = TypeStore.QualType;
const Value = @import("../Value.zig");
fn makeNan(comptime T: type, str: []const u8) T {
@ -22,22 +23,22 @@ fn makeNan(comptime T: type, str: []const u8) T {
return @bitCast(@as(UnsignedSameSize, bits) | @as(UnsignedSameSize, @bitCast(std.math.nan(T))));
}
pub fn eval(tag: Builtin.Tag, p: *Parser, args: []const NodeIndex) !Value {
pub fn eval(tag: Builtin.Tag, p: *Parser, args: []const Tree.Node.Index) !Value {
const builtin = Builtin.fromTag(tag);
if (!builtin.properties.attributes.const_evaluable) return .{};
switch (tag) {
Builtin.tagFromName("__builtin_inff").?,
Builtin.tagFromName("__builtin_inf").?,
Builtin.tagFromName("__builtin_infl").?,
.__builtin_inff,
.__builtin_inf,
.__builtin_infl,
=> {
const ty: Type = switch (tag) {
Builtin.tagFromName("__builtin_inff").? => .{ .specifier = .float },
Builtin.tagFromName("__builtin_inf").? => .{ .specifier = .double },
Builtin.tagFromName("__builtin_infl").? => .{ .specifier = .long_double },
const qt: QualType = switch (tag) {
.__builtin_inff => .float,
.__builtin_inf => .double,
.__builtin_infl => .long_double,
else => unreachable,
};
const f: Interner.Key.Float = switch (ty.bitSizeof(p.comp).?) {
const f: Interner.Key.Float = switch (qt.bitSizeof(p.comp)) {
32 => .{ .f32 = std.math.inf(f32) },
64 => .{ .f64 = std.math.inf(f64) },
80 => .{ .f80 = std.math.inf(f80) },
@ -46,14 +47,14 @@ pub fn eval(tag: Builtin.Tag, p: *Parser, args: []const NodeIndex) !Value {
};
return Value.intern(p.comp, .{ .float = f });
},
Builtin.tagFromName("__builtin_isinf").? => blk: {
.__builtin_isinf => blk: {
if (args.len == 0) break :blk;
const val = p.value_map.get(args[0]) orelse break :blk;
const val = p.tree.value_map.get(args[0]) orelse break :blk;
return Value.fromBool(val.isInf(p.comp));
},
Builtin.tagFromName("__builtin_isinf_sign").? => blk: {
.__builtin_isinf_sign => blk: {
if (args.len == 0) break :blk;
const val = p.value_map.get(args[0]) orelse break :blk;
const val = p.tree.value_map.get(args[0]) orelse break :blk;
switch (val.isInfSign(p.comp)) {
.unknown => {},
.finite => return Value.zero,
@ -61,17 +62,17 @@ pub fn eval(tag: Builtin.Tag, p: *Parser, args: []const NodeIndex) !Value {
.negative => return Value.int(@as(i64, -1), p.comp),
}
},
Builtin.tagFromName("__builtin_isnan").? => blk: {
.__builtin_isnan => blk: {
if (args.len == 0) break :blk;
const val = p.value_map.get(args[0]) orelse break :blk;
const val = p.tree.value_map.get(args[0]) orelse break :blk;
return Value.fromBool(val.isNan(p.comp));
},
Builtin.tagFromName("__builtin_nan").? => blk: {
.__builtin_nan => blk: {
if (args.len == 0) break :blk;
const val = p.getDecayedStringLiteral(args[0]) orelse break :blk;
const bytes = p.comp.interner.get(val.ref()).bytes;
const f: Interner.Key.Float = switch ((Type{ .specifier = .double }).bitSizeof(p.comp).?) {
const f: Interner.Key.Float = switch (Type.Float.double.bits(p.comp)) {
32 => .{ .f32 = makeNan(f32, bytes) },
64 => .{ .f64 = makeNan(f64, bytes) },
80 => .{ .f80 = makeNan(f80, bytes) },

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

99
lib/compiler/aro/aro/DepFile.zig vendored Normal file
View File

@ -0,0 +1,99 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
pub const Format = enum { make, nmake };
const DepFile = @This();
target: []const u8,
deps: std.StringArrayHashMapUnmanaged(void) = .empty,
format: Format,
pub fn deinit(d: *DepFile, gpa: Allocator) void {
d.deps.deinit(gpa);
d.* = undefined;
}
pub fn addDependency(d: *DepFile, gpa: Allocator, path: []const u8) !void {
try d.deps.put(gpa, path, {});
}
pub fn addDependencyDupe(d: *DepFile, gpa: Allocator, arena: Allocator, path: []const u8) !void {
const gop = try d.deps.getOrPut(gpa, path);
if (gop.found_existing) return;
gop.key_ptr.* = try arena.dupe(u8, path);
}
pub fn write(d: *const DepFile, w: *std.Io.Writer) std.Io.Writer.Error!void {
const max_columns = 75;
var columns: usize = 0;
try writeTarget(d.target, w);
columns += d.target.len;
try w.writeByte(':');
columns += 1;
for (d.deps.keys()) |path| {
if (std.mem.eql(u8, path, "<stdin>")) continue;
if (columns + path.len + " \\\n".len > max_columns) {
try w.writeAll(" \\\n ");
columns = 1;
}
try w.writeByte(' ');
try d.writePath(path, w);
columns += path.len + 1;
}
try w.writeByte('\n');
try w.flush();
}
fn writeTarget(path: []const u8, w: *std.Io.Writer) !void {
for (path, 0..) |c, i| {
switch (c) {
' ', '\t' => {
try w.writeByte('\\');
var j = i;
while (j != 0) {
j -= 1;
if (path[j] != '\\') break;
try w.writeByte('\\');
}
},
'$' => try w.writeByte('$'),
'#' => try w.writeByte('\\'),
else => {},
}
try w.writeByte(c);
}
}
fn writePath(d: *const DepFile, path: []const u8, w: *std.Io.Writer) !void {
switch (d.format) {
.nmake => {
if (std.mem.indexOfAny(u8, path, " #${}^!")) |_|
try w.print("\"{s}\"", .{path})
else
try w.writeAll(path);
},
.make => {
for (path, 0..) |c, i| {
switch (c) {
' ' => {
try w.writeByte('\\');
var j = i;
while (j != 0) {
j -= 1;
if (path[j] != '\\') break;
try w.writeByte('\\');
}
},
'$' => try w.writeByte('$'),
'#' => try w.writeByte('\\'),
else => {},
}
try w.writeByte(c);
}
},
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -96,7 +96,7 @@ fn findProgramByNamePosix(name: []const u8, path: ?[]const u8, buf: []u8) ?[]con
}
pub const Filesystem = union(enum) {
real: void,
real: std.fs.Dir,
fake: []const Entry,
const Entry = struct {
@ -172,8 +172,8 @@ pub const Filesystem = union(enum) {
pub fn exists(fs: Filesystem, path: []const u8) bool {
switch (fs) {
.real => {
std.fs.cwd().access(path, .{}) catch return false;
.real => |cwd| {
cwd.access(path, .{}) catch return false;
return true;
},
.fake => |paths| return existsFake(paths, path),
@ -210,8 +210,8 @@ pub const Filesystem = union(enum) {
/// Otherwise returns a slice of `buf`. If the file is larger than `buf` partial contents are returned
pub fn readFile(fs: Filesystem, path: []const u8, buf: []u8) ?[]const u8 {
return switch (fs) {
.real => {
const file = std.fs.cwd().openFile(path, .{}) catch return null;
.real => |cwd| {
const file = cwd.openFile(path, .{}) catch return null;
defer file.close();
const bytes_read = file.readAll(buf) catch return null;
@ -223,7 +223,7 @@ pub const Filesystem = union(enum) {
pub fn openDir(fs: Filesystem, dir_name: []const u8) std.fs.Dir.OpenError!Dir {
return switch (fs) {
.real => .{ .dir = try std.fs.cwd().openDir(dir_name, .{ .access_sub_paths = false, .iterate = true }) },
.real => |cwd| .{ .dir = try cwd.openDir(dir_name, .{ .access_sub_paths = false, .iterate = true }) },
.fake => |entries| .{ .fake = .{ .entries = entries, .path = dir_name } },
};
}

View File

@ -1,634 +0,0 @@
const std = @import("std");
const Toolchain = @import("../Toolchain.zig");
const target_util = @import("../target.zig");
const system_defaults = @import("system_defaults");
const GCCVersion = @import("GCCVersion.zig");
const Multilib = @import("Multilib.zig");
const GCCDetector = @This();
is_valid: bool = false,
install_path: []const u8 = "",
parent_lib_path: []const u8 = "",
version: GCCVersion = .{},
gcc_triple: []const u8 = "",
selected: Multilib = .{},
biarch_sibling: ?Multilib = null,
pub fn deinit(self: *GCCDetector) void {
if (!self.is_valid) return;
}
pub fn appendToolPath(self: *const GCCDetector, tc: *Toolchain) !void {
if (!self.is_valid) return;
return tc.addPathFromComponents(&.{
self.parent_lib_path,
"..",
self.gcc_triple,
"bin",
}, .program);
}
fn addDefaultGCCPrefixes(prefixes: *std.ArrayListUnmanaged([]const u8), tc: *const Toolchain) !void {
const sysroot = tc.getSysroot();
const target = tc.getTarget();
if (sysroot.len == 0 and target.os.tag == .linux and tc.filesystem.exists("/opt/rh")) {
prefixes.appendAssumeCapacity("/opt/rh/gcc-toolset-12/root/usr");
prefixes.appendAssumeCapacity("/opt/rh/gcc-toolset-11/root/usr");
prefixes.appendAssumeCapacity("/opt/rh/gcc-toolset-10/root/usr");
prefixes.appendAssumeCapacity("/opt/rh/devtoolset-12/root/usr");
prefixes.appendAssumeCapacity("/opt/rh/devtoolset-11/root/usr");
prefixes.appendAssumeCapacity("/opt/rh/devtoolset-10/root/usr");
prefixes.appendAssumeCapacity("/opt/rh/devtoolset-9/root/usr");
prefixes.appendAssumeCapacity("/opt/rh/devtoolset-8/root/usr");
prefixes.appendAssumeCapacity("/opt/rh/devtoolset-7/root/usr");
prefixes.appendAssumeCapacity("/opt/rh/devtoolset-6/root/usr");
prefixes.appendAssumeCapacity("/opt/rh/devtoolset-4/root/usr");
prefixes.appendAssumeCapacity("/opt/rh/devtoolset-3/root/usr");
prefixes.appendAssumeCapacity("/opt/rh/devtoolset-2/root/usr");
}
if (sysroot.len == 0) {
prefixes.appendAssumeCapacity("/usr");
} else {
var usr_path = try tc.arena.alloc(u8, 4 + sysroot.len);
@memcpy(usr_path[0..4], "/usr");
@memcpy(usr_path[4..], sysroot);
prefixes.appendAssumeCapacity(usr_path);
}
}
fn collectLibDirsAndTriples(
tc: *Toolchain,
lib_dirs: *std.ArrayListUnmanaged([]const u8),
triple_aliases: *std.ArrayListUnmanaged([]const u8),
biarch_libdirs: *std.ArrayListUnmanaged([]const u8),
biarch_triple_aliases: *std.ArrayListUnmanaged([]const u8),
) !void {
const AArch64LibDirs: [2][]const u8 = .{ "/lib64", "/lib" };
const AArch64Triples: [4][]const u8 = .{ "aarch64-none-linux-gnu", "aarch64-linux-gnu", "aarch64-redhat-linux", "aarch64-suse-linux" };
const AArch64beLibDirs: [1][]const u8 = .{"/lib"};
const AArch64beTriples: [2][]const u8 = .{ "aarch64_be-none-linux-gnu", "aarch64_be-linux-gnu" };
const ARMLibDirs: [1][]const u8 = .{"/lib"};
const ARMTriples: [1][]const u8 = .{"arm-linux-gnueabi"};
const ARMHFTriples: [4][]const u8 = .{ "arm-linux-gnueabihf", "armv7hl-redhat-linux-gnueabi", "armv6hl-suse-linux-gnueabi", "armv7hl-suse-linux-gnueabi" };
const ARMebLibDirs: [1][]const u8 = .{"/lib"};
const ARMebTriples: [1][]const u8 = .{"armeb-linux-gnueabi"};
const ARMebHFTriples: [2][]const u8 = .{ "armeb-linux-gnueabihf", "armebv7hl-redhat-linux-gnueabi" };
const AVRLibDirs: [1][]const u8 = .{"/lib"};
const AVRTriples: [1][]const u8 = .{"avr"};
const CSKYLibDirs: [1][]const u8 = .{"/lib"};
const CSKYTriples: [3][]const u8 = .{ "csky-linux-gnuabiv2", "csky-linux-uclibcabiv2", "csky-elf-noneabiv2" };
const X86_64LibDirs: [2][]const u8 = .{ "/lib64", "/lib" };
const X86_64Triples: [11][]const u8 = .{
"x86_64-linux-gnu", "x86_64-unknown-linux-gnu",
"x86_64-pc-linux-gnu", "x86_64-redhat-linux6E",
"x86_64-redhat-linux", "x86_64-suse-linux",
"x86_64-manbo-linux-gnu", "x86_64-linux-gnu",
"x86_64-slackware-linux", "x86_64-unknown-linux",
"x86_64-amazon-linux",
};
const X32Triples: [2][]const u8 = .{ "x86_64-linux-gnux32", "x86_64-pc-linux-gnux32" };
const X32LibDirs: [2][]const u8 = .{ "/libx32", "/lib" };
const X86LibDirs: [2][]const u8 = .{ "/lib32", "/lib" };
const X86Triples: [9][]const u8 = .{
"i586-linux-gnu", "i686-linux-gnu", "i686-pc-linux-gnu",
"i386-redhat-linux6E", "i686-redhat-linux", "i386-redhat-linux",
"i586-suse-linux", "i686-montavista-linux", "i686-gnu",
};
const LoongArch64LibDirs: [2][]const u8 = .{ "/lib64", "/lib" };
const LoongArch64Triples: [2][]const u8 = .{ "loongarch64-linux-gnu", "loongarch64-unknown-linux-gnu" };
const M68kLibDirs: [1][]const u8 = .{"/lib"};
const M68kTriples: [3][]const u8 = .{ "m68k-linux-gnu", "m68k-unknown-linux-gnu", "m68k-suse-linux" };
const MIPSLibDirs: [2][]const u8 = .{ "/libo32", "/lib" };
const MIPSTriples: [5][]const u8 = .{
"mips-linux-gnu", "mips-mti-linux",
"mips-mti-linux-gnu", "mips-img-linux-gnu",
"mipsisa32r6-linux-gnu",
};
const MIPSELLibDirs: [2][]const u8 = .{ "/libo32", "/lib" };
const MIPSELTriples: [3][]const u8 = .{ "mipsel-linux-gnu", "mips-img-linux-gnu", "mipsisa32r6el-linux-gnu" };
const MIPS64LibDirs: [2][]const u8 = .{ "/lib64", "/lib" };
const MIPS64Triples: [6][]const u8 = .{
"mips64-linux-gnu", "mips-mti-linux-gnu",
"mips-img-linux-gnu", "mips64-linux-gnuabi64",
"mipsisa64r6-linux-gnu", "mipsisa64r6-linux-gnuabi64",
};
const MIPS64ELLibDirs: [2][]const u8 = .{ "/lib64", "/lib" };
const MIPS64ELTriples: [6][]const u8 = .{
"mips64el-linux-gnu", "mips-mti-linux-gnu",
"mips-img-linux-gnu", "mips64el-linux-gnuabi64",
"mipsisa64r6el-linux-gnu", "mipsisa64r6el-linux-gnuabi64",
};
const MIPSN32LibDirs: [1][]const u8 = .{"/lib32"};
const MIPSN32Triples: [2][]const u8 = .{ "mips64-linux-gnuabin32", "mipsisa64r6-linux-gnuabin32" };
const MIPSN32ELLibDirs: [1][]const u8 = .{"/lib32"};
const MIPSN32ELTriples: [2][]const u8 = .{ "mips64el-linux-gnuabin32", "mipsisa64r6el-linux-gnuabin32" };
const MSP430LibDirs: [1][]const u8 = .{"/lib"};
const MSP430Triples: [1][]const u8 = .{"msp430-elf"};
const PPCLibDirs: [2][]const u8 = .{ "/lib32", "/lib" };
const PPCTriples: [5][]const u8 = .{
"powerpc-linux-gnu", "powerpc-unknown-linux-gnu", "powerpc-linux-gnuspe",
// On 32-bit PowerPC systems running SUSE Linux, gcc is configured as a
// 64-bit compiler which defaults to "-m32", hence "powerpc64-suse-linux".
"powerpc64-suse-linux", "powerpc-montavista-linuxspe",
};
const PPCLELibDirs: [2][]const u8 = .{ "/lib32", "/lib" };
const PPCLETriples: [3][]const u8 = .{ "powerpcle-linux-gnu", "powerpcle-unknown-linux-gnu", "powerpcle-linux-musl" };
const PPC64LibDirs: [2][]const u8 = .{ "/lib64", "/lib" };
const PPC64Triples: [4][]const u8 = .{
"powerpc64-linux-gnu", "powerpc64-unknown-linux-gnu",
"powerpc64-suse-linux", "ppc64-redhat-linux",
};
const PPC64LELibDirs: [2][]const u8 = .{ "/lib64", "/lib" };
const PPC64LETriples: [5][]const u8 = .{
"powerpc64le-linux-gnu", "powerpc64le-unknown-linux-gnu",
"powerpc64le-none-linux-gnu", "powerpc64le-suse-linux",
"ppc64le-redhat-linux",
};
const RISCV32LibDirs: [2][]const u8 = .{ "/lib32", "/lib" };
const RISCV32Triples: [3][]const u8 = .{ "riscv32-unknown-linux-gnu", "riscv32-linux-gnu", "riscv32-unknown-elf" };
const RISCV64LibDirs: [2][]const u8 = .{ "/lib64", "/lib" };
const RISCV64Triples: [3][]const u8 = .{
"riscv64-unknown-linux-gnu",
"riscv64-linux-gnu",
"riscv64-unknown-elf",
};
const SPARCv8LibDirs: [2][]const u8 = .{ "/lib32", "/lib" };
const SPARCv8Triples: [2][]const u8 = .{ "sparc-linux-gnu", "sparcv8-linux-gnu" };
const SPARCv9LibDirs: [2][]const u8 = .{ "/lib64", "/lib" };
const SPARCv9Triples: [2][]const u8 = .{ "sparc64-linux-gnu", "sparcv9-linux-gnu" };
const SystemZLibDirs: [2][]const u8 = .{ "/lib64", "/lib" };
const SystemZTriples: [5][]const u8 = .{
"s390x-linux-gnu", "s390x-unknown-linux-gnu", "s390x-ibm-linux-gnu",
"s390x-suse-linux", "s390x-redhat-linux",
};
const target = tc.getTarget();
if (target.os.tag == .solaris) {
// TODO
return;
}
if (target.abi.isAndroid()) {
const AArch64AndroidTriples: [1][]const u8 = .{"aarch64-linux-android"};
const ARMAndroidTriples: [1][]const u8 = .{"arm-linux-androideabi"};
const MIPSELAndroidTriples: [1][]const u8 = .{"mipsel-linux-android"};
const MIPS64ELAndroidTriples: [1][]const u8 = .{"mips64el-linux-android"};
const X86AndroidTriples: [1][]const u8 = .{"i686-linux-android"};
const X86_64AndroidTriples: [1][]const u8 = .{"x86_64-linux-android"};
switch (target.cpu.arch) {
.aarch64 => {
lib_dirs.appendSliceAssumeCapacity(&AArch64LibDirs);
triple_aliases.appendSliceAssumeCapacity(&AArch64AndroidTriples);
},
.arm,
.thumb,
=> {
lib_dirs.appendSliceAssumeCapacity(&ARMLibDirs);
triple_aliases.appendSliceAssumeCapacity(&ARMAndroidTriples);
},
.mipsel => {
lib_dirs.appendSliceAssumeCapacity(&MIPSELLibDirs);
triple_aliases.appendSliceAssumeCapacity(&MIPSELAndroidTriples);
biarch_libdirs.appendSliceAssumeCapacity(&MIPS64ELLibDirs);
biarch_triple_aliases.appendSliceAssumeCapacity(&MIPS64ELAndroidTriples);
},
.mips64el => {
lib_dirs.appendSliceAssumeCapacity(&MIPS64ELLibDirs);
triple_aliases.appendSliceAssumeCapacity(&MIPS64ELAndroidTriples);
biarch_libdirs.appendSliceAssumeCapacity(&MIPSELLibDirs);
biarch_triple_aliases.appendSliceAssumeCapacity(&MIPSELAndroidTriples);
},
.x86_64 => {
lib_dirs.appendSliceAssumeCapacity(&X86_64LibDirs);
triple_aliases.appendSliceAssumeCapacity(&X86_64AndroidTriples);
biarch_libdirs.appendSliceAssumeCapacity(&X86LibDirs);
biarch_triple_aliases.appendSliceAssumeCapacity(&X86AndroidTriples);
},
.x86 => {
lib_dirs.appendSliceAssumeCapacity(&X86LibDirs);
triple_aliases.appendSliceAssumeCapacity(&X86AndroidTriples);
biarch_libdirs.appendSliceAssumeCapacity(&X86_64LibDirs);
biarch_triple_aliases.appendSliceAssumeCapacity(&X86_64AndroidTriples);
},
else => {},
}
return;
}
switch (target.cpu.arch) {
.aarch64 => {
lib_dirs.appendSliceAssumeCapacity(&AArch64LibDirs);
triple_aliases.appendSliceAssumeCapacity(&AArch64Triples);
biarch_libdirs.appendSliceAssumeCapacity(&AArch64LibDirs);
biarch_triple_aliases.appendSliceAssumeCapacity(&AArch64Triples);
},
.aarch64_be => {
lib_dirs.appendSliceAssumeCapacity(&AArch64beLibDirs);
triple_aliases.appendSliceAssumeCapacity(&AArch64beTriples);
biarch_libdirs.appendSliceAssumeCapacity(&AArch64beLibDirs);
biarch_triple_aliases.appendSliceAssumeCapacity(&AArch64beTriples);
},
.arm, .thumb => {
lib_dirs.appendSliceAssumeCapacity(&ARMLibDirs);
if (target.abi == .gnueabihf) {
triple_aliases.appendSliceAssumeCapacity(&ARMHFTriples);
} else {
triple_aliases.appendSliceAssumeCapacity(&ARMTriples);
}
},
.armeb, .thumbeb => {
lib_dirs.appendSliceAssumeCapacity(&ARMebLibDirs);
if (target.abi == .gnueabihf) {
triple_aliases.appendSliceAssumeCapacity(&ARMebHFTriples);
} else {
triple_aliases.appendSliceAssumeCapacity(&ARMebTriples);
}
},
.avr => {
lib_dirs.appendSliceAssumeCapacity(&AVRLibDirs);
triple_aliases.appendSliceAssumeCapacity(&AVRTriples);
},
.csky => {
lib_dirs.appendSliceAssumeCapacity(&CSKYLibDirs);
triple_aliases.appendSliceAssumeCapacity(&CSKYTriples);
},
.x86_64 => {
if (target.abi == .gnux32 or target.abi == .muslx32) {
lib_dirs.appendSliceAssumeCapacity(&X32LibDirs);
triple_aliases.appendSliceAssumeCapacity(&X32Triples);
biarch_libdirs.appendSliceAssumeCapacity(&X86_64LibDirs);
biarch_triple_aliases.appendSliceAssumeCapacity(&X86_64Triples);
} else {
lib_dirs.appendSliceAssumeCapacity(&X86_64LibDirs);
triple_aliases.appendSliceAssumeCapacity(&X86_64Triples);
biarch_libdirs.appendSliceAssumeCapacity(&X32LibDirs);
biarch_triple_aliases.appendSliceAssumeCapacity(&X32Triples);
}
biarch_libdirs.appendSliceAssumeCapacity(&X86LibDirs);
biarch_triple_aliases.appendSliceAssumeCapacity(&X86Triples);
},
.x86 => {
lib_dirs.appendSliceAssumeCapacity(&X86LibDirs);
triple_aliases.appendSliceAssumeCapacity(&X86Triples);
biarch_libdirs.appendSliceAssumeCapacity(&X86_64LibDirs);
biarch_triple_aliases.appendSliceAssumeCapacity(&X86_64Triples);
biarch_libdirs.appendSliceAssumeCapacity(&X32LibDirs);
biarch_triple_aliases.appendSliceAssumeCapacity(&X32Triples);
},
.loongarch64 => {
lib_dirs.appendSliceAssumeCapacity(&LoongArch64LibDirs);
triple_aliases.appendSliceAssumeCapacity(&LoongArch64Triples);
},
.m68k => {
lib_dirs.appendSliceAssumeCapacity(&M68kLibDirs);
triple_aliases.appendSliceAssumeCapacity(&M68kTriples);
},
.mips => {
lib_dirs.appendSliceAssumeCapacity(&MIPSLibDirs);
triple_aliases.appendSliceAssumeCapacity(&MIPSTriples);
biarch_libdirs.appendSliceAssumeCapacity(&MIPS64LibDirs);
biarch_triple_aliases.appendSliceAssumeCapacity(&MIPS64Triples);
biarch_libdirs.appendSliceAssumeCapacity(&MIPSN32LibDirs);
biarch_triple_aliases.appendSliceAssumeCapacity(&MIPSN32Triples);
},
.mipsel => {
lib_dirs.appendSliceAssumeCapacity(&MIPSELLibDirs);
triple_aliases.appendSliceAssumeCapacity(&MIPSELTriples);
triple_aliases.appendSliceAssumeCapacity(&MIPSTriples);
biarch_libdirs.appendSliceAssumeCapacity(&MIPS64ELLibDirs);
biarch_triple_aliases.appendSliceAssumeCapacity(&MIPS64ELTriples);
biarch_libdirs.appendSliceAssumeCapacity(&MIPSN32ELLibDirs);
biarch_triple_aliases.appendSliceAssumeCapacity(&MIPSN32ELTriples);
},
.mips64 => {
lib_dirs.appendSliceAssumeCapacity(&MIPS64LibDirs);
triple_aliases.appendSliceAssumeCapacity(&MIPS64Triples);
biarch_libdirs.appendSliceAssumeCapacity(&MIPSLibDirs);
biarch_triple_aliases.appendSliceAssumeCapacity(&MIPSTriples);
biarch_libdirs.appendSliceAssumeCapacity(&MIPSN32LibDirs);
biarch_triple_aliases.appendSliceAssumeCapacity(&MIPSN32Triples);
},
.mips64el => {
lib_dirs.appendSliceAssumeCapacity(&MIPS64ELLibDirs);
triple_aliases.appendSliceAssumeCapacity(&MIPS64ELTriples);
biarch_libdirs.appendSliceAssumeCapacity(&MIPSELLibDirs);
biarch_triple_aliases.appendSliceAssumeCapacity(&MIPSELTriples);
biarch_libdirs.appendSliceAssumeCapacity(&MIPSN32ELLibDirs);
biarch_triple_aliases.appendSliceAssumeCapacity(&MIPSN32ELTriples);
biarch_triple_aliases.appendSliceAssumeCapacity(&MIPSTriples);
},
.msp430 => {
lib_dirs.appendSliceAssumeCapacity(&MSP430LibDirs);
triple_aliases.appendSliceAssumeCapacity(&MSP430Triples);
},
.powerpc => {
lib_dirs.appendSliceAssumeCapacity(&PPCLibDirs);
triple_aliases.appendSliceAssumeCapacity(&PPCTriples);
biarch_libdirs.appendSliceAssumeCapacity(&PPC64LibDirs);
biarch_triple_aliases.appendSliceAssumeCapacity(&PPC64Triples);
},
.powerpcle => {
lib_dirs.appendSliceAssumeCapacity(&PPCLELibDirs);
triple_aliases.appendSliceAssumeCapacity(&PPCLETriples);
biarch_libdirs.appendSliceAssumeCapacity(&PPC64LELibDirs);
biarch_triple_aliases.appendSliceAssumeCapacity(&PPC64LETriples);
},
.powerpc64 => {
lib_dirs.appendSliceAssumeCapacity(&PPC64LibDirs);
triple_aliases.appendSliceAssumeCapacity(&PPC64Triples);
biarch_libdirs.appendSliceAssumeCapacity(&PPCLibDirs);
biarch_triple_aliases.appendSliceAssumeCapacity(&PPCTriples);
},
.powerpc64le => {
lib_dirs.appendSliceAssumeCapacity(&PPC64LELibDirs);
triple_aliases.appendSliceAssumeCapacity(&PPC64LETriples);
biarch_libdirs.appendSliceAssumeCapacity(&PPCLELibDirs);
biarch_triple_aliases.appendSliceAssumeCapacity(&PPCLETriples);
},
.riscv32 => {
lib_dirs.appendSliceAssumeCapacity(&RISCV32LibDirs);
triple_aliases.appendSliceAssumeCapacity(&RISCV32Triples);
biarch_libdirs.appendSliceAssumeCapacity(&RISCV64LibDirs);
biarch_triple_aliases.appendSliceAssumeCapacity(&RISCV64Triples);
},
.riscv64 => {
lib_dirs.appendSliceAssumeCapacity(&RISCV64LibDirs);
triple_aliases.appendSliceAssumeCapacity(&RISCV64Triples);
biarch_libdirs.appendSliceAssumeCapacity(&RISCV32LibDirs);
biarch_triple_aliases.appendSliceAssumeCapacity(&RISCV32Triples);
},
.sparc => {
lib_dirs.appendSliceAssumeCapacity(&SPARCv8LibDirs);
triple_aliases.appendSliceAssumeCapacity(&SPARCv8Triples);
biarch_libdirs.appendSliceAssumeCapacity(&SPARCv9LibDirs);
biarch_triple_aliases.appendSliceAssumeCapacity(&SPARCv9Triples);
},
.sparc64 => {
lib_dirs.appendSliceAssumeCapacity(&SPARCv9LibDirs);
triple_aliases.appendSliceAssumeCapacity(&SPARCv9Triples);
biarch_libdirs.appendSliceAssumeCapacity(&SPARCv8LibDirs);
biarch_triple_aliases.appendSliceAssumeCapacity(&SPARCv8Triples);
},
.s390x => {
lib_dirs.appendSliceAssumeCapacity(&SystemZLibDirs);
triple_aliases.appendSliceAssumeCapacity(&SystemZTriples);
},
else => {},
}
}
pub fn discover(self: *GCCDetector, tc: *Toolchain) !void {
var path_buf: [std.fs.max_path_bytes]u8 = undefined;
var fib = std.heap.FixedBufferAllocator.init(&path_buf);
const target = tc.getTarget();
const biarch_variant_target = if (target.ptrBitWidth() == 32)
target_util.get64BitArchVariant(target)
else
target_util.get32BitArchVariant(target);
var candidate_lib_dirs_buffer: [16][]const u8 = undefined;
var candidate_lib_dirs = std.ArrayListUnmanaged([]const u8).initBuffer(&candidate_lib_dirs_buffer);
var candidate_triple_aliases_buffer: [16][]const u8 = undefined;
var candidate_triple_aliases = std.ArrayListUnmanaged([]const u8).initBuffer(&candidate_triple_aliases_buffer);
var candidate_biarch_lib_dirs_buffer: [16][]const u8 = undefined;
var candidate_biarch_lib_dirs = std.ArrayListUnmanaged([]const u8).initBuffer(&candidate_biarch_lib_dirs_buffer);
var candidate_biarch_triple_aliases_buffer: [16][]const u8 = undefined;
var candidate_biarch_triple_aliases = std.ArrayListUnmanaged([]const u8).initBuffer(&candidate_biarch_triple_aliases_buffer);
try collectLibDirsAndTriples(
tc,
&candidate_lib_dirs,
&candidate_triple_aliases,
&candidate_biarch_lib_dirs,
&candidate_biarch_triple_aliases,
);
var target_buf: [64]u8 = undefined;
const triple_str = target_util.toLLVMTriple(target, &target_buf);
candidate_triple_aliases.appendAssumeCapacity(triple_str);
// Also include the multiarch variant if it's different.
var biarch_buf: [64]u8 = undefined;
if (biarch_variant_target) |biarch_target| {
const biarch_triple_str = target_util.toLLVMTriple(biarch_target, &biarch_buf);
if (!std.mem.eql(u8, biarch_triple_str, triple_str)) {
candidate_triple_aliases.appendAssumeCapacity(biarch_triple_str);
}
}
var prefixes_buf: [16][]const u8 = undefined;
var prefixes = std.ArrayListUnmanaged([]const u8).initBuffer(&prefixes_buf);
const gcc_toolchain_dir = gccToolchainDir(tc);
if (gcc_toolchain_dir.len != 0) {
const adjusted = if (gcc_toolchain_dir[gcc_toolchain_dir.len - 1] == '/')
gcc_toolchain_dir[0 .. gcc_toolchain_dir.len - 1]
else
gcc_toolchain_dir;
prefixes.appendAssumeCapacity(adjusted);
} else {
const sysroot = tc.getSysroot();
if (sysroot.len > 0) {
prefixes.appendAssumeCapacity(sysroot);
try addDefaultGCCPrefixes(&prefixes, tc);
}
if (sysroot.len == 0) {
try addDefaultGCCPrefixes(&prefixes, tc);
}
// TODO: Special-case handling for Gentoo
}
const v0 = GCCVersion.parse("0.0.0");
for (prefixes.items) |prefix| {
if (!tc.filesystem.exists(prefix)) continue;
for (candidate_lib_dirs.items) |suffix| {
defer fib.reset();
const lib_dir = std.fs.path.join(fib.allocator(), &.{ prefix, suffix }) catch continue;
if (!tc.filesystem.exists(lib_dir)) continue;
const gcc_dir_exists = tc.filesystem.joinedExists(&.{ lib_dir, "/gcc" });
const gcc_cross_dir_exists = tc.filesystem.joinedExists(&.{ lib_dir, "/gcc-cross" });
try self.scanLibDirForGCCTriple(tc, target, lib_dir, triple_str, false, gcc_dir_exists, gcc_cross_dir_exists);
for (candidate_triple_aliases.items) |candidate| {
try self.scanLibDirForGCCTriple(tc, target, lib_dir, candidate, false, gcc_dir_exists, gcc_cross_dir_exists);
}
}
for (candidate_biarch_lib_dirs.items) |suffix| {
const lib_dir = std.fs.path.join(fib.allocator(), &.{ prefix, suffix }) catch continue;
if (!tc.filesystem.exists(lib_dir)) continue;
const gcc_dir_exists = tc.filesystem.joinedExists(&.{ lib_dir, "/gcc" });
const gcc_cross_dir_exists = tc.filesystem.joinedExists(&.{ lib_dir, "/gcc-cross" });
for (candidate_biarch_triple_aliases.items) |candidate| {
try self.scanLibDirForGCCTriple(tc, target, lib_dir, candidate, true, gcc_dir_exists, gcc_cross_dir_exists);
}
}
if (self.version.order(v0) == .gt) break;
}
}
fn findBiarchMultilibs(
tc: *const Toolchain,
result: *Multilib.Detected,
target: std.Target,
path: [2][]const u8,
needs_biarch_suffix: bool,
) !bool {
const suff64 = if (target.os.tag == .solaris) switch (target.cpu.arch) {
.x86, .x86_64 => "/amd64",
.sparc => "/sparcv9",
else => "/64",
} else "/64";
const alt_64 = Multilib.init(suff64, suff64, &.{ "-m32", "+m64", "-mx32" });
const alt_32 = Multilib.init("/32", "/32", &.{ "+m32", "-m64", "-mx32" });
const alt_x32 = Multilib.init("/x32", "/x32", &.{ "-m32", "-m64", "+mx32" });
const multilib_filter = Multilib.Filter{
.base = path,
.file = "crtbegin.o",
};
const Want = enum {
want32,
want64,
wantx32,
};
const is_x32 = target.abi == .gnux32 or target.abi == .muslx32;
const target_ptr_width = target.ptrBitWidth();
const want: Want = if (target_ptr_width == 32 and multilib_filter.exists(alt_32, tc.filesystem))
.want64
else if (target_ptr_width == 64 and is_x32 and multilib_filter.exists(alt_x32, tc.filesystem))
.want64
else if (target_ptr_width == 64 and !is_x32 and multilib_filter.exists(alt_64, tc.filesystem))
.want32
else if (target_ptr_width == 32)
if (needs_biarch_suffix) .want64 else .want32
else if (is_x32)
if (needs_biarch_suffix) .want64 else .wantx32
else if (needs_biarch_suffix) .want32 else .want64;
const default = switch (want) {
.want32 => Multilib.init("", "", &.{ "+m32", "-m64", "-mx32" }),
.want64 => Multilib.init("", "", &.{ "-m32", "+m64", "-mx32" }),
.wantx32 => Multilib.init("", "", &.{ "-m32", "-m64", "+mx32" }),
};
result.multilibs.appendSliceAssumeCapacity(&.{
default,
alt_64,
alt_32,
alt_x32,
});
result.filter(multilib_filter, tc.filesystem);
var flags: Multilib.Flags = .{};
flags.appendAssumeCapacity(if (target_ptr_width == 64 and !is_x32) "+m64" else "-m64");
flags.appendAssumeCapacity(if (target_ptr_width == 32) "+m32" else "-m32");
flags.appendAssumeCapacity(if (target_ptr_width == 64 and is_x32) "+mx32" else "-mx32");
return result.select(flags);
}
fn scanGCCForMultilibs(
self: *GCCDetector,
tc: *const Toolchain,
target: std.Target,
path: [2][]const u8,
needs_biarch_suffix: bool,
) !bool {
var detected: Multilib.Detected = .{};
if (target.cpu.arch == .csky) {
// TODO
} else if (target.cpu.arch.isMIPS()) {
// TODO
} else if (target.cpu.arch.isRISCV()) {
// TODO
} else if (target.cpu.arch == .msp430) {
// TODO
} else if (target.cpu.arch == .avr) {
// No multilibs
} else if (!try findBiarchMultilibs(tc, &detected, target, path, needs_biarch_suffix)) {
return false;
}
self.selected = detected.selected;
self.biarch_sibling = detected.biarch_sibling;
return true;
}
fn scanLibDirForGCCTriple(
self: *GCCDetector,
tc: *const Toolchain,
target: std.Target,
lib_dir: []const u8,
candidate_triple: []const u8,
needs_biarch_suffix: bool,
gcc_dir_exists: bool,
gcc_cross_dir_exists: bool,
) !void {
var path_buf: [std.fs.max_path_bytes]u8 = undefined;
var fib = std.heap.FixedBufferAllocator.init(&path_buf);
for (0..2) |i| {
if (i == 0 and !gcc_dir_exists) continue;
if (i == 1 and !gcc_cross_dir_exists) continue;
defer fib.reset();
const base: []const u8 = if (i == 0) "gcc" else "gcc-cross";
var lib_suffix_buf: [64]u8 = undefined;
var suffix_buf_fib = std.heap.FixedBufferAllocator.init(&lib_suffix_buf);
const lib_suffix = std.fs.path.join(suffix_buf_fib.allocator(), &.{ base, candidate_triple }) catch continue;
const dir_name = std.fs.path.join(fib.allocator(), &.{ lib_dir, lib_suffix }) catch continue;
var parent_dir = tc.filesystem.openDir(dir_name) catch continue;
defer parent_dir.close();
var it = parent_dir.iterate();
while (it.next() catch continue) |entry| {
if (entry.kind != .directory) continue;
const version_text = entry.name;
const candidate_version = GCCVersion.parse(version_text);
if (candidate_version.major != -1) {
// TODO: cache path so we're not repeatedly scanning
}
if (candidate_version.isLessThan(4, 1, 1, "")) continue;
switch (candidate_version.order(self.version)) {
.lt, .eq => continue,
.gt => {},
}
if (!try self.scanGCCForMultilibs(tc, target, .{ dir_name, version_text }, needs_biarch_suffix)) continue;
self.version = candidate_version;
self.gcc_triple = try tc.arena.dupe(u8, candidate_triple);
self.install_path = try std.fs.path.join(tc.arena, &.{ lib_dir, lib_suffix, version_text });
self.parent_lib_path = try std.fs.path.join(tc.arena, &.{ self.install_path, "..", "..", ".." });
self.is_valid = true;
}
}
}
fn gccToolchainDir(tc: *const Toolchain) []const u8 {
const sysroot = tc.getSysroot();
if (sysroot.len != 0) return "";
return system_defaults.gcc_install_prefix;
}

View File

@ -57,7 +57,7 @@ pub fn parse(text: []const u8) GCCVersion {
var good = bad;
var it = mem.splitScalar(u8, text, '.');
const first = it.next().?;
const first = it.first();
const second = it.next() orelse "";
const rest = it.next() orelse "";

View File

@ -1,47 +1,50 @@
const std = @import("std");
const Filesystem = @import("Filesystem.zig").Filesystem;
pub const Flags = std.BoundedArray([]const u8, 6);
/// Large enough for GCCDetector for Linux; may need to be increased to support other toolchains.
const max_multilibs = 4;
const MultilibArray = std.BoundedArray(Multilib, max_multilibs);
pub const Detected = struct {
multilibs: MultilibArray = .{},
multilib_buf: [max_multilibs]Multilib = undefined,
multilib_count: u8 = 0,
selected: Multilib = .{},
biarch_sibling: ?Multilib = null,
pub fn filter(self: *Detected, multilib_filter: Filter, fs: Filesystem) void {
var found_count: usize = 0;
for (self.multilibs.constSlice()) |multilib| {
pub fn filter(d: *Detected, multilib_filter: Filter, fs: Filesystem) void {
var found_count: u8 = 0;
for (d.multilibs()) |multilib| {
if (multilib_filter.exists(multilib, fs)) {
self.multilibs.set(found_count, multilib);
d.multilib_buf[found_count] = multilib;
found_count += 1;
}
}
self.multilibs.resize(found_count) catch unreachable;
d.multilib_count = found_count;
}
pub fn select(self: *Detected, flags: Flags) !bool {
var filtered: MultilibArray = .{};
for (self.multilibs.constSlice()) |multilib| {
for (multilib.flags.constSlice()) |multilib_flag| {
const matched = for (flags.constSlice()) |arg_flag| {
pub fn select(d: *Detected, check_flags: []const []const u8) !bool {
var selected: ?Multilib = null;
for (d.multilibs()) |multilib| {
for (multilib.flags()) |multilib_flag| {
const matched = for (check_flags) |arg_flag| {
if (std.mem.eql(u8, arg_flag[1..], multilib_flag[1..])) break arg_flag;
} else multilib_flag;
if (matched[0] != multilib_flag[0]) break;
} else if (selected != null) {
return error.TooManyMultilibs;
} else {
filtered.appendAssumeCapacity(multilib);
selected = multilib;
}
}
if (filtered.len == 0) return false;
if (filtered.len == 1) {
self.selected = filtered.get(0);
if (selected) |multilib| {
d.selected = multilib;
return true;
}
return error.TooManyMultilibs;
return false;
}
pub fn multilibs(d: *const Detected) []const Multilib {
return d.multilib_buf[0..d.multilib_count];
}
};
@ -58,14 +61,20 @@ const Multilib = @This();
gcc_suffix: []const u8 = "",
os_suffix: []const u8 = "",
include_suffix: []const u8 = "",
flags: Flags = .{},
flag_buf: [6][]const u8 = undefined,
flag_count: u8 = 0,
priority: u32 = 0,
pub fn init(gcc_suffix: []const u8, os_suffix: []const u8, flags: []const []const u8) Multilib {
pub fn init(gcc_suffix: []const u8, os_suffix: []const u8, init_flags: []const []const u8) Multilib {
var self: Multilib = .{
.gcc_suffix = gcc_suffix,
.os_suffix = os_suffix,
.flag_count = @intCast(init_flags.len),
};
self.flags.appendSliceAssumeCapacity(flags);
@memcpy(self.flag_buf[0..init_flags.len], init_flags);
return self;
}
pub fn flags(m: *const Multilib) []const []const u8 {
return m.flag_buf[0..m.flag_count];
}

View File

@ -10,8 +10,9 @@
const std = @import("std");
const mem = std.mem;
const Allocator = mem.Allocator;
const Source = @import("Source.zig");
const Compilation = @import("Compilation.zig");
const Source = @import("Source.zig");
const Tokenizer = @import("Tokenizer.zig");
pub const Hideset = @This();
@ -51,10 +52,10 @@ pub const Index = enum(u32) {
_,
};
map: std.AutoHashMapUnmanaged(Identifier, Index) = .empty,
map: std.AutoHashMapUnmanaged(Identifier, Index) = .{},
/// Used for computing union/intersection of two lists; stored here so that allocations can be retained
/// until hideset is deinit'ed
tmp_map: std.AutoHashMapUnmanaged(Identifier, void) = .empty,
tmp_map: std.AutoHashMapUnmanaged(Identifier, void) = .{},
linked_list: Item.List = .{},
comp: *const Compilation,

View File

@ -3,17 +3,16 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const testing = std.testing;
const Diagnostics = @import("Diagnostics.zig");
const Parser = @import("Parser.zig");
const Tree = @import("Tree.zig");
const Token = Tree.Token;
const TokenIndex = Tree.TokenIndex;
const NodeIndex = Tree.NodeIndex;
const Type = @import("Type.zig");
const Diagnostics = @import("Diagnostics.zig");
const NodeList = std.array_list.Managed(NodeIndex);
const Parser = @import("Parser.zig");
const Node = Tree.Node;
const Item = struct {
list: InitList = .{},
list: InitList,
index: u64,
fn order(_: void, a: Item, b: Item) std.math.Order {
@ -23,8 +22,8 @@ const Item = struct {
const InitList = @This();
list: std.ArrayListUnmanaged(Item) = .empty,
node: NodeIndex = .none,
list: std.ArrayList(Item) = .empty,
node: Node.OptIndex = .null,
tok: TokenIndex = 0,
/// Deinitialize freeing all memory.
@ -34,50 +33,6 @@ pub fn deinit(il: *InitList, gpa: Allocator) void {
il.* = undefined;
}
/// Insert initializer at index, returning previous entry if one exists.
pub fn put(il: *InitList, gpa: Allocator, index: usize, node: NodeIndex, tok: TokenIndex) !?TokenIndex {
const items = il.list.items;
var left: usize = 0;
var right: usize = items.len;
// Append new value to empty list
if (left == right) {
const item = try il.list.addOne(gpa);
item.* = .{
.list = .{ .node = node, .tok = tok },
.index = index,
};
return null;
}
while (left < right) {
// Avoid overflowing in the midpoint calculation
const mid = left + (right - left) / 2;
// Compare the key with the midpoint element
switch (std.math.order(index, items[mid].index)) {
.eq => {
// Replace previous entry.
const prev = items[mid].list.tok;
items[mid].list.deinit(gpa);
items[mid] = .{
.list = .{ .node = node, .tok = tok },
.index = index,
};
return prev;
},
.gt => left = mid + 1,
.lt => right = mid,
}
}
// Insert a new value into a sorted position.
try il.list.insert(gpa, left, .{
.list = .{ .node = node, .tok = tok },
.index = index,
});
return null;
}
/// Find item at index, create new if one does not exist.
pub fn find(il: *InitList, gpa: Allocator, index: u64) !*InitList {
const items = il.list.items;
@ -85,13 +40,21 @@ pub fn find(il: *InitList, gpa: Allocator, index: u64) !*InitList {
var right: usize = items.len;
// Append new value to empty list
if (left == right) {
if (il.list.items.len == 0) {
const item = try il.list.addOne(gpa);
item.* = .{
.list = .{ .node = .none, .tok = 0 },
.list = .{},
.index = index,
};
return &item.list;
} else if (il.list.items[il.list.items.len - 1].index < index) {
// Append a new value to the end of the list.
const new = try il.list.addOne(gpa);
new.* = .{
.list = .{},
.index = index,
};
return &new.list;
}
while (left < right) {
@ -107,7 +70,7 @@ pub fn find(il: *InitList, gpa: Allocator, index: u64) !*InitList {
// Insert a new value into a sorted position.
try il.list.insert(gpa, left, .{
.list = .{ .node = .none, .tok = 0 },
.list = .{},
.index = index,
});
return &il.list.items[left].list;
@ -118,22 +81,6 @@ test "basic usage" {
var il: InitList = .{};
defer il.deinit(gpa);
{
var i: usize = 0;
while (i < 5) : (i += 1) {
const prev = try il.put(gpa, i, .none, 0);
try testing.expect(prev == null);
}
}
{
const failing = testing.failing_allocator;
var i: usize = 0;
while (i < 5) : (i += 1) {
_ = try il.find(failing, i);
}
}
{
var item = try il.find(gpa, 0);
var i: usize = 1;

View File

@ -1,11 +1,20 @@
const std = @import("std");
const DiagnosticTag = @import("Diagnostics.zig").Tag;
const char_info = @import("char_info.zig");
const DiagnosticTag = @import("Diagnostics.zig").Tag;
pub const Compiler = enum {
clang,
gcc,
msvc,
pub fn defaultGccVersion(self: Compiler) u32 {
return switch (self) {
.clang => 4 * 10_000 + 2 * 100 + 1,
.gcc => 7 * 10_000 + 1 * 100 + 0,
.msvc => 0,
};
}
};
/// The floating-point evaluation method for intermediate results within a single expression
@ -138,20 +147,15 @@ preserve_comments_in_macros: bool = false,
/// Used ONLY for generating __GNUC__ and related macros. Does not control the presence/absence of any features
/// Encoded as major * 10,000 + minor * 100 + patch
/// e.g. 4.2.1 == 40201
gnuc_version: u32 = 0,
gnuc_version: ?u32 = null,
pub fn setStandard(self: *LangOpts, name: []const u8) error{InvalidStandard}!void {
self.standard = Standard.NameMap.get(name) orelse return error.InvalidStandard;
}
pub fn enableMSExtensions(self: *LangOpts) void {
self.declspec_attrs = true;
self.ms_extensions = true;
}
pub fn disableMSExtensions(self: *LangOpts) void {
self.declspec_attrs = false;
self.ms_extensions = true;
pub fn setMSExtensions(self: *LangOpts, enabled: bool) void {
self.declspec_attrs = enabled;
self.ms_extensions = enabled;
}
pub fn hasChar8_T(self: *const LangOpts) bool {
@ -164,7 +168,7 @@ pub fn hasDigraphs(self: *const LangOpts) bool {
pub fn setEmulatedCompiler(self: *LangOpts, compiler: Compiler) void {
self.emulate = compiler;
if (compiler == .msvc) self.enableMSExtensions();
self.setMSExtensions(compiler == .msvc);
}
pub fn setFpEvalMethod(self: *LangOpts, fp_eval_method: FPEvalMethod) void {

File diff suppressed because it is too large Load Diff

2424
lib/compiler/aro/aro/Parser/Diagnostic.zig vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,9 @@
const std = @import("std");
const Compilation = @import("Compilation.zig");
const Preprocessor = @import("Preprocessor.zig");
const Diagnostics = @import("Diagnostics.zig");
const Parser = @import("Parser.zig");
const Preprocessor = @import("Preprocessor.zig");
const TokenIndex = @import("Tree.zig").TokenIndex;
pub const Error = Compilation.Error || error{ UnknownPragma, StopPreprocessing };
@ -58,7 +60,7 @@ pub fn pasteTokens(pp: *Preprocessor, start_idx: TokenIndex) ![]const u8 {
.string_literal => {
if (rparen_count != 0) return error.ExpectedStringLiteral;
const str = pp.expandedSlice(tok);
try pp.char_buf.appendSlice(str[1 .. str.len - 1]);
try pp.char_buf.appendSlice(pp.comp.gpa, str[1 .. str.len - 1]);
},
else => return error.ExpectedStringLiteral,
}
@ -69,7 +71,7 @@ pub fn pasteTokens(pp: *Preprocessor, start_idx: TokenIndex) ![]const u8 {
pub fn shouldPreserveTokens(self: *Pragma, pp: *Preprocessor, start_idx: TokenIndex) bool {
if (self.preserveTokens) |func| return func(self, pp, start_idx);
return false;
return true;
}
pub fn preprocessorCB(self: *Pragma, pp: *Preprocessor, start_idx: TokenIndex) Error!void {
@ -81,3 +83,128 @@ pub fn parserCB(self: *Pragma, p: *Parser, start_idx: TokenIndex) Compilation.Er
defer std.debug.assert(tok_index == p.tok_i);
if (self.parserHandler) |func| return func(self, p, start_idx);
}
pub const Diagnostic = struct {
fmt: []const u8,
kind: Diagnostics.Message.Kind,
opt: ?Diagnostics.Option = null,
extension: bool = false,
pub const pragma_warning_message: Diagnostic = .{
.fmt = "{s}",
.kind = .warning,
.opt = .@"#pragma-messages",
};
pub const pragma_error_message: Diagnostic = .{
.fmt = "{s}",
.kind = .@"error",
};
pub const pragma_message: Diagnostic = .{
.fmt = "#pragma message: {s}",
.kind = .note,
};
pub const pragma_requires_string_literal: Diagnostic = .{
.fmt = "pragma {s} requires string literal",
.kind = .@"error",
};
pub const poisoned_identifier: Diagnostic = .{
.fmt = "attempt to use a poisoned identifier",
.kind = .@"error",
};
pub const pragma_poison_identifier: Diagnostic = .{
.fmt = "can only poison identifier tokens",
.kind = .@"error",
};
pub const pragma_poison_macro: Diagnostic = .{
.fmt = "poisoning existing macro",
.kind = .warning,
};
pub const unknown_gcc_pragma: Diagnostic = .{
.fmt = "pragma GCC expected 'error', 'warning', 'diagnostic', 'poison'",
.kind = .off,
.opt = .@"unknown-pragmas",
};
pub const unknown_gcc_pragma_directive: Diagnostic = .{
.fmt = "pragma GCC diagnostic expected 'error', 'warning', 'ignored', 'fatal', 'push', or 'pop'",
.kind = .warning,
.opt = .@"unknown-pragmas",
.extension = true,
};
pub const malformed_warning_check: Diagnostic = .{
.fmt = "{s} expected option name (e.g. \"-Wundef\")",
.opt = .@"malformed-warning-check",
.kind = .warning,
.extension = true,
};
pub const pragma_pack_lparen: Diagnostic = .{
.fmt = "missing '(' after '#pragma pack' - ignoring",
.kind = .warning,
.opt = .@"ignored-pragmas",
};
pub const pragma_pack_rparen: Diagnostic = .{
.fmt = "missing ')' after '#pragma pack' - ignoring",
.kind = .warning,
.opt = .@"ignored-pragmas",
};
pub const pragma_pack_unknown_action: Diagnostic = .{
.fmt = "unknown action for '#pragma pack' - ignoring",
.kind = .warning,
.opt = .@"ignored-pragmas",
};
pub const pragma_pack_show: Diagnostic = .{
.fmt = "value of #pragma pack(show) == {d}",
.kind = .warning,
};
pub const pragma_pack_int_ident: Diagnostic = .{
.fmt = "expected integer or identifier in '#pragma pack' - ignored",
.kind = .warning,
.opt = .@"ignored-pragmas",
};
pub const pragma_pack_int: Diagnostic = .{
.fmt = "expected #pragma pack parameter to be '1', '2', '4', '8', or '16'",
.opt = .@"ignored-pragmas",
.kind = .warning,
};
pub const pragma_pack_undefined_pop: Diagnostic = .{
.fmt = "specifying both a name and alignment to 'pop' is undefined",
.kind = .warning,
};
pub const pragma_pack_empty_stack: Diagnostic = .{
.fmt = "#pragma pack(pop, ...) failed: stack empty",
.opt = .@"ignored-pragmas",
.kind = .warning,
};
};
pub fn err(pp: *Preprocessor, tok_i: TokenIndex, diagnostic: Diagnostic, args: anytype) Compilation.Error!void {
var sf = std.heap.stackFallback(1024, pp.comp.gpa);
var allocating: std.Io.Writer.Allocating = .init(sf.get());
defer allocating.deinit();
Diagnostics.formatArgs(&allocating.writer, diagnostic.fmt, args) catch return error.OutOfMemory;
try pp.diagnostics.addWithLocation(pp.comp, .{
.kind = diagnostic.kind,
.opt = diagnostic.opt,
.text = allocating.written(),
.location = pp.tokens.items(.loc)[tok_i].expand(pp.comp),
.extension = diagnostic.extension,
}, pp.expansionSlice(tok_i), true);
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,458 @@
const std = @import("std");
const Diagnostics = @import("../Diagnostics.zig");
const LangOpts = @import("../LangOpts.zig");
const Compilation = @import("../Compilation.zig");
const Diagnostic = @This();
fmt: []const u8,
kind: Diagnostics.Message.Kind,
opt: ?Diagnostics.Option = null,
extension: bool = false,
show_in_system_headers: bool = false,
pub const elif_without_if: Diagnostic = .{
.fmt = "#elif without #if",
.kind = .@"error",
};
pub const elif_after_else: Diagnostic = .{
.fmt = "#elif after #else",
.kind = .@"error",
};
pub const elifdef_without_if: Diagnostic = .{
.fmt = "#elifdef without #if",
.kind = .@"error",
};
pub const elifdef_after_else: Diagnostic = .{
.fmt = "#elifdef after #else",
.kind = .@"error",
};
pub const elifndef_without_if: Diagnostic = .{
.fmt = "#elifndef without #if",
.kind = .@"error",
};
pub const elifndef_after_else: Diagnostic = .{
.fmt = "#elifndef after #else",
.kind = .@"error",
};
pub const else_without_if: Diagnostic = .{
.fmt = "#else without #if",
.kind = .@"error",
};
pub const else_after_else: Diagnostic = .{
.fmt = "#else after #else",
.kind = .@"error",
};
pub const endif_without_if: Diagnostic = .{
.fmt = "#endif without #if",
.kind = .@"error",
};
pub const unknown_pragma: Diagnostic = .{
.fmt = "unknown pragma ignored",
.opt = .@"unknown-pragmas",
.kind = .off,
};
pub const line_simple_digit: Diagnostic = .{
.fmt = "#line directive requires a simple digit sequence",
.kind = .@"error",
};
pub const line_invalid_filename: Diagnostic = .{
.fmt = "invalid filename for #line directive",
.kind = .@"error",
};
pub const unterminated_conditional_directive: Diagnostic = .{
.fmt = "unterminated conditional directive",
.kind = .@"error",
};
pub const invalid_preprocessing_directive: Diagnostic = .{
.fmt = "invalid preprocessing directive",
.kind = .@"error",
};
pub const error_directive: Diagnostic = .{
.fmt = "{s}",
.kind = .@"error",
};
pub const warning_directive: Diagnostic = .{
.fmt = "{s}",
.opt = .@"#warnings",
.kind = .warning,
.show_in_system_headers = true,
};
pub const macro_name_missing: Diagnostic = .{
.fmt = "macro name missing",
.kind = .@"error",
};
pub const extra_tokens_directive_end: Diagnostic = .{
.fmt = "extra tokens at end of macro directive",
.kind = .@"error",
};
pub const expected_value_in_expr: Diagnostic = .{
.fmt = "expected value in expression",
.kind = .@"error",
};
pub const defined_as_macro_name: Diagnostic = .{
.fmt = "'defined' cannot be used as a macro name",
.kind = .@"error",
};
pub const macro_name_must_be_identifier: Diagnostic = .{
.fmt = "macro name must be an identifier",
.kind = .@"error",
};
pub const whitespace_after_macro_name: Diagnostic = .{
.fmt = "ISO C99 requires whitespace after the macro name",
.opt = .@"c99-extensions",
.kind = .warning,
.extension = true,
};
pub const hash_hash_at_start: Diagnostic = .{
.fmt = "'##' cannot appear at the start of a macro expansion",
.kind = .@"error",
};
pub const hash_hash_at_end: Diagnostic = .{
.fmt = "'##' cannot appear at the end of a macro expansion",
.kind = .@"error",
};
pub const pasting_formed_invalid: Diagnostic = .{
.fmt = "pasting formed '{s}', an invalid preprocessing token",
.kind = .@"error",
};
pub const missing_paren_param_list: Diagnostic = .{
.fmt = "missing ')' in macro parameter list",
.kind = .@"error",
};
pub const unterminated_macro_param_list: Diagnostic = .{
.fmt = "unterminated macro param list",
.kind = .@"error",
};
pub const invalid_token_param_list: Diagnostic = .{
.fmt = "invalid token in macro parameter list",
.kind = .@"error",
};
pub const expected_comma_param_list: Diagnostic = .{
.fmt = "expected comma in macro parameter list",
.kind = .@"error",
};
pub const hash_not_followed_param: Diagnostic = .{
.fmt = "'#' is not followed by a macro parameter",
.kind = .@"error",
};
pub const expected_filename: Diagnostic = .{
.fmt = "expected \"FILENAME\" or <FILENAME>",
.kind = .@"error",
};
pub const empty_filename: Diagnostic = .{
.fmt = "empty filename",
.kind = .@"error",
};
pub const header_str_closing: Diagnostic = .{
.fmt = "expected closing '>'",
.kind = .@"error",
};
pub const header_str_match: Diagnostic = .{
.fmt = "to match this '<'",
.kind = .note,
};
pub const string_literal_in_pp_expr: Diagnostic = .{
.fmt = "string literal in preprocessor expression",
.kind = .@"error",
};
pub const empty_char_literal_warning: Diagnostic = .{
.fmt = "empty character constant",
.kind = .warning,
.opt = .@"invalid-pp-token",
.extension = true,
};
pub const unterminated_char_literal_warning: Diagnostic = .{
.fmt = "missing terminating ' character",
.kind = .warning,
.opt = .@"invalid-pp-token",
.extension = true,
};
pub const unterminated_string_literal_warning: Diagnostic = .{
.fmt = "missing terminating '\"' character",
.kind = .warning,
.opt = .@"invalid-pp-token",
.extension = true,
};
pub const unterminated_comment: Diagnostic = .{
.fmt = "unterminated comment",
.kind = .@"error",
};
pub const malformed_embed_param: Diagnostic = .{
.fmt = "unexpected token in embed parameter",
.kind = .@"error",
};
pub const malformed_embed_limit: Diagnostic = .{
.fmt = "the limit parameter expects one non-negative integer as a parameter",
.kind = .@"error",
};
pub const duplicate_embed_param: Diagnostic = .{
.fmt = "duplicate embed parameter '{s}'",
.kind = .warning,
.opt = .@"duplicate-embed-param",
};
pub const unsupported_embed_param: Diagnostic = .{
.fmt = "unsupported embed parameter '{s}' embed parameter",
.kind = .warning,
.opt = .@"unsupported-embed-param",
};
pub const va_opt_lparen: Diagnostic = .{
.fmt = "missing '(' following __VA_OPT__",
.kind = .@"error",
};
pub const va_opt_rparen: Diagnostic = .{
.fmt = "unterminated __VA_OPT__ argument list",
.kind = .@"error",
};
pub const keyword_macro: Diagnostic = .{
.fmt = "keyword is hidden by macro definition",
.kind = .off,
.opt = .@"keyword-macro",
.extension = true,
};
pub const undefined_macro: Diagnostic = .{
.fmt = "'{s}' is not defined, evaluates to 0",
.kind = .off,
.opt = .undef,
};
pub const fn_macro_undefined: Diagnostic = .{
.fmt = "function-like macro '{s}' is not defined",
.kind = .@"error",
};
// pub const preprocessing_directive_only: Diagnostic = .{
// .fmt = "'{s}' must be used within a preprocessing directive",
// .extra = .tok_id_expected,
// .kind = .@"error",
// };
pub const missing_lparen_after_builtin: Diagnostic = .{
.fmt = "Missing '(' after built-in macro '{s}'",
.kind = .@"error",
};
pub const too_many_includes: Diagnostic = .{
.fmt = "#include nested too deeply",
.kind = .@"error",
};
pub const include_next: Diagnostic = .{
.fmt = "#include_next is a language extension",
.kind = .off,
.opt = .@"gnu-include-next",
.extension = true,
};
pub const include_next_outside_header: Diagnostic = .{
.fmt = "#include_next in primary source file; will search from start of include path",
.kind = .warning,
.opt = .@"include-next-outside-header",
};
pub const comma_deletion_va_args: Diagnostic = .{
.fmt = "token pasting of ',' and __VA_ARGS__ is a GNU extension",
.kind = .off,
.opt = .@"gnu-zero-variadic-macro-arguments",
.extension = true,
};
pub const expansion_to_defined_obj: Diagnostic = .{
.fmt = "macro expansion producing 'defined' has undefined behavior",
.kind = .off,
.opt = .@"expansion-to-defined",
};
pub const expansion_to_defined_func: Diagnostic = .{
.fmt = expansion_to_defined_obj.fmt,
.kind = .off,
.opt = .@"expansion-to-defined",
.extension = true,
};
pub const invalid_pp_stringify_escape: Diagnostic = .{
.fmt = "invalid string literal, ignoring final '\\'",
.kind = .warning,
};
pub const gnu_va_macro: Diagnostic = .{
.fmt = "named variadic macros are a GNU extension",
.opt = .@"variadic-macros",
.kind = .off,
.extension = true,
};
pub const pragma_operator_string_literal: Diagnostic = .{
.fmt = "_Pragma requires exactly one string literal token",
.kind = .@"error",
};
pub const invalid_preproc_expr_start: Diagnostic = .{
.fmt = "invalid token at start of a preprocessor expression",
.kind = .@"error",
};
pub const newline_eof: Diagnostic = .{
.fmt = "no newline at end of file",
.opt = .@"newline-eof",
.kind = .off,
.extension = true,
};
pub const malformed_warning_check: Diagnostic = .{
.fmt = "{s} expected option name (e.g. \"-Wundef\")",
.opt = .@"malformed-warning-check",
.kind = .warning,
.extension = true,
};
pub const feature_check_requires_identifier: Diagnostic = .{
.fmt = "builtin feature check macro requires a parenthesized identifier",
.kind = .@"error",
};
pub const builtin_macro_redefined: Diagnostic = .{
.fmt = "redefining builtin macro",
.opt = .@"builtin-macro-redefined",
.kind = .warning,
.extension = true,
};
pub const macro_redefined: Diagnostic = .{
.fmt = "'{s}' macro redefined",
.opt = .@"macro-redefined",
.kind = .warning,
.extension = true,
};
pub const previous_definition: Diagnostic = .{
.fmt = "previous definition is here",
.kind = .note,
};
pub const unterminated_macro_arg_list: Diagnostic = .{
.fmt = "unterminated function macro argument list",
.kind = .@"error",
};
pub const to_match_paren: Diagnostic = .{
.fmt = "to match this '('",
.kind = .note,
};
pub const closing_paren: Diagnostic = .{
.fmt = "expected closing ')'",
.kind = .@"error",
};
pub const poisoned_identifier: Diagnostic = .{
.fmt = "attempt to use a poisoned identifier",
.kind = .@"error",
};
pub const expected_arguments: Diagnostic = .{
.fmt = "expected {d} argument(s) got {d}",
.kind = .@"error",
};
pub const expected_at_least_arguments: Diagnostic = .{
.fmt = "expected at least {d} argument(s) got {d}",
.kind = .warning,
};
pub const invalid_preproc_operator: Diagnostic = .{
.fmt = "token is not a valid binary operator in a preprocessor subexpression",
.kind = .@"error",
};
pub const expected_str_literal_in: Diagnostic = .{
.fmt = "expected string literal in '{s}'",
.kind = .@"error",
};
pub const builtin_missing_r_paren: Diagnostic = .{
.fmt = "missing ')', after {s}",
.kind = .@"error",
};
pub const cannot_convert_to_identifier: Diagnostic = .{
.fmt = "cannot convert {s} to an identifier",
.kind = .@"error",
};
pub const expected_identifier: Diagnostic = .{
.fmt = "expected identifier argument",
.kind = .@"error",
};
pub const incomplete_ucn: Diagnostic = .{
.fmt = "incomplete universal character name; treating as '\\' followed by identifier",
.kind = .warning,
.opt = .unicode,
};
pub const invalid_source_epoch: Diagnostic = .{
.fmt = "environment variable SOURCE_DATE_EPOCH must expand to a non-negative integer less than or equal to 253402300799",
.kind = .@"error",
};
pub const date_time: Diagnostic = .{
.fmt = "expansion of date or time macro is not reproducible",
.kind = .off,
.opt = .@"date-time",
.show_in_system_headers = true,
};
pub const no_argument_variadic_macro: Diagnostic = .{
.fmt = "passing no argument for the '...' parameter of a variadic macro is incompatible with C standards before C23",
.opt = .@"variadic-macro-arguments-omitted",
.kind = .off,
.extension = true,
};

View File

@ -24,6 +24,21 @@ pub const Location = struct {
pub fn eql(a: Location, b: Location) bool {
return a.id == b.id and a.byte_offset == b.byte_offset and a.line == b.line;
}
pub fn expand(loc: Location, comp: *const @import("Compilation.zig")) ExpandedLocation {
const source = comp.getSource(loc.id);
return source.lineCol(loc);
}
};
pub const ExpandedLocation = struct {
path: []const u8,
line: []const u8,
line_no: u32,
col: u32,
width: u32,
end_with_splice: bool,
kind: Kind,
};
const Source = @This();
@ -51,9 +66,7 @@ pub fn physicalLine(source: Source, loc: Location) u32 {
return loc.line + source.numSplicesBefore(loc.byte_offset);
}
const LineCol = struct { line: []const u8, line_no: u32, col: u32, width: u32, end_with_splice: bool };
pub fn lineCol(source: Source, loc: Location) LineCol {
pub fn lineCol(source: Source, loc: Location) ExpandedLocation {
var start: usize = 0;
// find the start of the line which is either a newline or a splice
if (std.mem.lastIndexOfScalar(u8, source.buf[0..loc.byte_offset], '\n')) |some| start = some + 1;
@ -102,11 +115,13 @@ pub fn lineCol(source: Source, loc: Location) LineCol {
nl = source.splice_locs[splice_index];
}
return .{
.path = source.path,
.line = source.buf[start..nl],
.line_no = loc.line + splice_index,
.col = col,
.width = width,
.end_with_splice = end_with_splice,
.kind = source.kind,
};
}

View File

@ -2,82 +2,34 @@ const std = @import("std");
const mem = std.mem;
const Compilation = @import("Compilation.zig");
const StringToIdMap = std.StringHashMapUnmanaged(StringId);
pub const StringId = enum(u32) {
empty,
_,
};
pub const TypeMapper = struct {
const LookupSpeed = enum {
fast,
slow,
};
data: union(LookupSpeed) {
fast: []const []const u8,
slow: *const StringToIdMap,
},
pub fn lookup(self: TypeMapper, string_id: StringInterner.StringId) []const u8 {
if (string_id == .empty) return "";
switch (self.data) {
.fast => |arr| return arr[@intFromEnum(string_id)],
.slow => |map| {
var it = map.iterator();
while (it.next()) |entry| {
if (entry.value_ptr.* == string_id) return entry.key_ptr.*;
}
unreachable;
},
}
}
pub fn deinit(self: TypeMapper, allocator: mem.Allocator) void {
switch (self.data) {
.slow => {},
.fast => |arr| allocator.free(arr),
}
}
};
const StringInterner = @This();
string_table: StringToIdMap = .{},
next_id: StringId = @enumFromInt(@intFromEnum(StringId.empty) + 1),
pub const StringId = enum(u32) {
empty = std.math.maxInt(u32),
_,
pub fn deinit(self: *StringInterner, allocator: mem.Allocator) void {
self.string_table.deinit(allocator);
pub fn lookup(id: StringId, comp: *const Compilation) []const u8 {
if (id == .empty) return "";
return comp.string_interner.table.keys()[@intFromEnum(id)];
}
pub fn lookupExtra(id: StringId, si: StringInterner) []const u8 {
if (id == .empty) return "";
return si.table.keys()[@intFromEnum(id)];
}
};
table: std.StringArrayHashMapUnmanaged(void) = .empty,
pub fn deinit(si: *StringInterner, allocator: mem.Allocator) void {
si.table.deinit(allocator);
si.* = undefined;
}
pub fn intern(comp: *Compilation, str: []const u8) !StringId {
return comp.string_interner.internExtra(comp.gpa, str);
}
pub fn internExtra(self: *StringInterner, allocator: mem.Allocator, str: []const u8) !StringId {
/// Intern externally owned string.
pub fn intern(si: *StringInterner, allocator: mem.Allocator, str: []const u8) !StringId {
if (str.len == 0) return .empty;
const gop = try self.string_table.getOrPut(allocator, str);
if (gop.found_existing) return gop.value_ptr.*;
defer self.next_id = @enumFromInt(@intFromEnum(self.next_id) + 1);
gop.value_ptr.* = self.next_id;
return self.next_id;
}
/// deinit for the returned TypeMapper is a no-op and does not need to be called
pub fn getSlowTypeMapper(self: *const StringInterner) TypeMapper {
return TypeMapper{ .data = .{ .slow = &self.string_table } };
}
/// Caller must call `deinit` on the returned TypeMapper
pub fn getFastTypeMapper(self: *const StringInterner, allocator: mem.Allocator) !TypeMapper {
var strings = try allocator.alloc([]const u8, @intFromEnum(self.next_id));
var it = self.string_table.iterator();
strings[0] = "";
while (it.next()) |entry| {
strings[@intFromEnum(entry.value_ptr.*)] = entry.key_ptr.*;
}
return TypeMapper{ .data = .{ .fast = strings } };
const gop = try si.table.getOrPut(allocator, str);
return @enumFromInt(gop.index);
}

View File

@ -2,22 +2,24 @@ const std = @import("std");
const mem = std.mem;
const Allocator = mem.Allocator;
const assert = std.debug.assert;
const Parser = @import("Parser.zig");
const StringId = @import("StringInterner.zig").StringId;
const Tree = @import("Tree.zig");
const Token = Tree.Token;
const TokenIndex = Tree.TokenIndex;
const NodeIndex = Tree.NodeIndex;
const Type = @import("Type.zig");
const Parser = @import("Parser.zig");
const Node = Tree.Node;
const QualType = @import("TypeStore.zig").QualType;
const Value = @import("Value.zig");
const StringId = @import("StringInterner.zig").StringId;
const SymbolStack = @This();
pub const Symbol = struct {
name: StringId,
ty: Type,
qt: QualType,
tok: TokenIndex,
node: NodeIndex = .none,
node: Node.OptIndex = .null,
out_of_scope: bool = false,
kind: Kind,
val: Value,
};
@ -33,7 +35,7 @@ pub const Kind = enum {
constexpr,
};
scopes: std.ArrayListUnmanaged(Scope) = .empty,
scopes: std.ArrayList(Scope) = .empty,
/// allocations from nested scopes are retained after popping; `active_len` is the number
/// of currently-active items in `scopes`.
active_len: usize = 0,
@ -64,7 +66,7 @@ pub fn deinit(s: *SymbolStack, gpa: Allocator) void {
pub fn pushScope(s: *SymbolStack, p: *Parser) !void {
if (s.active_len + 1 > s.scopes.items.len) {
try s.scopes.append(p.gpa, .{});
try s.scopes.append(p.comp.gpa, .{});
s.active_len = s.scopes.items.len;
} else {
s.scopes.items[s.active_len].clearRetainingCapacity();
@ -82,17 +84,17 @@ pub fn findTypedef(s: *SymbolStack, p: *Parser, name: StringId, name_tok: TokenI
.typedef => return prev,
.@"struct" => {
if (no_type_yet) return null;
try p.errStr(.must_use_struct, name_tok, p.tokSlice(name_tok));
try p.err(name_tok, .must_use_struct, .{p.tokSlice(name_tok)});
return prev;
},
.@"union" => {
if (no_type_yet) return null;
try p.errStr(.must_use_union, name_tok, p.tokSlice(name_tok));
try p.err(name_tok, .must_use_union, .{p.tokSlice(name_tok)});
return prev;
},
.@"enum" => {
if (no_type_yet) return null;
try p.errStr(.must_use_enum, name_tok, p.tokSlice(name_tok));
try p.err(name_tok, .must_use_enum, .{p.tokSlice(name_tok)});
return prev;
},
else => return null,
@ -120,8 +122,8 @@ pub fn findTag(
else => unreachable,
}
if (s.get(name, .tags) == null) return null;
try p.errStr(.wrong_tag, name_tok, p.tokSlice(name_tok));
try p.errTok(.previous_definition, prev.tok);
try p.err(name_tok, .wrong_tag, .{p.tokSlice(name_tok)});
try p.err(prev.tok, .previous_definition, .{});
return null;
}
@ -171,38 +173,34 @@ pub fn defineTypedef(
s: *SymbolStack,
p: *Parser,
name: StringId,
ty: Type,
qt: QualType,
tok: TokenIndex,
node: NodeIndex,
node: Node.Index,
) !void {
if (s.get(name, .vars)) |prev| {
switch (prev.kind) {
.typedef => {
if (!prev.ty.is(.invalid)) {
if (!ty.eql(prev.ty, p.comp, true)) {
try p.errStr(.redefinition_of_typedef, tok, try p.typePairStrExtra(ty, " vs ", prev.ty));
if (prev.tok != 0) try p.errTok(.previous_definition, prev.tok);
}
if (!prev.qt.isInvalid() and !qt.eqlQualified(prev.qt, p.comp)) {
if (qt.isInvalid()) return;
const non_typedef_qt = qt.type(p.comp).typedef.base;
const non_typedef_prev_qt = prev.qt.type(p.comp).typedef.base;
try p.err(tok, .redefinition_of_typedef, .{ non_typedef_qt, non_typedef_prev_qt });
if (prev.tok != 0) try p.err(prev.tok, .previous_definition, .{});
}
},
.enumeration, .decl, .def, .constexpr => {
try p.errStr(.redefinition_different_sym, tok, p.tokSlice(tok));
try p.errTok(.previous_definition, prev.tok);
try p.err(tok, .redefinition_different_sym, .{p.tokSlice(tok)});
try p.err(prev.tok, .previous_definition, .{});
},
else => unreachable,
}
}
try s.define(p.gpa, .{
try s.define(p.comp.gpa, .{
.kind = .typedef,
.name = name,
.tok = tok,
.ty = .{
.name = name,
.specifier = ty.specifier,
.qual = ty.qual,
.data = ty.data,
},
.node = node,
.qt = qt,
.node = .pack(node),
.val = .{},
});
}
@ -211,42 +209,48 @@ pub fn defineSymbol(
s: *SymbolStack,
p: *Parser,
name: StringId,
ty: Type,
qt: QualType,
tok: TokenIndex,
node: NodeIndex,
node: Node.Index,
val: Value,
constexpr: bool,
) !void {
if (s.get(name, .vars)) |prev| {
switch (prev.kind) {
.enumeration => {
try p.errStr(.redefinition_different_sym, tok, p.tokSlice(tok));
try p.errTok(.previous_definition, prev.tok);
if (qt.isInvalid()) return;
try p.err(tok, .redefinition_different_sym, .{p.tokSlice(tok)});
try p.err(prev.tok, .previous_definition, .{});
},
.decl => {
if (!ty.eql(prev.ty, p.comp, true)) {
try p.errStr(.redefinition_incompatible, tok, p.tokSlice(tok));
try p.errTok(.previous_definition, prev.tok);
if (!prev.qt.isInvalid() and !qt.eqlQualified(prev.qt, p.comp)) {
if (qt.isInvalid()) return;
try p.err(tok, .redefinition_incompatible, .{p.tokSlice(tok)});
try p.err(prev.tok, .previous_definition, .{});
} else {
if (prev.node.unpack()) |some| p.setTentativeDeclDefinition(some, node);
}
},
.def, .constexpr => {
try p.errStr(.redefinition, tok, p.tokSlice(tok));
try p.errTok(.previous_definition, prev.tok);
.def, .constexpr => if (!prev.qt.isInvalid()) {
if (qt.isInvalid()) return;
try p.err(tok, .redefinition, .{p.tokSlice(tok)});
try p.err(prev.tok, .previous_definition, .{});
},
.typedef => {
try p.errStr(.redefinition_different_sym, tok, p.tokSlice(tok));
try p.errTok(.previous_definition, prev.tok);
if (qt.isInvalid()) return;
try p.err(tok, .redefinition_different_sym, .{p.tokSlice(tok)});
try p.err(prev.tok, .previous_definition, .{});
},
else => unreachable,
}
}
try s.define(p.gpa, .{
try s.define(p.comp.gpa, .{
.kind = if (constexpr) .constexpr else .def,
.name = name,
.tok = tok,
.ty = ty,
.node = node,
.qt = qt,
.node = .pack(node),
.val = val,
});
}
@ -264,69 +268,96 @@ pub fn declareSymbol(
s: *SymbolStack,
p: *Parser,
name: StringId,
ty: Type,
qt: QualType,
tok: TokenIndex,
node: NodeIndex,
node: Node.Index,
) !void {
if (s.get(name, .vars)) |prev| {
switch (prev.kind) {
.enumeration => {
try p.errStr(.redefinition_different_sym, tok, p.tokSlice(tok));
try p.errTok(.previous_definition, prev.tok);
if (qt.isInvalid()) return;
try p.err(tok, .redefinition_different_sym, .{p.tokSlice(tok)});
try p.err(prev.tok, .previous_definition, .{});
},
.decl => {
if (!ty.eql(prev.ty, p.comp, true)) {
try p.errStr(.redefinition_incompatible, tok, p.tokSlice(tok));
try p.errTok(.previous_definition, prev.tok);
if (!prev.qt.isInvalid() and !qt.eqlQualified(prev.qt, p.comp)) {
if (qt.isInvalid()) return;
try p.err(tok, .redefinition_incompatible, .{p.tokSlice(tok)});
try p.err(prev.tok, .previous_definition, .{});
} else {
if (prev.node.unpack()) |some| p.setTentativeDeclDefinition(node, some);
}
},
.def, .constexpr => {
if (!ty.eql(prev.ty, p.comp, true)) {
try p.errStr(.redefinition_incompatible, tok, p.tokSlice(tok));
try p.errTok(.previous_definition, prev.tok);
if (!prev.qt.isInvalid() and !qt.eqlQualified(prev.qt, p.comp)) {
if (qt.isInvalid()) return;
try p.err(tok, .redefinition_incompatible, .{p.tokSlice(tok)});
try p.err(prev.tok, .previous_definition, .{});
} else {
if (prev.node.unpack()) |some| p.setTentativeDeclDefinition(node, some);
return;
}
},
.typedef => {
try p.errStr(.redefinition_different_sym, tok, p.tokSlice(tok));
try p.errTok(.previous_definition, prev.tok);
if (qt.isInvalid()) return;
try p.err(tok, .redefinition_different_sym, .{p.tokSlice(tok)});
try p.err(prev.tok, .previous_definition, .{});
},
else => unreachable,
}
}
try s.define(p.gpa, .{
try s.define(p.comp.gpa, .{
.kind = .decl,
.name = name,
.tok = tok,
.ty = ty,
.node = node,
.qt = qt,
.node = .pack(node),
.val = .{},
});
// Declare out of scope symbol for functions declared in functions.
if (s.active_len > 1 and !p.comp.langopts.standard.atLeast(.c23) and qt.is(p.comp, .func)) {
try s.scopes.items[0].vars.put(p.comp.gpa, name, .{
.kind = .decl,
.name = name,
.tok = tok,
.qt = qt,
.node = .pack(node),
.val = .{},
.out_of_scope = true,
});
}
}
pub fn defineParam(s: *SymbolStack, p: *Parser, name: StringId, ty: Type, tok: TokenIndex) !void {
pub fn defineParam(
s: *SymbolStack,
p: *Parser,
name: StringId,
qt: QualType,
tok: TokenIndex,
node: ?Node.Index,
) !void {
if (s.get(name, .vars)) |prev| {
switch (prev.kind) {
.enumeration, .decl, .def, .constexpr => {
try p.errStr(.redefinition_of_parameter, tok, p.tokSlice(tok));
try p.errTok(.previous_definition, prev.tok);
.enumeration, .decl, .def, .constexpr => if (!prev.qt.isInvalid()) {
if (qt.isInvalid()) return;
try p.err(tok, .redefinition_of_parameter, .{p.tokSlice(tok)});
try p.err(prev.tok, .previous_definition, .{});
},
.typedef => {
try p.errStr(.redefinition_different_sym, tok, p.tokSlice(tok));
try p.errTok(.previous_definition, prev.tok);
if (qt.isInvalid()) return;
try p.err(tok, .redefinition_different_sym, .{p.tokSlice(tok)});
try p.err(prev.tok, .previous_definition, .{});
},
else => unreachable,
}
}
if (ty.is(.fp16) and !p.comp.hasHalfPrecisionFloatABI()) {
try p.errStr(.suggest_pointer_for_invalid_fp16, tok, "parameters");
}
try s.define(p.gpa, .{
try s.define(p.comp.gpa, .{
.kind = .def,
.name = name,
.tok = tok,
.ty = ty,
.qt = qt,
.node = .packOpt(node),
.val = .{},
});
}
@ -342,20 +373,20 @@ pub fn defineTag(
switch (prev.kind) {
.@"enum" => {
if (kind == .keyword_enum) return prev;
try p.errStr(.wrong_tag, tok, p.tokSlice(tok));
try p.errTok(.previous_definition, prev.tok);
try p.err(tok, .wrong_tag, .{p.tokSlice(tok)});
try p.err(prev.tok, .previous_definition, .{});
return null;
},
.@"struct" => {
if (kind == .keyword_struct) return prev;
try p.errStr(.wrong_tag, tok, p.tokSlice(tok));
try p.errTok(.previous_definition, prev.tok);
try p.err(tok, .wrong_tag, .{p.tokSlice(tok)});
try p.err(prev.tok, .previous_definition, .{});
return null;
},
.@"union" => {
if (kind == .keyword_union) return prev;
try p.errStr(.wrong_tag, tok, p.tokSlice(tok));
try p.errTok(.previous_definition, prev.tok);
try p.err(tok, .wrong_tag, .{p.tokSlice(tok)});
try p.err(prev.tok, .previous_definition, .{});
return null;
},
else => unreachable,
@ -366,34 +397,39 @@ pub fn defineEnumeration(
s: *SymbolStack,
p: *Parser,
name: StringId,
ty: Type,
qt: QualType,
tok: TokenIndex,
val: Value,
node: Node.Index,
) !void {
if (s.get(name, .vars)) |prev| {
switch (prev.kind) {
.enumeration => {
try p.errStr(.redefinition, tok, p.tokSlice(tok));
try p.errTok(.previous_definition, prev.tok);
.enumeration => if (!prev.qt.isInvalid()) {
if (qt.isInvalid()) return;
try p.err(tok, .redefinition, .{p.tokSlice(tok)});
try p.err(prev.tok, .previous_definition, .{});
return;
},
.decl, .def, .constexpr => {
try p.errStr(.redefinition_different_sym, tok, p.tokSlice(tok));
try p.errTok(.previous_definition, prev.tok);
if (qt.isInvalid()) return;
try p.err(tok, .redefinition_different_sym, .{p.tokSlice(tok)});
try p.err(prev.tok, .previous_definition, .{});
return;
},
.typedef => {
try p.errStr(.redefinition_different_sym, tok, p.tokSlice(tok));
try p.errTok(.previous_definition, prev.tok);
if (qt.isInvalid()) return;
try p.err(tok, .redefinition_different_sym, .{p.tokSlice(tok)});
try p.err(prev.tok, .previous_definition, .{});
},
else => unreachable,
}
}
try s.define(p.gpa, .{
try s.define(p.comp.gpa, .{
.kind = .enumeration,
.name = name,
.tok = tok,
.ty = ty,
.qt = qt,
.val = val,
.node = .pack(node),
});
}

View File

@ -1,8 +1,45 @@
const std = @import("std");
const assert = std.debug.assert;
const Compilation = @import("Compilation.zig");
const Source = @import("Source.zig");
const LangOpts = @import("LangOpts.zig");
const Source = @import("Source.zig");
/// Value for valid escapes indicates how many characters to consume, not counting leading backslash
const UCNKind = enum(u8) {
/// Just `\`
none,
/// \u or \U followed by an insufficient number of hex digits
incomplete,
/// `\uxxxx`
hex4 = 5,
/// `\Uxxxxxxxx`
hex8 = 9,
/// In the classification phase we do not care if the escape represents a valid universal character name
/// e.g. \UFFFFFFFF is acceptable.
fn classify(buf: []const u8) UCNKind {
assert(buf[0] == '\\');
if (buf.len == 1) return .none;
switch (buf[1]) {
'u' => {
if (buf.len < 6) return .incomplete;
for (buf[2..6]) |c| {
if (!std.ascii.isHex(c)) return .incomplete;
}
return .hex4;
},
'U' => {
if (buf.len < 10) return .incomplete;
for (buf[2..10]) |c| {
if (!std.ascii.isHex(c)) return .incomplete;
}
return .hex8;
},
else => return .none,
}
}
};
pub const Token = struct {
id: Id,
@ -18,7 +55,7 @@ pub const Token = struct {
eof,
/// identifier containing solely basic character set characters
identifier,
/// identifier with at least one extended character
/// identifier with at least one extended character or UCN escape sequence
extended_identifier,
// string literals with prefixes
@ -147,6 +184,10 @@ pub const Token = struct {
macro_counter,
/// Special token for implementing _Pragma
macro_param_pragma_operator,
/// Special token for implementing __identifier (MS extension)
macro_param_ms_identifier,
/// Special token for implementing __pragma (MS extension)
macro_param_ms_pragma,
/// Special identifier for implementing __func__
macro_func,
@ -154,6 +195,12 @@ pub const Token = struct {
macro_function,
/// Special identifier for implementing __PRETTY_FUNCTION__
macro_pretty_func,
/// Special identifier for implementing __DATE__
macro_date,
/// Special identifier for implementing __TIME__
macro_time,
/// Special identifier for implementing __TIMESTAMP__
macro_timestamp,
keyword_auto,
keyword_auto_type,
@ -290,13 +337,21 @@ pub const Token = struct {
keyword_thiscall2,
keyword_vectorcall,
keyword_vectorcall2,
keyword_fastcall,
keyword_fastcall2,
keyword_regcall,
keyword_cdecl,
keyword_cdecl2,
keyword_forceinline,
keyword_forceinline2,
keyword_unaligned,
keyword_unaligned2,
// builtins that require special parsing
builtin_choose_expr,
builtin_va_arg,
builtin_offsetof,
builtin_bitoffsetof,
builtin_types_compatible_p,
// Type nullability
keyword_nonnull,
keyword_nullable,
keyword_nullable_result,
keyword_null_unspecified,
/// Generated by #embed directive
/// Decimal value with no prefix or suffix
@ -323,6 +378,12 @@ pub const Token = struct {
/// A comment token if asked to preserve comments.
comment,
/// Incomplete universal character name
/// This happens if the source text contains `\u` or `\U` followed by an insufficient number of hex
/// digits. This token id represents just the backslash; the subsequent `u` or `U` will be treated as the
/// leading character of the following identifier token.
incomplete_ucn,
/// Return true if token is identifier or keyword.
pub fn isMacroIdentifier(id: Id) bool {
switch (id) {
@ -347,6 +408,9 @@ pub const Token = struct {
.macro_func,
.macro_function,
.macro_pretty_func,
.macro_date,
.macro_time,
.macro_timestamp,
.keyword_auto,
.keyword_auto_type,
.keyword_break,
@ -409,11 +473,6 @@ pub const Token = struct {
.keyword_restrict2,
.keyword_alignof1,
.keyword_alignof2,
.builtin_choose_expr,
.builtin_va_arg,
.builtin_offsetof,
.builtin_bitoffsetof,
.builtin_types_compatible_p,
.keyword_attribute1,
.keyword_attribute2,
.keyword_extension,
@ -444,6 +503,19 @@ pub const Token = struct {
.keyword_thiscall2,
.keyword_vectorcall,
.keyword_vectorcall2,
.keyword_fastcall,
.keyword_fastcall2,
.keyword_regcall,
.keyword_cdecl,
.keyword_cdecl2,
.keyword_forceinline,
.keyword_forceinline2,
.keyword_unaligned,
.keyword_unaligned2,
.keyword_nonnull,
.keyword_nullable,
.keyword_nullable_result,
.keyword_null_unspecified,
.keyword_bit_int,
.keyword_c23_alignas,
.keyword_c23_alignof,
@ -547,11 +619,18 @@ pub const Token = struct {
.macro_file,
.macro_line,
.macro_counter,
.macro_time,
.macro_date,
.macro_timestamp,
.macro_param_pragma_operator,
.macro_param_ms_identifier,
.macro_param_ms_pragma,
.placemarker,
=> "",
.macro_ws => " ",
.incomplete_ucn => "\\",
.macro_func => "__func__",
.macro_function => "__FUNCTION__",
.macro_pretty_func => "__PRETTY_FUNCTION__",
@ -695,11 +774,6 @@ pub const Token = struct {
.keyword_alignof2 => "__alignof__",
.keyword_typeof1 => "__typeof",
.keyword_typeof2 => "__typeof__",
.builtin_choose_expr => "__builtin_choose_expr",
.builtin_va_arg => "__builtin_va_arg",
.builtin_offsetof => "__builtin_offsetof",
.builtin_bitoffsetof => "__builtin_bitoffsetof",
.builtin_types_compatible_p => "__builtin_types_compatible_p",
.keyword_attribute1 => "__attribute",
.keyword_attribute2 => "__attribute__",
.keyword_extension => "__extension__",
@ -730,6 +804,19 @@ pub const Token = struct {
.keyword_thiscall2 => "_thiscall",
.keyword_vectorcall => "__vectorcall",
.keyword_vectorcall2 => "_vectorcall",
.keyword_fastcall => "__fastcall",
.keyword_fastcall2 => "_fastcall",
.keyword_regcall => "__regcall",
.keyword_cdecl => "__cdecl",
.keyword_cdecl2 => "_cdecl",
.keyword_forceinline => "__forceinline",
.keyword_forceinline2 => "_forceinline",
.keyword_unaligned => "__unaligned",
.keyword_unaligned2 => "_unaligned",
.keyword_nonnull => "_Nonnull",
.keyword_nullable => "_Nullable",
.keyword_nullable_result => "_Nullable_result",
.keyword_null_unspecified => "_Null_unspecified",
};
}
@ -742,11 +829,6 @@ pub const Token = struct {
.macro_func,
.macro_function,
.macro_pretty_func,
.builtin_choose_expr,
.builtin_va_arg,
.builtin_offsetof,
.builtin_bitoffsetof,
.builtin_types_compatible_p,
=> "an identifier",
.string_literal,
.string_literal_utf_16,
@ -763,7 +845,7 @@ pub const Token = struct {
.unterminated_char_literal,
.empty_char_literal,
=> "a character literal",
.pp_num, .embed_byte => "A number",
.pp_num, .embed_byte => "a number",
else => id.lexeme().?,
};
}
@ -871,6 +953,12 @@ pub const Token = struct {
.keyword_stdcall2,
.keyword_thiscall2,
.keyword_vectorcall2,
.keyword_fastcall2,
.keyword_cdecl2,
.keyword_forceinline,
.keyword_forceinline2,
.keyword_unaligned,
.keyword_unaligned2,
=> if (langopts.ms_extensions) kw else .identifier,
else => kw,
};
@ -1013,13 +1101,21 @@ pub const Token = struct {
.{ "_thiscall", .keyword_thiscall2 },
.{ "__vectorcall", .keyword_vectorcall },
.{ "_vectorcall", .keyword_vectorcall2 },
.{ "__fastcall", .keyword_fastcall },
.{ "_fastcall", .keyword_fastcall2 },
.{ "_regcall", .keyword_regcall },
.{ "__cdecl", .keyword_cdecl },
.{ "_cdecl", .keyword_cdecl2 },
.{ "__forceinline", .keyword_forceinline },
.{ "_forceinline", .keyword_forceinline2 },
.{ "__unaligned", .keyword_unaligned },
.{ "_unaligned", .keyword_unaligned2 },
// builtins that require special parsing
.{ "__builtin_choose_expr", .builtin_choose_expr },
.{ "__builtin_va_arg", .builtin_va_arg },
.{ "__builtin_offsetof", .builtin_offsetof },
.{ "__builtin_bitoffsetof", .builtin_bitoffsetof },
.{ "__builtin_types_compatible_p", .builtin_types_compatible_p },
// Type nullability
.{ "_Nonnull", .keyword_nonnull },
.{ "_Nullable", .keyword_nullable },
.{ "_Nullable_result", .keyword_nullable_result },
.{ "_Null_unspecified", .keyword_null_unspecified },
});
};
@ -1099,6 +1195,26 @@ pub fn next(self: *Tokenizer) Token {
'u' => state = .u,
'U' => state = .U,
'L' => state = .L,
'\\' => {
const ucn_kind = UCNKind.classify(self.buf[self.index..]);
switch (ucn_kind) {
.none => {
self.index += 1;
id = .invalid;
break;
},
.incomplete => {
self.index += 1;
id = .incomplete_ucn;
break;
},
.hex4, .hex8 => {
self.index += @intFromEnum(ucn_kind);
id = .extended_identifier;
state = .extended_identifier;
},
}
},
'a'...'t', 'v'...'z', 'A'...'K', 'M'...'T', 'V'...'Z', '_' => state = .identifier,
'=' => state = .equal,
'!' => state = .bang,
@ -1324,6 +1440,20 @@ pub fn next(self: *Tokenizer) Token {
break;
},
0x80...0xFF => state = .extended_identifier,
'\\' => {
const ucn_kind = UCNKind.classify(self.buf[self.index..]);
switch (ucn_kind) {
.none, .incomplete => {
id = if (state == .identifier) Token.getTokenId(self.langopts, self.buf[start..self.index]) else .extended_identifier;
break;
},
.hex4, .hex8 => {
state = .extended_identifier;
self.index += @intFromEnum(ucn_kind);
},
}
},
else => {
id = if (state == .identifier) Token.getTokenId(self.langopts, self.buf[start..self.index]) else .extended_identifier;
break;
@ -1731,7 +1861,10 @@ pub fn next(self: *Tokenizer) Token {
}
} else if (self.index == self.buf.len) {
switch (state) {
.start, .line_comment => {},
.start => {},
.line_comment => if (self.langopts.preserve_comments) {
id = .comment;
},
.u, .u8, .U, .L, .identifier => id = Token.getTokenId(self.langopts, self.buf[start..self.index]),
.extended_identifier => id = .extended_identifier,
@ -2105,6 +2238,15 @@ test "comments" {
.hash,
.identifier,
});
try expectTokensExtra(
\\//foo
\\void
\\//bar
, &.{
.comment, .nl,
.keyword_void, .nl,
.comment,
}, .{ .preserve_comments = true });
}
test "extended identifiers" {
@ -2147,36 +2289,76 @@ test "C23 keywords" {
.keyword_c23_thread_local,
.keyword_nullptr,
.keyword_typeof_unqual,
}, .c23);
}, .{ .standard = .c23 });
}
test "Universal character names" {
try expectTokens("\\", &.{.invalid});
try expectTokens("\\g", &.{ .invalid, .identifier });
try expectTokens("\\u", &.{ .incomplete_ucn, .identifier });
try expectTokens("\\ua", &.{ .incomplete_ucn, .identifier });
try expectTokens("\\U9", &.{ .incomplete_ucn, .identifier });
try expectTokens("\\ug", &.{ .incomplete_ucn, .identifier });
try expectTokens("\\uag", &.{ .incomplete_ucn, .identifier });
try expectTokens("\\ ", &.{ .invalid, .eof });
try expectTokens("\\g ", &.{ .invalid, .identifier, .eof });
try expectTokens("\\u ", &.{ .incomplete_ucn, .identifier, .eof });
try expectTokens("\\ua ", &.{ .incomplete_ucn, .identifier, .eof });
try expectTokens("\\U9 ", &.{ .incomplete_ucn, .identifier, .eof });
try expectTokens("\\ug ", &.{ .incomplete_ucn, .identifier, .eof });
try expectTokens("\\uag ", &.{ .incomplete_ucn, .identifier, .eof });
try expectTokens("a\\", &.{ .identifier, .invalid });
try expectTokens("a\\g", &.{ .identifier, .invalid, .identifier });
try expectTokens("a\\u", &.{ .identifier, .incomplete_ucn, .identifier });
try expectTokens("a\\ua", &.{ .identifier, .incomplete_ucn, .identifier });
try expectTokens("a\\U9", &.{ .identifier, .incomplete_ucn, .identifier });
try expectTokens("a\\ug", &.{ .identifier, .incomplete_ucn, .identifier });
try expectTokens("a\\uag", &.{ .identifier, .incomplete_ucn, .identifier });
try expectTokens("a\\ ", &.{ .identifier, .invalid, .eof });
try expectTokens("a\\g ", &.{ .identifier, .invalid, .identifier, .eof });
try expectTokens("a\\u ", &.{ .identifier, .incomplete_ucn, .identifier, .eof });
try expectTokens("a\\ua ", &.{ .identifier, .incomplete_ucn, .identifier, .eof });
try expectTokens("a\\U9 ", &.{ .identifier, .incomplete_ucn, .identifier, .eof });
try expectTokens("a\\ug ", &.{ .identifier, .incomplete_ucn, .identifier, .eof });
try expectTokens("a\\uag ", &.{ .identifier, .incomplete_ucn, .identifier, .eof });
}
test "Tokenizer fuzz test" {
var comp = Compilation.init(std.testing.allocator, std.fs.cwd());
defer comp.deinit();
const Context = struct {
fn testOne(_: @This(), input_bytes: []const u8) anyerror!void {
var arena: std.heap.ArenaAllocator = .init(std.testing.allocator);
defer arena.deinit();
var comp = Compilation.init(std.testing.allocator, arena.allocator(), undefined, std.fs.cwd());
defer comp.deinit();
const input_bytes = std.testing.fuzzInput(.{});
if (input_bytes.len == 0) return;
const source = try comp.addSourceFromBuffer("fuzz.c", input_bytes);
const source = try comp.addSourceFromBuffer("fuzz.c", input_bytes);
var tokenizer: Tokenizer = .{
.buf = source.buf,
.source = source.id,
.langopts = comp.langopts,
var tokenizer: Tokenizer = .{
.buf = source.buf,
.source = source.id,
.langopts = comp.langopts,
};
while (true) {
const prev_index = tokenizer.index;
const tok = tokenizer.next();
if (tok.id == .eof) break;
try std.testing.expect(prev_index < tokenizer.index); // ensure that the tokenizer always makes progress
}
}
};
while (true) {
const prev_index = tokenizer.index;
const tok = tokenizer.next();
if (tok.id == .eof) break;
try std.testing.expect(prev_index < tokenizer.index); // ensure that the tokenizer always makes progress
}
return std.testing.fuzz(Context{}, Context.testOne, .{});
}
fn expectTokensExtra(contents: []const u8, expected_tokens: []const Token.Id, standard: ?LangOpts.Standard) !void {
var comp = Compilation.init(std.testing.allocator, std.fs.cwd());
fn expectTokensExtra(contents: []const u8, expected_tokens: []const Token.Id, langopts: ?LangOpts) !void {
var arena: std.heap.ArenaAllocator = .init(std.testing.allocator);
defer arena.deinit();
var comp = Compilation.init(std.testing.allocator, arena.allocator(), undefined, std.fs.cwd());
defer comp.deinit();
if (standard) |provided| {
comp.langopts.standard = provided;
if (langopts) |provided| {
comp.langopts = provided;
}
const source = try comp.addSourceFromBuffer("path", contents);
var tokenizer = Tokenizer{

View File

@ -1,14 +1,15 @@
const std = @import("std");
const Driver = @import("Driver.zig");
const Compilation = @import("Compilation.zig");
const mem = std.mem;
const system_defaults = @import("system_defaults");
const target_util = @import("target.zig");
const Linux = @import("toolchains/Linux.zig");
const Multilib = @import("Driver/Multilib.zig");
const Filesystem = @import("Driver/Filesystem.zig").Filesystem;
pub const PathList = std.ArrayListUnmanaged([]const u8);
const system_defaults = @import("system_defaults");
const Compilation = @import("Compilation.zig");
const Driver = @import("Driver.zig");
const Filesystem = @import("Driver/Filesystem.zig").Filesystem;
const Multilib = @import("Driver/Multilib.zig");
const target_util = @import("target.zig");
pub const PathList = std.ArrayList([]const u8);
pub const RuntimeLibKind = enum {
compiler_rt,
@ -35,22 +36,13 @@ pub const UnwindLibKind = enum {
const Inner = union(enum) {
uninitialized,
linux: Linux,
unknown: void,
fn deinit(self: *Inner, allocator: mem.Allocator) void {
switch (self.*) {
.linux => |*linux| linux.deinit(allocator),
.uninitialized, .unknown => {},
}
}
};
const Toolchain = @This();
filesystem: Filesystem = .{ .real = {} },
filesystem: Filesystem,
driver: *Driver,
arena: mem.Allocator,
/// The list of toolchain specific path prefixes to search for libraries.
library_paths: PathList = .{},
@ -72,7 +64,6 @@ pub fn getTarget(tc: *const Toolchain) std.Target {
fn getDefaultLinker(tc: *const Toolchain) []const u8 {
return switch (tc.inner) {
.uninitialized => unreachable,
.linux => |linux| linux.getDefaultLinker(tc.getTarget()),
.unknown => "ld",
};
}
@ -81,36 +72,26 @@ fn getDefaultLinker(tc: *const Toolchain) []const u8 {
pub fn discover(tc: *Toolchain) !void {
if (tc.inner != .uninitialized) return;
const target = tc.getTarget();
tc.inner = switch (target.os.tag) {
.linux => if (target.cpu.arch == .hexagon)
.{ .unknown = {} } // TODO
else if (target.cpu.arch.isMIPS())
.{ .unknown = {} } // TODO
else if (target.cpu.arch.isPowerPC())
.{ .unknown = {} } // TODO
else if (target.cpu.arch == .ve)
.{ .unknown = {} } // TODO
else
.{ .linux = .{} },
else => .{ .unknown = {} }, // TODO
};
tc.inner = .unknown;
return switch (tc.inner) {
.uninitialized => unreachable,
.linux => |*linux| linux.discover(tc),
.unknown => {},
};
}
pub fn deinit(tc: *Toolchain) void {
const gpa = tc.driver.comp.gpa;
tc.inner.deinit(gpa);
tc.library_paths.deinit(gpa);
tc.file_paths.deinit(gpa);
tc.program_paths.deinit(gpa);
}
/// Write assembler path to `buf` and return a slice of it
pub fn getAssemblerPath(tc: *const Toolchain, buf: []u8) ![]const u8 {
return tc.getProgramPath("as", buf);
}
/// Write linker path to `buf` and return a slice of it
pub fn getLinkerPath(tc: *const Toolchain, buf: []u8) ![]const u8 {
// --ld-path= takes precedence over -fuse-ld= and specifies the executable
@ -149,7 +130,12 @@ pub fn getLinkerPath(tc: *const Toolchain, buf: []u8) ![]const u8 {
// to a relative path is surprising. This is more complex due to priorities
// among -B, COMPILER_PATH and PATH. --ld-path= should be used instead.
if (mem.indexOfScalar(u8, use_linker, '/') != null) {
try tc.driver.comp.addDiagnostic(.{ .tag = .fuse_ld_path }, &.{});
try tc.driver.comp.diagnostics.add(.{
.text = "'-fuse-ld=' taking a path is deprecated; use '--ld-path=' instead",
.kind = .off,
.opt = .@"fuse-ld-path",
.location = null,
});
}
if (std.fs.path.isAbsolute(use_linker)) {
@ -157,8 +143,11 @@ pub fn getLinkerPath(tc: *const Toolchain, buf: []u8) ![]const u8 {
return use_linker;
}
} else {
var linker_name = try std.array_list.Managed(u8).initCapacity(tc.driver.comp.gpa, 5 + use_linker.len); // "ld64." ++ use_linker
defer linker_name.deinit();
const gpa = tc.driver.comp.gpa;
var linker_name: std.ArrayList(u8) = .empty;
defer linker_name.deinit(gpa);
try linker_name.ensureUnusedCapacity(tc.driver.comp.gpa, 5 + use_linker.len); // "ld64." ++ use_linker
if (tc.getTarget().os.tag.isDarwin()) {
linker_name.appendSliceAssumeCapacity("ld64.");
} else {
@ -185,27 +174,33 @@ pub fn getLinkerPath(tc: *const Toolchain, buf: []u8) ![]const u8 {
/// TODO: this isn't exactly right since our target names don't necessarily match up
/// with GCC's.
/// For example the Zig target `arm-freestanding-eabi` would need the `arm-none-eabi` tools
fn possibleProgramNames(raw_triple: ?[]const u8, name: []const u8, buf: *[64]u8) std.BoundedArray([]const u8, 2) {
var possible_names: std.BoundedArray([]const u8, 2) = .{};
fn possibleProgramNames(
raw_triple: ?[]const u8,
name: []const u8,
buf: *[64]u8,
possible_name_buf: *[2][]const u8,
) []const []const u8 {
var i: u32 = 0;
if (raw_triple) |triple| {
if (std.fmt.bufPrint(buf, "{s}-{s}", .{ triple, name })) |res| {
possible_names.appendAssumeCapacity(res);
possible_name_buf[i] = res;
i += 1;
} else |_| {}
}
possible_names.appendAssumeCapacity(name);
possible_name_buf[i] = name;
return possible_names;
return possible_name_buf[0..i];
}
/// Add toolchain `file_paths` to argv as `-L` arguments
pub fn addFilePathLibArgs(tc: *const Toolchain, argv: *std.array_list.Managed([]const u8)) !void {
try argv.ensureUnusedCapacity(tc.file_paths.items.len);
pub fn addFilePathLibArgs(tc: *const Toolchain, argv: *std.ArrayList([]const u8)) !void {
try argv.ensureUnusedCapacity(tc.driver.comp.gpa, tc.file_paths.items.len);
var bytes_needed: usize = 0;
for (tc.file_paths.items) |path| {
bytes_needed += path.len + 2; // +2 for `-L`
}
var bytes = try tc.arena.alloc(u8, bytes_needed);
var bytes = try tc.driver.comp.arena.alloc(u8, bytes_needed);
var index: usize = 0;
for (tc.file_paths.items) |path| {
@memcpy(bytes[index..][0..2], "-L");
@ -223,9 +218,10 @@ fn getProgramPath(tc: *const Toolchain, name: []const u8, buf: []u8) []const u8
var fib = std.heap.FixedBufferAllocator.init(&path_buf);
var tool_specific_buf: [64]u8 = undefined;
const possible_names = possibleProgramNames(tc.driver.raw_target_triple, name, &tool_specific_buf);
var possible_name_buf: [2][]const u8 = undefined;
const possible_names = possibleProgramNames(tc.driver.raw_target_triple, name, &tool_specific_buf, &possible_name_buf);
for (possible_names.constSlice()) |tool_name| {
for (possible_names) |tool_name| {
for (tc.program_paths.items) |program_path| {
defer fib.reset();
@ -252,6 +248,7 @@ pub fn getFilePath(tc: *const Toolchain, name: []const u8) ![]const u8 {
var path_buf: [std.fs.max_path_bytes]u8 = undefined;
var fib = std.heap.FixedBufferAllocator.init(&path_buf);
const allocator = fib.allocator();
const arena = tc.driver.comp.arena;
const sysroot = tc.getSysroot();
@ -260,15 +257,15 @@ pub fn getFilePath(tc: *const Toolchain, name: []const u8) ![]const u8 {
const aro_dir = std.fs.path.dirname(tc.driver.aro_name) orelse "";
const candidate = try std.fs.path.join(allocator, &.{ aro_dir, "..", name });
if (tc.filesystem.exists(candidate)) {
return tc.arena.dupe(u8, candidate);
return arena.dupe(u8, candidate);
}
if (tc.searchPaths(&fib, sysroot, tc.library_paths.items, name)) |path| {
return tc.arena.dupe(u8, path);
return arena.dupe(u8, path);
}
if (tc.searchPaths(&fib, sysroot, tc.file_paths.items, name)) |path| {
return try tc.arena.dupe(u8, path);
return try arena.dupe(u8, path);
}
return name;
@ -299,7 +296,7 @@ const PathKind = enum {
program,
};
/// Join `components` into a path. If the path exists, dupe it into the toolchain arena and
/// Join `components` into a path. If the path exists, dupe it into the Compilation arena and
/// add it to the specified path list.
pub fn addPathIfExists(tc: *Toolchain, components: []const []const u8, dest_kind: PathKind) !void {
var path_buf: [std.fs.max_path_bytes]u8 = undefined;
@ -308,7 +305,7 @@ pub fn addPathIfExists(tc: *Toolchain, components: []const []const u8, dest_kind
const candidate = try std.fs.path.join(fib.allocator(), components);
if (tc.filesystem.exists(candidate)) {
const duped = try tc.arena.dupe(u8, candidate);
const duped = try tc.driver.comp.arena.dupe(u8, candidate);
const dest = switch (dest_kind) {
.library => &tc.library_paths,
.file => &tc.file_paths,
@ -318,10 +315,10 @@ pub fn addPathIfExists(tc: *Toolchain, components: []const []const u8, dest_kind
}
}
/// Join `components` using the toolchain arena and add the resulting path to `dest_kind`. Does not check
/// Join `components` using the Compilation arena and add the resulting path to `dest_kind`. Does not check
/// whether the path actually exists
pub fn addPathFromComponents(tc: *Toolchain, components: []const []const u8, dest_kind: PathKind) !void {
const full_path = try std.fs.path.join(tc.arena, components);
const full_path = try std.fs.path.join(tc.driver.comp.arena, components);
const dest = switch (dest_kind) {
.library => &tc.library_paths,
.file => &tc.file_paths,
@ -330,16 +327,6 @@ pub fn addPathFromComponents(tc: *Toolchain, components: []const []const u8, des
try dest.append(tc.driver.comp.gpa, full_path);
}
/// Add linker args to `argv`. Does not add path to linker executable as first item; that must be handled separately
/// Items added to `argv` will be string literals or owned by `tc.arena` so they must not be individually freed
pub fn buildLinkerArgs(tc: *Toolchain, argv: *std.array_list.Managed([]const u8)) !void {
return switch (tc.inner) {
.uninitialized => unreachable,
.linux => |*linux| linux.buildLinkerArgs(tc, argv),
.unknown => @panic("This toolchain does not support linking yet"),
};
}
fn getDefaultRuntimeLibKind(tc: *const Toolchain) RuntimeLibKind {
if (tc.getTarget().abi.isAndroid()) {
return .compiler_rt;
@ -396,7 +383,7 @@ fn getUnwindLibKind(tc: *const Toolchain) !UnwindLibKind {
return .libgcc;
} else if (mem.eql(u8, libname, "libunwind")) {
if (tc.getRuntimeLibKind() == .libgcc) {
try tc.driver.comp.addDiagnostic(.{ .tag = .incompatible_unwindlib }, &.{});
try tc.driver.err("--rtlib=libgcc requires --unwindlib=libgcc", .{});
}
return .compiler_rt;
} else {
@ -412,7 +399,7 @@ fn getAsNeededOption(is_solaris: bool, needed: bool) []const u8 {
}
}
fn addUnwindLibrary(tc: *const Toolchain, argv: *std.array_list.Managed([]const u8)) !void {
fn addUnwindLibrary(tc: *const Toolchain, argv: *std.ArrayList([]const u8)) !void {
const unw = try tc.getUnwindLibKind();
const target = tc.getTarget();
if ((target.abi.isAndroid() and unw == .libgcc) or
@ -422,46 +409,49 @@ fn addUnwindLibrary(tc: *const Toolchain, argv: *std.array_list.Managed([]const
const lgk = tc.getLibGCCKind();
const as_needed = lgk == .unspecified and !target.abi.isAndroid() and !target_util.isCygwinMinGW(target) and target.os.tag != .aix;
try argv.ensureUnusedCapacity(tc.driver.comp.gpa, 3);
if (as_needed) {
try argv.append(getAsNeededOption(target.os.tag == .solaris, true));
argv.appendAssumeCapacity(getAsNeededOption(target.os.tag == .solaris, true));
}
switch (unw) {
.none => return,
.libgcc => if (lgk == .static) try argv.append("-lgcc_eh") else try argv.append("-lgcc_s"),
.libgcc => argv.appendAssumeCapacity(if (lgk == .static) "-lgcc_eh" else "-lgcc_s"),
.compiler_rt => if (target.os.tag == .aix) {
if (lgk != .static) {
try argv.append("-lunwind");
argv.appendAssumeCapacity("-lunwind");
}
} else if (lgk == .static) {
try argv.append("-l:libunwind.a");
argv.appendAssumeCapacity("-l:libunwind.a");
} else if (lgk == .shared) {
if (target_util.isCygwinMinGW(target)) {
try argv.append("-l:libunwind.dll.a");
argv.appendAssumeCapacity("-l:libunwind.dll.a");
} else {
try argv.append("-l:libunwind.so");
argv.appendAssumeCapacity("-l:libunwind.so");
}
} else {
try argv.append("-lunwind");
argv.appendAssumeCapacity("-lunwind");
},
}
if (as_needed) {
try argv.append(getAsNeededOption(target.os.tag == .solaris, false));
argv.appendAssumeCapacity(getAsNeededOption(target.os.tag == .solaris, false));
}
}
fn addLibGCC(tc: *const Toolchain, argv: *std.array_list.Managed([]const u8)) !void {
fn addLibGCC(tc: *const Toolchain, argv: *std.ArrayList([]const u8)) !void {
const gpa = tc.driver.comp.gpa;
const libgcc_kind = tc.getLibGCCKind();
if (libgcc_kind == .static or libgcc_kind == .unspecified) {
try argv.append("-lgcc");
try argv.append(gpa, "-lgcc");
}
try tc.addUnwindLibrary(argv);
if (libgcc_kind == .shared) {
try argv.append("-lgcc");
try argv.append(gpa, "-lgcc");
}
}
pub fn addRuntimeLibs(tc: *const Toolchain, argv: *std.array_list.Managed([]const u8)) !void {
pub fn addRuntimeLibs(tc: *const Toolchain, argv: *std.ArrayList([]const u8)) !void {
const target = tc.getTarget();
const rlt = tc.getRuntimeLibKind();
switch (rlt) {
@ -472,7 +462,7 @@ pub fn addRuntimeLibs(tc: *const Toolchain, argv: *std.array_list.Managed([]cons
if (target_util.isKnownWindowsMSVCEnvironment(target)) {
const rtlib_str = tc.driver.rtlib orelse system_defaults.rtlib;
if (!mem.eql(u8, rtlib_str, "platform")) {
try tc.driver.comp.addDiagnostic(.{ .tag = .unsupported_rtlib_gcc, .extra = .{ .str = "MSVC" } }, &.{});
try tc.driver.err("unsupported runtime library 'libgcc' for platform 'MSVC'", .{});
}
} else {
try tc.addLibGCC(argv);
@ -481,20 +471,19 @@ pub fn addRuntimeLibs(tc: *const Toolchain, argv: *std.array_list.Managed([]cons
}
if (target.abi.isAndroid() and !tc.driver.static and !tc.driver.static_pie) {
try argv.append("-ldl");
try argv.append(tc.driver.comp.gpa, "-ldl");
}
}
pub fn defineSystemIncludes(tc: *Toolchain) !void {
return switch (tc.inner) {
.uninitialized => unreachable,
.linux => |*linux| linux.defineSystemIncludes(tc),
.unknown => {
if (tc.driver.nostdinc) return;
const comp = tc.driver.comp;
if (!tc.driver.nobuiltininc) {
try comp.addBuiltinIncludeDir(tc.driver.aro_name);
try comp.addBuiltinIncludeDir(tc.driver.aro_name, tc.driver.resource_dir);
}
if (!tc.driver.nostdlibinc) {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

3008
lib/compiler/aro/aro/TypeStore.zig vendored Normal file

File diff suppressed because it is too large Load Diff

View File

@ -2,14 +2,14 @@ const std = @import("std");
const assert = std.debug.assert;
const BigIntConst = std.math.big.int.Const;
const BigIntMutable = std.math.big.int.Mutable;
const backend = @import("../backend.zig");
const Interner = backend.Interner;
const Interner = @import("../backend.zig").Interner;
const BigIntSpace = Interner.Tag.Int.BigIntSpace;
const Compilation = @import("Compilation.zig");
const Type = @import("Type.zig");
const target_util = @import("target.zig");
const annex_g = @import("annex_g.zig");
const Writer = std.Io.Writer;
const Compilation = @import("Compilation.zig");
const target_util = @import("target.zig");
const QualType = @import("TypeStore.zig").QualType;
const Value = @This();
@ -33,11 +33,19 @@ pub fn int(i: anytype, comp: *Compilation) !Value {
}
}
pub fn pointer(r: Interner.Key.Pointer, comp: *Compilation) !Value {
return intern(comp, .{ .pointer = r });
}
pub fn ref(v: Value) Interner.Ref {
std.debug.assert(v.opt_ref != .none);
return @enumFromInt(@intFromEnum(v.opt_ref));
}
pub fn fromRef(r: Interner.Ref) Value {
return .{ .opt_ref = @enumFromInt(@intFromEnum(r)) };
}
pub fn is(v: Value, tag: std.meta.Tag(Interner.Key), comp: *const Compilation) bool {
if (v.opt_ref == .none) return false;
return comp.interner.get(v.ref()) == tag;
@ -68,7 +76,11 @@ test "minUnsignedBits" {
}
};
var comp = Compilation.init(std.testing.allocator, std.fs.cwd());
var arena_state: std.heap.ArenaAllocator = .init(std.testing.allocator);
defer arena_state.deinit();
const arena = arena_state.allocator();
var comp = Compilation.init(std.testing.allocator, arena, undefined, std.fs.cwd());
defer comp.deinit();
const target_query = try std.Target.Query.parse(.{ .arch_os_abi = "x86_64-linux-gnu" });
comp.target = try std.zig.system.resolveTargetQuery(target_query);
@ -103,7 +115,11 @@ test "minSignedBits" {
}
};
var comp = Compilation.init(std.testing.allocator, std.fs.cwd());
var arena_state: std.heap.ArenaAllocator = .init(std.testing.allocator);
defer arena_state.deinit();
const arena = arena_state.allocator();
var comp = Compilation.init(std.testing.allocator, arena, undefined, std.fs.cwd());
defer comp.deinit();
const target_query = try std.Target.Query.parse(.{ .arch_os_abi = "x86_64-linux-gnu" });
comp.target = try std.zig.system.resolveTargetQuery(target_query);
@ -133,24 +149,27 @@ pub const FloatToIntChangeKind = enum {
/// Converts the stored value from a float to an integer.
/// `.none` value remains unchanged.
pub fn floatToInt(v: *Value, dest_ty: Type, comp: *Compilation) !FloatToIntChangeKind {
pub fn floatToInt(v: *Value, dest_ty: QualType, comp: *Compilation) !FloatToIntChangeKind {
if (v.opt_ref == .none) return .none;
const float_val = v.toFloat(f128, comp);
const was_zero = float_val == 0;
if (dest_ty.is(.bool)) {
if (dest_ty.is(comp, .bool)) {
const was_one = float_val == 1.0;
v.* = fromBool(!was_zero);
if (was_zero or was_one) return .none;
return .value_changed;
} else if (dest_ty.isUnsignedInt(comp) and float_val < 0) {
} else if (dest_ty.signedness(comp) == .unsigned and float_val < 0) {
v.* = zero;
return .out_of_range;
} else if (!std.math.isFinite(float_val)) {
v.* = .{};
return .overflow;
}
const signedness = dest_ty.signedness(comp);
const bits: usize = @intCast(dest_ty.bitSizeof(comp).?);
const bits: usize = @intCast(dest_ty.bitSizeof(comp));
var big_int: std.math.big.int.Mutable = .{
.limbs = try comp.gpa.alloc(std.math.big.Limb, @max(
@ -160,6 +179,7 @@ pub fn floatToInt(v: *Value, dest_ty: Type, comp: *Compilation) !FloatToIntChang
.len = undefined,
.positive = undefined,
};
defer comp.gpa.free(big_int.limbs);
const had_fraction = switch (big_int.setFloat(float_val, .trunc)) {
.inexact => true,
.exact => false,
@ -177,11 +197,11 @@ pub fn floatToInt(v: *Value, dest_ty: Type, comp: *Compilation) !FloatToIntChang
/// Converts the stored value from an integer to a float.
/// `.none` value remains unchanged.
pub fn intToFloat(v: *Value, dest_ty: Type, comp: *Compilation) !void {
pub fn intToFloat(v: *Value, dest_ty: QualType, comp: *Compilation) !void {
if (v.opt_ref == .none) return;
if (dest_ty.isComplex()) {
const bits = dest_ty.bitSizeof(comp).?;
if (dest_ty.is(comp, .complex)) {
const bits = dest_ty.bitSizeof(comp);
const cf: Interner.Key.Complex = switch (bits) {
32 => .{ .cf16 = .{ v.toFloat(f16, comp), 0 } },
64 => .{ .cf32 = .{ v.toFloat(f32, comp), 0 } },
@ -193,7 +213,7 @@ pub fn intToFloat(v: *Value, dest_ty: Type, comp: *Compilation) !void {
v.* = try intern(comp, .{ .complex = cf });
return;
}
const bits = dest_ty.bitSizeof(comp).?;
const bits = dest_ty.bitSizeof(comp);
return switch (comp.interner.get(v.ref()).int) {
inline .u64, .i64 => |data| {
const f: Interner.Key.Float = switch (bits) {
@ -232,14 +252,16 @@ pub const IntCastChangeKind = enum {
/// Truncates or extends bits based on type.
/// `.none` value remains unchanged.
pub fn intCast(v: *Value, dest_ty: Type, comp: *Compilation) !IntCastChangeKind {
pub fn intCast(v: *Value, dest_ty: QualType, comp: *Compilation) !IntCastChangeKind {
if (v.opt_ref == .none) return .none;
const key = comp.interner.get(v.ref());
if (key == .pointer or key == .bytes) return .none;
const dest_bits: usize = @intCast(dest_ty.bitSizeof(comp).?);
const dest_bits: usize = @intCast(dest_ty.bitSizeof(comp));
const dest_signed = dest_ty.signedness(comp) == .signed;
var space: BigIntSpace = undefined;
const big = v.toBigInt(&space, comp);
const big = key.toBigInt(&space);
const value_bits = big.bitCountTwosComp();
// if big is negative, then is signed.
@ -269,10 +291,10 @@ pub fn intCast(v: *Value, dest_ty: Type, comp: *Compilation) !IntCastChangeKind
/// Converts the stored value to a float of the specified type
/// `.none` value remains unchanged.
pub fn floatCast(v: *Value, dest_ty: Type, comp: *Compilation) !void {
pub fn floatCast(v: *Value, dest_ty: QualType, comp: *Compilation) !void {
if (v.opt_ref == .none) return;
const bits = dest_ty.bitSizeof(comp).?;
if (dest_ty.isComplex()) {
const bits = dest_ty.bitSizeof(comp);
if (dest_ty.is(comp, .complex)) {
const cf: Interner.Key.Complex = switch (bits) {
32 => .{ .cf16 = .{ v.toFloat(f16, comp), v.imag(f16, comp) } },
64 => .{ .cf32 = .{ v.toFloat(f32, comp), v.imag(f32, comp) } },
@ -370,11 +392,8 @@ fn bigIntToFloat(limbs: []const std.math.big.Limb, positive: bool) f128 {
}
}
pub fn toBigInt(val: Value, space: *BigIntSpace, comp: *const Compilation) BigIntConst {
return switch (comp.interner.get(val.ref()).int) {
inline .u64, .i64 => |x| BigIntMutable.init(&space.limbs, x).toConst(),
.big_int => |b| b,
};
fn toBigInt(val: Value, space: *BigIntSpace, comp: *const Compilation) BigIntConst {
return comp.interner.get(val.ref()).toBigInt(space);
}
pub fn isZero(v: Value, comp: *const Compilation) bool {
@ -398,6 +417,7 @@ pub fn isZero(v: Value, comp: *const Compilation) bool {
inline else => |data| return data[0] == 0.0 and data[1] == 0.0,
},
.bytes => return false,
.pointer => return false,
else => unreachable,
}
}
@ -461,12 +481,19 @@ pub fn toBool(v: Value, comp: *const Compilation) bool {
pub fn toInt(v: Value, comptime T: type, comp: *const Compilation) ?T {
if (v.opt_ref == .none) return null;
if (comp.interner.get(v.ref()) != .int) return null;
const key = comp.interner.get(v.ref());
if (key != .int) return null;
var space: BigIntSpace = undefined;
const big_int = v.toBigInt(&space, comp);
const big_int = key.toBigInt(&space);
return big_int.toInt(T) catch null;
}
pub fn toBytes(v: Value, comp: *const Compilation) []const u8 {
assert(v.opt_ref != .none);
const key = comp.interner.get(v.ref());
return key.bytes;
}
const ComplexOp = enum {
add,
sub,
@ -492,10 +519,11 @@ fn complexAddSub(lhs: Value, rhs: Value, comptime T: type, op: ComplexOp, comp:
};
}
pub fn add(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !bool {
const bits: usize = @intCast(ty.bitSizeof(comp).?);
if (ty.isFloat()) {
if (ty.isComplex()) {
pub fn add(res: *Value, lhs: Value, rhs: Value, qt: QualType, comp: *Compilation) !bool {
const bits: usize = @intCast(qt.bitSizeof(comp));
const scalar_kind = qt.scalarKind(comp);
if (scalar_kind.isFloat()) {
if (scalar_kind == .complex_float) {
res.* = switch (bits) {
32 => try complexAddSub(lhs, rhs, f16, .add, comp),
64 => try complexAddSub(lhs, rhs, f32, .add, comp),
@ -516,29 +544,60 @@ pub fn add(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !b
};
res.* = try intern(comp, .{ .float = f });
return false;
} else {
var lhs_space: BigIntSpace = undefined;
var rhs_space: BigIntSpace = undefined;
const lhs_bigint = lhs.toBigInt(&lhs_space, comp);
const rhs_bigint = rhs.toBigInt(&rhs_space, comp);
const limbs = try comp.gpa.alloc(
std.math.big.Limb,
std.math.big.int.calcTwosCompLimbCount(bits),
);
defer comp.gpa.free(limbs);
var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
const overflowed = result_bigint.addWrap(lhs_bigint, rhs_bigint, ty.signedness(comp), bits);
res.* = try intern(comp, .{ .int = .{ .big_int = result_bigint.toConst() } });
return overflowed;
}
const lhs_key = comp.interner.get(lhs.ref());
const rhs_key = comp.interner.get(rhs.ref());
if (lhs_key == .bytes or rhs_key == .bytes) {
res.* = .{};
return false;
}
if (lhs_key == .pointer or rhs_key == .pointer) {
const rel, const index = if (lhs_key == .pointer)
.{ lhs_key.pointer, rhs }
else
.{ rhs_key.pointer, lhs };
const elem_size = try int(qt.childType(comp).sizeofOrNull(comp) orelse 1, comp);
var total_offset: Value = undefined;
const mul_overflow = try total_offset.mul(elem_size, index, comp.type_store.ptrdiff, comp);
const old_offset = fromRef(rel.offset);
const add_overflow = try total_offset.add(total_offset, old_offset, comp.type_store.ptrdiff, comp);
_ = try total_offset.intCast(comp.type_store.ptrdiff, comp);
res.* = try pointer(.{ .node = rel.node, .offset = total_offset.ref() }, comp);
return mul_overflow or add_overflow;
}
var lhs_space: BigIntSpace = undefined;
var rhs_space: BigIntSpace = undefined;
const lhs_bigint = lhs_key.toBigInt(&lhs_space);
const rhs_bigint = rhs_key.toBigInt(&rhs_space);
const limbs = try comp.gpa.alloc(
std.math.big.Limb,
std.math.big.int.calcTwosCompLimbCount(bits),
);
defer comp.gpa.free(limbs);
var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
const overflowed = result_bigint.addWrap(lhs_bigint, rhs_bigint, qt.signedness(comp), bits);
res.* = try intern(comp, .{ .int = .{ .big_int = result_bigint.toConst() } });
return overflowed;
}
pub fn sub(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !bool {
const bits: usize = @intCast(ty.bitSizeof(comp).?);
if (ty.isFloat()) {
if (ty.isComplex()) {
pub fn negate(res: *Value, val: Value, qt: QualType, comp: *Compilation) !bool {
return res.sub(zero, val, qt, undefined, comp);
}
pub fn decrement(res: *Value, val: Value, qt: QualType, comp: *Compilation) !bool {
return res.sub(val, one, qt, undefined, comp);
}
/// elem_size is only used when subtracting two pointers, so we can scale the result by the size of the element type
pub fn sub(res: *Value, lhs: Value, rhs: Value, qt: QualType, elem_size: u64, comp: *Compilation) !bool {
const bits: usize = @intCast(qt.bitSizeof(comp));
const scalar_kind = qt.scalarKind(comp);
if (scalar_kind.isFloat()) {
if (scalar_kind == .complex_float) {
res.* = switch (bits) {
32 => try complexAddSub(lhs, rhs, f16, .sub, comp),
64 => try complexAddSub(lhs, rhs, f32, .sub, comp),
@ -559,29 +618,61 @@ pub fn sub(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !b
};
res.* = try intern(comp, .{ .float = f });
return false;
} else {
var lhs_space: BigIntSpace = undefined;
var rhs_space: BigIntSpace = undefined;
const lhs_bigint = lhs.toBigInt(&lhs_space, comp);
const rhs_bigint = rhs.toBigInt(&rhs_space, comp);
const limbs = try comp.gpa.alloc(
std.math.big.Limb,
std.math.big.int.calcTwosCompLimbCount(bits),
);
defer comp.gpa.free(limbs);
var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
const overflowed = result_bigint.subWrap(lhs_bigint, rhs_bigint, ty.signedness(comp), bits);
res.* = try intern(comp, .{ .int = .{ .big_int = result_bigint.toConst() } });
return overflowed;
}
const lhs_key = comp.interner.get(lhs.ref());
const rhs_key = comp.interner.get(rhs.ref());
if (lhs_key == .bytes or rhs_key == .bytes) {
res.* = .{};
return false;
}
if (lhs_key == .pointer and rhs_key == .pointer) {
const lhs_pointer = lhs_key.pointer;
const rhs_pointer = rhs_key.pointer;
if (lhs_pointer.node != rhs_pointer.node) {
res.* = .{};
return false;
}
const lhs_offset = fromRef(lhs_pointer.offset);
const rhs_offset = fromRef(rhs_pointer.offset);
const overflowed = try res.sub(lhs_offset, rhs_offset, comp.type_store.ptrdiff, undefined, comp);
const rhs_size = try int(elem_size, comp);
_ = try res.div(res.*, rhs_size, comp.type_store.ptrdiff, comp);
return overflowed;
} else if (lhs_key == .pointer) {
const rel = lhs_key.pointer;
const lhs_size = try int(elem_size, comp);
var total_offset: Value = undefined;
const mul_overflow = try total_offset.mul(lhs_size, rhs, comp.type_store.ptrdiff, comp);
const old_offset = fromRef(rel.offset);
const add_overflow = try total_offset.sub(old_offset, total_offset, comp.type_store.ptrdiff, undefined, comp);
_ = try total_offset.intCast(comp.type_store.ptrdiff, comp);
res.* = try pointer(.{ .node = rel.node, .offset = total_offset.ref() }, comp);
return mul_overflow or add_overflow;
}
var lhs_space: BigIntSpace = undefined;
var rhs_space: BigIntSpace = undefined;
const lhs_bigint = lhs_key.toBigInt(&lhs_space);
const rhs_bigint = rhs_key.toBigInt(&rhs_space);
const limbs = try comp.gpa.alloc(
std.math.big.Limb,
std.math.big.int.calcTwosCompLimbCount(bits),
);
defer comp.gpa.free(limbs);
var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
const overflowed = result_bigint.subWrap(lhs_bigint, rhs_bigint, qt.signedness(comp), bits);
res.* = try intern(comp, .{ .int = .{ .big_int = result_bigint.toConst() } });
return overflowed;
}
pub fn mul(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !bool {
const bits: usize = @intCast(ty.bitSizeof(comp).?);
if (ty.isFloat()) {
if (ty.isComplex()) {
pub fn mul(res: *Value, lhs: Value, rhs: Value, qt: QualType, comp: *Compilation) !bool {
const bits: usize = @intCast(qt.bitSizeof(comp));
const scalar_kind = qt.scalarKind(comp);
if (scalar_kind.isFloat()) {
if (scalar_kind == .complex_float) {
const cf: Interner.Key.Complex = switch (bits) {
32 => .{ .cf16 = annex_g.complexFloatMul(f16, lhs.toFloat(f16, comp), lhs.imag(f16, comp), rhs.toFloat(f16, comp), rhs.imag(f16, comp)) },
64 => .{ .cf32 = annex_g.complexFloatMul(f32, lhs.toFloat(f32, comp), lhs.imag(f32, comp), rhs.toFloat(f32, comp), rhs.imag(f32, comp)) },
@ -624,7 +715,7 @@ pub fn mul(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !b
result_bigint.mul(lhs_bigint, rhs_bigint, limbs_buffer, comp.gpa);
const signedness = ty.signedness(comp);
const signedness = qt.signedness(comp);
const overflowed = !result_bigint.toConst().fitsInTwosComp(signedness, bits);
if (overflowed) {
result_bigint.truncate(result_bigint.toConst(), signedness, bits);
@ -635,10 +726,11 @@ pub fn mul(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !b
}
/// caller guarantees rhs != 0
pub fn div(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !bool {
const bits: usize = @intCast(ty.bitSizeof(comp).?);
if (ty.isFloat()) {
if (ty.isComplex()) {
pub fn div(res: *Value, lhs: Value, rhs: Value, qt: QualType, comp: *Compilation) !bool {
const bits: usize = @intCast(qt.bitSizeof(comp));
const scalar_kind = qt.scalarKind(comp);
if (scalar_kind.isFloat()) {
if (scalar_kind == .complex_float) {
const cf: Interner.Key.Complex = switch (bits) {
32 => .{ .cf16 = annex_g.complexFloatDiv(f16, lhs.toFloat(f16, comp), lhs.imag(f16, comp), rhs.toFloat(f16, comp), rhs.imag(f16, comp)) },
64 => .{ .cf32 = annex_g.complexFloatDiv(f32, lhs.toFloat(f32, comp), lhs.imag(f32, comp), rhs.toFloat(f32, comp), rhs.imag(f32, comp)) },
@ -689,22 +781,21 @@ pub fn div(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !b
result_q.divTrunc(&result_r, lhs_bigint, rhs_bigint, limbs_buffer);
res.* = try intern(comp, .{ .int = .{ .big_int = result_q.toConst() } });
return !result_q.toConst().fitsInTwosComp(ty.signedness(comp), bits);
return !result_q.toConst().fitsInTwosComp(qt.signedness(comp), bits);
}
}
/// caller guarantees rhs != 0
/// caller guarantees lhs != std.math.minInt(T) OR rhs != -1
pub fn rem(lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !Value {
pub fn rem(lhs: Value, rhs: Value, qt: QualType, comp: *Compilation) !Value {
var lhs_space: BigIntSpace = undefined;
var rhs_space: BigIntSpace = undefined;
const lhs_bigint = lhs.toBigInt(&lhs_space, comp);
const rhs_bigint = rhs.toBigInt(&rhs_space, comp);
const signedness = ty.signedness(comp);
if (signedness == .signed) {
if (qt.signedness(comp) == .signed) {
var spaces: [2]BigIntSpace = undefined;
const min_val = try Value.minInt(ty, comp);
const min_val = try Value.minInt(qt, comp);
const negative = BigIntMutable.init(&spaces[0].limbs, -1).toConst();
const big_one = BigIntMutable.init(&spaces[1].limbs, 1).toConst();
if (lhs.compare(.eq, min_val, comp) and rhs_bigint.eql(negative)) {
@ -712,9 +803,9 @@ pub fn rem(lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !Value {
} else if (rhs_bigint.order(big_one).compare(.lt)) {
// lhs - @divTrunc(lhs, rhs) * rhs
var tmp: Value = undefined;
_ = try tmp.div(lhs, rhs, ty, comp);
_ = try tmp.mul(tmp, rhs, ty, comp);
_ = try tmp.sub(lhs, tmp, ty, comp);
_ = try tmp.div(lhs, rhs, qt, comp);
_ = try tmp.mul(tmp, rhs, qt, comp);
_ = try tmp.sub(lhs, tmp, qt, undefined, comp);
return tmp;
}
}
@ -801,8 +892,8 @@ pub fn bitAnd(lhs: Value, rhs: Value, comp: *Compilation) !Value {
return intern(comp, .{ .int = .{ .big_int = result_bigint.toConst() } });
}
pub fn bitNot(val: Value, ty: Type, comp: *Compilation) !Value {
const bits: usize = @intCast(ty.bitSizeof(comp).?);
pub fn bitNot(val: Value, qt: QualType, comp: *Compilation) !Value {
const bits: usize = @intCast(qt.bitSizeof(comp));
var val_space: Value.BigIntSpace = undefined;
const val_bigint = val.toBigInt(&val_space, comp);
@ -813,21 +904,21 @@ pub fn bitNot(val: Value, ty: Type, comp: *Compilation) !Value {
defer comp.gpa.free(limbs);
var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
result_bigint.bitNotWrap(val_bigint, ty.signedness(comp), bits);
result_bigint.bitNotWrap(val_bigint, qt.signedness(comp), bits);
return intern(comp, .{ .int = .{ .big_int = result_bigint.toConst() } });
}
pub fn shl(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !bool {
pub fn shl(res: *Value, lhs: Value, rhs: Value, qt: QualType, comp: *Compilation) !bool {
var lhs_space: Value.BigIntSpace = undefined;
const lhs_bigint = lhs.toBigInt(&lhs_space, comp);
const shift = rhs.toInt(usize, comp) orelse std.math.maxInt(usize);
const bits: usize = @intCast(ty.bitSizeof(comp).?);
const bits: usize = @intCast(qt.bitSizeof(comp));
if (shift > bits) {
if (lhs_bigint.positive) {
res.* = try Value.maxInt(ty, comp);
res.* = try Value.maxInt(qt, comp);
} else {
res.* = try Value.minInt(ty, comp);
res.* = try Value.minInt(qt, comp);
}
return true;
}
@ -840,7 +931,7 @@ pub fn shl(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !b
var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
result_bigint.shiftLeft(lhs_bigint, shift);
const signedness = ty.signedness(comp);
const signedness = qt.signedness(comp);
const overflowed = !result_bigint.toConst().fitsInTwosComp(signedness, bits);
if (overflowed) {
result_bigint.truncate(result_bigint.toConst(), signedness, bits);
@ -849,7 +940,7 @@ pub fn shl(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !b
return overflowed;
}
pub fn shr(lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !Value {
pub fn shr(lhs: Value, rhs: Value, qt: QualType, comp: *Compilation) !Value {
var lhs_space: Value.BigIntSpace = undefined;
const lhs_bigint = lhs.toBigInt(&lhs_space, comp);
const shift = rhs.toInt(usize, comp) orelse return zero;
@ -865,7 +956,7 @@ pub fn shr(lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !Value {
}
}
const bits: usize = @intCast(ty.bitSizeof(comp).?);
const bits: usize = @intCast(qt.bitSizeof(comp));
const limbs = try comp.gpa.alloc(
std.math.big.Limb,
std.math.big.int.calcTwosCompLimbCount(bits),
@ -877,8 +968,8 @@ pub fn shr(lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !Value {
return intern(comp, .{ .int = .{ .big_int = result_bigint.toConst() } });
}
pub fn complexConj(val: Value, ty: Type, comp: *Compilation) !Value {
const bits = ty.bitSizeof(comp).?;
pub fn complexConj(val: Value, qt: QualType, comp: *Compilation) !Value {
const bits = qt.bitSizeof(comp);
const cf: Interner.Key.Complex = switch (bits) {
32 => .{ .cf16 = .{ val.toFloat(f16, comp), -val.imag(f16, comp) } },
64 => .{ .cf32 = .{ val.toFloat(f32, comp), -val.imag(f32, comp) } },
@ -890,12 +981,17 @@ pub fn complexConj(val: Value, ty: Type, comp: *Compilation) !Value {
return intern(comp, .{ .complex = cf });
}
pub fn compare(lhs: Value, op: std.math.CompareOperator, rhs: Value, comp: *const Compilation) bool {
fn shallowCompare(lhs: Value, op: std.math.CompareOperator, rhs: Value) ?bool {
if (op == .eq) {
return lhs.opt_ref == rhs.opt_ref;
} else if (lhs.opt_ref == rhs.opt_ref) {
return std.math.Order.eq.compare(op);
}
return null;
}
pub fn compare(lhs: Value, op: std.math.CompareOperator, rhs: Value, comp: *const Compilation) bool {
if (lhs.shallowCompare(op, rhs)) |val| return val;
const lhs_key = comp.interner.get(lhs.ref());
const rhs_key = comp.interner.get(rhs.ref());
@ -918,10 +1014,33 @@ pub fn compare(lhs: Value, op: std.math.CompareOperator, rhs: Value, comp: *cons
return lhs_bigint.order(rhs_bigint).compare(op);
}
fn twosCompIntLimit(limit: std.math.big.int.TwosCompIntLimit, ty: Type, comp: *Compilation) !Value {
const signedness = ty.signedness(comp);
/// Returns null for values that cannot be compared at compile time (e.g. `&x < &y`) for globals `x` and `y`.
pub fn comparePointers(lhs: Value, op: std.math.CompareOperator, rhs: Value, comp: *const Compilation) ?bool {
if (lhs.shallowCompare(op, rhs)) |val| return val;
const lhs_key = comp.interner.get(lhs.ref());
const rhs_key = comp.interner.get(rhs.ref());
if (lhs_key == .pointer and rhs_key == .pointer) {
const lhs_pointer = lhs_key.pointer;
const rhs_pointer = rhs_key.pointer;
switch (op) {
.eq => if (lhs_pointer.node != rhs_pointer.node) return false,
.neq => if (lhs_pointer.node != rhs_pointer.node) return true,
else => if (lhs_pointer.node != rhs_pointer.node) return null,
}
const lhs_offset = fromRef(lhs_pointer.offset);
const rhs_offset = fromRef(rhs_pointer.offset);
return lhs_offset.compare(op, rhs_offset, comp);
}
return null;
}
fn twosCompIntLimit(limit: std.math.big.int.TwosCompIntLimit, qt: QualType, comp: *Compilation) !Value {
const signedness = qt.signedness(comp);
if (limit == .min and signedness == .unsigned) return Value.zero;
const mag_bits: usize = @intCast(ty.bitSizeof(comp).?);
const mag_bits: usize = @intCast(qt.bitSizeof(comp));
switch (mag_bits) {
inline 8, 16, 32, 64 => |bits| {
if (limit == .min) return Value.int(@as(i64, std.math.minInt(std.meta.Int(.signed, bits))), comp);
@ -946,44 +1065,63 @@ fn twosCompIntLimit(limit: std.math.big.int.TwosCompIntLimit, ty: Type, comp: *C
return Value.intern(comp, .{ .int = .{ .big_int = result_bigint.toConst() } });
}
pub fn minInt(ty: Type, comp: *Compilation) !Value {
return twosCompIntLimit(.min, ty, comp);
pub fn minInt(qt: QualType, comp: *Compilation) !Value {
return twosCompIntLimit(.min, qt, comp);
}
pub fn maxInt(ty: Type, comp: *Compilation) !Value {
return twosCompIntLimit(.max, ty, comp);
pub fn maxInt(qt: QualType, comp: *Compilation) !Value {
return twosCompIntLimit(.max, qt, comp);
}
pub fn print(v: Value, ty: Type, comp: *const Compilation, w: *Writer) Writer.Error!void {
if (ty.is(.bool)) {
return w.writeAll(if (v.isZero(comp)) "false" else "true");
const NestedPrint = union(enum) {
pointer: struct {
node: u32,
offset: Value,
},
};
pub fn printPointer(offset: Value, base: []const u8, comp: *const Compilation, w: *std.Io.Writer) std.Io.Writer.Error!void {
try w.writeByte('&');
try w.writeAll(base);
if (!offset.isZero(comp)) {
const maybe_nested = try offset.print(comp.type_store.ptrdiff, comp, w);
std.debug.assert(maybe_nested == null);
}
}
pub fn print(v: Value, qt: QualType, comp: *const Compilation, w: *std.Io.Writer) std.Io.Writer.Error!?NestedPrint {
if (qt.is(comp, .bool)) {
try w.writeAll(if (v.isZero(comp)) "false" else "true");
return null;
}
const key = comp.interner.get(v.ref());
switch (key) {
.null => return w.writeAll("nullptr_t"),
.null => try w.writeAll("nullptr_t"),
.int => |repr| switch (repr) {
inline .u64, .i64, .big_int => |x| return w.print("{d}", .{x}),
inline else => |x| try w.print("{d}", .{x}),
},
.float => |repr| switch (repr) {
.f16 => |x| return w.print("{d}", .{@round(@as(f64, @floatCast(x)) * 1000) / 1000}),
.f32 => |x| return w.print("{d}", .{@round(@as(f64, @floatCast(x)) * 1000000) / 1000000}),
inline else => |x| return w.print("{d}", .{@as(f64, @floatCast(x))}),
.f16 => |x| try w.print("{d}", .{@round(@as(f64, @floatCast(x)) * 1000) / 1000}),
.f32 => |x| try w.print("{d}", .{@round(@as(f64, @floatCast(x)) * 1000000) / 1000000}),
inline else => |x| try w.print("{d}", .{@as(f64, @floatCast(x))}),
},
.bytes => |b| return printString(b, ty, comp, w),
.bytes => |b| try printString(b, qt, comp, w),
.complex => |repr| switch (repr) {
.cf32 => |components| return w.print("{d} + {d}i", .{ @round(@as(f64, @floatCast(components[0])) * 1000000) / 1000000, @round(@as(f64, @floatCast(components[1])) * 1000000) / 1000000 }),
inline else => |components| return w.print("{d} + {d}i", .{ @as(f64, @floatCast(components[0])), @as(f64, @floatCast(components[1])) }),
.cf32 => |components| try w.print("{d} + {d}i", .{ @round(@as(f64, @floatCast(components[0])) * 1000000) / 1000000, @round(@as(f64, @floatCast(components[1])) * 1000000) / 1000000 }),
inline else => |components| try w.print("{d} + {d}i", .{ @as(f64, @floatCast(components[0])), @as(f64, @floatCast(components[1])) }),
},
.pointer => |ptr| return .{ .pointer = .{ .node = ptr.node, .offset = fromRef(ptr.offset) } },
else => unreachable, // not a value
}
return null;
}
pub fn printString(bytes: []const u8, ty: Type, comp: *const Compilation, w: *Writer) Writer.Error!void {
const size: Compilation.CharUnitSize = @enumFromInt(ty.elemType().sizeof(comp).?);
pub fn printString(bytes: []const u8, qt: QualType, comp: *const Compilation, w: *std.Io.Writer) std.Io.Writer.Error!void {
const size: Compilation.CharUnitSize = @enumFromInt(qt.childType(comp).sizeof(comp));
const without_null = bytes[0 .. bytes.len - @intFromEnum(size)];
try w.writeByte('"');
switch (size) {
.@"1" => try w.print("{f}", .{std.zig.fmtString(without_null)}),
.@"1" => try std.zig.stringEscape(without_null, w),
.@"2" => {
var items: [2]u16 = undefined;
var i: usize = 0;

View File

@ -442,48 +442,48 @@ pub fn isInvisible(codepoint: u21) bool {
}
/// Checks for identifier characters which resemble non-identifier characters
pub fn homoglyph(codepoint: u21) ?u21 {
pub fn homoglyph(codepoint: u21) ?[]const u8 {
assert(codepoint > 0x7F);
return switch (codepoint) {
0x01c3 => '!', // LATIN LETTER RETROFLEX CLICK
0x037e => ';', // GREEK QUESTION MARK
0x2212 => '-', // MINUS SIGN
0x2215 => '/', // DIVISION SLASH
0x2216 => '\\', // SET MINUS
0x2217 => '*', // ASTERISK OPERATOR
0x2223 => '|', // DIVIDES
0x2227 => '^', // LOGICAL AND
0x2236 => ':', // RATIO
0x223c => '~', // TILDE OPERATOR
0xa789 => ':', // MODIFIER LETTER COLON
0xff01 => '!', // FULLWIDTH EXCLAMATION MARK
0xff03 => '#', // FULLWIDTH NUMBER SIGN
0xff04 => '$', // FULLWIDTH DOLLAR SIGN
0xff05 => '%', // FULLWIDTH PERCENT SIGN
0xff06 => '&', // FULLWIDTH AMPERSAND
0xff08 => '(', // FULLWIDTH LEFT PARENTHESIS
0xff09 => ')', // FULLWIDTH RIGHT PARENTHESIS
0xff0a => '*', // FULLWIDTH ASTERISK
0xff0b => '+', // FULLWIDTH ASTERISK
0xff0c => ',', // FULLWIDTH COMMA
0xff0d => '-', // FULLWIDTH HYPHEN-MINUS
0xff0e => '.', // FULLWIDTH FULL STOP
0xff0f => '/', // FULLWIDTH SOLIDUS
0xff1a => ':', // FULLWIDTH COLON
0xff1b => ';', // FULLWIDTH SEMICOLON
0xff1c => '<', // FULLWIDTH LESS-THAN SIGN
0xff1d => '=', // FULLWIDTH EQUALS SIGN
0xff1e => '>', // FULLWIDTH GREATER-THAN SIGN
0xff1f => '?', // FULLWIDTH QUESTION MARK
0xff20 => '@', // FULLWIDTH COMMERCIAL AT
0xff3b => '[', // FULLWIDTH LEFT SQUARE BRACKET
0xff3c => '\\', // FULLWIDTH REVERSE SOLIDUS
0xff3d => ']', // FULLWIDTH RIGHT SQUARE BRACKET
0xff3e => '^', // FULLWIDTH CIRCUMFLEX ACCENT
0xff5b => '{', // FULLWIDTH LEFT CURLY BRACKET
0xff5c => '|', // FULLWIDTH VERTICAL LINE
0xff5d => '}', // FULLWIDTH RIGHT CURLY BRACKET
0xff5e => '~', // FULLWIDTH TILDE
0x01c3 => "!", // LATIN LETTER RETROFLEX CLICK
0x037e => ";", // GREEK QUESTION MARK
0x2212 => "-", // MINUS SIGN
0x2215 => "/", // DIVISION SLASH
0x2216 => "\\", // SET MINUS
0x2217 => "*", // ASTERISK OPERATOR
0x2223 => "|", // DIVIDES
0x2227 => "^", // LOGICAL AND
0x2236 => ":", // RATIO
0x223c => "~", // TILDE OPERATOR
0xa789 => ":", // MODIFIER LETTER COLON
0xff01 => "!", // FULLWIDTH EXCLAMATION MARK
0xff03 => "#", // FULLWIDTH NUMBER SIGN
0xff04 => "$", // FULLWIDTH DOLLAR SIGN
0xff05 => "%", // FULLWIDTH PERCENT SIGN
0xff06 => "&", // FULLWIDTH AMPERSAND
0xff08 => "(", // FULLWIDTH LEFT PARENTHESIS
0xff09 => ")", // FULLWIDTH RIGHT PARENTHESIS
0xff0a => "*", // FULLWIDTH ASTERISK
0xff0b => "+", // FULLWIDTH ASTERISK
0xff0c => ",", // FULLWIDTH COMMA
0xff0d => "-", // FULLWIDTH HYPHEN-MINUS
0xff0e => ".", // FULLWIDTH FULL STOP
0xff0f => "/", // FULLWIDTH SOLIDUS
0xff1a => ":", // FULLWIDTH COLON
0xff1b => ";", // FULLWIDTH SEMICOLON
0xff1c => "<", // FULLWIDTH LESS-THAN SIGN
0xff1d => "=", // FULLWIDTH EQUALS SIGN
0xff1e => ">", // FULLWIDTH GREATER-THAN SIGN
0xff1f => "?", // FULLWIDTH QUESTION MARK
0xff20 => "@", // FULLWIDTH COMMERCIAL AT
0xff3b => "[", // FULLWIDTH LEFT SQUARE BRACKET
0xff3c => "\\", // FULLWIDTH REVERSE SOLIDUS
0xff3d => "]", // FULLWIDTH RIGHT SQUARE BRACKET
0xff3e => "^", // FULLWIDTH CIRCUMFLEX ACCENT
0xff5b => "{", // FULLWIDTH LEFT CURLY BRACKET
0xff5c => "|", // FULLWIDTH VERTICAL LINE
0xff5d => "}", // FULLWIDTH RIGHT CURLY BRACKET
0xff5e => "~", // FULLWIDTH TILDE
else => null,
};
}

View File

@ -57,13 +57,13 @@ pub fn hasExtension(comp: *Compilation, ext: []const u8) bool {
// C11 features
.c_alignas = true,
.c_alignof = true,
.c_atomic = false, // TODO
.c_atomic = true,
.c_generic_selections = true,
.c_static_assert = true,
.c_thread_local = target_util.isTlsSupported(comp.target),
// misc
.overloadable_unmarked = false, // TODO
.statement_attributes_with_gnu_syntax = false, // TODO
.statement_attributes_with_gnu_syntax = true,
.gnu_asm = true,
.gnu_asm_goto_with_outputs = true,
.matrix_types = false, // TODO

View File

@ -1,10 +1,11 @@
const std = @import("std");
const mem = std.mem;
const Compilation = @import("../Compilation.zig");
const Pragma = @import("../Pragma.zig");
const Diagnostics = @import("../Diagnostics.zig");
const Preprocessor = @import("../Preprocessor.zig");
const Parser = @import("../Parser.zig");
const Pragma = @import("../Pragma.zig");
const Preprocessor = @import("../Preprocessor.zig");
const TokenIndex = @import("../Tree.zig").TokenIndex;
const GCC = @This();
@ -18,8 +19,8 @@ pragma: Pragma = .{
.parserHandler = parserHandler,
.preserveTokens = preserveTokens,
},
original_options: Diagnostics.Options = .{},
options_stack: std.ArrayListUnmanaged(Diagnostics.Options) = .empty,
original_state: Diagnostics.State = .{},
state_stack: std.ArrayList(Diagnostics.State) = .empty,
const Directive = enum {
warning,
@ -38,19 +39,19 @@ const Directive = enum {
fn beforePreprocess(pragma: *Pragma, comp: *Compilation) void {
var self: *GCC = @fieldParentPtr("pragma", pragma);
self.original_options = comp.diagnostics.options;
self.original_state = comp.diagnostics.state;
}
fn beforeParse(pragma: *Pragma, comp: *Compilation) void {
var self: *GCC = @fieldParentPtr("pragma", pragma);
comp.diagnostics.options = self.original_options;
self.options_stack.items.len = 0;
comp.diagnostics.state = self.original_state;
self.state_stack.items.len = 0;
}
fn afterParse(pragma: *Pragma, comp: *Compilation) void {
var self: *GCC = @fieldParentPtr("pragma", pragma);
comp.diagnostics.options = self.original_options;
self.options_stack.items.len = 0;
comp.diagnostics.state = self.original_state;
self.state_stack.items.len = 0;
}
pub fn init(allocator: mem.Allocator) !*Pragma {
@ -61,7 +62,7 @@ pub fn init(allocator: mem.Allocator) !*Pragma {
fn deinit(pragma: *Pragma, comp: *Compilation) void {
var self: *GCC = @fieldParentPtr("pragma", pragma);
self.options_stack.deinit(comp.gpa);
self.state_stack.deinit(comp.gpa);
comp.gpa.destroy(self);
}
@ -76,23 +77,14 @@ fn diagnosticHandler(self: *GCC, pp: *Preprocessor, start_idx: TokenIndex) Pragm
.ignored, .warning, .@"error", .fatal => {
const str = Pragma.pasteTokens(pp, start_idx + 1) catch |err| switch (err) {
error.ExpectedStringLiteral => {
return pp.comp.addDiagnostic(.{
.tag = .pragma_requires_string_literal,
.loc = diagnostic_tok.loc,
.extra = .{ .str = "GCC diagnostic" },
}, pp.expansionSlice(start_idx));
return Pragma.err(pp, start_idx, .pragma_requires_string_literal, .{"GCC diagnostic"});
},
else => |e| return e,
};
if (!mem.startsWith(u8, str, "-W")) {
const next = pp.tokens.get(start_idx + 1);
return pp.comp.addDiagnostic(.{
.tag = .malformed_warning_check,
.loc = next.loc,
.extra = .{ .str = "GCC diagnostic" },
}, pp.expansionSlice(start_idx + 1));
return Pragma.err(pp, start_idx + 1, .malformed_warning_check, .{"GCC diagnostic"});
}
const new_kind: Diagnostics.Kind = switch (diagnostic) {
const new_kind: Diagnostics.Message.Kind = switch (diagnostic) {
.ignored => .off,
.warning => .warning,
.@"error" => .@"error",
@ -100,10 +92,10 @@ fn diagnosticHandler(self: *GCC, pp: *Preprocessor, start_idx: TokenIndex) Pragm
else => unreachable,
};
try pp.comp.diagnostics.set(str[2..], new_kind);
try pp.diagnostics.set(str[2..], new_kind);
},
.push => try self.options_stack.append(pp.comp.gpa, pp.comp.diagnostics.options),
.pop => pp.comp.diagnostics.options = self.options_stack.pop() orelse self.original_options,
.push => try self.state_stack.append(pp.comp.gpa, pp.diagnostics.state),
.pop => pp.diagnostics.state = self.state_stack.pop() orelse self.original_state,
}
}
@ -112,38 +104,24 @@ fn preprocessorHandler(pragma: *Pragma, pp: *Preprocessor, start_idx: TokenIndex
const directive_tok = pp.tokens.get(start_idx + 1);
if (directive_tok.id == .nl) return;
const gcc_pragma = std.meta.stringToEnum(Directive, pp.expandedSlice(directive_tok)) orelse
return pp.comp.addDiagnostic(.{
.tag = .unknown_gcc_pragma,
.loc = directive_tok.loc,
}, pp.expansionSlice(start_idx + 1));
const gcc_pragma = std.meta.stringToEnum(Directive, pp.expandedSlice(directive_tok)) orelse {
return Pragma.err(pp, start_idx + 1, .unknown_gcc_pragma, .{});
};
switch (gcc_pragma) {
.warning, .@"error" => {
const text = Pragma.pasteTokens(pp, start_idx + 2) catch |err| switch (err) {
error.ExpectedStringLiteral => {
return pp.comp.addDiagnostic(.{
.tag = .pragma_requires_string_literal,
.loc = directive_tok.loc,
.extra = .{ .str = @tagName(gcc_pragma) },
}, pp.expansionSlice(start_idx + 1));
return Pragma.err(pp, start_idx + 1, .pragma_requires_string_literal, .{@tagName(gcc_pragma)});
},
else => |e| return e,
};
const extra = Diagnostics.Message.Extra{ .str = try pp.comp.diagnostics.arena.allocator().dupe(u8, text) };
const diagnostic_tag: Diagnostics.Tag = if (gcc_pragma == .warning) .pragma_warning_message else .pragma_error_message;
return pp.comp.addDiagnostic(
.{ .tag = diagnostic_tag, .loc = directive_tok.loc, .extra = extra },
pp.expansionSlice(start_idx + 1),
);
return Pragma.err(pp, start_idx + 1, if (gcc_pragma == .warning) .pragma_warning_message else .pragma_error_message, .{text});
},
.diagnostic => return self.diagnosticHandler(pp, start_idx + 2) catch |err| switch (err) {
error.UnknownPragma => {
const tok = pp.tokens.get(start_idx + 2);
return pp.comp.addDiagnostic(.{
.tag = .unknown_gcc_pragma_directive,
.loc = tok.loc,
}, pp.expansionSlice(start_idx + 2));
return Pragma.err(pp, start_idx + 2, .unknown_gcc_pragma_directive, .{});
},
else => |e| return e,
},
@ -154,19 +132,13 @@ fn preprocessorHandler(pragma: *Pragma, pp: *Preprocessor, start_idx: TokenIndex
if (tok.id == .nl) break;
if (!tok.id.isMacroIdentifier()) {
return pp.comp.addDiagnostic(.{
.tag = .pragma_poison_identifier,
.loc = tok.loc,
}, pp.expansionSlice(start_idx + i));
return Pragma.err(pp, start_idx + i, .pragma_poison_identifier, .{});
}
const str = pp.expandedSlice(tok);
if (pp.defines.get(str) != null) {
try pp.comp.addDiagnostic(.{
.tag = .pragma_poison_macro,
.loc = tok.loc,
}, pp.expansionSlice(start_idx + i));
try Pragma.err(pp, start_idx + i, .pragma_poison_macro, .{});
}
try pp.poisoned_identifiers.put(str, {});
try pp.poisoned_identifiers.put(pp.comp.gpa, str, {});
}
return;
},

View File

@ -1,12 +1,13 @@
const std = @import("std");
const mem = std.mem;
const Compilation = @import("../Compilation.zig");
const Pragma = @import("../Pragma.zig");
const Diagnostics = @import("../Diagnostics.zig");
const Preprocessor = @import("../Preprocessor.zig");
const Parser = @import("../Parser.zig");
const TokenIndex = @import("../Tree.zig").TokenIndex;
const Pragma = @import("../Pragma.zig");
const Preprocessor = @import("../Preprocessor.zig");
const Source = @import("../Source.zig");
const TokenIndex = @import("../Tree.zig").TokenIndex;
const Message = @This();
@ -27,24 +28,32 @@ fn deinit(pragma: *Pragma, comp: *Compilation) void {
}
fn preprocessorHandler(_: *Pragma, pp: *Preprocessor, start_idx: TokenIndex) Pragma.Error!void {
const message_tok = pp.tokens.get(start_idx);
const message_expansion_locs = pp.expansionSlice(start_idx);
const str = Pragma.pasteTokens(pp, start_idx + 1) catch |err| switch (err) {
error.ExpectedStringLiteral => {
return pp.comp.addDiagnostic(.{
.tag = .pragma_requires_string_literal,
.loc = message_tok.loc,
.extra = .{ .str = "message" },
}, message_expansion_locs);
return Pragma.err(pp, start_idx, .pragma_requires_string_literal, .{"message"});
},
else => |e| return e,
};
const message_tok = pp.tokens.get(start_idx);
const message_expansion_locs = pp.expansionSlice(start_idx);
const loc = if (message_expansion_locs.len != 0)
message_expansion_locs[message_expansion_locs.len - 1]
else
message_tok.loc;
const extra = Diagnostics.Message.Extra{ .str = try pp.comp.diagnostics.arena.allocator().dupe(u8, str) };
return pp.comp.addDiagnostic(.{ .tag = .pragma_message, .loc = loc, .extra = extra }, &.{});
const diagnostic: Pragma.Diagnostic = .pragma_message;
var sf = std.heap.stackFallback(1024, pp.comp.gpa);
var allocating: std.Io.Writer.Allocating = .init(sf.get());
defer allocating.deinit();
Diagnostics.formatArgs(&allocating.writer, diagnostic.fmt, .{str}) catch return error.OutOfMemory;
try pp.diagnostics.add(.{
.text = allocating.written(),
.kind = diagnostic.kind,
.opt = diagnostic.opt,
.location = loc.expand(pp.comp),
});
}

View File

@ -1,12 +1,13 @@
const std = @import("std");
const mem = std.mem;
const Compilation = @import("../Compilation.zig");
const Pragma = @import("../Pragma.zig");
const Diagnostics = @import("../Diagnostics.zig");
const Preprocessor = @import("../Preprocessor.zig");
const Parser = @import("../Parser.zig");
const TokenIndex = @import("../Tree.zig").TokenIndex;
const Pragma = @import("../Pragma.zig");
const Preprocessor = @import("../Preprocessor.zig");
const Source = @import("../Source.zig");
const TokenIndex = @import("../Tree.zig").TokenIndex;
const Once = @This();
@ -14,15 +15,14 @@ pragma: Pragma = .{
.afterParse = afterParse,
.deinit = deinit,
.preprocessorHandler = preprocessorHandler,
.preserveTokens = preserveTokens,
},
pragma_once: std.AutoHashMap(Source.Id, void),
pragma_once: std.AutoHashMapUnmanaged(Source.Id, void) = .empty,
preprocess_count: u32 = 0,
pub fn init(allocator: mem.Allocator) !*Pragma {
var once = try allocator.create(Once);
once.* = .{
.pragma_once = std.AutoHashMap(Source.Id, void).init(allocator),
};
once.* = .{};
return &once.pragma;
}
@ -33,8 +33,9 @@ fn afterParse(pragma: *Pragma, _: *Compilation) void {
fn deinit(pragma: *Pragma, comp: *Compilation) void {
var self: *Once = @fieldParentPtr("pragma", pragma);
self.pragma_once.deinit();
self.pragma_once.deinit(comp.gpa);
comp.gpa.destroy(self);
pragma.* = undefined;
}
fn preprocessorHandler(pragma: *Pragma, pp: *Preprocessor, start_idx: TokenIndex) Pragma.Error!void {
@ -42,15 +43,22 @@ fn preprocessorHandler(pragma: *Pragma, pp: *Preprocessor, start_idx: TokenIndex
const name_tok = pp.tokens.get(start_idx);
const next = pp.tokens.get(start_idx + 1);
if (next.id != .nl) {
try pp.comp.addDiagnostic(.{
.tag = .extra_tokens_directive_end,
.loc = name_tok.loc,
}, pp.expansionSlice(start_idx + 1));
const diagnostic: Preprocessor.Diagnostic = .extra_tokens_directive_end;
return pp.diagnostics.addWithLocation(pp.comp, .{
.text = diagnostic.fmt,
.kind = diagnostic.kind,
.opt = diagnostic.opt,
.location = name_tok.loc.expand(pp.comp),
}, pp.expansionSlice(start_idx + 1), true);
}
const seen = self.preprocess_count == pp.preprocess_count;
const prev = try self.pragma_once.fetchPut(name_tok.loc.id, {});
const prev = try self.pragma_once.fetchPut(pp.comp.gpa, name_tok.loc.id, {});
if (prev != null and !seen) {
return error.StopPreprocessing;
}
self.preprocess_count = pp.preprocess_count;
}
fn preserveTokens(_: *Pragma, _: *Preprocessor, _: TokenIndex) bool {
return false;
}

View File

@ -1,10 +1,11 @@
const std = @import("std");
const mem = std.mem;
const Compilation = @import("../Compilation.zig");
const Pragma = @import("../Pragma.zig");
const Diagnostics = @import("../Diagnostics.zig");
const Preprocessor = @import("../Preprocessor.zig");
const Parser = @import("../Parser.zig");
const Pragma = @import("../Pragma.zig");
const Preprocessor = @import("../Preprocessor.zig");
const Tree = @import("../Tree.zig");
const TokenIndex = Tree.TokenIndex;
@ -13,9 +14,8 @@ const Pack = @This();
pragma: Pragma = .{
.deinit = deinit,
.parserHandler = parserHandler,
.preserveTokens = preserveTokens,
},
stack: std.ArrayListUnmanaged(struct { label: []const u8, val: u8 }) = .empty,
stack: std.ArrayList(struct { label: []const u8, val: u8 }) = .empty,
pub fn init(allocator: mem.Allocator) !*Pragma {
var pack = try allocator.create(Pack);
@ -34,10 +34,7 @@ fn parserHandler(pragma: *Pragma, p: *Parser, start_idx: TokenIndex) Compilation
var idx = start_idx + 1;
const l_paren = p.pp.tokens.get(idx);
if (l_paren.id != .l_paren) {
return p.comp.addDiagnostic(.{
.tag = .pragma_pack_lparen,
.loc = l_paren.loc,
}, p.pp.expansionSlice(idx));
return Pragma.err(p.pp, idx, .pragma_pack_lparen, .{});
}
idx += 1;
@ -54,11 +51,11 @@ fn parserHandler(pragma: *Pragma, p: *Parser, start_idx: TokenIndex) Compilation
pop,
};
const action = std.meta.stringToEnum(Action, p.tokSlice(arg)) orelse {
return p.errTok(.pragma_pack_unknown_action, arg);
return Pragma.err(p.pp, arg, .pragma_pack_unknown_action, .{});
};
switch (action) {
.show => {
try p.errExtra(.pragma_pack_show, arg, .{ .unsigned = p.pragma_pack orelse 8 });
return Pragma.err(p.pp, arg, .pragma_pack_show, .{p.pragma_pack orelse 8});
},
.push, .pop => {
var new_val: ?u8 = null;
@ -75,21 +72,23 @@ fn parserHandler(pragma: *Pragma, p: *Parser, start_idx: TokenIndex) Compilation
idx += 1;
const int = idx;
idx += 1;
if (tok_ids[int] != .pp_num) return p.errTok(.pragma_pack_int_ident, int);
if (tok_ids[int] != .pp_num) {
return Pragma.err(p.pp, int, .pragma_pack_int_ident, .{});
}
new_val = (try packInt(p, int)) orelse return;
}
},
else => return p.errTok(.pragma_pack_int_ident, next),
else => return Pragma.err(p.pp, next, .pragma_pack_int_ident, .{}),
}
}
if (action == .push) {
try pack.stack.append(p.gpa, .{ .label = label orelse "", .val = p.pragma_pack orelse 8 });
try pack.stack.append(p.comp.gpa, .{ .label = label orelse "", .val = p.pragma_pack orelse 8 });
} else {
pack.pop(p, label);
if (new_val != null) {
try p.errTok(.pragma_pack_undefined_pop, arg);
try Pragma.err(p.pp, arg, .pragma_pack_undefined_pop, .{});
} else if (pack.stack.items.len == 0) {
try p.errTok(.pragma_pack_empty_stack, arg);
try Pragma.err(p.pp, arg, .pragma_pack_empty_stack, .{});
}
}
if (new_val) |some| {
@ -115,14 +114,14 @@ fn parserHandler(pragma: *Pragma, p: *Parser, start_idx: TokenIndex) Compilation
}
if (tok_ids[idx] != .r_paren) {
return p.errTok(.pragma_pack_rparen, idx);
return Pragma.err(p.pp, idx, .pragma_pack_rparen, .{});
}
}
fn packInt(p: *Parser, tok_i: TokenIndex) Compilation.Error!?u8 {
const res = p.parseNumberToken(tok_i) catch |err| switch (err) {
error.ParsingFailed => {
try p.errTok(.pragma_pack_int, tok_i);
try Pragma.err(p.pp, tok_i, .pragma_pack_int, .{});
return null;
},
else => |e| return e,
@ -131,7 +130,7 @@ fn packInt(p: *Parser, tok_i: TokenIndex) Compilation.Error!?u8 {
switch (int) {
1, 2, 4, 8, 16 => return @intCast(int),
else => {
try p.errTok(.pragma_pack_int, tok_i);
try Pragma.err(p.pp, tok_i, .pragma_pack_int, .{});
return null;
},
}
@ -156,9 +155,3 @@ fn pop(pack: *Pack, p: *Parser, maybe_label: ?[]const u8) void {
p.pragma_pack = prev.val;
}
}
fn preserveTokens(_: *Pragma, pp: *Preprocessor, start_idx: TokenIndex) bool {
_ = pp;
_ = start_idx;
return true;
}

View File

@ -2,15 +2,18 @@
//! Licensed under MIT license: https://github.com/mahkoh/repr-c/tree/master/repc/facade
const std = @import("std");
const Type = @import("Type.zig");
const Attribute = @import("Attribute.zig");
const Compilation = @import("Compilation.zig");
const Parser = @import("Parser.zig");
const target_util = @import("target.zig");
const TypeStore = @import("TypeStore.zig");
const QualType = TypeStore.QualType;
const Type = TypeStore.Type;
const Record = Type.Record;
const Field = Record.Field;
const TypeLayout = Type.TypeLayout;
const FieldLayout = Type.FieldLayout;
const target_util = @import("target.zig");
const RecordLayout = Type.Record.Layout;
const FieldLayout = Type.Record.Field.Layout;
const BITS_PER_BYTE = 8;
@ -42,36 +45,33 @@ const SysVContext = struct {
comp: *const Compilation,
fn init(ty: Type, comp: *const Compilation, pragma_pack: ?u8) SysVContext {
fn init(qt: QualType, comp: *const Compilation, pragma_pack: ?u8) SysVContext {
const pack_value: ?u64 = if (pragma_pack) |pak| @as(u64, pak) * BITS_PER_BYTE else null;
const req_align = @as(u32, (ty.requestedAlignment(comp) orelse 1)) * BITS_PER_BYTE;
const req_align = @as(u32, (qt.requestedAlignment(comp) orelse 1)) * BITS_PER_BYTE;
return SysVContext{
.attr_packed = ty.hasAttribute(.@"packed"),
.attr_packed = qt.hasAttribute(comp, .@"packed"),
.max_field_align_bits = pack_value,
.aligned_bits = req_align,
.is_union = ty.is(.@"union"),
.is_union = qt.is(comp, .@"union"),
.size_bits = 0,
.comp = comp,
.ongoing_bitfield = null,
};
}
fn layoutFields(self: *SysVContext, rec: *const Record) !void {
for (rec.fields, 0..) |*fld, fld_indx| {
if (fld.ty.specifier == .invalid) continue;
const type_layout = computeLayout(fld.ty, self.comp);
fn layoutFields(self: *SysVContext, fields: []Type.Record.Field) !void {
for (fields) |*field| {
if (field.qt.isInvalid()) continue;
const type_layout = computeLayout(field.qt, self.comp);
var field_attrs: ?[]const Attribute = null;
if (rec.field_attributes) |attrs| {
field_attrs = attrs[fld_indx];
}
const attributes = field.attributes(self.comp);
if (self.comp.target.isMinGW()) {
fld.layout = try self.layoutMinGWField(fld, field_attrs, type_layout);
field.layout = try self.layoutMinGWField(field, attributes, type_layout);
} else {
if (fld.isRegularField()) {
fld.layout = try self.layoutRegularField(field_attrs, type_layout);
if (field.bit_width.unpack()) |bit_width| {
field.layout = try self.layoutBitField(attributes, type_layout, field.name_tok != 0, bit_width);
} else {
fld.layout = try self.layoutBitField(field_attrs, type_layout, fld.isNamed(), fld.specifiedBitWidth());
field.layout = try self.layoutRegularField(attributes, type_layout);
}
}
}
@ -83,7 +83,7 @@ const SysVContext = struct {
/// - the field is a bit-field and the previous field was a non-zero-sized bit-field with the same type size
/// - the field is a zero-sized bit-field and the previous field was not a non-zero-sized bit-field
/// See test case 0068.
fn ignoreTypeAlignment(is_attr_packed: bool, bit_width: ?u32, ongoing_bitfield: ?OngoingBitfield, fld_layout: TypeLayout) bool {
fn ignoreTypeAlignment(is_attr_packed: bool, bit_width: ?u32, ongoing_bitfield: ?OngoingBitfield, fld_layout: RecordLayout) bool {
if (is_attr_packed) return true;
if (bit_width) |width| {
if (ongoing_bitfield) |ongoing| {
@ -98,12 +98,12 @@ const SysVContext = struct {
fn layoutMinGWField(
self: *SysVContext,
field: *const Field,
field_attrs: ?[]const Attribute,
field_layout: TypeLayout,
field_attrs: []const Attribute,
field_layout: RecordLayout,
) !FieldLayout {
const annotation_alignment_bits = BITS_PER_BYTE * @as(u32, (Type.annotationAlignment(self.comp, Attribute.Iterator.initSlice(field_attrs)) orelse 1));
const annotation_alignment_bits = BITS_PER_BYTE * (QualType.annotationAlignment(self.comp, Attribute.Iterator.initSlice(field_attrs)) orelse 1);
const is_attr_packed = self.attr_packed or isPacked(field_attrs);
const ignore_type_alignment = ignoreTypeAlignment(is_attr_packed, field.bit_width, self.ongoing_bitfield, field_layout);
const ignore_type_alignment = ignoreTypeAlignment(is_attr_packed, field.bit_width.unpack(), self.ongoing_bitfield, field_layout);
var field_alignment_bits: u64 = field_layout.field_alignment_bits;
if (ignore_type_alignment) {
@ -120,16 +120,16 @@ const SysVContext = struct {
// - the field is a non-zero-width bit-field and not packed.
// See test case 0069.
const update_record_alignment =
field.isRegularField() or
(field.specifiedBitWidth() == 0 and self.ongoing_bitfield != null) or
(field.specifiedBitWidth() != 0 and !is_attr_packed);
field.bit_width == .null or
(field.bit_width.unpack().? == 0 and self.ongoing_bitfield != null) or
(field.bit_width.unpack().? != 0 and !is_attr_packed);
// If a field affects the alignment of a record, the alignment is calculated in the
// usual way except that __attribute__((packed)) is ignored on a zero-width bit-field.
// See test case 0068.
if (update_record_alignment) {
var ty_alignment_bits = field_layout.field_alignment_bits;
if (is_attr_packed and (field.isRegularField() or field.specifiedBitWidth() != 0)) {
if (is_attr_packed and (field.bit_width == .null or field.bit_width.unpack().? != 0)) {
ty_alignment_bits = BITS_PER_BYTE;
}
ty_alignment_bits = @max(ty_alignment_bits, annotation_alignment_bits);
@ -145,10 +145,10 @@ const SysVContext = struct {
// @attr_packed _ { size: 64, alignment: 64 }long long:0,
// { offset: 8, size: 8 }d { size: 8, alignment: 8 }char,
// }
if (field.isRegularField()) {
return self.layoutRegularFieldMinGW(field_layout.size_bits, field_alignment_bits);
if (field.bit_width.unpack()) |bit_width| {
return self.layoutBitFieldMinGW(field_layout.size_bits, field_alignment_bits, field.name_tok != 0, bit_width);
} else {
return self.layoutBitFieldMinGW(field_layout.size_bits, field_alignment_bits, field.isNamed(), field.specifiedBitWidth());
return self.layoutRegularFieldMinGW(field_layout.size_bits, field_alignment_bits);
}
}
@ -227,8 +227,8 @@ const SysVContext = struct {
fn layoutRegularField(
self: *SysVContext,
fld_attrs: ?[]const Attribute,
fld_layout: TypeLayout,
fld_attrs: []const Attribute,
fld_layout: RecordLayout,
) !FieldLayout {
var fld_align_bits = fld_layout.field_alignment_bits;
@ -240,7 +240,7 @@ const SysVContext = struct {
// The field alignment can be increased by __attribute__((aligned)) annotations on the
// field. See test case 0085.
if (Type.annotationAlignment(self.comp, Attribute.Iterator.initSlice(fld_attrs))) |anno| {
if (QualType.annotationAlignment(self.comp, Attribute.Iterator.initSlice(fld_attrs))) |anno| {
fld_align_bits = @max(fld_align_bits, @as(u32, anno) * BITS_PER_BYTE);
}
@ -268,8 +268,8 @@ const SysVContext = struct {
fn layoutBitField(
self: *SysVContext,
fld_attrs: ?[]const Attribute,
fld_layout: TypeLayout,
fld_attrs: []const Attribute,
fld_layout: RecordLayout,
is_named: bool,
bit_width: u64,
) !FieldLayout {
@ -302,7 +302,7 @@ const SysVContext = struct {
const attr_packed = self.attr_packed or isPacked(fld_attrs);
const has_packing_annotation = attr_packed or self.max_field_align_bits != null;
const annotation_alignment = if (Type.annotationAlignment(self.comp, Attribute.Iterator.initSlice(fld_attrs))) |anno| @as(u32, anno) * BITS_PER_BYTE else 1;
const annotation_alignment = if (QualType.annotationAlignment(self.comp, Attribute.Iterator.initSlice(fld_attrs))) |anno| @as(u32, anno) * BITS_PER_BYTE else 1;
const first_unused_bit: u64 = if (self.is_union) 0 else self.size_bits;
var field_align_bits: u64 = 1;
@ -403,9 +403,9 @@ const MsvcContext = struct {
is_union: bool,
comp: *const Compilation,
fn init(ty: Type, comp: *const Compilation, pragma_pack: ?u8) MsvcContext {
fn init(qt: QualType, comp: *const Compilation, pragma_pack: ?u8) MsvcContext {
var pack_value: ?u32 = null;
if (ty.hasAttribute(.@"packed")) {
if (qt.hasAttribute(comp, .@"packed")) {
// __attribute__((packed)) behaves like #pragma pack(1) in clang. See test case 0056.
pack_value = BITS_PER_BYTE;
}
@ -420,8 +420,8 @@ const MsvcContext = struct {
// The required alignment can be increased by adding a __declspec(align)
// annotation. See test case 0023.
const must_align = @as(u32, (ty.requestedAlignment(comp) orelse 1)) * BITS_PER_BYTE;
return MsvcContext{
const must_align = @as(u32, (qt.requestedAlignment(comp) orelse 1)) * BITS_PER_BYTE;
return .{
.req_align_bits = must_align,
.pointer_align_bits = must_align,
.field_align_bits = must_align,
@ -429,26 +429,26 @@ const MsvcContext = struct {
.max_field_align_bits = pack_value,
.ongoing_bitfield = null,
.contains_non_bitfield = false,
.is_union = ty.is(.@"union"),
.is_union = qt.is(comp, .@"union"),
.comp = comp,
};
}
fn layoutField(self: *MsvcContext, fld: *const Field, fld_attrs: ?[]const Attribute) !FieldLayout {
const type_layout = computeLayout(fld.ty, self.comp);
fn layoutField(self: *MsvcContext, fld: *const Field, fld_attrs: []const Attribute) !FieldLayout {
const type_layout = computeLayout(fld.qt, self.comp);
// The required alignment of the field is the maximum of the required alignment of the
// underlying type and the __declspec(align) annotation on the field itself.
// See test case 0028.
var req_align = type_layout.required_alignment_bits;
if (Type.annotationAlignment(self.comp, Attribute.Iterator.initSlice(fld_attrs))) |anno| {
if (QualType.annotationAlignment(self.comp, Attribute.Iterator.initSlice(fld_attrs))) |anno| {
req_align = @max(@as(u32, anno) * BITS_PER_BYTE, req_align);
}
// The required alignment of a record is the maximum of the required alignments of its
// fields except that the required alignment of bitfields is ignored.
// See test case 0029.
if (fld.isRegularField()) {
if (fld.bit_width == .null) {
self.req_align_bits = @max(self.req_align_bits, req_align);
}
@ -459,7 +459,7 @@ const MsvcContext = struct {
fld_align_bits = @min(fld_align_bits, max_align);
}
// check the requested alignment of the field type.
if (fld.ty.requestedAlignment(self.comp)) |type_req_align| {
if (fld.qt.requestedAlignment(self.comp)) |type_req_align| {
fld_align_bits = @max(fld_align_bits, type_req_align * 8);
}
@ -471,10 +471,10 @@ const MsvcContext = struct {
// __attribute__((packed)) on a field is a clang extension. It behaves as if #pragma
// pack(1) had been applied only to this field. See test case 0057.
fld_align_bits = @max(fld_align_bits, req_align);
if (fld.isRegularField()) {
return self.layoutRegularField(type_layout.size_bits, fld_align_bits);
if (fld.bit_width.unpack()) |bit_width| {
return self.layoutBitField(type_layout.size_bits, fld_align_bits, bit_width);
} else {
return self.layoutBitField(type_layout.size_bits, fld_align_bits, fld.specifiedBitWidth());
return self.layoutRegularField(type_layout.size_bits, fld_align_bits);
}
}
@ -567,16 +567,16 @@ const MsvcContext = struct {
}
};
pub fn compute(rec: *Type.Record, ty: Type, comp: *const Compilation, pragma_pack: ?u8) Error!void {
pub fn compute(fields: []Type.Record.Field, qt: QualType, comp: *const Compilation, pragma_pack: ?u8) Error!Type.Record.Layout {
switch (comp.langopts.emulate) {
.gcc, .clang => {
var context = SysVContext.init(ty, comp, pragma_pack);
var context = SysVContext.init(qt, comp, pragma_pack);
try context.layoutFields(rec);
try context.layoutFields(fields);
context.size_bits = try alignForward(context.size_bits, context.aligned_bits);
rec.type_layout = .{
return .{
.size_bits = context.size_bits,
.field_alignment_bits = context.aligned_bits,
.pointer_alignment_bits = context.aligned_bits,
@ -584,15 +584,10 @@ pub fn compute(rec: *Type.Record, ty: Type, comp: *const Compilation, pragma_pac
};
},
.msvc => {
var context = MsvcContext.init(ty, comp, pragma_pack);
for (rec.fields, 0..) |*fld, fld_indx| {
if (fld.ty.specifier == .invalid) continue;
var field_attrs: ?[]const Attribute = null;
if (rec.field_attributes) |attrs| {
field_attrs = attrs[fld_indx];
}
fld.layout = try context.layoutField(fld, field_attrs);
var context = MsvcContext.init(qt, comp, pragma_pack);
for (fields) |*field| {
if (field.qt.isInvalid()) continue;
field.layout = try context.layoutField(field, field.attributes(comp));
}
if (context.size_bits == 0) {
// As an extension, MSVC allows records that only contain zero-sized bitfields and empty
@ -601,7 +596,7 @@ pub fn compute(rec: *Type.Record, ty: Type, comp: *const Compilation, pragma_pac
context.handleZeroSizedRecord();
}
context.size_bits = try alignForward(context.size_bits, context.pointer_align_bits);
rec.type_layout = .{
return .{
.size_bits = context.size_bits,
.field_alignment_bits = context.field_align_bits,
.pointer_alignment_bits = context.pointer_align_bits,
@ -611,23 +606,26 @@ pub fn compute(rec: *Type.Record, ty: Type, comp: *const Compilation, pragma_pac
}
}
fn computeLayout(ty: Type, comp: *const Compilation) TypeLayout {
if (ty.getRecord()) |rec| {
const requested = BITS_PER_BYTE * (ty.requestedAlignment(comp) orelse 0);
return .{
.size_bits = rec.type_layout.size_bits,
.pointer_alignment_bits = @max(requested, rec.type_layout.pointer_alignment_bits),
.field_alignment_bits = @max(requested, rec.type_layout.field_alignment_bits),
.required_alignment_bits = rec.type_layout.required_alignment_bits,
};
} else {
const type_align = ty.alignof(comp) * BITS_PER_BYTE;
return .{
.size_bits = ty.bitSizeof(comp) orelse 0,
.pointer_alignment_bits = type_align,
.field_alignment_bits = type_align,
.required_alignment_bits = BITS_PER_BYTE,
};
fn computeLayout(qt: QualType, comp: *const Compilation) RecordLayout {
switch (qt.base(comp).type) {
.@"struct", .@"union" => |record| {
const requested = BITS_PER_BYTE * (qt.requestedAlignment(comp) orelse 0);
return .{
.size_bits = record.layout.?.size_bits,
.pointer_alignment_bits = @max(requested, record.layout.?.pointer_alignment_bits),
.field_alignment_bits = @max(requested, record.layout.?.field_alignment_bits),
.required_alignment_bits = record.layout.?.required_alignment_bits,
};
},
else => {
const type_align = qt.alignof(comp) * BITS_PER_BYTE;
return .{
.size_bits = qt.bitSizeofOrNull(comp) orelse 0,
.pointer_alignment_bits = type_align,
.field_alignment_bits = type_align,
.required_alignment_bits = BITS_PER_BYTE,
};
},
}
}

View File

@ -1,15 +1,18 @@
const std = @import("std");
const backend = @import("../backend.zig");
const LangOpts = @import("LangOpts.zig");
const Type = @import("Type.zig");
const TargetSet = @import("Builtins/Properties.zig").TargetSet;
const QualType = @import("TypeStore.zig").QualType;
/// intmax_t for this target
pub fn intMaxType(target: std.Target) Type {
pub fn intMaxType(target: std.Target) QualType {
switch (target.cpu.arch) {
.aarch64,
.aarch64_be,
.sparc64,
=> if (target.os.tag != .openbsd) return .{ .specifier = .long },
=> if (target.os.tag != .openbsd) return .long,
.bpfel,
.bpfeb,
@ -19,28 +22,28 @@ pub fn intMaxType(target: std.Target) Type {
.powerpc64,
.powerpc64le,
.ve,
=> return .{ .specifier = .long },
=> return .long,
.x86_64 => switch (target.os.tag) {
.windows, .openbsd => {},
else => switch (target.abi) {
.gnux32, .muslx32 => {},
else => return .{ .specifier = .long },
else => return .long,
},
},
else => {},
}
return .{ .specifier = .long_long };
return .long_long;
}
/// intptr_t for this target
pub fn intPtrType(target: std.Target) Type {
if (target.os.tag == .haiku) return .{ .specifier = .long };
pub fn intPtrType(target: std.Target) QualType {
if (target.os.tag == .haiku) return .long;
switch (target.cpu.arch) {
.aarch64, .aarch64_be => switch (target.os.tag) {
.windows => return .{ .specifier = .long_long },
.windows => return .long_long,
else => {},
},
@ -55,28 +58,28 @@ pub fn intPtrType(target: std.Target) Type {
.spirv32,
.arc,
.avr,
=> return .{ .specifier = .int },
=> return .int,
.sparc => switch (target.os.tag) {
.netbsd, .openbsd => {},
else => return .{ .specifier = .int },
else => return .int,
},
.powerpc, .powerpcle => switch (target.os.tag) {
.linux, .freebsd, .netbsd => return .{ .specifier = .int },
.linux, .freebsd, .netbsd => return .int,
else => {},
},
// 32-bit x86 Darwin, OpenBSD, and RTEMS use long (the default); others use int
.x86 => switch (target.os.tag) {
.openbsd, .rtems => {},
else => if (!target.os.tag.isDarwin()) return .{ .specifier = .int },
else => if (!target.os.tag.isDarwin()) return .int,
},
.x86_64 => switch (target.os.tag) {
.windows => return .{ .specifier = .long_long },
.windows => return .long_long,
else => switch (target.abi) {
.gnux32, .muslx32 => return .{ .specifier = .int },
.gnux32, .muslx32 => return .int,
else => {},
},
},
@ -84,29 +87,29 @@ pub fn intPtrType(target: std.Target) Type {
else => {},
}
return .{ .specifier = .long };
return .long;
}
/// int16_t for this target
pub fn int16Type(target: std.Target) Type {
pub fn int16Type(target: std.Target) QualType {
return switch (target.cpu.arch) {
.avr => .{ .specifier = .int },
else => .{ .specifier = .short },
.avr => .int,
else => .short,
};
}
/// sig_atomic_t for this target
pub fn sigAtomicType(target: std.Target) Type {
if (target.cpu.arch.isWasm()) return .{ .specifier = .long };
pub fn sigAtomicType(target: std.Target) QualType {
if (target.cpu.arch.isWasm()) return .long;
return switch (target.cpu.arch) {
.avr => .{ .specifier = .schar },
.msp430 => .{ .specifier = .long },
else => .{ .specifier = .int },
.avr => .schar,
.msp430 => .long,
else => .int,
};
}
/// int64_t for this target
pub fn int64Type(target: std.Target) Type {
pub fn int64Type(target: std.Target) QualType {
switch (target.cpu.arch) {
.loongarch64,
.ve,
@ -116,20 +119,20 @@ pub fn int64Type(target: std.Target) Type {
.powerpc64le,
.bpfel,
.bpfeb,
=> return .{ .specifier = .long },
=> return .long,
.sparc64 => return intMaxType(target),
.x86, .x86_64 => if (!target.os.tag.isDarwin()) return intMaxType(target),
.aarch64, .aarch64_be => if (!target.os.tag.isDarwin() and target.os.tag != .openbsd and target.os.tag != .windows) return .{ .specifier = .long },
.aarch64, .aarch64_be => if (!target.os.tag.isDarwin() and target.os.tag != .openbsd and target.os.tag != .windows) return .long,
else => {},
}
return .{ .specifier = .long_long };
return .long_long;
}
pub fn float80Type(target: std.Target) ?Type {
pub fn float80Type(target: std.Target) ?QualType {
switch (target.cpu.arch) {
.x86, .x86_64 => return .{ .specifier = .long_double },
.x86, .x86_64 => return .long_double,
else => {},
}
return null;
@ -165,7 +168,7 @@ pub fn ignoreNonZeroSizedBitfieldTypeAlignment(target: std.Target) bool {
switch (target.cpu.arch) {
.avr => return true,
.arm => {
if (target.cpu.has(.arm, .has_v7)) {
if (std.Target.arm.featureSetHas(target.cpu.features, .has_v7)) {
switch (target.os.tag) {
.ios => return true,
else => return false,
@ -188,7 +191,7 @@ pub fn minZeroWidthBitfieldAlignment(target: std.Target) ?u29 {
switch (target.cpu.arch) {
.avr => return 8,
.arm => {
if (target.cpu.has(.arm, .has_v7)) {
if (std.Target.arm.featureSetHas(target.cpu.features, .has_v7)) {
switch (target.os.tag) {
.ios => return 32,
else => return null,
@ -206,7 +209,7 @@ pub fn unnamedFieldAffectsAlignment(target: std.Target) bool {
return true;
},
.armeb => {
if (target.cpu.has(.arm, .has_v7)) {
if (std.Target.arm.featureSetHas(target.cpu.features, .has_v7)) {
if (std.Target.Abi.default(target.cpu.arch, target.os.tag) == .eabi) return true;
}
},
@ -233,7 +236,7 @@ pub fn defaultAlignment(target: std.Target) u29 {
switch (target.cpu.arch) {
.avr => return 1,
.arm => if (target.abi.isAndroid() or target.os.tag == .ios) return 16 else return 8,
.sparc => if (target.cpu.has(.sparc, .v9)) return 16 else return 8,
.sparc => if (std.Target.sparc.featureSetHas(target.cpu.features, .v9)) return 16 else return 8,
.mips, .mipsel => switch (target.abi) {
.none, .gnuabi64 => return 16,
else => return 8,
@ -245,7 +248,8 @@ pub fn defaultAlignment(target: std.Target) u29 {
pub fn systemCompiler(target: std.Target) LangOpts.Compiler {
// Android is linux but not gcc, so these checks go first
// the rest for documentation as fn returns .clang
if (target.abi.isAndroid() or
if (target.os.tag.isDarwin() or
target.abi.isAndroid() or
target.os.tag.isBSD() or
target.os.tag == .fuchsia or
target.os.tag == .solaris or
@ -271,7 +275,7 @@ pub fn systemCompiler(target: std.Target) LangOpts.Compiler {
pub fn hasFloat128(target: std.Target) bool {
if (target.cpu.arch.isWasm()) return true;
if (target.os.tag.isDarwin()) return false;
if (target.cpu.arch.isPowerPC()) return target.cpu.has(.powerpc, .float128);
if (target.cpu.arch.isPowerPC()) return std.Target.powerpc.featureSetHas(target.cpu.features, .float128);
return switch (target.os.tag) {
.dragonfly,
.haiku,
@ -339,7 +343,7 @@ pub const FPSemantics = enum {
.spirv32,
.spirv64,
=> return .IEEEHalf,
.x86, .x86_64 => if (target.cpu.has(.x86, .sse2)) return .IEEEHalf,
.x86, .x86_64 => if (std.Target.x86.featureSetHas(target.cpu.features, .sse2)) return .IEEEHalf,
else => {},
}
return null;
@ -374,6 +378,10 @@ pub fn isCygwinMinGW(target: std.Target) bool {
return target.os.tag == .windows and (target.abi == .gnu or target.abi == .cygnus);
}
pub fn isPS(target: std.Target) bool {
return (target.os.tag == .ps4 or target.os.tag == .ps5) and target.cpu.arch == .x86_64;
}
pub fn builtinEnabled(target: std.Target, enabled_for: TargetSet) bool {
var it = enabled_for.iterator();
while (it.next()) |val| {
@ -404,7 +412,7 @@ pub fn defaultFpEvalMethod(target: std.Target) LangOpts.FPEvalMethod {
return .double;
}
}
if (target.cpu.has(.x86, .sse)) {
if (std.Target.x86.featureSetHas(target.cpu.features, .sse)) {
return .source;
}
return .extended;
@ -497,6 +505,8 @@ pub fn get32BitArchVariant(target: std.Target) ?std.Target {
.spirv32,
.loongarch32,
.xtensa,
.propeller,
.or1k,
=> {}, // Already 32 bit
.aarch64 => copy.cpu.arch = .arm,
@ -530,6 +540,8 @@ pub fn get64BitArchVariant(target: std.Target) ?std.Target {
.msp430,
.xcore,
.xtensa,
.propeller,
.or1k,
=> return null,
.aarch64,
@ -621,11 +633,14 @@ pub fn toLLVMTriple(target: std.Target, buf: []u8) []const u8 {
.nvptx64 => "nvptx64",
.spirv32 => "spirv32",
.spirv64 => "spirv64",
.kalimba => "kalimba",
.lanai => "lanai",
.wasm32 => "wasm32",
.wasm64 => "wasm64",
.ve => "ve",
// Note: propeller1, kalimba and or1k are not supported in LLVM; this is the Zig arch name
.kalimba => "kalimba",
.propeller => "propeller",
.or1k => "or1k",
};
writer.writeAll(llvm_arch) catch unreachable;
writer.writeByte('-') catch unreachable;
@ -666,10 +681,12 @@ pub fn toLLVMTriple(target: std.Target, buf: []u8) []const u8 {
.driverkit => "driverkit",
.visionos => "xros",
.serenity => "serenity",
.vulkan => "vulkan",
.managarm => "managarm",
.@"3ds",
.vita,
.opencl,
.opengl,
.vulkan,
.plan9,
.other,
=> "unknown",
@ -721,64 +738,262 @@ pub fn toLLVMTriple(target: std.Target, buf: []u8) []const u8 {
return writer.buffered();
}
pub const DefaultPIStatus = enum { yes, no, depends_on_linker };
pub fn isPIEDefault(target: std.Target) DefaultPIStatus {
return switch (target.os.tag) {
.aix,
.haiku,
.macos,
.ios,
.tvos,
.watchos,
.visionos,
.driverkit,
.dragonfly,
.netbsd,
.freebsd,
.solaris,
.cuda,
.amdhsa,
.amdpal,
.mesa3d,
.ps4,
.ps5,
.hurd,
.zos,
=> .no,
.openbsd,
.fuchsia,
=> .yes,
.linux => {
if (target.abi == .ohos)
return .yes;
switch (target.cpu.arch) {
.ve => return .no,
else => return if (target.os.tag == .linux or target.abi.isAndroid() or target.abi.isMusl()) .yes else .no,
}
},
.windows => {
if (target.isMinGW())
return .no;
if (target.abi == .itanium)
return if (target.cpu.arch == .x86_64) .yes else .no;
if (target.abi == .msvc or target.abi == .none)
return .depends_on_linker;
return .no;
},
else => {
switch (target.cpu.arch) {
.hexagon => {
// CLANG_DEFAULT_PIE_ON_LINUX
return if (target.os.tag == .linux or target.abi.isAndroid() or target.abi.isMusl()) .yes else .no;
},
else => return .no,
}
},
};
}
pub fn isPICdefault(target: std.Target) DefaultPIStatus {
return switch (target.os.tag) {
.aix,
.haiku,
.macos,
.ios,
.tvos,
.watchos,
.visionos,
.driverkit,
.amdhsa,
.amdpal,
.mesa3d,
.ps4,
.ps5,
=> .yes,
.fuchsia,
.cuda,
.zos,
=> .no,
.dragonfly,
.openbsd,
.netbsd,
.freebsd,
.solaris,
.hurd,
=> {
return switch (target.cpu.arch) {
.mips64, .mips64el => .yes,
else => .no,
};
},
.linux => {
if (target.abi == .ohos)
return .no;
return switch (target.cpu.arch) {
.mips64, .mips64el => .yes,
else => .no,
};
},
.windows => {
if (target.isMinGW())
return if (target.cpu.arch == .x86_64 or target.cpu.arch == .aarch64) .yes else .no;
if (target.abi == .itanium)
return if (target.cpu.arch == .x86_64) .yes else .no;
if (target.abi == .msvc or target.abi == .none)
return .depends_on_linker;
if (target.ofmt == .macho)
return .yes;
return switch (target.cpu.arch) {
.x86_64, .mips64, .mips64el => .yes,
else => .no,
};
},
else => {
if (target.ofmt == .macho)
return .yes;
return switch (target.cpu.arch) {
.mips64, .mips64el => .yes,
else => .no,
};
},
};
}
pub fn isPICDefaultForced(target: std.Target) DefaultPIStatus {
return switch (target.os.tag) {
.aix, .amdhsa, .amdpal, .mesa3d => .yes,
.haiku,
.dragonfly,
.openbsd,
.netbsd,
.freebsd,
.solaris,
.cuda,
.ps4,
.ps5,
.hurd,
.linux,
.fuchsia,
.zos,
=> .no,
.windows => {
if (target.isMinGW())
return .yes;
if (target.abi == .itanium)
return if (target.cpu.arch == .x86_64) .yes else .no;
// if (bfd) return target.cpu.arch == .x86_64 else target.cpu.arch == .x86_64 or target.cpu.arch == .aarch64;
if (target.abi == .msvc or target.abi == .none)
return .depends_on_linker;
if (target.ofmt == .macho)
return if (target.cpu.arch == .aarch64 or target.cpu.arch == .x86_64) .yes else .no;
return if (target.cpu.arch == .x86_64) .yes else .no;
},
.macos,
.ios,
.tvos,
.watchos,
.visionos,
.driverkit,
=> if (target.cpu.arch == .x86_64 or target.cpu.arch == .aarch64) .yes else .no,
else => {
return switch (target.cpu.arch) {
.hexagon,
.lanai,
.avr,
.riscv32,
.riscv64,
.csky,
.xcore,
.wasm32,
.wasm64,
.ve,
.spirv32,
.spirv64,
=> .no,
.msp430 => .yes,
else => {
if (target.ofmt == .macho)
return if (target.cpu.arch == .aarch64 or target.cpu.arch == .x86_64) .yes else .no;
return .no;
},
};
},
};
}
test "alignment functions - smoke test" {
var target: std.Target = undefined;
const x86 = std.Target.Cpu.Arch.x86_64;
target.os = std.Target.Os.Tag.defaultVersionRange(.linux, x86, .none);
target.cpu = std.Target.Cpu.baseline(x86, target.os);
target.abi = std.Target.Abi.default(x86, target.os.tag);
const linux: std.Target.Os = .{ .tag = .linux, .version_range = .{ .none = {} } };
const x86_64_target: std.Target = .{
.abi = std.Target.Abi.default(.x86_64, linux.tag),
.cpu = std.Target.Cpu.Model.generic(.x86_64).toCpu(.x86_64),
.os = linux,
.ofmt = .elf,
};
try std.testing.expect(isTlsSupported(target));
try std.testing.expect(!ignoreNonZeroSizedBitfieldTypeAlignment(target));
try std.testing.expect(minZeroWidthBitfieldAlignment(target) == null);
try std.testing.expect(!unnamedFieldAffectsAlignment(target));
try std.testing.expect(defaultAlignment(target) == 16);
try std.testing.expect(!packAllEnums(target));
try std.testing.expect(systemCompiler(target) == .gcc);
const arm = std.Target.Cpu.Arch.arm;
target.os = std.Target.Os.Tag.defaultVersionRange(.ios, arm, .none);
target.cpu = std.Target.Cpu.baseline(arm, target.os);
target.abi = std.Target.Abi.default(arm, target.os.tag);
try std.testing.expect(!isTlsSupported(target));
try std.testing.expect(ignoreNonZeroSizedBitfieldTypeAlignment(target));
try std.testing.expectEqual(@as(?u29, 32), minZeroWidthBitfieldAlignment(target));
try std.testing.expect(unnamedFieldAffectsAlignment(target));
try std.testing.expect(defaultAlignment(target) == 16);
try std.testing.expect(!packAllEnums(target));
try std.testing.expect(systemCompiler(target) == .clang);
try std.testing.expect(isTlsSupported(x86_64_target));
try std.testing.expect(!ignoreNonZeroSizedBitfieldTypeAlignment(x86_64_target));
try std.testing.expect(minZeroWidthBitfieldAlignment(x86_64_target) == null);
try std.testing.expect(!unnamedFieldAffectsAlignment(x86_64_target));
try std.testing.expect(defaultAlignment(x86_64_target) == 16);
try std.testing.expect(!packAllEnums(x86_64_target));
try std.testing.expect(systemCompiler(x86_64_target) == .gcc);
}
test "target size/align tests" {
var comp: @import("Compilation.zig") = undefined;
const x86 = std.Target.Cpu.Arch.x86;
comp.target.cpu.arch = x86;
comp.target.cpu.model = &std.Target.x86.cpu.i586;
comp.target.os = std.Target.Os.Tag.defaultVersionRange(.linux, x86, .none);
comp.target.abi = std.Target.Abi.gnu;
const tt: Type = .{
.specifier = .long_long,
const linux: std.Target.Os = .{ .tag = .linux, .version_range = .{ .none = {} } };
const x86_target: std.Target = .{
.abi = std.Target.Abi.default(.x86, linux.tag),
.cpu = std.Target.Cpu.Model.generic(.x86).toCpu(.x86),
.os = linux,
.ofmt = .elf,
};
comp.target = x86_target;
try std.testing.expectEqual(@as(u64, 8), tt.sizeof(&comp).?);
const tt: QualType = .long_long;
try std.testing.expectEqual(@as(u64, 8), tt.sizeof(&comp));
try std.testing.expectEqual(@as(u64, 4), tt.alignof(&comp));
const arm = std.Target.Cpu.Arch.arm;
comp.target.cpu = std.Target.Cpu.Model.toCpu(&std.Target.arm.cpu.cortex_r4, arm);
comp.target.os = std.Target.Os.Tag.defaultVersionRange(.ios, arm, .none);
comp.target.abi = std.Target.Abi.none;
const ct: Type = .{
.specifier = .char,
};
try std.testing.expectEqual(true, comp.target.cpu.has(.arm, .has_v7));
try std.testing.expectEqual(@as(u64, 1), ct.sizeof(&comp).?);
try std.testing.expectEqual(@as(u64, 1), ct.alignof(&comp));
try std.testing.expectEqual(true, ignoreNonZeroSizedBitfieldTypeAlignment(comp.target));
}
/// The canonical integer representation of nullptr_t.

View File

@ -1,11 +1,13 @@
//! Parsing and classification of string and character literals
const std = @import("std");
const mem = std.mem;
const Compilation = @import("Compilation.zig");
const Type = @import("Type.zig");
const Diagnostics = @import("Diagnostics.zig");
const Tokenizer = @import("Tokenizer.zig");
const mem = std.mem;
const QualType = @import("TypeStore.zig").QualType;
const Source = @import("Source.zig");
pub const Item = union(enum) {
/// decoded hex or character escape
@ -18,11 +20,6 @@ pub const Item = union(enum) {
utf8_text: std.unicode.Utf8View,
};
const CharDiagnostic = struct {
tag: Diagnostics.Tag,
extra: Diagnostics.Message.Extra,
};
pub const Kind = enum {
char,
wide,
@ -91,13 +88,13 @@ pub const Kind = enum {
}
/// The C type of a character literal of this kind
pub fn charLiteralType(kind: Kind, comp: *const Compilation) Type {
pub fn charLiteralType(kind: Kind, comp: *const Compilation) QualType {
return switch (kind) {
.char => Type.int,
.wide => comp.types.wchar,
.utf_8 => .{ .specifier = .uchar },
.utf_16 => comp.types.uint_least16_t,
.utf_32 => comp.types.uint_least32_t,
.char => .int,
.wide => comp.type_store.wchar,
.utf_8 => .uchar,
.utf_16 => comp.type_store.uint_least16_t,
.utf_32 => comp.type_store.uint_least32_t,
.unterminated => unreachable,
};
}
@ -120,7 +117,7 @@ pub const Kind = enum {
pub fn charUnitSize(kind: Kind, comp: *const Compilation) Compilation.CharUnitSize {
return switch (kind) {
.char => .@"1",
.wide => switch (comp.types.wchar.sizeof(comp).?) {
.wide => switch (comp.type_store.wchar.sizeof(comp)) {
2 => .@"2",
4 => .@"4",
else => unreachable,
@ -140,37 +137,52 @@ pub const Kind = enum {
}
/// The C type of an element of a string literal of this kind
pub fn elementType(kind: Kind, comp: *const Compilation) Type {
pub fn elementType(kind: Kind, comp: *const Compilation) QualType {
return switch (kind) {
.unterminated => unreachable,
.char => .{ .specifier = .char },
.utf_8 => if (comp.langopts.hasChar8_T()) .{ .specifier = .uchar } else .{ .specifier = .char },
.char => .char,
.utf_8 => if (comp.langopts.hasChar8_T()) .uchar else .char,
else => kind.charLiteralType(comp),
};
}
};
pub const Ascii = struct {
val: u7,
pub fn init(val: anytype) Ascii {
return .{ .val = @intCast(val) };
}
pub fn format(ctx: Ascii, w: *std.Io.Writer, fmt: []const u8) !usize {
const i = Diagnostics.templateIndex(w, fmt, "{c}");
if (std.ascii.isPrint(ctx.val)) {
try w.writeByte(ctx.val);
} else {
try w.print("x{x:0>2}", .{ctx.val});
}
return i;
}
};
pub const Parser = struct {
comp: *const Compilation,
literal: []const u8,
i: usize = 0,
kind: Kind,
max_codepoint: u21,
loc: Source.Location,
/// Offset added to `loc.byte_offset` when emitting an error.
offset: u32 = 0,
expansion_locs: []const Source.Location,
/// We only want to issue a max of 1 error per char literal
errored: bool = false,
errors_buffer: [4]CharDiagnostic,
errors_len: usize,
comp: *const Compilation,
pub fn init(literal: []const u8, kind: Kind, max_codepoint: u21, comp: *const Compilation) Parser {
return .{
.literal = literal,
.comp = comp,
.kind = kind,
.max_codepoint = max_codepoint,
.errors_buffer = undefined,
.errors_len = 0,
};
}
/// Makes incorrect encoding always an error.
/// Used when concatenating string literals.
incorrect_encoding_is_error: bool = false,
/// If this is false, do not issue any diagnostics for incorrect character encoding
/// Incorrect encoding is allowed if we are unescaping an identifier in the preprocessor
diagnose_incorrect_encoding: bool = true,
fn prefixLen(self: *const Parser) usize {
return switch (self.kind) {
@ -181,65 +193,204 @@ pub const Parser = struct {
};
}
pub fn errors(p: *Parser) []CharDiagnostic {
return p.errors_buffer[0..p.errors_len];
const Diagnostic = struct {
fmt: []const u8,
kind: Diagnostics.Message.Kind,
opt: ?Diagnostics.Option = null,
extension: bool = false,
pub const illegal_char_encoding_error: Diagnostic = .{
.fmt = "illegal character encoding in character literal",
.kind = .@"error",
};
pub const illegal_char_encoding_warning: Diagnostic = .{
.fmt = "illegal character encoding in character literal",
.kind = .warning,
.opt = .@"invalid-source-encoding",
};
pub const missing_hex_escape: Diagnostic = .{
.fmt = "\\{c} used with no following hex digits",
.kind = .@"error",
};
pub const escape_sequence_overflow: Diagnostic = .{
.fmt = "escape sequence out of range",
.kind = .@"error",
};
pub const incomplete_universal_character: Diagnostic = .{
.fmt = "incomplete universal character name",
.kind = .@"error",
};
pub const invalid_universal_character: Diagnostic = .{
.fmt = "invalid universal character",
.kind = .@"error",
};
pub const char_too_large: Diagnostic = .{
.fmt = "character too large for enclosing character literal type",
.kind = .@"error",
};
pub const ucn_basic_char_error: Diagnostic = .{
.fmt = "character '{c}' cannot be specified by a universal character name",
.kind = .@"error",
};
pub const ucn_basic_char_warning: Diagnostic = .{
.fmt = "specifying character '{c}' with a universal character name is incompatible with C standards before C23",
.kind = .off,
.opt = .@"pre-c23-compat",
};
pub const ucn_control_char_error: Diagnostic = .{
.fmt = "universal character name refers to a control character",
.kind = .@"error",
};
pub const ucn_control_char_warning: Diagnostic = .{
.fmt = "universal character name referring to a control character is incompatible with C standards before C23",
.kind = .off,
.opt = .@"pre-c23-compat",
};
pub const c89_ucn_in_literal: Diagnostic = .{
.fmt = "universal character names are only valid in C99 or later",
.kind = .warning,
.opt = .unicode,
};
const non_standard_escape_char: Diagnostic = .{
.fmt = "use of non-standard escape character '\\{c}'",
.kind = .off,
.extension = true,
};
pub const unknown_escape_sequence: Diagnostic = .{
.fmt = "unknown escape sequence '\\{c}'",
.kind = .warning,
.opt = .@"unknown-escape-sequence",
};
pub const four_char_char_literal: Diagnostic = .{
.fmt = "multi-character character constant",
.opt = .@"four-char-constants",
.kind = .off,
};
pub const multichar_literal_warning: Diagnostic = .{
.fmt = "multi-character character constant",
.kind = .warning,
.opt = .multichar,
};
pub const invalid_multichar_literal: Diagnostic = .{
.fmt = "{s} character literals may not contain multiple characters",
.kind = .@"error",
};
pub const char_lit_too_wide: Diagnostic = .{
.fmt = "character constant too long for its type",
.kind = .warning,
};
// pub const wide_multichar_literal: Diagnostic = .{
// .fmt = "extraneous characters in character constant ignored",
// .kind = .warning,
// };
};
pub fn err(p: *Parser, diagnostic: Diagnostic, args: anytype) !void {
defer p.offset = 0;
if (p.errored) return;
defer p.errored = true;
try p.warn(diagnostic, args);
}
pub fn err(self: *Parser, tag: Diagnostics.Tag, extra: Diagnostics.Message.Extra) void {
if (self.errored) return;
self.errored = true;
const diagnostic: CharDiagnostic = .{ .tag = tag, .extra = extra };
if (self.errors_len == self.errors_buffer.len) {
self.errors_buffer[self.errors_buffer.len - 1] = diagnostic;
} else {
self.errors_buffer[self.errors_len] = diagnostic;
self.errors_len += 1;
pub fn warn(p: *Parser, diagnostic: Diagnostic, args: anytype) Compilation.Error!void {
defer p.offset = 0;
if (p.errored) return;
if (p.comp.diagnostics.effectiveKind(diagnostic) == .off) return;
var sf = std.heap.stackFallback(1024, p.comp.gpa);
var allocating: std.Io.Writer.Allocating = .init(sf.get());
defer allocating.deinit();
formatArgs(&allocating.writer, diagnostic.fmt, args) catch return error.OutOfMemory;
var offset_location = p.loc;
offset_location.byte_offset += p.offset;
try p.comp.diagnostics.addWithLocation(p.comp, .{
.kind = diagnostic.kind,
.text = allocating.written(),
.opt = diagnostic.opt,
.extension = diagnostic.extension,
.location = offset_location.expand(p.comp),
}, p.expansion_locs, true);
}
fn formatArgs(w: *std.Io.Writer, fmt: []const u8, args: anytype) !void {
var i: usize = 0;
inline for (std.meta.fields(@TypeOf(args))) |arg_info| {
const arg = @field(args, arg_info.name);
i += switch (@TypeOf(arg)) {
[]const u8 => try Diagnostics.formatString(w, fmt[i..], arg),
Ascii => try arg.format(w, fmt[i..]),
else => switch (@typeInfo(@TypeOf(arg))) {
.int, .comptime_int => try Diagnostics.formatInt(w, fmt[i..], arg),
.pointer => try Diagnostics.formatString(w, fmt[i..], arg),
else => comptime unreachable,
},
};
}
try w.writeAll(fmt[i..]);
}
pub fn warn(self: *Parser, tag: Diagnostics.Tag, extra: Diagnostics.Message.Extra) void {
if (self.errored) return;
if (self.errors_len < self.errors_buffer.len) {
self.errors_buffer[self.errors_len] = .{ .tag = tag, .extra = extra };
self.errors_len += 1;
}
}
pub fn next(p: *Parser) !?Item {
if (p.i >= p.literal.len) return null;
pub fn next(self: *Parser) ?Item {
if (self.i >= self.literal.len) return null;
const start = self.i;
if (self.literal[start] != '\\') {
self.i = mem.indexOfScalarPos(u8, self.literal, start + 1, '\\') orelse self.literal.len;
const unescaped_slice = self.literal[start..self.i];
const start = p.i;
if (p.literal[start] != '\\') {
p.i = mem.indexOfScalarPos(u8, p.literal, start + 1, '\\') orelse p.literal.len;
const unescaped_slice = p.literal[start..p.i];
const view = std.unicode.Utf8View.init(unescaped_slice) catch {
if (self.kind != .char) {
self.err(.illegal_char_encoding_error, .{ .none = {} });
if (!p.diagnose_incorrect_encoding) {
return .{ .improperly_encoded = p.literal[start..p.i] };
}
if (p.incorrect_encoding_is_error) {
try p.warn(.illegal_char_encoding_error, .{});
return .{ .improperly_encoded = p.literal[start..p.i] };
}
if (p.kind != .char) {
try p.err(.illegal_char_encoding_error, .{});
return null;
}
self.warn(.illegal_char_encoding_warning, .{ .none = {} });
return .{ .improperly_encoded = self.literal[start..self.i] };
try p.warn(.illegal_char_encoding_warning, .{});
return .{ .improperly_encoded = p.literal[start..p.i] };
};
return .{ .utf8_text = view };
}
switch (self.literal[start + 1]) {
'u', 'U' => return self.parseUnicodeEscape(),
else => return self.parseEscapedChar(),
switch (p.literal[start + 1]) {
'u', 'U' => return try p.parseUnicodeEscape(),
else => return try p.parseEscapedChar(),
}
}
fn parseUnicodeEscape(self: *Parser) ?Item {
const start = self.i;
fn parseUnicodeEscape(p: *Parser) !?Item {
const start = p.i;
std.debug.assert(self.literal[self.i] == '\\');
std.debug.assert(p.literal[p.i] == '\\');
const kind = self.literal[self.i + 1];
const kind = p.literal[p.i + 1];
std.debug.assert(kind == 'u' or kind == 'U');
self.i += 2;
if (self.i >= self.literal.len or !std.ascii.isHex(self.literal[self.i])) {
self.err(.missing_hex_escape, .{ .ascii = @intCast(kind) });
p.i += 2;
if (p.i >= p.literal.len or !std.ascii.isHex(p.literal[p.i])) {
try p.err(.missing_hex_escape, .{Ascii.init(kind)});
return null;
}
const expected_len: usize = if (kind == 'u') 4 else 8;
@ -247,66 +398,66 @@ pub const Parser = struct {
var count: usize = 0;
var val: u32 = 0;
for (self.literal[self.i..], 0..) |c, i| {
for (p.literal[p.i..], 0..) |c, i| {
if (i == expected_len) break;
const char = std.fmt.charToDigit(c, 16) catch {
break;
};
const char = std.fmt.charToDigit(c, 16) catch break;
val, const overflow = @shlWithOverflow(val, 4);
overflowed = overflowed or overflow != 0;
val |= char;
count += 1;
}
self.i += expected_len;
p.i += expected_len;
if (overflowed) {
self.err(.escape_sequence_overflow, .{ .offset = start + self.prefixLen() });
p.offset += @intCast(start + p.prefixLen());
try p.err(.escape_sequence_overflow, .{});
return null;
}
if (count != expected_len) {
self.err(.incomplete_universal_character, .{ .none = {} });
try p.err(.incomplete_universal_character, .{});
return null;
}
if (val > std.math.maxInt(u21) or !std.unicode.utf8ValidCodepoint(@intCast(val))) {
self.err(.invalid_universal_character, .{ .offset = start + self.prefixLen() });
p.offset += @intCast(start + p.prefixLen());
try p.err(.invalid_universal_character, .{});
return null;
}
if (val > self.max_codepoint) {
self.err(.char_too_large, .{ .none = {} });
if (val > p.max_codepoint) {
try p.err(.char_too_large, .{});
return null;
}
if (val < 0xA0 and (val != '$' and val != '@' and val != '`')) {
const is_error = !self.comp.langopts.standard.atLeast(.c23);
const is_error = !p.comp.langopts.standard.atLeast(.c23);
if (val >= 0x20 and val <= 0x7F) {
if (is_error) {
self.err(.ucn_basic_char_error, .{ .ascii = @intCast(val) });
} else {
self.warn(.ucn_basic_char_warning, .{ .ascii = @intCast(val) });
try p.err(.ucn_basic_char_error, .{Ascii.init(val)});
} else if (!p.comp.langopts.standard.atLeast(.c23)) {
try p.warn(.ucn_basic_char_warning, .{Ascii.init(val)});
}
} else {
if (is_error) {
self.err(.ucn_control_char_error, .{ .none = {} });
} else {
self.warn(.ucn_control_char_warning, .{ .none = {} });
try p.err(.ucn_control_char_error, .{});
} else if (!p.comp.langopts.standard.atLeast(.c23)) {
try p.warn(.ucn_control_char_warning, .{});
}
}
}
self.warn(.c89_ucn_in_literal, .{ .none = {} });
if (!p.comp.langopts.standard.atLeast(.c99)) try p.warn(.c89_ucn_in_literal, .{});
return .{ .codepoint = @intCast(val) };
}
fn parseEscapedChar(self: *Parser) Item {
self.i += 1;
const c = self.literal[self.i];
fn parseEscapedChar(p: *Parser) !Item {
p.i += 1;
const c = p.literal[p.i];
defer if (c != 'x' and (c < '0' or c > '7')) {
self.i += 1;
p.i += 1;
};
switch (c) {
@ -319,36 +470,40 @@ pub const Parser = struct {
'a' => return .{ .value = 0x07 },
'b' => return .{ .value = 0x08 },
'e', 'E' => {
self.warn(.non_standard_escape_char, .{ .invalid_escape = .{ .char = c, .offset = @intCast(self.i) } });
p.offset += @intCast(p.i);
try p.warn(.non_standard_escape_char, .{Ascii.init(c)});
return .{ .value = 0x1B };
},
'(', '{', '[', '%' => {
self.warn(.non_standard_escape_char, .{ .invalid_escape = .{ .char = c, .offset = @intCast(self.i) } });
p.offset += @intCast(p.i);
try p.warn(.non_standard_escape_char, .{Ascii.init(c)});
return .{ .value = c };
},
'f' => return .{ .value = 0x0C },
'v' => return .{ .value = 0x0B },
'x' => return .{ .value = self.parseNumberEscape(.hex) },
'0'...'7' => return .{ .value = self.parseNumberEscape(.octal) },
'x' => return .{ .value = try p.parseNumberEscape(.hex) },
'0'...'7' => return .{ .value = try p.parseNumberEscape(.octal) },
'u', 'U' => unreachable, // handled by parseUnicodeEscape
else => {
self.warn(.unknown_escape_sequence, .{ .invalid_escape = .{ .char = c, .offset = @intCast(self.i) } });
p.offset += @intCast(p.i);
try p.warn(.unknown_escape_sequence, .{Ascii.init(c)});
return .{ .value = c };
},
}
}
fn parseNumberEscape(self: *Parser, base: EscapeBase) u32 {
fn parseNumberEscape(p: *Parser, base: EscapeBase) !u32 {
var val: u32 = 0;
var count: usize = 0;
var overflowed = false;
const start = self.i;
defer self.i += count;
const start = p.i;
defer p.i += count;
const slice = switch (base) {
.octal => self.literal[self.i..@min(self.literal.len, self.i + 3)], // max 3 chars
.octal => p.literal[p.i..@min(p.literal.len, p.i + 3)], // max 3 chars
.hex => blk: {
self.i += 1;
break :blk self.literal[self.i..]; // skip over 'x'; could have an arbitrary number of chars
p.i += 1;
break :blk p.literal[p.i..]; // skip over 'x'; could have an arbitrary number of chars
},
};
for (slice) |c| {
@ -358,13 +513,14 @@ pub const Parser = struct {
val += char;
count += 1;
}
if (overflowed or val > self.kind.maxInt(self.comp)) {
self.err(.escape_sequence_overflow, .{ .offset = start + self.prefixLen() });
if (overflowed or val > p.kind.maxInt(p.comp)) {
p.offset += @intCast(start + p.prefixLen());
try p.err(.escape_sequence_overflow, .{});
return 0;
}
if (count == 0) {
std.debug.assert(base == .hex);
self.err(.missing_hex_escape, .{ .ascii = 'x' });
try p.err(.missing_hex_escape, .{Ascii.init('x')});
}
return val;
}

View File

@ -1,517 +0,0 @@
const std = @import("std");
const mem = std.mem;
const Compilation = @import("../Compilation.zig");
const GCCDetector = @import("../Driver/GCCDetector.zig");
const Toolchain = @import("../Toolchain.zig");
const Driver = @import("../Driver.zig");
const Distro = @import("../Driver/Distro.zig");
const target_util = @import("../target.zig");
const system_defaults = @import("system_defaults");
const Linux = @This();
distro: Distro.Tag = .unknown,
extra_opts: std.ArrayListUnmanaged([]const u8) = .empty,
gcc_detector: GCCDetector = .{},
pub fn discover(self: *Linux, tc: *Toolchain) !void {
self.distro = Distro.detect(tc.getTarget(), tc.filesystem);
try self.gcc_detector.discover(tc);
tc.selected_multilib = self.gcc_detector.selected;
try self.gcc_detector.appendToolPath(tc);
try self.buildExtraOpts(tc);
try self.findPaths(tc);
}
fn buildExtraOpts(self: *Linux, tc: *const Toolchain) !void {
const gpa = tc.driver.comp.gpa;
const target = tc.getTarget();
const is_android = target.abi.isAndroid();
if (self.distro.isAlpine() or is_android) {
try self.extra_opts.ensureUnusedCapacity(gpa, 2);
self.extra_opts.appendAssumeCapacity("-z");
self.extra_opts.appendAssumeCapacity("now");
}
if (self.distro.isOpenSUSE() or self.distro.isUbuntu() or self.distro.isAlpine() or is_android) {
try self.extra_opts.ensureUnusedCapacity(gpa, 2);
self.extra_opts.appendAssumeCapacity("-z");
self.extra_opts.appendAssumeCapacity("relro");
}
if ((target.cpu.arch.isArm() and !target.cpu.arch.isThumb()) or target.cpu.arch.isAARCH64() or is_android) {
try self.extra_opts.ensureUnusedCapacity(gpa, 2);
self.extra_opts.appendAssumeCapacity("-z");
self.extra_opts.appendAssumeCapacity("max-page-size=4096");
}
if (target.cpu.arch == .arm or target.cpu.arch == .thumb) {
try self.extra_opts.append(gpa, "-X");
}
if (!target.cpu.arch.isMIPS() and target.cpu.arch != .hexagon) {
const hash_style = if (is_android) .both else self.distro.getHashStyle();
try self.extra_opts.append(gpa, switch (hash_style) {
inline else => |tag| "--hash-style=" ++ @tagName(tag),
});
}
if (system_defaults.enable_linker_build_id) {
try self.extra_opts.append(gpa, "--build-id");
}
}
fn addMultiLibPaths(self: *Linux, tc: *Toolchain, sysroot: []const u8, os_lib_dir: []const u8) !void {
if (!self.gcc_detector.is_valid) return;
const gcc_triple = self.gcc_detector.gcc_triple;
const lib_path = self.gcc_detector.parent_lib_path;
// Add lib/gcc/$triple/$version, with an optional /multilib suffix.
try tc.addPathIfExists(&.{ self.gcc_detector.install_path, tc.selected_multilib.gcc_suffix }, .file);
// Add lib/gcc/$triple/$libdir
// For GCC built with --enable-version-specific-runtime-libs.
try tc.addPathIfExists(&.{ self.gcc_detector.install_path, "..", os_lib_dir }, .file);
try tc.addPathIfExists(&.{ lib_path, "..", gcc_triple, "lib", "..", os_lib_dir, tc.selected_multilib.os_suffix }, .file);
// If the GCC installation we found is inside of the sysroot, we want to
// prefer libraries installed in the parent prefix of the GCC installation.
// It is important to *not* use these paths when the GCC installation is
// outside of the system root as that can pick up unintended libraries.
// This usually happens when there is an external cross compiler on the
// host system, and a more minimal sysroot available that is the target of
// the cross. Note that GCC does include some of these directories in some
// configurations but this seems somewhere between questionable and simply
// a bug.
if (mem.startsWith(u8, lib_path, sysroot)) {
try tc.addPathIfExists(&.{ lib_path, "..", os_lib_dir }, .file);
}
}
fn addMultiArchPaths(self: *Linux, tc: *Toolchain) !void {
if (!self.gcc_detector.is_valid) return;
const lib_path = self.gcc_detector.parent_lib_path;
const gcc_triple = self.gcc_detector.gcc_triple;
const multilib = self.gcc_detector.selected;
try tc.addPathIfExists(&.{ lib_path, "..", gcc_triple, "lib", multilib.os_suffix }, .file);
}
/// TODO: Very incomplete
fn findPaths(self: *Linux, tc: *Toolchain) !void {
const target = tc.getTarget();
const sysroot = tc.getSysroot();
var output: [64]u8 = undefined;
const os_lib_dir = getOSLibDir(target);
const multiarch_triple = getMultiarchTriple(target) orelse target_util.toLLVMTriple(target, &output);
try self.addMultiLibPaths(tc, sysroot, os_lib_dir);
try tc.addPathIfExists(&.{ sysroot, "/lib", multiarch_triple }, .file);
try tc.addPathIfExists(&.{ sysroot, "/lib", "..", os_lib_dir }, .file);
if (target.abi.isAndroid()) {
// TODO
}
try tc.addPathIfExists(&.{ sysroot, "/usr", "lib", multiarch_triple }, .file);
try tc.addPathIfExists(&.{ sysroot, "/usr", "lib", "..", os_lib_dir }, .file);
try self.addMultiArchPaths(tc);
try tc.addPathIfExists(&.{ sysroot, "/lib" }, .file);
try tc.addPathIfExists(&.{ sysroot, "/usr", "lib" }, .file);
}
pub fn deinit(self: *Linux, allocator: std.mem.Allocator) void {
self.extra_opts.deinit(allocator);
}
fn isPIEDefault(self: *const Linux) bool {
_ = self;
return false;
}
fn getPIE(self: *const Linux, d: *const Driver) bool {
if (d.shared or d.static or d.relocatable or d.static_pie) {
return false;
}
return d.pie orelse self.isPIEDefault();
}
fn getStaticPIE(self: *const Linux, d: *Driver) !bool {
_ = self;
if (d.static_pie and d.pie != null) {
try d.err("cannot specify 'nopie' along with 'static-pie'");
}
return d.static_pie;
}
fn getStatic(self: *const Linux, d: *const Driver) bool {
_ = self;
return d.static and !d.static_pie;
}
pub fn getDefaultLinker(self: *const Linux, target: std.Target) []const u8 {
_ = self;
if (target.abi.isAndroid()) {
return "ld.lld";
}
return "ld";
}
pub fn buildLinkerArgs(self: *const Linux, tc: *const Toolchain, argv: *std.array_list.Managed([]const u8)) Compilation.Error!void {
const d = tc.driver;
const target = tc.getTarget();
const is_pie = self.getPIE(d);
const is_static_pie = try self.getStaticPIE(d);
const is_static = self.getStatic(d);
const is_android = target.abi.isAndroid();
const is_ve = target.cpu.arch == .ve;
const has_crt_begin_end_files = target.abi != .none; // TODO: clang checks for MIPS vendor
if (is_pie) {
try argv.append("-pie");
}
if (is_static_pie) {
try argv.appendSlice(&.{ "-static", "-pie", "--no-dynamic-linker", "-z", "text" });
}
if (d.rdynamic) {
try argv.append("-export-dynamic");
}
if (d.strip) {
try argv.append("-s");
}
try argv.appendSlice(self.extra_opts.items);
try argv.append("--eh-frame-hdr");
// Todo: Driver should parse `-EL`/`-EB` for arm to set endianness for arm targets
if (target_util.ldEmulationOption(d.comp.target, null)) |emulation| {
try argv.appendSlice(&.{ "-m", emulation });
} else {
try d.err("Unknown target triple");
return;
}
if (d.comp.target.cpu.arch.isRISCV()) {
try argv.append("-X");
}
if (d.shared) {
try argv.append("-shared");
}
if (is_static) {
try argv.append("-static");
} else {
if (d.rdynamic) {
try argv.append("-export-dynamic");
}
if (!d.shared and !is_static_pie and !d.relocatable) {
const dynamic_linker = d.comp.target.standardDynamicLinkerPath();
// todo: check for --dyld-prefix
if (dynamic_linker.get()) |path| {
try argv.appendSlice(&.{ "-dynamic-linker", try tc.arena.dupe(u8, path) });
} else {
try d.err("Could not find dynamic linker path");
}
}
}
try argv.appendSlice(&.{ "-o", d.output_name orelse "a.out" });
if (!d.nostdlib and !d.nostartfiles and !d.relocatable) {
if (!is_android) {
if (!d.shared) {
const crt1 = if (is_pie)
"Scrt1.o"
else if (is_static_pie)
"rcrt1.o"
else
"crt1.o";
try argv.append(try tc.getFilePath(crt1));
}
try argv.append(try tc.getFilePath("crti.o"));
}
if (is_ve) {
try argv.appendSlice(&.{ "-z", "max-page-size=0x4000000" });
}
if (has_crt_begin_end_files) {
var path: []const u8 = "";
if (tc.getRuntimeLibKind() == .compiler_rt and !is_android) {
const crt_begin = try tc.getCompilerRt("crtbegin", .object);
if (tc.filesystem.exists(crt_begin)) {
path = crt_begin;
}
}
if (path.len == 0) {
const crt_begin = if (tc.driver.shared)
if (is_android) "crtbegin_so.o" else "crtbeginS.o"
else if (is_static)
if (is_android) "crtbegin_static.o" else "crtbeginT.o"
else if (is_pie or is_static_pie)
if (is_android) "crtbegin_dynamic.o" else "crtbeginS.o"
else if (is_android) "crtbegin_dynamic.o" else "crtbegin.o";
path = try tc.getFilePath(crt_begin);
}
try argv.append(path);
}
}
// TODO add -L opts
// TODO add -u opts
try tc.addFilePathLibArgs(argv);
// TODO handle LTO
try argv.appendSlice(d.link_objects.items);
if (!d.nostdlib and !d.relocatable) {
if (!d.nodefaultlibs) {
if (is_static or is_static_pie) {
try argv.append("--start-group");
}
try tc.addRuntimeLibs(argv);
// TODO: add pthread if needed
if (!d.nolibc) {
try argv.append("-lc");
}
if (is_static or is_static_pie) {
try argv.append("--end-group");
} else {
try tc.addRuntimeLibs(argv);
}
}
if (!d.nostartfiles) {
if (has_crt_begin_end_files) {
var path: []const u8 = "";
if (tc.getRuntimeLibKind() == .compiler_rt and !is_android) {
const crt_end = try tc.getCompilerRt("crtend", .object);
if (tc.filesystem.exists(crt_end)) {
path = crt_end;
}
}
if (path.len == 0) {
const crt_end = if (d.shared)
if (is_android) "crtend_so.o" else "crtendS.o"
else if (is_pie or is_static_pie)
if (is_android) "crtend_android.o" else "crtendS.o"
else if (is_android) "crtend_android.o" else "crtend.o";
path = try tc.getFilePath(crt_end);
}
try argv.append(path);
}
if (!is_android) {
try argv.append(try tc.getFilePath("crtn.o"));
}
}
}
// TODO add -T args
}
fn getMultiarchTriple(target: std.Target) ?[]const u8 {
const is_android = target.abi.isAndroid();
const is_mips_r6 = target.cpu.has(.mips, .mips32r6);
return switch (target.cpu.arch) {
.arm, .thumb => if (is_android) "arm-linux-androideabi" else if (target.abi == .gnueabihf) "arm-linux-gnueabihf" else "arm-linux-gnueabi",
.armeb, .thumbeb => if (target.abi == .gnueabihf) "armeb-linux-gnueabihf" else "armeb-linux-gnueabi",
.aarch64 => if (is_android) "aarch64-linux-android" else "aarch64-linux-gnu",
.aarch64_be => "aarch64_be-linux-gnu",
.x86 => if (is_android) "i686-linux-android" else "i386-linux-gnu",
.x86_64 => if (is_android) "x86_64-linux-android" else if (target.abi == .gnux32) "x86_64-linux-gnux32" else "x86_64-linux-gnu",
.m68k => "m68k-linux-gnu",
.mips => if (is_mips_r6) "mipsisa32r6-linux-gnu" else "mips-linux-gnu",
.mipsel => if (is_android) "mipsel-linux-android" else if (is_mips_r6) "mipsisa32r6el-linux-gnu" else "mipsel-linux-gnu",
.powerpcle => "powerpcle-linux-gnu",
.powerpc64 => "powerpc64-linux-gnu",
.powerpc64le => "powerpc64le-linux-gnu",
.riscv64 => "riscv64-linux-gnu",
.sparc => "sparc-linux-gnu",
.sparc64 => "sparc64-linux-gnu",
.s390x => "s390x-linux-gnu",
// TODO: expand this
else => null,
};
}
fn getOSLibDir(target: std.Target) []const u8 {
switch (target.cpu.arch) {
.x86,
.powerpc,
.powerpcle,
.sparc,
=> return "lib32",
else => {},
}
if (target.cpu.arch == .x86_64 and (target.abi == .gnux32 or target.abi == .muslx32)) {
return "libx32";
}
if (target.cpu.arch == .riscv32) {
return "lib32";
}
if (target.ptrBitWidth() == 32) {
return "lib";
}
return "lib64";
}
pub fn defineSystemIncludes(self: *const Linux, tc: *const Toolchain) !void {
if (tc.driver.nostdinc) return;
const comp = tc.driver.comp;
const target = tc.getTarget();
// musl prefers /usr/include before builtin includes, so musl targets will add builtins
// at the end of this function (unless disabled with nostdlibinc)
if (!tc.driver.nobuiltininc and (!target.abi.isMusl() or tc.driver.nostdlibinc)) {
try comp.addBuiltinIncludeDir(tc.driver.aro_name);
}
if (tc.driver.nostdlibinc) return;
const sysroot = tc.getSysroot();
const local_include = try std.fmt.allocPrint(comp.gpa, "{s}{s}", .{ sysroot, "/usr/local/include" });
defer comp.gpa.free(local_include);
try comp.addSystemIncludeDir(local_include);
if (self.gcc_detector.is_valid) {
const gcc_include_path = try std.fs.path.join(comp.gpa, &.{ self.gcc_detector.parent_lib_path, "..", self.gcc_detector.gcc_triple, "include" });
defer comp.gpa.free(gcc_include_path);
try comp.addSystemIncludeDir(gcc_include_path);
}
if (getMultiarchTriple(target)) |triple| {
const joined = try std.fs.path.join(comp.gpa, &.{ sysroot, "usr", "include", triple });
defer comp.gpa.free(joined);
if (tc.filesystem.exists(joined)) {
try comp.addSystemIncludeDir(joined);
}
}
if (target.os.tag == .rtems) return;
try comp.addSystemIncludeDir("/include");
try comp.addSystemIncludeDir("/usr/include");
std.debug.assert(!tc.driver.nostdlibinc);
if (!tc.driver.nobuiltininc and target.abi.isMusl()) {
try comp.addBuiltinIncludeDir(tc.driver.aro_name);
}
}
test Linux {
if (@import("builtin").os.tag == .windows) return error.SkipZigTest;
var arena_instance = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena_instance.deinit();
const arena = arena_instance.allocator();
var comp = Compilation.init(std.testing.allocator, std.fs.cwd());
defer comp.deinit();
comp.environment = .{
.path = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
};
defer comp.environment = .{};
const raw_triple = "x86_64-linux-gnu";
const target_query = try std.Target.Query.parse(.{ .arch_os_abi = raw_triple });
comp.target = try std.zig.system.resolveTargetQuery(target_query);
comp.langopts.setEmulatedCompiler(.gcc);
var driver: Driver = .{ .comp = &comp };
defer driver.deinit();
driver.raw_target_triple = raw_triple;
const link_obj = try driver.comp.gpa.dupe(u8, "/tmp/foo.o");
try driver.link_objects.append(driver.comp.gpa, link_obj);
driver.temp_file_count += 1;
var toolchain: Toolchain = .{ .driver = &driver, .arena = arena, .filesystem = .{ .fake = &.{
.{ .path = "/tmp" },
.{ .path = "/usr" },
.{ .path = "/usr/lib64" },
.{ .path = "/usr/bin" },
.{ .path = "/usr/bin/ld", .executable = true },
.{ .path = "/lib" },
.{ .path = "/lib/x86_64-linux-gnu" },
.{ .path = "/lib/x86_64-linux-gnu/crt1.o" },
.{ .path = "/lib/x86_64-linux-gnu/crti.o" },
.{ .path = "/lib/x86_64-linux-gnu/crtn.o" },
.{ .path = "/lib64" },
.{ .path = "/usr/lib" },
.{ .path = "/usr/lib/gcc" },
.{ .path = "/usr/lib/gcc/x86_64-linux-gnu" },
.{ .path = "/usr/lib/gcc/x86_64-linux-gnu/9" },
.{ .path = "/usr/lib/gcc/x86_64-linux-gnu/9/crtbegin.o" },
.{ .path = "/usr/lib/gcc/x86_64-linux-gnu/9/crtend.o" },
.{ .path = "/usr/lib/x86_64-linux-gnu" },
.{ .path = "/etc/lsb-release", .contents =
\\DISTRIB_ID=Ubuntu
\\DISTRIB_RELEASE=20.04
\\DISTRIB_CODENAME=focal
\\DISTRIB_DESCRIPTION="Ubuntu 20.04.6 LTS"
\\
},
} } };
defer toolchain.deinit();
try toolchain.discover();
var argv = std.array_list.Managed([]const u8).init(driver.comp.gpa);
defer argv.deinit();
var linker_path_buf: [std.fs.max_path_bytes]u8 = undefined;
const linker_path = try toolchain.getLinkerPath(&linker_path_buf);
try argv.append(linker_path);
try toolchain.buildLinkerArgs(&argv);
const expected = [_][]const u8{
"/usr/bin/ld",
"-z",
"relro",
"--hash-style=gnu",
"--eh-frame-hdr",
"-m",
"elf_x86_64",
"-dynamic-linker",
"/lib64/ld-linux-x86-64.so.2",
"-o",
"a.out",
"/lib/x86_64-linux-gnu/crt1.o",
"/lib/x86_64-linux-gnu/crti.o",
"/usr/lib/gcc/x86_64-linux-gnu/9/crtbegin.o",
"-L/usr/lib/gcc/x86_64-linux-gnu/9",
"-L/usr/lib/gcc/x86_64-linux-gnu/9/../../../../lib64",
"-L/lib/x86_64-linux-gnu",
"-L/lib/../lib64",
"-L/usr/lib/x86_64-linux-gnu",
"-L/usr/lib/../lib64",
"-L/lib",
"-L/usr/lib",
link_obj,
"-lgcc",
"--as-needed",
"-lgcc_s",
"--no-as-needed",
"-lc",
"-lgcc",
"--as-needed",
"-lgcc_s",
"--no-as-needed",
"/usr/lib/gcc/x86_64-linux-gnu/9/crtend.o",
"/lib/x86_64-linux-gnu/crtn.o",
};
try std.testing.expectEqual(expected.len, argv.items.len);
for (expected, argv.items) |expected_item, actual_item| {
try std.testing.expectEqualStrings(expected_item, actual_item);
}
}

12
lib/compiler/aro/assembly_backend.zig vendored Normal file
View File

@ -0,0 +1,12 @@
const std = @import("std");
const aro = @import("aro");
pub const x86_64 = @import("assembly_backend/x86_64.zig");
pub fn genAsm(target: std.Target, tree: *const aro.Tree) aro.Compilation.Error!aro.Assembly {
return switch (target.cpu.arch) {
.x86_64 => x86_64.genAsm(tree),
else => std.debug.panic("genAsm not implemented: {s}", .{@tagName(target.cpu.arch)}),
};
}

View File

@ -0,0 +1,255 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
const assert = std.debug.assert;
const aro = @import("aro");
const Assembly = aro.Assembly;
const Compilation = aro.Compilation;
const Node = Tree.Node;
const Source = aro.Source;
const Tree = aro.Tree;
const QualType = aro.QualType;
const Value = aro.Value;
const AsmCodeGen = @This();
const Error = aro.Compilation.Error;
tree: *const Tree,
comp: *Compilation,
text: *std.Io.Writer,
data: *std.Io.Writer,
const StorageUnit = enum(u8) {
byte = 8,
short = 16,
long = 32,
quad = 64,
fn trunc(self: StorageUnit, val: u64) u64 {
return switch (self) {
.byte => @as(u8, @truncate(val)),
.short => @as(u16, @truncate(val)),
.long => @as(u32, @truncate(val)),
.quad => val,
};
}
};
fn serializeInt(value: u64, storage_unit: StorageUnit, w: *std.Io.Writer) !void {
try w.print(" .{s} 0x{x}\n", .{ @tagName(storage_unit), storage_unit.trunc(value) });
}
fn serializeFloat(comptime T: type, value: T, w: *std.Io.Writer) !void {
switch (T) {
f128 => {
const bytes = std.mem.asBytes(&value);
const first = std.mem.bytesToValue(u64, bytes[0..8]);
try serializeInt(first, .quad, w);
const second = std.mem.bytesToValue(u64, bytes[8..16]);
return serializeInt(second, .quad, w);
},
f80 => {
const bytes = std.mem.asBytes(&value);
const first = std.mem.bytesToValue(u64, bytes[0..8]);
try serializeInt(first, .quad, w);
const second = std.mem.bytesToValue(u16, bytes[8..10]);
try serializeInt(second, .short, w);
return w.writeAll(" .zero 6\n");
},
else => {
const size = @bitSizeOf(T);
const storage_unit = std.meta.intToEnum(StorageUnit, size) catch unreachable;
const IntTy = @Type(.{ .int = .{ .signedness = .unsigned, .bits = size } });
const int_val: IntTy = @bitCast(value);
return serializeInt(int_val, storage_unit, w);
},
}
}
pub fn todo(c: *AsmCodeGen, msg: []const u8, tok: Tree.TokenIndex) Error {
const loc: Source.Location = c.tree.tokens.items(.loc)[tok];
var sf = std.heap.stackFallback(1024, c.comp.gpa);
const allocator = sf.get();
var buf: std.ArrayList(u8) = .empty;
defer buf.deinit(allocator);
try buf.print(allocator, "TODO: {s}", .{msg});
try c.comp.diagnostics.add(.{
.text = buf.items,
.kind = .@"error",
.location = loc.expand(c.comp),
});
return error.FatalError;
}
fn emitAggregate(c: *AsmCodeGen, qt: QualType, node: Node.Index) !void {
_ = qt;
return c.todo("Codegen aggregates", node.tok(c.tree));
}
fn emitSingleValue(c: *AsmCodeGen, qt: QualType, node: Node.Index) !void {
const value = c.tree.value_map.get(node) orelse return;
const bit_size = qt.bitSizeof(c.comp);
const scalar_kind = qt.scalarKind(c.comp);
if (!scalar_kind.isReal()) {
return c.todo("Codegen _Complex values", node.tok(c.tree));
} else if (scalar_kind.isInt()) {
const storage_unit = std.meta.intToEnum(StorageUnit, bit_size) catch return c.todo("Codegen _BitInt values", node.tok(c.tree));
try c.data.print(" .{s} ", .{@tagName(storage_unit)});
_ = try value.print(qt, c.comp, c.data);
try c.data.writeByte('\n');
} else if (scalar_kind.isFloat()) {
switch (bit_size) {
16 => return serializeFloat(f16, value.toFloat(f16, c.comp), c.data),
32 => return serializeFloat(f32, value.toFloat(f32, c.comp), c.data),
64 => return serializeFloat(f64, value.toFloat(f64, c.comp), c.data),
80 => return serializeFloat(f80, value.toFloat(f80, c.comp), c.data),
128 => return serializeFloat(f128, value.toFloat(f128, c.comp), c.data),
else => unreachable,
}
} else if (scalar_kind.isPointer()) {
return c.todo("Codegen pointer", node.tok(c.tree));
} else if (qt.is(c.comp, .array)) {
// Todo:
// Handle truncated initializers e.g. char x[3] = "hello";
// Zero out remaining bytes if initializer is shorter than storage capacity
// Handle non-char strings
const bytes = value.toBytes(c.comp);
const directive = if (bytes.len > bit_size / 8) "ascii" else "string";
try c.data.print(" .{s} ", .{directive});
try Value.printString(bytes, qt, c.comp, c.data);
try c.data.writeByte('\n');
} else unreachable;
}
fn emitValue(c: *AsmCodeGen, qt: QualType, node: Node.Index) !void {
switch (node.get(c.tree)) {
.array_init_expr,
.struct_init_expr,
.union_init_expr,
=> return c.todo("Codegen multiple inits", node.tok(c.tree)),
else => return c.emitSingleValue(qt, node),
}
}
pub fn genAsm(tree: *const Tree) Error!Assembly {
var data: std.Io.Writer.Allocating = .init(tree.comp.gpa);
defer data.deinit();
var text: std.Io.Writer.Allocating = .init(tree.comp.gpa);
defer text.deinit();
var codegen: AsmCodeGen = .{
.tree = tree,
.comp = tree.comp,
.text = &text.writer,
.data = &data.writer,
};
codegen.genDecls() catch |err| switch (err) {
error.WriteFailed => return error.OutOfMemory,
error.OutOfMemory => return error.OutOfMemory,
error.FatalError => return error.FatalError,
};
const text_slice = try text.toOwnedSlice();
errdefer tree.comp.gpa.free(text_slice);
const data_slice = try data.toOwnedSlice();
return .{
.text = text_slice,
.data = data_slice,
};
}
fn genDecls(c: *AsmCodeGen) !void {
if (c.tree.comp.code_gen_options.debug != .strip) {
const sources = c.tree.comp.sources.values();
for (sources) |source| {
try c.data.print(" .file {d} \"{s}\"\n", .{ @intFromEnum(source.id) - 1, source.path });
}
}
for (c.tree.root_decls.items) |decl| {
switch (decl.get(c.tree)) {
.static_assert,
.typedef,
.struct_decl,
.union_decl,
.enum_decl,
=> {},
.function => |function| {
if (function.body == null) continue;
try c.genFn(function);
},
.variable => |variable| try c.genVar(variable),
else => unreachable,
}
}
try c.text.writeAll(" .section .note.GNU-stack,\"\",@progbits\n");
}
fn genFn(c: *AsmCodeGen, function: Node.Function) !void {
return c.todo("Codegen functions", function.name_tok);
}
fn genVar(c: *AsmCodeGen, variable: Node.Variable) !void {
const comp = c.comp;
const qt = variable.qt;
const is_tentative = variable.initializer == null;
const size = qt.sizeofOrNull(comp) orelse blk: {
// tentative array definition assumed to have one element
std.debug.assert(is_tentative and qt.is(c.comp, .array));
break :blk qt.childType(c.comp).sizeof(comp);
};
const name = c.tree.tokSlice(variable.name_tok);
const nat_align = qt.alignof(comp);
const alignment = if (qt.is(c.comp, .array) and size >= 16) @max(16, nat_align) else nat_align;
if (variable.storage_class == .static) {
try c.data.print(" .local \"{s}\"\n", .{name});
} else {
try c.data.print(" .globl \"{s}\"\n", .{name});
}
if (is_tentative and comp.code_gen_options.common) {
try c.data.print(" .comm \"{s}\", {d}, {d}\n", .{ name, size, alignment });
return;
}
if (variable.initializer) |init| {
if (variable.thread_local and comp.code_gen_options.data_sections) {
try c.data.print(" .section .tdata.\"{s}\",\"awT\",@progbits\n", .{name});
} else if (variable.thread_local) {
try c.data.writeAll(" .section .tdata,\"awT\",@progbits\n");
} else if (comp.code_gen_options.data_sections) {
try c.data.print(" .section .data.\"{s}\",\"aw\",@progbits\n", .{name});
} else {
try c.data.writeAll(" .data\n");
}
try c.data.print(" .type \"{s}\", @object\n", .{name});
try c.data.print(" .size \"{s}\", {d}\n", .{ name, size });
try c.data.print(" .align {d}\n", .{alignment});
try c.data.print("\"{s}\":\n", .{name});
try c.emitValue(qt, init);
return;
}
if (variable.thread_local and comp.code_gen_options.data_sections) {
try c.data.print(" .section .tbss.\"{s}\",\"awT\",@nobits\n", .{name});
} else if (variable.thread_local) {
try c.data.writeAll(" .section .tbss,\"awT\",@nobits\n");
} else if (comp.code_gen_options.data_sections) {
try c.data.print(" .section .bss.\"{s}\",\"aw\",@nobits\n", .{name});
} else {
try c.data.writeAll(" .bss\n");
}
try c.data.print(" .align {d}\n", .{alignment});
try c.data.print("\"{s}\":\n", .{name});
try c.data.print(" .zero {d}\n", .{size});
}

View File

@ -1,12 +1,23 @@
pub const Assembly = @import("backend/Assembly.zig");
pub const CodeGenOptions = @import("backend/CodeGenOptions.zig");
pub const Interner = @import("backend/Interner.zig");
pub const Ir = @import("backend/Ir.zig");
pub const Object = @import("backend/Object.zig");
pub const CallingConvention = enum {
C,
c,
stdcall,
thiscall,
vectorcall,
fastcall,
regcall,
riscv_vector,
aarch64_sve_pcs,
aarch64_vector_pcs,
arm_aapcs,
arm_aapcs_vfp,
x86_64_sysv,
x86_64_win,
};
pub const version_str = "aro-zig";

20
lib/compiler/aro/backend/Assembly.zig vendored Normal file
View File

@ -0,0 +1,20 @@
const std = @import("std");
const Allocator = std.mem.Allocator;
data: []const u8,
text: []const u8,
const Assembly = @This();
pub fn deinit(self: *const Assembly, gpa: Allocator) void {
gpa.free(self.data);
gpa.free(self.text);
}
pub fn writeToFile(self: Assembly, file: std.fs.File) !void {
var vec: [2]std.posix.iovec_const = .{
.{ .base = self.data.ptr, .len = self.data.len },
.{ .base = self.text.ptr, .len = self.text.len },
};
return file.writevAll(&vec);
}

View File

@ -0,0 +1,94 @@
const std = @import("std");
/// place uninitialized global variables in a common block
common: bool,
/// Place each function into its own section in the output file if the target supports arbitrary sections
func_sections: bool,
/// Place each data item into its own section in the output file if the target supports arbitrary sections
data_sections: bool,
pic_level: PicLevel,
/// Generate position-independent code that can only be linked into executables
is_pie: bool,
optimization_level: OptimizationLevel,
/// Generate debug information
debug: DebugFormat,
dwarf_version: DwarfVersion,
pub const DebugFormat = union(enum) {
strip,
dwarf: std.dwarf.Format,
code_view,
};
pub const DwarfVersion = enum(u3) {
@"0" = 0,
@"2" = 2,
@"3" = 3,
@"4" = 4,
@"5" = 5,
};
pub const PicLevel = enum(u8) {
/// Do not generate position-independent code
none = 0,
/// Generate position-independent code (PIC) suitable for use in a shared library, if supported for the target machine.
one = 1,
/// If supported for the target machine, emit position-independent code, suitable for dynamic linking and avoiding
/// any limit on the size of the global offset table.
two = 2,
};
pub const OptimizationLevel = enum {
@"0",
@"1",
@"2",
@"3",
/// Optimize for size
s,
/// Disregard strict standards compliance
fast,
/// Optimize debugging experience
g,
/// Optimize aggressively for size rather than speed
z,
const level_map = std.StaticStringMap(OptimizationLevel).initComptime(.{
.{ "0", .@"0" },
.{ "1", .@"1" },
.{ "2", .@"2" },
.{ "3", .@"3" },
.{ "s", .s },
.{ "fast", .fast },
.{ "g", .g },
.{ "z", .z },
});
pub fn fromString(str: []const u8) ?OptimizationLevel {
return level_map.get(str);
}
pub fn isSizeOptimized(self: OptimizationLevel) bool {
return switch (self) {
.s, .z => true,
.@"0", .@"1", .@"2", .@"3", .fast, .g => false,
};
}
pub fn hasAnyOptimizations(self: OptimizationLevel) bool {
return switch (self) {
.@"0" => false,
.@"1", .@"2", .@"3", .s, .fast, .g, .z => true,
};
}
};
pub const default: @This() = .{
.common = false,
.func_sections = false,
.data_sections = false,
.pic_level = .none,
.is_pie = false,
.optimization_level = .@"0",
.debug = .strip,
.dwarf_version = .@"0",
};

View File

@ -12,10 +12,10 @@ map: std.AutoArrayHashMapUnmanaged(void, void) = .empty,
items: std.MultiArrayList(struct {
tag: Tag,
data: u32,
}) = .{},
extra: std.ArrayListUnmanaged(u32) = .empty,
limbs: std.ArrayListUnmanaged(Limb) = .empty,
strings: std.ArrayListUnmanaged(u8) = .empty,
}) = .empty,
extra: std.ArrayList(u32) = .empty,
limbs: std.ArrayList(Limb) = .empty,
strings: std.ArrayList(u8) = .empty,
const KeyAdapter = struct {
interner: *const Interner,
@ -65,6 +65,7 @@ pub const Key = union(enum) {
float: Float,
complex: Complex,
bytes: []const u8,
pointer: Pointer,
pub const Float = union(enum) {
f16: f16,
@ -80,6 +81,12 @@ pub const Key = union(enum) {
cf80: [2]f80,
cf128: [2]f128,
};
pub const Pointer = struct {
/// NodeIndex of decl or compound literal whose address we are offsetting from
node: u32,
/// Offset in bytes
offset: Ref,
};
pub fn hash(key: Key) u32 {
var hasher = Hash.init(0);
@ -199,6 +206,10 @@ pub const Key = union(enum) {
}
return null;
}
pub fn toBigInt(key: Key, space: *Tag.Int.BigIntSpace) BigIntConst {
return key.int.toBigInt(space);
}
};
pub const Ref = enum(u32) {
@ -303,6 +314,8 @@ pub const Tag = enum(u8) {
bytes,
/// `data` is `Record`
record_ty,
/// `data` is Pointer
pointer,
pub const Array = struct {
len0: u32,
@ -322,6 +335,11 @@ pub const Tag = enum(u8) {
child: Ref,
};
pub const Pointer = struct {
node: u32,
offset: Ref,
};
pub const Int = struct {
limbs_index: u32,
limbs_len: u32,
@ -606,6 +624,15 @@ pub fn put(i: *Interner, gpa: Allocator, key: Key) !Ref {
}),
});
},
.pointer => |info| {
i.items.appendAssumeCapacity(.{
.tag = .pointer,
.data = try i.addExtra(gpa, Tag.Pointer{
.node = info.node,
.offset = info.offset,
}),
});
},
.int => |repr| int: {
var space: Tag.Int.BigIntSpace = undefined;
const big = repr.toBigInt(&space);
@ -792,6 +819,13 @@ pub fn get(i: *const Interner, ref: Ref) Key {
.child = vector_ty.child,
} };
},
.pointer => {
const pointer = i.extraData(Tag.Pointer, data);
return .{ .pointer = .{
.node = pointer.node,
.offset = pointer.offset,
} };
},
.u32 => .{ .int = .{ .u64 = data } },
.i32 => .{ .int = .{ .i64 = @as(i32, @bitCast(data)) } },
.int_positive, .int_negative => {

View File

@ -11,7 +11,7 @@ decls: std.StringArrayHashMapUnmanaged(Decl),
pub const Decl = struct {
instructions: std.MultiArrayList(Inst),
body: std.ArrayListUnmanaged(Ref),
body: std.ArrayList(Ref),
arena: std.heap.ArenaAllocator.State,
pub fn deinit(decl: *Decl, gpa: Allocator) void {
@ -27,8 +27,8 @@ pub const Builder = struct {
interner: *Interner,
decls: std.StringArrayHashMapUnmanaged(Decl) = .empty,
instructions: std.MultiArrayList(Ir.Inst) = .{},
body: std.ArrayListUnmanaged(Ref) = .empty,
instructions: std.MultiArrayList(Ir.Inst) = .empty,
body: std.ArrayList(Ref) = .empty,
alloc_count: u32 = 0,
arg_count: u32 = 0,
current_label: Ref = undefined,
@ -380,23 +380,24 @@ const REF = std.Io.tty.Color.bright_blue;
const LITERAL = std.Io.tty.Color.bright_green;
const ATTRIBUTE = std.Io.tty.Color.bright_yellow;
const RefMap = std.AutoArrayHashMap(Ref, void);
const RefMap = std.AutoArrayHashMapUnmanaged(Ref, void);
pub fn dump(ir: *const Ir, gpa: Allocator, config: std.Io.tty.Config, w: anytype) !void {
pub fn dump(ir: *const Ir, gpa: Allocator, config: std.Io.tty.Config, w: *std.Io.Writer) !void {
for (ir.decls.keys(), ir.decls.values()) |name, *decl| {
try ir.dumpDecl(decl, gpa, name, config, w);
}
try w.flush();
}
fn dumpDecl(ir: *const Ir, decl: *const Decl, gpa: Allocator, name: []const u8, config: std.Io.tty.Config, w: anytype) !void {
fn dumpDecl(ir: *const Ir, decl: *const Decl, gpa: Allocator, name: []const u8, config: std.Io.tty.Config, w: *std.Io.Writer) !void {
const tags = decl.instructions.items(.tag);
const data = decl.instructions.items(.data);
var ref_map = RefMap.init(gpa);
defer ref_map.deinit();
var ref_map: RefMap = .empty;
defer ref_map.deinit(gpa);
var label_map = RefMap.init(gpa);
defer label_map.deinit();
var label_map: RefMap = .empty;
defer label_map.deinit(gpa);
const ret_inst = decl.body.items[decl.body.items.len - 1];
const ret_operand = data[@intFromEnum(ret_inst)].un;
@ -412,14 +413,14 @@ fn dumpDecl(ir: *const Ir, decl: *const Decl, gpa: Allocator, name: []const u8,
const ref = decl.body.items[arg_count];
if (tags[@intFromEnum(ref)] != .arg) break;
if (arg_count != 0) try w.writeAll(", ");
try ref_map.put(ref, {});
try ref_map.put(gpa, ref, {});
try ir.writeRef(decl, &ref_map, ref, config, w);
try config.setColor(w, .reset);
}
try w.writeAll(") {\n");
for (decl.body.items[arg_count..]) |ref| {
switch (tags[@intFromEnum(ref)]) {
.label => try label_map.put(ref, {}),
.label => try label_map.put(gpa, ref, {}),
else => {},
}
}
@ -460,7 +461,7 @@ fn dumpDecl(ir: *const Ir, decl: *const Decl, gpa: Allocator, name: []const u8,
},
.select => {
const br = data[i].branch;
try ir.writeNewRef(decl, &ref_map, ref, config, w);
try ir.writeNewRef(gpa, decl, &ref_map, ref, config, w);
try w.writeAll("select ");
try ir.writeRef(decl, &ref_map, br.cond, config, w);
try config.setColor(w, .reset);
@ -500,7 +501,7 @@ fn dumpDecl(ir: *const Ir, decl: *const Decl, gpa: Allocator, name: []const u8,
},
.call => {
const call = data[i].call;
try ir.writeNewRef(decl, &ref_map, ref, config, w);
try ir.writeNewRef(gpa, decl, &ref_map, ref, config, w);
try w.writeAll("call ");
try ir.writeRef(decl, &ref_map, call.func, config, w);
try config.setColor(w, .reset);
@ -514,7 +515,7 @@ fn dumpDecl(ir: *const Ir, decl: *const Decl, gpa: Allocator, name: []const u8,
},
.alloc => {
const alloc = data[i].alloc;
try ir.writeNewRef(decl, &ref_map, ref, config, w);
try ir.writeNewRef(gpa, decl, &ref_map, ref, config, w);
try w.writeAll("alloc ");
try config.setColor(w, ATTRIBUTE);
try w.writeAll("size ");
@ -527,7 +528,7 @@ fn dumpDecl(ir: *const Ir, decl: *const Decl, gpa: Allocator, name: []const u8,
try w.writeByte('\n');
},
.phi => {
try ir.writeNewRef(decl, &ref_map, ref, config, w);
try ir.writeNewRef(gpa, decl, &ref_map, ref, config, w);
try w.writeAll("phi");
try config.setColor(w, .reset);
try w.writeAll(" {");
@ -559,7 +560,7 @@ fn dumpDecl(ir: *const Ir, decl: *const Decl, gpa: Allocator, name: []const u8,
try w.writeByte('\n');
},
.load => {
try ir.writeNewRef(decl, &ref_map, ref, config, w);
try ir.writeNewRef(gpa, decl, &ref_map, ref, config, w);
try w.writeAll("load ");
try ir.writeRef(decl, &ref_map, data[i].un, config, w);
try w.writeByte('\n');
@ -582,7 +583,7 @@ fn dumpDecl(ir: *const Ir, decl: *const Decl, gpa: Allocator, name: []const u8,
.mod,
=> {
const bin = data[i].bin;
try ir.writeNewRef(decl, &ref_map, ref, config, w);
try ir.writeNewRef(gpa, decl, &ref_map, ref, config, w);
try w.print("{s} ", .{@tagName(tag)});
try ir.writeRef(decl, &ref_map, bin.lhs, config, w);
try config.setColor(w, .reset);
@ -597,7 +598,7 @@ fn dumpDecl(ir: *const Ir, decl: *const Decl, gpa: Allocator, name: []const u8,
.sext,
=> {
const un = data[i].un;
try ir.writeNewRef(decl, &ref_map, ref, config, w);
try ir.writeNewRef(gpa, decl, &ref_map, ref, config, w);
try w.print("{s} ", .{@tagName(tag)});
try ir.writeRef(decl, &ref_map, un, config, w);
try w.writeByte('\n');
@ -609,7 +610,7 @@ fn dumpDecl(ir: *const Ir, decl: *const Decl, gpa: Allocator, name: []const u8,
try w.writeAll("}\n\n");
}
fn writeType(ir: Ir, ty_ref: Interner.Ref, config: std.Io.tty.Config, w: anytype) !void {
fn writeType(ir: Ir, ty_ref: Interner.Ref, config: std.Io.tty.Config, w: *std.Io.Writer) !void {
const ty = ir.interner.get(ty_ref);
try config.setColor(w, TYPE);
switch (ty) {
@ -639,7 +640,7 @@ fn writeType(ir: Ir, ty_ref: Interner.Ref, config: std.Io.tty.Config, w: anytype
}
}
fn writeValue(ir: Ir, val: Interner.Ref, config: std.Io.tty.Config, w: anytype) !void {
fn writeValue(ir: Ir, val: Interner.Ref, config: std.Io.tty.Config, w: *std.Io.Writer) !void {
try config.setColor(w, LITERAL);
const key = ir.interner.get(val);
switch (key) {
@ -650,12 +651,12 @@ fn writeValue(ir: Ir, val: Interner.Ref, config: std.Io.tty.Config, w: anytype)
.float => |repr| switch (repr) {
inline else => |x| return w.print("{d}", .{@as(f64, @floatCast(x))}),
},
.bytes => |b| return std.zig.stringEscape(b, "", .{}, w),
.bytes => |b| return std.zig.stringEscape(b, w),
else => unreachable, // not a value
}
}
fn writeRef(ir: Ir, decl: *const Decl, ref_map: *RefMap, ref: Ref, config: std.Io.tty.Config, w: anytype) !void {
fn writeRef(ir: Ir, decl: *const Decl, ref_map: *RefMap, ref: Ref, config: std.Io.tty.Config, w: *std.Io.Writer) !void {
assert(ref != .none);
const index = @intFromEnum(ref);
const ty_ref = decl.instructions.items(.ty)[index];
@ -678,8 +679,8 @@ fn writeRef(ir: Ir, decl: *const Decl, ref_map: *RefMap, ref: Ref, config: std.I
try w.print(" %{d}", .{ref_index});
}
fn writeNewRef(ir: Ir, decl: *const Decl, ref_map: *RefMap, ref: Ref, config: std.Io.tty.Config, w: anytype) !void {
try ref_map.put(ref, {});
fn writeNewRef(ir: Ir, gpa: Allocator, decl: *const Decl, ref_map: *RefMap, ref: Ref, config: std.Io.tty.Config, w: *std.Io.Writer) !void {
try ref_map.put(gpa, ref, {});
try w.writeAll(" ");
try ir.writeRef(decl, ref_map, ref, config, w);
try config.setColor(w, .reset);
@ -687,7 +688,7 @@ fn writeNewRef(ir: Ir, decl: *const Decl, ref_map: *RefMap, ref: Ref, config: st
try config.setColor(w, INST);
}
fn writeLabel(decl: *const Decl, label_map: *RefMap, ref: Ref, config: std.Io.tty.Config, w: anytype) !void {
fn writeLabel(decl: *const Decl, label_map: *RefMap, ref: Ref, config: std.Io.tty.Config, w: *std.Io.Writer) !void {
assert(ref != .none);
const index = @intFromEnum(ref);
const label = decl.instructions.items(.data)[index].label;

View File

@ -30,7 +30,7 @@ pub const Section = union(enum) {
custom: []const u8,
};
pub fn getSection(obj: *Object, section: Section) !*std.array_list.Managed(u8) {
pub fn getSection(obj: *Object, section: Section) !*std.ArrayList(u8) {
switch (obj.format) {
.elf => return @as(*Elf, @alignCast(@fieldParentPtr("obj", obj))).getSection(section),
else => unreachable,
@ -65,9 +65,9 @@ pub fn addRelocation(obj: *Object, name: []const u8, section: Section, address:
}
}
pub fn finish(obj: *Object, file: std.fs.File) !void {
pub fn finish(obj: *Object, w: *std.Io.Writer) !void {
switch (obj.format) {
.elf => return @as(*Elf, @alignCast(@fieldParentPtr("obj", obj))).finish(file),
.elf => return @as(*Elf, @alignCast(@fieldParentPtr("obj", obj))).finish(w),
else => unreachable,
}
}

View File

@ -4,8 +4,8 @@ const Target = std.Target;
const Object = @import("../Object.zig");
const Section = struct {
data: std.array_list.Managed(u8),
relocations: std.ArrayListUnmanaged(Relocation) = .empty,
data: std.ArrayList(u8) = .empty,
relocations: std.ArrayList(Relocation) = .empty,
flags: u64,
type: u32,
index: u16 = undefined,
@ -58,7 +58,7 @@ pub fn deinit(elf: *Elf) void {
{
var it = elf.sections.valueIterator();
while (it.next()) |sect| {
sect.*.data.deinit();
sect.*.data.deinit(gpa);
sect.*.relocations.deinit(gpa);
}
}
@ -80,12 +80,12 @@ fn sectionString(sec: Object.Section) []const u8 {
};
}
pub fn getSection(elf: *Elf, section_kind: Object.Section) !*std.array_list.Managed(u8) {
pub fn getSection(elf: *Elf, section_kind: Object.Section) !*std.ArrayList(u8) {
const section_name = sectionString(section_kind);
const section = elf.sections.get(section_name) orelse blk: {
const section = try elf.arena.allocator().create(Section);
section.* = .{
.data = std.array_list.Managed(u8).init(elf.arena.child_allocator),
.data = std.ArrayList(u8).init(elf.arena.child_allocator),
.type = std.elf.SHT_PROGBITS,
.flags = switch (section_kind) {
.func, .custom => std.elf.SHF_ALLOC + std.elf.SHF_EXECINSTR,
@ -170,12 +170,8 @@ pub fn addRelocation(elf: *Elf, name: []const u8, section_kind: Object.Section,
/// relocations
/// strtab
/// section headers
pub fn finish(elf: *Elf, file: std.fs.File) !void {
var file_buffer: [1024]u8 = undefined;
var file_writer = file.writer(&file_buffer);
const w = &file_writer.interface;
var num_sections: std.elf.Elf64_Half = additional_sections;
pub fn finish(elf: *Elf, w: *std.Io.Writer) !void {
var num_sections: std.elf.Half = additional_sections;
var relocations_len: std.elf.Elf64_Off = 0;
var sections_len: std.elf.Elf64_Off = 0;
{
@ -196,8 +192,9 @@ pub fn finish(elf: *Elf, file: std.fs.File) !void {
const strtab_offset = rela_offset + relocations_len;
const sh_offset = strtab_offset + elf.strtab_len;
const sh_offset_aligned = std.mem.alignForward(u64, sh_offset, 16);
const endian = elf.obj.target.cpu.arch.endian();
const elf_header = std.elf.Elf64_Ehdr{
const elf_header: std.elf.Elf64_Ehdr = .{
.e_ident = .{ 0x7F, 'E', 'L', 'F', 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
.e_type = std.elf.ET.REL, // we only produce relocatables
.e_machine = elf.obj.target.toElfMachine(),
@ -213,7 +210,7 @@ pub fn finish(elf: *Elf, file: std.fs.File) !void {
.e_shnum = num_sections,
.e_shstrndx = strtab_index,
};
try w.writeStruct(elf_header);
try w.writeStruct(elf_header, endian);
// write contents of sections
{
@ -222,13 +219,13 @@ pub fn finish(elf: *Elf, file: std.fs.File) !void {
}
// pad to 8 bytes
try w.writeByteNTimes(0, @intCast(symtab_offset_aligned - symtab_offset));
try w.splatByteAll(0, @intCast(symtab_offset_aligned - symtab_offset));
var name_offset: u32 = strtab_default.len;
// write symbols
{
// first symbol must be null
try w.writeStruct(std.mem.zeroes(std.elf.Elf64_Sym));
try w.writeStruct(std.mem.zeroes(std.elf.Elf64_Sym), endian);
var sym_index: u16 = 1;
var it = elf.local_symbols.iterator();
@ -241,7 +238,7 @@ pub fn finish(elf: *Elf, file: std.fs.File) !void {
.st_shndx = if (sym.section) |some| some.index else 0,
.st_value = sym.offset,
.st_size = sym.size,
});
}, endian);
sym.index = sym_index;
sym_index += 1;
name_offset += @intCast(entry.key_ptr.len + 1); // +1 for null byte
@ -256,7 +253,7 @@ pub fn finish(elf: *Elf, file: std.fs.File) !void {
.st_shndx = if (sym.section) |some| some.index else 0,
.st_value = sym.offset,
.st_size = sym.size,
});
}, endian);
sym.index = sym_index;
sym_index += 1;
name_offset += @intCast(entry.key_ptr.len + 1); // +1 for null byte
@ -272,7 +269,7 @@ pub fn finish(elf: *Elf, file: std.fs.File) !void {
.r_offset = rela.offset,
.r_addend = rela.addend,
.r_info = (@as(u64, rela.symbol.index) << 32) | rela.type,
});
}, endian);
}
}
}
@ -294,13 +291,13 @@ pub fn finish(elf: *Elf, file: std.fs.File) !void {
}
// pad to 16 bytes
try w.writeByteNTimes(0, @intCast(sh_offset_aligned - sh_offset));
try w.splatByteAll(0, @intCast(sh_offset_aligned - sh_offset));
// mandatory null header
try w.writeStruct(std.mem.zeroes(std.elf.Elf64_Shdr));
try w.writeStruct(std.mem.zeroes(std.elf.Elf64_Shdr), endian);
// write strtab section header
{
const sect_header = std.elf.Elf64_Shdr{
const sect_header: std.elf.Elf64_Shdr = .{
.sh_name = strtab_name,
.sh_type = std.elf.SHT_STRTAB,
.sh_flags = 0,
@ -312,12 +309,12 @@ pub fn finish(elf: *Elf, file: std.fs.File) !void {
.sh_addralign = 1,
.sh_entsize = 0,
};
try w.writeStruct(sect_header);
try w.writeStruct(sect_header, endian);
}
// write symtab section header
{
const sect_header = std.elf.Elf64_Shdr{
const sect_header: std.elf.Elf64_Shdr = .{
.sh_name = symtab_name,
.sh_type = std.elf.SHT_SYMTAB,
.sh_flags = 0,
@ -329,7 +326,7 @@ pub fn finish(elf: *Elf, file: std.fs.File) !void {
.sh_addralign = 8,
.sh_entsize = @sizeOf(std.elf.Elf64_Sym),
};
try w.writeStruct(sect_header);
try w.writeStruct(sect_header, endian);
}
// remaining section headers
@ -352,7 +349,7 @@ pub fn finish(elf: *Elf, file: std.fs.File) !void {
.sh_info = 0,
.sh_addralign = if (sect.flags & std.elf.SHF_EXECINSTR != 0) 16 else 1,
.sh_entsize = 0,
});
}, endian);
if (rela_count != 0) {
const size = rela_count * @sizeOf(std.elf.Elf64_Rela);
@ -367,7 +364,7 @@ pub fn finish(elf: *Elf, file: std.fs.File) !void {
.sh_info = sect.index,
.sh_addralign = 8,
.sh_entsize = @sizeOf(std.elf.Elf64_Rela),
});
}, endian);
rela_sect_offset += size;
}

126
lib/compiler/aro/include/float.h vendored Normal file
View File

@ -0,0 +1,126 @@
/* <float.h> for the Aro C compiler */
#pragma once
#undef FLT_RADIX
#define FLT_RADIX __FLT_RADIX__
#undef FLT_MANT_DIG
#define FLT_MANT_DIG __FLT_MANT_DIG__
#undef DBL_MANT_DIG
#define DBL_MANT_DIG __DBL_MANT_DIG__
#undef LDBL_MANT_DIG
#define LDBL_MANT_DIG __LDBL_MANT_DIG__
#if __STDC_VERSION__ >= 199901L
#undef FLT_EVAL_METHOD
#define FLT_EVAL_METHOD __FLT_EVAL_METHOD__
#undef DECIMAL_DIG
#define DECIMAL_DIG __DECIMAL_DIG__
#endif /* __STDC_VERSION__ >= 199901L */
#undef FLT_DIG
#define FLT_DIG __FLT_DIG__
#undef DBL_DIG
#define DBL_DIG __DBL_DIG__
#undef LDBL_DIG
#define LDBL_DIG __LDBL_DIG__
#undef FLT_MIN_EXP
#define FLT_MIN_EXP __FLT_MIN_EXP__
#undef DBL_MIN_EXP
#define DBL_MIN_EXP __DBL_MIN_EXP__
#undef LDBL_MIN_EXP
#define LDBL_MIN_EXP __LDBL_MIN_EXP__
#undef FLT_MIN_10_EXP
#define FLT_MIN_10_EXP __FLT_MIN_10_EXP__
#undef DBL_MIN_10_EXP
#define DBL_MIN_10_EXP __DBL_MIN_10_EXP__
#undef LDBL_MIN_10_EXP
#define LDBL_MIN_10_EXP __LDBL_MIN_10_EXP__
#undef FLT_MAX_EXP
#define FLT_MAX_EXP __FLT_MAX_EXP__
#undef DBL_MAX_EXP
#define DBL_MAX_EXP __DBL_MAX_EXP__
#undef LDBL_MAX_EXP
#define LDBL_MAX_EXP __LDBL_MAX_EXP__
#undef FLT_MAX_10_EXP
#define FLT_MAX_10_EXP __FLT_MAX_10_EXP__
#undef DBL_MAX_10_EXP
#define DBL_MAX_10_EXP __DBL_MAX_10_EXP__
#undef LDBL_MAX_10_EXP
#define LDBL_MAX_10_EXP __LDBL_MAX_10_EXP__
#undef FLT_MAX
#define FLT_MAX __FLT_MAX__
#undef DBL_MAX
#define DBL_MAX __DBL_MAX__
#undef LDBL_MAX
#define LDBL_MAX __LDBL_MAX__
#undef FLT_EPSILON
#define FLT_EPSILON __FLT_EPSILON__
#undef DBL_EPSILON
#define DBL_EPSILON __DBL_EPSILON__
#undef LDBL_EPSILON
#define LDBL_EPSILON __LDBL_EPSILON__
#undef FLT_MIN
#define FLT_MIN __FLT_MIN__
#undef DBL_MIN
#define DBL_MIN __DBL_MIN__
#undef LDBL_MIN
#define LDBL_MIN __LDBL_MIN__
#if __STDC_VERSION__ >= 201112L
#undef FLT_TRUE_MIN
#define FLT_TRUE_MIN __FLT_DENORM_MIN__
#undef DBL_TRUE_MIN
#define DBL_TRUE_MIN __DBL_DENORM_MIN__
#undef LDBL_TRUE_MIN
#define LDBL_TRUE_MIN __LDBL_DENORM_MIN__
#undef FLT_DECIMAL_DIG
#define FLT_DECIMAL_DIG __FLT_DECIMAL_DIG__
#undef DBL_DECIMAL_DIG
#define DBL_DECIMAL_DIG __DBL_DECIMAL_DIG__
#undef LDBL_DECIMAL_DIG
#define LDBL_DECIMAL_DIG __LDBL_DECIMAL_DIG__
#undef FLT_HAS_SUBNORM
#define FLT_HAS_SUBNORM __FLT_HAS_DENORM__
#undef DBL_HAS_SUBNORM
#define DBL_HAS_SUBNORM __DBL_HAS_DENORM__
#undef LDBL_HAS_SUBNORM
#define LDBL_HAS_SUBNORM __LDBL_HAS_DENORM__
#endif /* __STDC_VERSION__ >= 201112L */

15
lib/compiler/aro/include/iso646.h vendored Normal file
View File

@ -0,0 +1,15 @@
/* <iso646.h> for the Aro C compiler */
#pragma once
#define and &&
#define and_eq &=
#define bitand &
#define bitor |
#define compl ~
#define not !
#define not_eq !=
#define or ||
#define or_eq |=
#define xor ^
#define xor_eq ^=

124
lib/compiler/aro/include/limits.h vendored Normal file
View File

@ -0,0 +1,124 @@
/* <limits.h> for the Aro C compiler */
#pragma once
/* GlibC will try to include_next GCC's limits.h which will fail.
Define _GCC_LIMITS_H_ to prevent it. */
#if defined __GNUC__ && !defined _GCC_LIMITS_H_
#define _GCC_LIMITS_H_
#endif
/* Include the system's limits.h */
#if __STDC_HOSTED__ && __has_include_next(<limits.h>)
#include_next <limits.h>
#endif
#undef SCHAR_MAX
#define SCHAR_MAX __SCHAR_MAX__
#undef SHRT_MAX
#define SHRT_MAX __SHRT_MAX__
#undef INT_MAX
#define INT_MAX __INT_MAX__
#undef LONG_MAX
#define LONG_MAX __LONG_MAX__
#undef SCHAR_MIN
#define SCHAR_MIN (-__SCHAR_MAX__-1)
#undef SHRT_MIN
#define SHRT_MIN (-__SHRT_MAX__ -1)
#undef INT_MIN
#define INT_MIN (-__INT_MAX__ -1)
#undef LONG_MIN
#define LONG_MIN (-__LONG_MAX__ -1L)
#undef UCHAR_MAX
#define UCHAR_MAX (__SCHAR_MAX__*2 +1)
#undef USHRT_MAX
#define USHRT_MAX (__SHRT_MAX__ *2 +1)
#undef UINT_MAX
#define UINT_MAX (__INT_MAX__ *2U +1U)
#undef ULONG_MAX
#define ULONG_MAX (__LONG_MAX__ *2UL+1UL)
#ifndef MB_LEN_MAX
#define MB_LEN_MAX 1
#endif
#undef CHAR_BIT
#define CHAR_BIT __CHAR_BIT__
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
#undef BOOL_WIDTH
#define BOOL_WIDTH __BOOL_WIDTH__
#undef CHAR_WIDTH
#define CHAR_WIDTH CHAR_BIT
#undef SCHAR_WIDTH
#define SCHAR_WIDTH CHAR_BIT
#undef UCHAR_WIDTH
#define UCHAR_WIDTH CHAR_BIT
#undef USHRT_WIDTH
#define USHRT_WIDTH __SHRT_WIDTH__
#undef SHRT_WIDTH
#define SHRT_WIDTH __SHRT_WIDTH__
#undef UINT_WIDTH
#define UINT_WIDTH __INT_WIDTH__
#undef INT_WIDTH
#define INT_WIDTH __INT_WIDTH__
#undef ULONG_WIDTH
#define ULONG_WIDTH __LONG_WIDTH__
#undef LONG_WIDTH
#define LONG_WIDTH __LONG_WIDTH__
#undef ULLONG_WIDTH
#define ULLONG_WIDTH __LLONG_WIDTH__
#undef LLONG_WIDTH
#define LLONG_WIDTH __LLONG_WIDTH__
#undef BITINT_MAXWIDTH
#define BITINT_MAXWIDTH __BITINT_MAXWIDTH__
#endif /* defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L */
#undef CHAR_MIN
#undef CHAR_MAX
#ifdef __CHAR_UNSIGNED__
#define CHAR_MIN 0
#define CHAR_MAX UCHAR_MAX
#else
#define CHAR_MIN SCHAR_MIN
#define CHAR_MAX __SCHAR_MAX__
#endif
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
#undef LLONG_MIN
#define LLONG_MIN (-__LONG_LONG_MAX__-1LL)
#undef LLONG_MAX
#define LLONG_MAX __LONG_LONG_MAX__
#undef ULLONG_MAX
#define ULLONG_MAX (__LONG_LONG_MAX__*2ULL+1ULL)
#endif

11
lib/compiler/aro/include/stdalign.h vendored Normal file
View File

@ -0,0 +1,11 @@
/* <stdalign.h> for the Aro C compiler */
#pragma once
#if __STDC_VERSION__ < 202311L
#define alignas _Alignas
#define alignof _Alignof
#define __alignas_is_defined 1
#define __alignof_is_defined 1
#endif

28
lib/compiler/aro/include/stdarg.h vendored Normal file
View File

@ -0,0 +1,28 @@
/* <stdarg.h> for the Aro C compiler */
#pragma once
/* Todo: Set to 202311L once header is compliant with C23 */
#define __STDC_VERSION_STDARG_H__ 0
typedef __builtin_va_list va_list;
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202000L
/* C23 no longer requires the second parameter */
#define va_start(ap, ...) __builtin_va_start(ap, __VA_ARGS__)
#else
#define va_start(ap, param) __builtin_va_start(ap, param)
#endif
#define va_end(ap) __builtin_va_end(ap)
#define va_arg(ap, type) __builtin_va_arg(ap, type)
/* GCC and Clang always define __va_copy */
#define __va_copy(d, s) __builtin_va_copy(d, s)
/* but va_copy only on c99+ or when strict ansi mode is turned off */
#if __STDC_VERSION__ >= 199901L || !defined(__STRICT_ANSI__)
#define va_copy(d, s) __builtin_va_copy(d, s)
#endif
#ifndef __GNUC_VA_LIST
#define __GNUC_VA_LIST 1
typedef __builtin_va_list __gnuc_va_list;
#endif

138
lib/compiler/aro/include/stdatomic.h vendored Normal file
View File

@ -0,0 +1,138 @@
/* <stdatomic.h> for the Aro C compiler */
#pragma once
#define __STDC_VERSION_STDATOMIC_H__ 202311L
#if __STDC_HOSTED__ && __has_include_next(<stdatomic.h>)
#include_next <stdatomic.h>
#else
#include <stddef.h>
#include <stdint.h>
#define ATOMIC_BOOL_LOCK_FREE __ATOMIC_BOOL_LOCK_FREE
#define ATOMIC_CHAR_LOCK_FREE __ATOMIC_CHAR_LOCK_FREE
#define ATOMIC_CHAR16_T_LOCK_FREE __ATOMIC_CHAR16_T_LOCK_FREE
#define ATOMIC_CHAR32_T_LOCK_FREE __ATOMIC_CHAR32_T_LOCK_FREE
#define ATOMIC_WCHAR_T_LOCK_FREE __ATOMIC_WCHAR_T_LOCK_FREE
#define ATOMIC_SHORT_LOCK_FREE __ATOMIC_SHORT_LOCK_FREE
#define ATOMIC_INT_LOCK_FREE __ATOMIC_INT_LOCK_FREE
#define ATOMIC_LONG_LOCK_FREE __ATOMIC_LONG_LOCK_FREE
#define ATOMIC_LLONG_LOCK_FREE __ATOMIC_LLONG_LOCK_FREE
#define ATOMIC_POINTER_LOCK_FREE __ATOMIC_POINTER_LOCK_FREE
#if defined(__ATOMIC_CHAR8_T_LOCK_FREE)
#define ATOMIC_CHAR8_T_LOCK_FREE __ATOMIC_CHAR8_T_LOCK_FREE
#endif
#if __STDC_VERSION__ < 202311L
/* ATOMIC_VAR_INIT was removed in C23 */
#define ATOMIC_VAR_INIT(value) (value)
#endif
#define atomic_init __c11_atomic_init
typedef enum memory_order {
memory_order_relaxed = __ATOMIC_RELAXED,
memory_order_consume = __ATOMIC_CONSUME,
memory_order_acquire = __ATOMIC_ACQUIRE,
memory_order_release = __ATOMIC_RELEASE,
memory_order_acq_rel = __ATOMIC_ACQ_REL,
memory_order_seq_cst = __ATOMIC_SEQ_CST
} memory_order;
#define kill_dependency(y) (y)
void atomic_thread_fence(memory_order);
void atomic_signal_fence(memory_order);
#define atomic_thread_fence(order) __c11_atomic_thread_fence(order)
#define atomic_signal_fence(order) __c11_atomic_signal_fence(order)
#define atomic_is_lock_free(obj) __c11_atomic_is_lock_free(sizeof(*(obj)))
typedef _Atomic(_Bool) atomic_bool;
typedef _Atomic(char) atomic_char;
typedef _Atomic(signed char) atomic_schar;
typedef _Atomic(unsigned char) atomic_uchar;
typedef _Atomic(short) atomic_short;
typedef _Atomic(unsigned short) atomic_ushort;
typedef _Atomic(int) atomic_int;
typedef _Atomic(unsigned int) atomic_uint;
typedef _Atomic(long) atomic_long;
typedef _Atomic(unsigned long) atomic_ulong;
typedef _Atomic(long long) atomic_llong;
typedef _Atomic(unsigned long long) atomic_ullong;
typedef _Atomic(uint_least16_t) atomic_char16_t;
typedef _Atomic(uint_least32_t) atomic_char32_t;
typedef _Atomic(wchar_t) atomic_wchar_t;
typedef _Atomic(int_least8_t) atomic_int_least8_t;
typedef _Atomic(uint_least8_t) atomic_uint_least8_t;
typedef _Atomic(int_least16_t) atomic_int_least16_t;
typedef _Atomic(uint_least16_t) atomic_uint_least16_t;
typedef _Atomic(int_least32_t) atomic_int_least32_t;
typedef _Atomic(uint_least32_t) atomic_uint_least32_t;
typedef _Atomic(int_least64_t) atomic_int_least64_t;
typedef _Atomic(uint_least64_t) atomic_uint_least64_t;
typedef _Atomic(int_fast8_t) atomic_int_fast8_t;
typedef _Atomic(uint_fast8_t) atomic_uint_fast8_t;
typedef _Atomic(int_fast16_t) atomic_int_fast16_t;
typedef _Atomic(uint_fast16_t) atomic_uint_fast16_t;
typedef _Atomic(int_fast32_t) atomic_int_fast32_t;
typedef _Atomic(uint_fast32_t) atomic_uint_fast32_t;
typedef _Atomic(int_fast64_t) atomic_int_fast64_t;
typedef _Atomic(uint_fast64_t) atomic_uint_fast64_t;
typedef _Atomic(intptr_t) atomic_intptr_t;
typedef _Atomic(uintptr_t) atomic_uintptr_t;
typedef _Atomic(size_t) atomic_size_t;
typedef _Atomic(ptrdiff_t) atomic_ptrdiff_t;
typedef _Atomic(intmax_t) atomic_intmax_t;
typedef _Atomic(uintmax_t) atomic_uintmax_t;
#define atomic_store(object, desired) __c11_atomic_store(object, desired, __ATOMIC_SEQ_CST)
#define atomic_store_explicit __c11_atomic_store
#define atomic_load(object) __c11_atomic_load(object, __ATOMIC_SEQ_CST)
#define atomic_load_explicit __c11_atomic_load
#define atomic_exchange(object, desired) __c11_atomic_exchange(object, desired, __ATOMIC_SEQ_CST)
#define atomic_exchange_explicit __c11_atomic_exchange
#define atomic_compare_exchange_strong(object, expected, desired) __c11_atomic_compare_exchange_strong(object, expected, desired, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)
#define atomic_compare_exchange_strong_explicit __c11_atomic_compare_exchange_strong
#define atomic_compare_exchange_weak(object, expected, desired) __c11_atomic_compare_exchange_weak(object, expected, desired, __ATOMIC_SEQ_CST, __ATOMIC_SEQ_CST)
#define atomic_compare_exchange_weak_explicit __c11_atomic_compare_exchange_weak
#define atomic_fetch_add(object, operand) __c11_atomic_fetch_add(object, operand, __ATOMIC_SEQ_CST)
#define atomic_fetch_add_explicit __c11_atomic_fetch_add
#define atomic_fetch_sub(object, operand) __c11_atomic_fetch_sub(object, operand, __ATOMIC_SEQ_CST)
#define atomic_fetch_sub_explicit __c11_atomic_fetch_sub
#define atomic_fetch_or(object, operand) __c11_atomic_fetch_or(object, operand, __ATOMIC_SEQ_CST)
#define atomic_fetch_or_explicit __c11_atomic_fetch_or
#define atomic_fetch_xor(object, operand) __c11_atomic_fetch_xor(object, operand, __ATOMIC_SEQ_CST)
#define atomic_fetch_xor_explicit __c11_atomic_fetch_xor
#define atomic_fetch_and(object, operand) __c11_atomic_fetch_and(object, operand, __ATOMIC_SEQ_CST)
#define atomic_fetch_and_explicit __c11_atomic_fetch_and
typedef struct atomic_flag { atomic_bool _Value; } atomic_flag;
#define ATOMIC_FLAG_INIT { 0 }
_Bool atomic_flag_test_and_set(volatile atomic_flag *);
_Bool atomic_flag_test_and_set_explicit(volatile atomic_flag *, memory_order);
void atomic_flag_clear(volatile atomic_flag *);
void atomic_flag_clear_explicit(volatile atomic_flag *, memory_order);
#define atomic_flag_test_and_set(object) __c11_atomic_exchange(&(object)->_Value, 1, __ATOMIC_SEQ_CST)
#define atomic_flag_test_and_set_explicit(object, order) __c11_atomic_exchange(&(object)->_Value, 1, order)
#define atomic_flag_clear(object) __c11_atomic_store(&(object)->_Value, 0, __ATOMIC_SEQ_CST)
#define atomic_flag_clear_explicit(object, order) __c11_atomic_store(&(object)->_Value, 0, order)
#endif

13
lib/compiler/aro/include/stdbool.h vendored Normal file
View File

@ -0,0 +1,13 @@
/* <stdbool.h> for the Aro C compiler */
#pragma once
#if __STDC_VERSION__ < 202311L
#define bool _Bool
#define true 1
#define false 0
#define __bool_true_false_are_defined 1
#endif

9
lib/compiler/aro/include/stdckdint.h vendored Normal file
View File

@ -0,0 +1,9 @@
/* <stdckdint.h> for the Aro C compiler */
#pragma once
#define __STDC_VERSION_STDCKDINT_H__ 202311L
#define ckd_add(result, a, b) __builtin_add_overflow(a, b, result)
#define ckd_sub(result, a, b) __builtin_sub_overflow(a, b, result)
#define ckd_mul(result, a, b) __builtin_mul_overflow(a, b, result)

31
lib/compiler/aro/include/stddef.h vendored Normal file
View File

@ -0,0 +1,31 @@
/* <stddef.h> for the Aro C compiler */
#pragma once
#define __STDC_VERSION_STDDEF_H__ 202311L
typedef __PTRDIFF_TYPE__ ptrdiff_t;
typedef __SIZE_TYPE__ size_t;
typedef __WCHAR_TYPE__ wchar_t;
/* define max_align_t to match GCC and Clang */
typedef struct {
long long __aro_max_align_ll;
long double __aro_max_align_ld;
} max_align_t;
#define NULL ((void*)0)
#define offsetof(T, member) __builtin_offsetof(T, member)
#if __STDC_VERSION__ >= 202311L
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Wpre-c23-compat"
typedef typeof(nullptr) nullptr_t;
# pragma GCC diagnostic pop
# if defined unreachable
# error unreachable() is a standard macro in C23
# else
# define unreachable() __builtin_unreachable()
# endif
#endif

289
lib/compiler/aro/include/stdint.h vendored Normal file
View File

@ -0,0 +1,289 @@
/* <stdint.h> for the Aro C compiler */
#pragma once
#if __STDC_HOSTED__ && __has_include_next(<stdint.h>)
# include_next <stdint.h>
#else
#define __stdint_int_c_cat(X, Y) X ## Y
#define __stdint_int_c(V, SUFFIX) __stdint_int_c_cat(V, SUFFIX)
#define __stdint_uint_c(V, SUFFIX) __stdint_int_c_cat(V##U, SUFFIX)
#define INTPTR_MIN (-__INTPTR_MAX__-1)
#define INTPTR_MAX __INTPTR_MAX__
#define UINTPTR_MAX __UINTPTR_MAX__
#define PTRDIFF_MIN (-__PTRDIFF_MAX__-1)
#define PTRDIFF_MAX __PTRDIFF_MAX__
#define SIZE_MAX __SIZE_MAX__
#define INTMAX_MIN (-__INTMAX_MAX__-1)
#define INTMAX_MAX __INTMAX_MAX__
#define UINTMAX_MAX __UINTMAX_MAX__
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
# define INTPTR_WIDTH __INTPTR_WIDTH__
# define UINTPTR_WIDTH __UINTPTR_WIDTH__
# define INTMAX_WIDTH __INTMAX_WIDTH__
# define UINTMAX_WIDTH __UINTMAX_WIDTH__
# define PTRDIFF_WIDTH __PTRDIFF_WIDTH__
# define SIZE_WIDTH __SIZE_WIDTH__
# define WCHAR_WIDTH __WCHAR_WIDTH__
#endif
typedef __INTMAX_TYPE__ intmax_t;
typedef __UINTMAX_TYPE__ uintmax_t;
#ifndef _INTPTR_T
# ifndef __intptr_t_defined
typedef __INTPTR_TYPE__ intptr_t;
# define __intptr_t_defined
# define _INTPTR_T
# endif
#endif
#ifndef _UINTPTR_T
typedef __UINTPTR_TYPE__ uintptr_t;
# define _UINTPTR_T
#endif
#ifdef __INT64_TYPE__
# ifndef __int8_t_defined /* glibc sys/types.h also defines int64_t*/
typedef __INT64_TYPE__ int64_t;
# endif /* __int8_t_defined */
typedef __UINT64_TYPE__ uint64_t;
# undef __int64_c_suffix
# undef __int32_c_suffix
# undef __int16_c_suffix
# undef __int8_c_suffix
# ifdef __INT64_C_SUFFIX__
# define __int64_c_suffix __INT64_C_SUFFIX__
# define __int32_c_suffix __INT64_C_SUFFIX__
# define __int16_c_suffix __INT64_C_SUFFIX__
# define __int8_c_suffix __INT64_C_SUFFIX__
# endif /* __INT64_C_SUFFIX__ */
# ifdef __int64_c_suffix
# define INT64_C(v) (__stdint_int_c(v, __int64_c_suffix))
# define UINT64_C(v) (__stdint_uint_c(v, __int64_c_suffix))
# else
# define INT64_C(v) (v)
# define UINT64_C(v) (v ## U)
# endif /* __int64_c_suffix */
# define INT64_MAX INT64_C( 9223372036854775807)
# define INT64_MIN (-INT64_C( 9223372036854775807)-1)
# define UINT64_MAX UINT64_C(18446744073709551615)
# if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
# define UINT64_WIDTH 64
# define INT64_WIDTH UINT64_WIDTH
# endif /* __STDC_VERSION__ */
#endif /* __INT64_TYPE__ */
#ifdef __INT32_TYPE__
# ifndef __int8_t_defined /* glibc sys/types.h also defines int32_t*/
typedef __INT32_TYPE__ int32_t;
# endif /* __int8_t_defined */
typedef __UINT32_TYPE__ uint32_t;
# undef __int32_c_suffix
# undef __int16_c_suffix
# undef __int8_c_suffix
# ifdef __INT32_C_SUFFIX__
# define __int32_c_suffix __INT32_C_SUFFIX__
# define __int16_c_suffix __INT32_C_SUFFIX__
# define __int8_c_suffix __INT32_C_SUFFIX__
# endif /* __INT32_C_SUFFIX__ */
# ifdef __int32_c_suffix
# define INT32_C(v) (__stdint_int_c(v, __int32_c_suffix))
# define UINT32_C(v) (__stdint_uint_c(v, __int32_c_suffix))
# else
# define INT32_C(v) (v)
# define UINT32_C(v) (v ## U)
# endif /* __int32_c_suffix */
# define INT32_MAX INT32_C( 2147483647)
# define INT32_MIN (-INT32_C( 2147483647)-1)
# define UINT32_MAX UINT32_C(4294967295)
# if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
# define UINT32_WIDTH 32
# define INT32_WIDTH UINT32_WIDTH
# endif /* __STDC_VERSION__ */
#endif /* __INT32_TYPE__ */
#ifdef __INT16_TYPE__
# ifndef __int8_t_defined /* glibc sys/types.h also defines int16_t*/
typedef __INT16_TYPE__ int16_t;
# endif /* __int8_t_defined */
typedef __UINT16_TYPE__ uint16_t;
# undef __int16_c_suffix
# undef __int8_c_suffix
# ifdef __INT16_C_SUFFIX__
# define __int16_c_suffix __INT16_C_SUFFIX__
# define __int8_c_suffix __INT16_C_SUFFIX__
# endif /* __INT16_C_SUFFIX__ */
# ifdef __int16_c_suffix
# define INT16_C(v) (__stdint_int_c(v, __int16_c_suffix))
# define UINT16_C(v) (__stdint_uint_c(v, __int16_c_suffix))
# else
# define INT16_C(v) (v)
# define UINT16_C(v) (v ## U)
# endif /* __int16_c_suffix */
# define INT16_MAX INT16_C( 32767)
# define INT16_MIN (-INT16_C( 32767)-1)
# define UINT16_MAX UINT16_C(65535)
# if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
# define UINT16_WIDTH 16
# define INT16_WIDTH UINT16_WIDTH
# endif /* __STDC_VERSION__ */
#endif /* __INT16_TYPE__ */
#ifdef __INT8_TYPE__
# ifndef __int8_t_defined /* glibc sys/types.h also defines int8_t*/
typedef __INT8_TYPE__ int8_t;
# endif /* __int8_t_defined */
typedef __UINT8_TYPE__ uint8_t;
# undef __int8_c_suffix
# ifdef __INT8_C_SUFFIX__
# define __int8_c_suffix __INT8_C_SUFFIX__
# endif /* __INT8_C_SUFFIX__ */
# ifdef __int8_c_suffix
# define INT8_C(v) (__stdint_int_c(v, __int8_c_suffix))
# define UINT8_C(v) (__stdint_uint_c(v, __int8_c_suffix))
# else
# define INT8_C(v) (v)
# define UINT8_C(v) (v ## U)
# endif /* __int8_c_suffix */
# define INT8_MAX INT8_C(127)
# define INT8_MIN (-INT8_C(127)-1)
# define UINT8_MAX UINT8_C(255)
# if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
# define UINT8_WIDTH 8
# define INT8_WIDTH UINT8_WIDTH
# endif /* __STDC_VERSION__ */
#endif /* __INT8_TYPE__ */
typedef __INT_LEAST64_TYPE__ int_least64_t;
typedef __INT_LEAST32_TYPE__ int_least32_t;
typedef __INT_LEAST16_TYPE__ int_least16_t;
typedef __INT_LEAST8_TYPE__ int_least8_t;
typedef __UINT_LEAST64_TYPE__ uint_least64_t;
typedef __UINT_LEAST32_TYPE__ uint_least32_t;
typedef __UINT_LEAST16_TYPE__ uint_least16_t;
typedef __UINT_LEAST8_TYPE__ uint_least8_t;
#define INT_LEAST8_MAX __INT_LEAST8_MAX__
#define INT_LEAST8_MIN (-__INT_LEAST8_MAX__-1)
#define UINT_LEAST8_MAX __UINT_LEAST8_MAX__
#define INT_LEAST16_MAX __INT_LEAST16_MAX__
#define INT_LEAST16_MIN (-__INT_LEAST16_MAX__-1)
#define UINT_LEAST16_MAX __UINT_LEAST16_MAX__
#define INT_LEAST32_MAX __INT_LEAST32_MAX__
#define INT_LEAST32_MIN (-__INT_LEAST32_MAX__-1)
#define UINT_LEAST32_MAX __UINT_LEAST32_MAX__
#define INT_LEAST64_MAX __INT_LEAST64_MAX__
#define INT_LEAST64_MIN (-__INT_LEAST64_MAX__-1)
#define UINT_LEAST64_MAX __UINT_LEAST64_MAX__
typedef __INT_FAST64_TYPE__ int_fast64_t;
typedef __INT_FAST32_TYPE__ int_fast32_t;
typedef __INT_FAST16_TYPE__ int_fast16_t;
typedef __INT_FAST8_TYPE__ int_fast8_t;
typedef __UINT_FAST64_TYPE__ uint_fast64_t;
typedef __UINT_FAST32_TYPE__ uint_fast32_t;
typedef __UINT_FAST16_TYPE__ uint_fast16_t;
typedef __UINT_FAST8_TYPE__ uint_fast8_t;
#define INT_FAST8_MAX __INT_FAST8_MAX__
#define INT_FAST8_MIN (-__INT_FAST8_MAX__-1)
#define UINT_FAST8_MAX __UINT_FAST8_MAX__
#define INT_FAST16_MAX __INT_FAST16_MAX__
#define INT_FAST16_MIN (-__INT_FAST16_MAX__-1)
#define UINT_FAST16_MAX __UINT_FAST16_MAX__
#define INT_FAST32_MAX __INT_FAST32_MAX__
#define INT_FAST32_MIN (-__INT_FAST32_MAX__-1)
#define UINT_FAST32_MAX __UINT_FAST32_MAX__
#define INT_FAST64_MAX __INT_FAST64_MAX__
#define INT_FAST64_MIN (-__INT_FAST64_MAX__-1)
#define UINT_FAST64_MAX __UINT_FAST64_MAX__
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
#define INT_FAST8_WIDTH __INT_FAST8_WIDTH__
#define UINT_FAST8_WIDTH __INT_FAST8_WIDTH__
#define INT_LEAST8_WIDTH __INT_LEAST8_WIDTH__
#define UINT_LEAST8_WIDTH __INT_LEAST8_WIDTH__
#define INT_FAST16_WIDTH __INT_FAST16_WIDTH__
#define UINT_FAST16_WIDTH __INT_FAST16_WIDTH__
#define INT_LEAST16_WIDTH __INT_LEAST16_WIDTH__
#define UINT_LEAST16_WIDTH __INT_LEAST16_WIDTH__
#define INT_FAST32_WIDTH __INT_FAST32_WIDTH__
#define UINT_FAST32_WIDTH __INT_FAST32_WIDTH__
#define INT_LEAST32_WIDTH __INT_LEAST32_WIDTH__
#define UINT_LEAST32_WIDTH __INT_LEAST32_WIDTH__
#define INT_FAST64_WIDTH __INT_FAST64_WIDTH__
#define UINT_FAST64_WIDTH __INT_FAST64_WIDTH__
#define INT_LEAST64_WIDTH __INT_LEAST64_WIDTH__
#define UINT_LEAST64_WIDTH __INT_LEAST64_WIDTH__
#endif
#ifdef __SIZEOF_INT128__
typedef signed __int128 int128_t;
typedef unsigned __int128 uint128_t;
typedef signed __int128 int_fast128_t;
typedef unsigned __int128 uint_fast128_t;
typedef signed __int128 int_least128_t;
typedef unsigned __int128 uint_least128_t;
# define UINT128_MAX ((uint128_t)-1)
# define INT128_MAX ((int128_t)+(UINT128_MAX/2))
# define INT128_MIN (-INT128_MAX-1)
# define UINT_LEAST128_MAX UINT128_MAX
# define INT_LEAST128_MAX INT128_MAX
# define INT_LEAST128_MIN INT128_MIN
# define UINT_FAST128_MAX UINT128_MAX
# define INT_FAST128_MAX INT128_MAX
# define INT_FAST128_MIN INT128_MIN
# define INT128_WIDTH 128
# define UINT128_WIDTH 128
# define INT_LEAST128_WIDTH 128
# define UINT_LEAST128_WIDTH 128
# define INT_FAST128_WIDTH 128
# define UINT_FAST128_WIDTH 128
# if UINT128_WIDTH > __LLONG_WIDTH__
# define INT128_C(N) ((int_least128_t)+N ## WB)
# define UINT128_C(N) ((uint_least128_t)+N ## WBU)
# else
# define INT128_C(N) ((int_least128_t)+N ## LL)
# define UINT128_C(N) ((uint_least128_t)+N ## LLU)
# endif
#endif
#endif /* __STDC_HOSTED__ && __has_include_next(<stdint.h>) */

View File

@ -0,0 +1,6 @@
/* <stdnoreturn.h> for the Aro C compiler */
#pragma once
#define noreturn _Noreturn
#define __noreturn_is_defined 1

3
lib/compiler/aro/include/varargs.h vendored Normal file
View File

@ -0,0 +1,3 @@
/* <varargs.h> for the Aro C compiler */
#pragma once
#error please use <stdarg.h> instead of <varargs.h>

80
lib/compiler/aro/main.zig vendored Normal file
View File

@ -0,0 +1,80 @@
const std = @import("std");
const Allocator = mem.Allocator;
const mem = std.mem;
const process = std.process;
const aro = @import("aro");
const Compilation = aro.Compilation;
const Diagnostics = aro.Diagnostics;
const Driver = aro.Driver;
const Toolchain = aro.Toolchain;
const assembly_backend = @import("assembly_backend");
var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){};
pub fn main() u8 {
const gpa = if (@import("builtin").link_libc)
std.heap.raw_c_allocator
else
general_purpose_allocator.allocator();
defer if (!@import("builtin").link_libc) {
_ = general_purpose_allocator.deinit();
};
var arena_instance = std.heap.ArenaAllocator.init(gpa);
defer arena_instance.deinit();
const arena = arena_instance.allocator();
const fast_exit = @import("builtin").mode != .Debug;
const args = process.argsAlloc(arena) catch {
std.debug.print("out of memory\n", .{});
if (fast_exit) process.exit(1);
return 1;
};
const aro_name = std.fs.selfExePathAlloc(gpa) catch {
std.debug.print("unable to find Aro executable path\n", .{});
if (fast_exit) process.exit(1);
return 1;
};
defer gpa.free(aro_name);
var stderr_buf: [1024]u8 = undefined;
var stderr = std.fs.File.stderr().writer(&stderr_buf);
var diagnostics: Diagnostics = .{
.output = .{ .to_writer = .{
.color = .detect(stderr.file),
.writer = &stderr.interface,
} },
};
var comp = Compilation.initDefault(gpa, arena, &diagnostics, std.fs.cwd()) catch |er| switch (er) {
error.OutOfMemory => {
std.debug.print("out of memory\n", .{});
if (fast_exit) process.exit(1);
return 1;
},
};
defer comp.deinit();
var driver: Driver = .{ .comp = &comp, .aro_name = aro_name, .diagnostics = &diagnostics };
defer driver.deinit();
var toolchain: Toolchain = .{ .driver = &driver, .filesystem = .{ .real = comp.cwd } };
defer toolchain.deinit();
driver.main(&toolchain, args, fast_exit, assembly_backend.genAsm) catch |er| switch (er) {
error.OutOfMemory => {
std.debug.print("out of memory\n", .{});
if (fast_exit) process.exit(1);
return 1;
},
error.FatalError => {
driver.printDiagnosticsStats();
if (fast_exit) process.exit(1);
return 1;
},
};
if (fast_exit) process.exit(@intFromBool(comp.diagnostics.errors != 0));
return @intFromBool(diagnostics.errors != 0);
}

File diff suppressed because it is too large Load Diff

View File

@ -120,7 +120,20 @@ pub fn main() !void {
defer aro_arena_state.deinit();
const aro_arena = aro_arena_state.allocator();
var comp = aro.Compilation.init(aro_arena, std.fs.cwd());
var stderr_buf: [512]u8 = undefined;
var stderr_writer = stderr.writer(&stderr_buf);
var diagnostics: aro.Diagnostics = switch (zig_integration) {
false => .{ .output = .{ .to_writer = .{
.writer = &stderr_writer.interface,
.color = stderr_config,
} } },
true => .{ .output = .{ .to_list = .{
.arena = .init(allocator),
} } },
};
defer diagnostics.deinit();
var comp = aro.Compilation.init(aro_arena, aro_arena, &diagnostics, std.fs.cwd());
defer comp.deinit();
var argv: std.ArrayList([]const u8) = .empty;
@ -145,18 +158,22 @@ pub fn main() !void {
preprocess.preprocess(&comp, &preprocessed_buf.writer, argv.items, maybe_dependencies) catch |err| switch (err) {
error.GeneratedSourceError => {
try error_handler.emitAroDiagnostics(allocator, "failed during preprocessor setup (this is always a bug):", &comp);
try error_handler.emitAroDiagnostics(allocator, "failed during preprocessor setup (this is always a bug)", &comp);
std.process.exit(1);
},
// ArgError can occur if e.g. the .rc file is not found
error.ArgError, error.PreprocessError => {
try error_handler.emitAroDiagnostics(allocator, "failed during preprocessing:", &comp);
try error_handler.emitAroDiagnostics(allocator, "failed during preprocessing", &comp);
std.process.exit(1);
},
error.StreamTooLong => {
error.FileTooBig => {
try error_handler.emitMessage(allocator, .err, "failed during preprocessing: maximum file size exceeded", .{});
std.process.exit(1);
},
error.WriteFailed => {
try error_handler.emitMessage(allocator, .err, "failed during preprocessing: error writing the preprocessed output", .{});
std.process.exit(1);
},
error.OutOfMemory => |e| return e,
};
@ -660,11 +677,10 @@ const ErrorHandler = union(enum) {
try server.serveErrorBundle(error_bundle);
},
.tty => {
// extra newline to separate this line from the aro errors
// aro errors have already been emitted
const stderr = std.debug.lockStderrWriter(&.{});
defer std.debug.unlockStderrWriter();
try renderErrorMessage(stderr, self.tty, .err, "{s}\n", .{fail_msg});
aro.Diagnostics.render(comp, self.tty);
try renderErrorMessage(stderr, self.tty, .err, "{s}", .{fail_msg});
},
}
}
@ -883,12 +899,10 @@ fn aroDiagnosticsToErrorBundle(
.msg = try bundle.addString(fail_msg),
});
var msg_writer = MsgWriter.init(gpa);
defer msg_writer.deinit();
var cur_err: ?ErrorBundle.ErrorMessage = null;
var cur_notes: std.ArrayList(ErrorBundle.ErrorMessage) = .empty;
defer cur_notes.deinit(gpa);
for (comp.diagnostics.list.items) |msg| {
for (comp.diagnostics.output.to_list.messages.items) |msg| {
switch (msg.kind) {
// Clear the current error so that notes don't bleed into unassociated errors
.off, .warning => {
@ -897,28 +911,19 @@ fn aroDiagnosticsToErrorBundle(
},
.note => if (cur_err == null) continue,
.@"fatal error", .@"error" => {},
.default => unreachable,
}
msg_writer.resetRetainingCapacity();
aro.Diagnostics.renderMessage(comp, &msg_writer, msg);
const src_loc = src_loc: {
if (msg_writer.path) |src_path| {
var src_loc: ErrorBundle.SourceLocation = .{
.src_path = try bundle.addString(src_path),
.line = msg_writer.line - 1, // 1-based -> 0-based
.column = msg_writer.col - 1, // 1-based -> 0-based
.span_start = 0,
.span_main = 0,
.span_end = 0,
};
if (msg_writer.source_line) |source_line| {
src_loc.span_start = msg_writer.span_main;
src_loc.span_main = msg_writer.span_main;
src_loc.span_end = msg_writer.span_main;
src_loc.source_line = try bundle.addString(source_line);
}
break :src_loc try bundle.addSourceLocation(src_loc);
if (msg.location) |location| {
break :src_loc try bundle.addSourceLocation(.{
.src_path = try bundle.addString(location.path),
.line = location.line_no - 1, // 1-based -> 0-based
.column = location.col - 1, // 1-based -> 0-based
.span_start = location.width,
.span_main = location.width,
.span_end = location.width,
.source_line = try bundle.addString(location.line),
});
}
break :src_loc ErrorBundle.SourceLocationIndex.none;
};
@ -929,7 +934,7 @@ fn aroDiagnosticsToErrorBundle(
try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items);
}
cur_err = .{
.msg = try bundle.addString(msg_writer.buf.items),
.msg = try bundle.addString(msg.text),
.src_loc = src_loc,
};
cur_notes.clearRetainingCapacity();
@ -937,11 +942,11 @@ fn aroDiagnosticsToErrorBundle(
.note => {
cur_err.?.notes_len += 1;
try cur_notes.append(gpa, .{
.msg = try bundle.addString(msg_writer.buf.items),
.msg = try bundle.addString(msg.text),
.src_loc = src_loc,
});
},
.off, .warning, .default => unreachable,
.off, .warning => unreachable,
}
}
if (cur_err) |err| {
@ -950,63 +955,3 @@ fn aroDiagnosticsToErrorBundle(
return try bundle.toOwnedBundle("");
}
// Similar to aro.Diagnostics.MsgWriter but:
// - Writers to an ArrayList
// - Only prints the message itself (no location, source line, error: prefix, etc)
// - Keeps track of source path/line/col instead
const MsgWriter = struct {
buf: std.array_list.Managed(u8),
path: ?[]const u8 = null,
// 1-indexed
line: u32 = undefined,
col: u32 = undefined,
source_line: ?[]const u8 = null,
span_main: u32 = undefined,
fn init(allocator: std.mem.Allocator) MsgWriter {
return .{
.buf = std.array_list.Managed(u8).init(allocator),
};
}
fn deinit(m: *MsgWriter) void {
m.buf.deinit();
}
fn resetRetainingCapacity(m: *MsgWriter) void {
m.buf.clearRetainingCapacity();
m.path = null;
m.source_line = null;
}
pub fn print(m: *MsgWriter, comptime fmt: []const u8, args: anytype) void {
m.buf.print(fmt, args) catch {};
}
pub fn write(m: *MsgWriter, msg: []const u8) void {
m.buf.appendSlice(msg) catch {};
}
pub fn setColor(m: *MsgWriter, color: std.Io.tty.Color) void {
_ = m;
_ = color;
}
pub fn location(m: *MsgWriter, path: []const u8, line: u32, col: u32) void {
m.path = path;
m.line = line;
m.col = col;
}
pub fn start(m: *MsgWriter, kind: aro.Diagnostics.Kind) void {
_ = m;
_ = kind;
}
pub fn end(m: *MsgWriter, maybe_line: ?[]const u8, col: u32, end_with_splice: bool) void {
_ = end_with_splice;
m.source_line = maybe_line;
m.span_main = col;
}
};

View File

@ -5,7 +5,7 @@ const cli = @import("cli.zig");
const Dependencies = @import("compile.zig").Dependencies;
const aro = @import("aro");
const PreprocessError = error{ ArgError, GeneratedSourceError, PreprocessError, StreamTooLong, OutOfMemory };
const PreprocessError = error{ ArgError, GeneratedSourceError, PreprocessError, FileTooBig, OutOfMemory, WriteFailed };
pub fn preprocess(
comp: *aro.Compilation,
@ -16,18 +16,18 @@ pub fn preprocess(
) PreprocessError!void {
try comp.addDefaultPragmaHandlers();
var driver: aro.Driver = .{ .comp = comp, .aro_name = "arocc" };
var driver: aro.Driver = .{ .comp = comp, .diagnostics = comp.diagnostics, .aro_name = "arocc" };
defer driver.deinit();
var macro_buf: std.Io.Writer.Allocating = .init(comp.gpa);
defer macro_buf.deinit();
var macro_buf: std.ArrayListUnmanaged(u8) = .empty;
defer macro_buf.deinit(comp.gpa);
var trash: [64]u8 = undefined;
var discarding: std.Io.Writer.Discarding = .init(&trash);
_ = driver.parseArgs(&discarding.writer, &macro_buf.writer, argv) catch |err| switch (err) {
var discard_buffer: [64]u8 = undefined;
var discarding: std.Io.Writer.Discarding = .init(&discard_buffer);
_ = driver.parseArgs(&discarding.writer, &macro_buf, argv) catch |err| switch (err) {
error.FatalError => return error.ArgError,
error.OutOfMemory => |e| return e,
error.WriteFailed => return error.OutOfMemory,
error.WriteFailed => unreachable,
};
if (hasAnyErrors(comp)) return error.ArgError;
@ -37,7 +37,7 @@ pub fn preprocess(
error.FatalError => return error.GeneratedSourceError,
else => |e| return e,
};
const user_macros = comp.addSourceFromBuffer("<command line>", macro_buf.written()) catch |err| switch (err) {
const user_macros = comp.addSourceFromBuffer("<command line>", macro_buf.items) catch |err| switch (err) {
error.FatalError => return error.GeneratedSourceError,
else => |e| return e,
};
@ -46,7 +46,10 @@ pub fn preprocess(
if (hasAnyErrors(comp)) return error.GeneratedSourceError;
comp.generated_buf.items.len = 0;
var pp = try aro.Preprocessor.initDefault(comp);
var pp = aro.Preprocessor.initDefault(comp) catch |err| switch (err) {
error.FatalError => return error.GeneratedSourceError,
error.OutOfMemory => |e| return e,
};
defer pp.deinit();
if (comp.langopts.ms_extensions) {
@ -79,16 +82,7 @@ pub fn preprocess(
}
fn hasAnyErrors(comp: *aro.Compilation) bool {
// In theory we could just check Diagnostics.errors != 0, but that only
// gets set during rendering of the error messages, see:
// https://github.com/Vexu/arocc/issues/603
for (comp.diagnostics.list.items) |msg| {
switch (msg.kind) {
.@"fatal error", .@"error" => return true,
else => {},
}
}
return false;
return comp.diagnostics.errors != 0;
}
/// `arena` is used for temporary -D argument strings and the INCLUDE environment variable.
@ -98,6 +92,7 @@ pub fn appendAroArgs(arena: Allocator, argv: *std.ArrayList([]const u8), options
"-E",
"--comments",
"-fuse-line-directives",
"-fgnuc-version=4.2.1",
"--target=x86_64-windows-msvc",
"--emulate=msvc",
"-nostdinc",

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,288 @@
const std = @import("std");
const mem = std.mem;
const assert = std.debug.assert;
const aro = @import("aro");
const CToken = aro.Tokenizer.Token;
const helpers = @import("helpers.zig");
const Translator = @import("Translator.zig");
const Error = Translator.Error;
pub const MacroProcessingError = Error || error{UnexpectedMacroToken};
const Impl = std.meta.DeclEnum(std.zig.c_translation.helpers);
const Template = struct { []const u8, Impl };
/// Templates must be function-like macros
/// first element is macro source, second element is the name of the function
/// in __helpers which implements it
const templates = [_]Template{
.{ "f_SUFFIX(X) (X ## f)", .F_SUFFIX },
.{ "F_SUFFIX(X) (X ## F)", .F_SUFFIX },
.{ "u_SUFFIX(X) (X ## u)", .U_SUFFIX },
.{ "U_SUFFIX(X) (X ## U)", .U_SUFFIX },
.{ "l_SUFFIX(X) (X ## l)", .L_SUFFIX },
.{ "L_SUFFIX(X) (X ## L)", .L_SUFFIX },
.{ "ul_SUFFIX(X) (X ## ul)", .UL_SUFFIX },
.{ "uL_SUFFIX(X) (X ## uL)", .UL_SUFFIX },
.{ "Ul_SUFFIX(X) (X ## Ul)", .UL_SUFFIX },
.{ "UL_SUFFIX(X) (X ## UL)", .UL_SUFFIX },
.{ "ll_SUFFIX(X) (X ## ll)", .LL_SUFFIX },
.{ "LL_SUFFIX(X) (X ## LL)", .LL_SUFFIX },
.{ "ull_SUFFIX(X) (X ## ull)", .ULL_SUFFIX },
.{ "uLL_SUFFIX(X) (X ## uLL)", .ULL_SUFFIX },
.{ "Ull_SUFFIX(X) (X ## Ull)", .ULL_SUFFIX },
.{ "ULL_SUFFIX(X) (X ## ULL)", .ULL_SUFFIX },
.{ "f_SUFFIX(X) X ## f", .F_SUFFIX },
.{ "F_SUFFIX(X) X ## F", .F_SUFFIX },
.{ "u_SUFFIX(X) X ## u", .U_SUFFIX },
.{ "U_SUFFIX(X) X ## U", .U_SUFFIX },
.{ "l_SUFFIX(X) X ## l", .L_SUFFIX },
.{ "L_SUFFIX(X) X ## L", .L_SUFFIX },
.{ "ul_SUFFIX(X) X ## ul", .UL_SUFFIX },
.{ "uL_SUFFIX(X) X ## uL", .UL_SUFFIX },
.{ "Ul_SUFFIX(X) X ## Ul", .UL_SUFFIX },
.{ "UL_SUFFIX(X) X ## UL", .UL_SUFFIX },
.{ "ll_SUFFIX(X) X ## ll", .LL_SUFFIX },
.{ "LL_SUFFIX(X) X ## LL", .LL_SUFFIX },
.{ "ull_SUFFIX(X) X ## ull", .ULL_SUFFIX },
.{ "uLL_SUFFIX(X) X ## uLL", .ULL_SUFFIX },
.{ "Ull_SUFFIX(X) X ## Ull", .ULL_SUFFIX },
.{ "ULL_SUFFIX(X) X ## ULL", .ULL_SUFFIX },
.{ "CAST_OR_CALL(X, Y) (X)(Y)", .CAST_OR_CALL },
.{ "CAST_OR_CALL(X, Y) ((X)(Y))", .CAST_OR_CALL },
.{
\\wl_container_of(ptr, sample, member) \
\\(__typeof__(sample))((char *)(ptr) - \
\\ offsetof(__typeof__(*sample), member))
,
.WL_CONTAINER_OF,
},
.{ "IGNORE_ME(X) ((void)(X))", .DISCARD },
.{ "IGNORE_ME(X) (void)(X)", .DISCARD },
.{ "IGNORE_ME(X) ((const void)(X))", .DISCARD },
.{ "IGNORE_ME(X) (const void)(X)", .DISCARD },
.{ "IGNORE_ME(X) ((volatile void)(X))", .DISCARD },
.{ "IGNORE_ME(X) (volatile void)(X)", .DISCARD },
.{ "IGNORE_ME(X) ((const volatile void)(X))", .DISCARD },
.{ "IGNORE_ME(X) (const volatile void)(X)", .DISCARD },
.{ "IGNORE_ME(X) ((volatile const void)(X))", .DISCARD },
.{ "IGNORE_ME(X) (volatile const void)(X)", .DISCARD },
};
const Pattern = struct {
slicer: MacroSlicer,
impl: Impl,
fn init(pl: *Pattern, allocator: mem.Allocator, template: Template) Error!void {
const source = template[0];
const impl = template[1];
var tok_list: std.ArrayList(CToken) = .empty;
defer tok_list.deinit(allocator);
pl.* = .{
.slicer = try tokenizeMacro(allocator, source, &tok_list),
.impl = impl,
};
}
fn deinit(pl: *Pattern, allocator: mem.Allocator) void {
allocator.free(pl.slicer.tokens);
pl.* = undefined;
}
/// This function assumes that `ms` has already been validated to contain a function-like
/// macro, and that the parsed template macro in `pl` also contains a function-like
/// macro. Please review this logic carefully if changing that assumption. Two
/// function-like macros are considered equivalent if and only if they contain the same
/// list of tokens, modulo parameter names.
fn matches(pat: Pattern, ms: MacroSlicer) bool {
if (ms.params != pat.slicer.params) return false;
if (ms.tokens.len != pat.slicer.tokens.len) return false;
for (ms.tokens, pat.slicer.tokens) |macro_tok, pat_tok| {
if (macro_tok.id != pat_tok.id) return false;
switch (macro_tok.id) {
.macro_param, .macro_param_no_expand => {
// `.end` is the parameter index.
if (macro_tok.end != pat_tok.end) return false;
},
.identifier, .extended_identifier, .string_literal, .char_literal, .pp_num => {
const macro_bytes = ms.slice(macro_tok);
const pattern_bytes = pat.slicer.slice(pat_tok);
if (!mem.eql(u8, pattern_bytes, macro_bytes)) return false;
},
else => {
// other tags correspond to keywords and operators that do not contain a "payload"
// that can vary
},
}
}
return true;
}
};
const PatternList = @This();
patterns: []Pattern,
pub const MacroSlicer = struct {
source: []const u8,
tokens: []const CToken,
params: u32,
fn slice(pl: MacroSlicer, token: CToken) []const u8 {
return pl.source[token.start..token.end];
}
};
pub fn init(allocator: mem.Allocator) Error!PatternList {
const patterns = try allocator.alloc(Pattern, templates.len);
for (patterns, templates) |*pattern, template| {
try pattern.init(allocator, template);
}
return .{ .patterns = patterns };
}
pub fn deinit(pl: *PatternList, allocator: mem.Allocator) void {
for (pl.patterns) |*pattern| pattern.deinit(allocator);
allocator.free(pl.patterns);
pl.* = undefined;
}
pub fn match(pl: PatternList, ms: MacroSlicer) Error!?Impl {
for (pl.patterns) |pattern| if (pattern.matches(ms)) return pattern.impl;
return null;
}
fn tokenizeMacro(allocator: mem.Allocator, source: []const u8, tok_list: *std.ArrayList(CToken)) Error!MacroSlicer {
var param_count: u32 = 0;
var param_buf: [8][]const u8 = undefined;
var tokenizer: aro.Tokenizer = .{
.buf = source,
.source = .unused,
.langopts = .{},
};
{
const name_tok = tokenizer.nextNoWS();
assert(name_tok.id == .identifier);
const l_paren = tokenizer.nextNoWS();
assert(l_paren.id == .l_paren);
}
while (true) {
const param = tokenizer.nextNoWS();
if (param.id == .r_paren) break;
assert(param.id == .identifier);
const slice = source[param.start..param.end];
param_buf[param_count] = slice;
param_count += 1;
const comma = tokenizer.nextNoWS();
if (comma.id == .r_paren) break;
assert(comma.id == .comma);
}
outer: while (true) {
const tok = tokenizer.next();
switch (tok.id) {
.whitespace, .comment => continue,
.identifier => {
const slice = source[tok.start..tok.end];
for (param_buf[0..param_count], 0..) |param, i| {
if (std.mem.eql(u8, param, slice)) {
try tok_list.append(allocator, .{
.id = .macro_param,
.source = .unused,
.end = @intCast(i),
});
continue :outer;
}
}
},
.hash_hash => {
if (tok_list.items[tok_list.items.len - 1].id == .macro_param) {
tok_list.items[tok_list.items.len - 1].id = .macro_param_no_expand;
}
},
.nl, .eof => break,
else => {},
}
try tok_list.append(allocator, tok);
}
return .{
.source = source,
.tokens = try tok_list.toOwnedSlice(allocator),
.params = param_count,
};
}
test "Macro matching" {
const testing = std.testing;
const helper = struct {
fn checkMacro(
allocator: mem.Allocator,
pattern_list: PatternList,
source: []const u8,
comptime expected_match: ?Impl,
) !void {
var tok_list: std.ArrayList(CToken) = .empty;
defer tok_list.deinit(allocator);
const ms = try tokenizeMacro(allocator, source, &tok_list);
defer allocator.free(ms.tokens);
const matched = try pattern_list.match(ms);
if (expected_match) |expected| {
try testing.expectEqual(expected, matched);
} else {
try testing.expectEqual(@as(@TypeOf(matched), null), matched);
}
}
};
const allocator = std.testing.allocator;
var pattern_list = try PatternList.init(allocator);
defer pattern_list.deinit(allocator);
try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## F)", .F_SUFFIX);
try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## U)", .U_SUFFIX);
try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## L)", .L_SUFFIX);
try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## LL)", .LL_SUFFIX);
try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## UL)", .UL_SUFFIX);
try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## ULL)", .ULL_SUFFIX);
try helper.checkMacro(allocator, pattern_list,
\\container_of(a, b, c) \
\\(__typeof__(b))((char *)(a) - \
\\ offsetof(__typeof__(*b), c))
, .WL_CONTAINER_OF);
try helper.checkMacro(allocator, pattern_list, "NO_MATCH(X, Y) (X + Y)", null);
try helper.checkMacro(allocator, pattern_list, "CAST_OR_CALL(X, Y) (X)(Y)", .CAST_OR_CALL);
try helper.checkMacro(allocator, pattern_list, "CAST_OR_CALL(X, Y) ((X)(Y))", .CAST_OR_CALL);
try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (void)(X)", .DISCARD);
try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((void)(X))", .DISCARD);
try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (const void)(X)", .DISCARD);
try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((const void)(X))", .DISCARD);
try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (volatile void)(X)", .DISCARD);
try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((volatile void)(X))", .DISCARD);
try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (const volatile void)(X)", .DISCARD);
try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((const volatile void)(X))", .DISCARD);
try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (volatile const void)(X)", .DISCARD);
try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((volatile const void)(X))", .DISCARD);
}

View File

@ -0,0 +1,399 @@
const std = @import("std");
const aro = @import("aro");
const ast = @import("ast.zig");
const Translator = @import("Translator.zig");
const Scope = @This();
pub const SymbolTable = std.StringArrayHashMapUnmanaged(ast.Node);
pub const AliasList = std.ArrayListUnmanaged(struct {
alias: []const u8,
name: []const u8,
});
/// Associates a container (structure or union) with its relevant member functions.
pub const ContainerMemberFns = struct {
container_decl_ptr: *ast.Node,
member_fns: std.ArrayListUnmanaged(*ast.Payload.Func) = .empty,
};
pub const ContainerMemberFnsHashMap = std.AutoArrayHashMapUnmanaged(aro.QualType, ContainerMemberFns);
id: Id,
parent: ?*Scope,
pub const Id = enum {
block,
root,
condition,
loop,
do_loop,
};
/// Used for the scope of condition expressions, for example `if (cond)`.
/// The block is lazily initialized because it is only needed for rare
/// cases of comma operators being used.
pub const Condition = struct {
base: Scope,
block: ?Block = null,
fn getBlockScope(cond: *Condition, t: *Translator) !*Block {
if (cond.block) |*b| return b;
cond.block = try Block.init(t, &cond.base, true);
return &cond.block.?;
}
pub fn deinit(cond: *Condition) void {
if (cond.block) |*b| b.deinit();
}
};
/// Represents an in-progress Node.Block. This struct is stack-allocated.
/// When it is deinitialized, it produces an Node.Block which is allocated
/// into the main arena.
pub const Block = struct {
base: Scope,
translator: *Translator,
statements: std.ArrayListUnmanaged(ast.Node),
variables: AliasList,
mangle_count: u32 = 0,
label: ?[]const u8 = null,
/// By default all variables are discarded, since we do not know in advance if they
/// will be used. This maps the variable's name to the Discard payload, so that if
/// the variable is subsequently referenced we can indicate that the discard should
/// be skipped during the intermediate AST -> Zig AST render step.
variable_discards: std.StringArrayHashMapUnmanaged(*ast.Payload.Discard),
/// When the block corresponds to a function, keep track of the return type
/// so that the return expression can be cast, if necessary
return_type: ?aro.QualType = null,
/// C static local variables are wrapped in a block-local struct. The struct
/// is named `mangle(static_local_ + name)` and the Zig variable within the
/// struct keeps the name of the C variable.
pub const static_local_prefix = "static_local";
/// C extern local variables are wrapped in a block-local struct. The struct
/// is named `mangle(extern_local + name)` and the Zig variable within the
/// struct keeps the name of the C variable.
pub const extern_local_prefix = "extern_local";
pub fn init(t: *Translator, parent: *Scope, labeled: bool) !Block {
var blk: Block = .{
.base = .{
.id = .block,
.parent = parent,
},
.translator = t,
.statements = .empty,
.variables = .empty,
.variable_discards = .empty,
};
if (labeled) {
blk.label = try blk.makeMangledName("blk");
}
return blk;
}
pub fn deinit(block: *Block) void {
block.statements.deinit(block.translator.gpa);
block.variables.deinit(block.translator.gpa);
block.variable_discards.deinit(block.translator.gpa);
block.* = undefined;
}
pub fn complete(block: *Block) !ast.Node {
const arena = block.translator.arena;
if (block.base.parent.?.id == .do_loop) {
// We reserve 1 extra statement if the parent is a do_loop. This is in case of
// do while, we want to put `if (cond) break;` at the end.
const alloc_len = block.statements.items.len + @intFromBool(block.base.parent.?.id == .do_loop);
var stmts = try arena.alloc(ast.Node, alloc_len);
stmts.len = block.statements.items.len;
@memcpy(stmts[0..block.statements.items.len], block.statements.items);
return ast.Node.Tag.block.create(arena, .{
.label = block.label,
.stmts = stmts,
});
}
if (block.statements.items.len == 0) return ast.Node.Tag.empty_block.init();
return ast.Node.Tag.block.create(arena, .{
.label = block.label,
.stmts = try arena.dupe(ast.Node, block.statements.items),
});
}
/// Given the desired name, return a name that does not shadow anything from outer scopes.
/// Inserts the returned name into the scope.
/// The name will not be visible to callers of getAlias.
pub fn reserveMangledName(block: *Block, name: []const u8) ![]const u8 {
return block.createMangledName(name, true, null);
}
/// Same as reserveMangledName, but enables the alias immediately.
pub fn makeMangledName(block: *Block, name: []const u8) ![]const u8 {
return block.createMangledName(name, false, null);
}
pub fn createMangledName(block: *Block, name: []const u8, reservation: bool, prefix_opt: ?[]const u8) ![]const u8 {
const arena = block.translator.arena;
const name_copy = try arena.dupe(u8, name);
const alias_base = if (prefix_opt) |prefix|
try std.fmt.allocPrint(arena, "{s}_{s}", .{ prefix, name })
else
name;
var proposed_name = alias_base;
while (block.contains(proposed_name)) {
block.mangle_count += 1;
proposed_name = try std.fmt.allocPrint(arena, "{s}_{d}", .{ alias_base, block.mangle_count });
}
const new_mangle = try block.variables.addOne(block.translator.gpa);
if (reservation) {
new_mangle.* = .{ .name = name_copy, .alias = name_copy };
} else {
new_mangle.* = .{ .name = name_copy, .alias = proposed_name };
}
return proposed_name;
}
fn getAlias(block: *Block, name: []const u8) ?[]const u8 {
for (block.variables.items) |p| {
if (std.mem.eql(u8, p.name, name))
return p.alias;
}
return block.base.parent.?.getAlias(name);
}
fn localContains(block: *Block, name: []const u8) bool {
for (block.variables.items) |p| {
if (std.mem.eql(u8, p.alias, name))
return true;
}
return false;
}
fn contains(block: *Block, name: []const u8) bool {
if (block.localContains(name))
return true;
return block.base.parent.?.contains(name);
}
pub fn discardVariable(block: *Block, name: []const u8) Translator.Error!void {
const gpa = block.translator.gpa;
const arena = block.translator.arena;
const name_node = try ast.Node.Tag.identifier.create(arena, name);
const discard = try ast.Node.Tag.discard.create(arena, .{ .should_skip = false, .value = name_node });
try block.statements.append(gpa, discard);
try block.variable_discards.putNoClobber(gpa, name, discard.castTag(.discard).?);
}
};
pub const Root = struct {
base: Scope,
translator: *Translator,
sym_table: SymbolTable,
blank_macros: std.StringArrayHashMapUnmanaged(void),
nodes: std.ArrayListUnmanaged(ast.Node),
container_member_fns_map: ContainerMemberFnsHashMap,
pub fn init(t: *Translator) Root {
return .{
.base = .{
.id = .root,
.parent = null,
},
.translator = t,
.sym_table = .empty,
.blank_macros = .empty,
.nodes = .empty,
.container_member_fns_map = .empty,
};
}
pub fn deinit(root: *Root) void {
root.sym_table.deinit(root.translator.gpa);
root.blank_macros.deinit(root.translator.gpa);
root.nodes.deinit(root.translator.gpa);
for (root.container_member_fns_map.values()) |*members| {
members.member_fns.deinit(root.translator.gpa);
}
root.container_member_fns_map.deinit(root.translator.gpa);
}
/// Check if the global scope contains this name, without looking into the "future", e.g.
/// ignore the preprocessed decl and macro names.
pub fn containsNow(root: *Root, name: []const u8) bool {
return root.sym_table.contains(name);
}
/// Check if the global scope contains the name, includes all decls that haven't been translated yet.
pub fn contains(root: *Root, name: []const u8) bool {
return root.containsNow(name) or root.translator.global_names.contains(name) or root.translator.weak_global_names.contains(name);
}
pub fn addMemberFunction(root: *Root, func_ty: aro.Type.Func, func: *ast.Payload.Func) !void {
std.debug.assert(func.data.name != null);
if (func_ty.params.len == 0) return;
const param1_base = func_ty.params[0].qt.base(root.translator.comp);
const container_qt = if (param1_base.type == .pointer)
param1_base.type.pointer.child.base(root.translator.comp).qt
else
param1_base.qt;
if (root.container_member_fns_map.getPtr(container_qt)) |members| {
try members.member_fns.append(root.translator.gpa, func);
}
}
pub fn processContainerMemberFns(root: *Root) !void {
const gpa = root.translator.gpa;
const arena = root.translator.arena;
var member_names: std.StringArrayHashMapUnmanaged(u32) = .empty;
defer member_names.deinit(gpa);
for (root.container_member_fns_map.values()) |members| {
member_names.clearRetainingCapacity();
const decls_ptr = switch (members.container_decl_ptr.tag()) {
.@"struct", .@"union" => blk_record: {
const payload: *ast.Payload.Container = @alignCast(@fieldParentPtr("base", members.container_decl_ptr.ptr_otherwise));
// Avoid duplication with field names
for (payload.data.fields) |field| {
try member_names.put(gpa, field.name, 0);
}
break :blk_record &payload.data.decls;
},
.opaque_literal => blk_opaque: {
const container_decl = try ast.Node.Tag.@"opaque".create(arena, .{
.layout = .none,
.fields = &.{},
.decls = &.{},
});
members.container_decl_ptr.* = container_decl;
break :blk_opaque &container_decl.castTag(.@"opaque").?.data.decls;
},
else => return,
};
const old_decls = decls_ptr.*;
const new_decls = try arena.alloc(ast.Node, old_decls.len + members.member_fns.items.len);
@memcpy(new_decls[0..old_decls.len], old_decls);
// Assume the allocator of payload.data.decls is arena,
// so don't add arena.free(old_variables).
const func_ref_vars = new_decls[old_decls.len..];
var count: u32 = 0;
for (members.member_fns.items) |func| {
const func_name = func.data.name.?;
const last_index = std.mem.lastIndexOf(u8, func_name, "_");
const last_name = if (last_index) |index| func_name[index + 1 ..] else continue;
var same_count: u32 = 0;
const gop = try member_names.getOrPutValue(gpa, last_name, same_count);
if (gop.found_existing) {
gop.value_ptr.* += 1;
same_count = gop.value_ptr.*;
}
const var_name = if (same_count == 0)
last_name
else
try std.fmt.allocPrint(arena, "{s}{d}", .{ last_name, same_count });
func_ref_vars[count] = try ast.Node.Tag.pub_var_simple.create(arena, .{
.name = var_name,
.init = try ast.Node.Tag.identifier.create(arena, func_name),
});
count += 1;
}
decls_ptr.* = new_decls[0 .. old_decls.len + count];
}
}
};
pub fn findBlockScope(inner: *Scope, t: *Translator) !*Block {
var scope = inner;
while (true) {
switch (scope.id) {
.root => unreachable,
.block => return @fieldParentPtr("base", scope),
.condition => return @as(*Condition, @fieldParentPtr("base", scope)).getBlockScope(t),
else => scope = scope.parent.?,
}
}
}
pub fn findBlockReturnType(inner: *Scope) aro.QualType {
var scope = inner;
while (true) {
switch (scope.id) {
.root => unreachable,
.block => {
const block: *Block = @fieldParentPtr("base", scope);
if (block.return_type) |qt| return qt;
scope = scope.parent.?;
},
else => scope = scope.parent.?,
}
}
}
pub fn getAlias(scope: *Scope, name: []const u8) ?[]const u8 {
return switch (scope.id) {
.root => null,
.block => @as(*Block, @fieldParentPtr("base", scope)).getAlias(name),
.loop, .do_loop, .condition => scope.parent.?.getAlias(name),
};
}
fn contains(scope: *Scope, name: []const u8) bool {
return switch (scope.id) {
.root => @as(*Root, @fieldParentPtr("base", scope)).contains(name),
.block => @as(*Block, @fieldParentPtr("base", scope)).contains(name),
.loop, .do_loop, .condition => scope.parent.?.contains(name),
};
}
/// Appends a node to the first block scope if inside a function, or to the root tree if not.
pub fn appendNode(inner: *Scope, node: ast.Node) !void {
var scope = inner;
while (true) {
switch (scope.id) {
.root => {
const root: *Root = @fieldParentPtr("base", scope);
return root.nodes.append(root.translator.gpa, node);
},
.block => {
const block: *Block = @fieldParentPtr("base", scope);
return block.statements.append(block.translator.gpa, node);
},
else => scope = scope.parent.?,
}
}
}
pub fn skipVariableDiscard(inner: *Scope, name: []const u8) void {
if (true) {
// TODO: due to 'local variable is never mutated' errors, we can
// only skip discards if a variable is used as an lvalue, which
// we don't currently have detection for in translate-c.
// Once #17584 is completed, perhaps we can do away with this
// logic entirely, and instead rely on render to fixup code.
return;
}
var scope = inner;
while (true) {
switch (scope.id) {
.root => return,
.block => {
const block: *Block = @fieldParentPtr("base", scope);
if (block.variable_discards.get(name)) |discard| {
discard.data.should_skip = true;
return;
}
},
else => {},
}
scope = scope.parent.?;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,76 @@
const std = @import("std");
const ast = @import("ast.zig");
/// All builtins need to have a source so that macros can reference them
/// but for some it is possible to directly call an equivalent Zig builtin
/// which is preferrable.
pub const Builtin = struct {
/// The name of the builtin in `c_builtins.zig`.
name: []const u8,
tag: ?ast.Node.Tag = null,
};
pub const map = std.StaticStringMap(Builtin).initComptime([_]struct { []const u8, Builtin }{
.{ "__builtin_abs", .{ .name = "abs" } },
.{ "__builtin_assume", .{ .name = "assume" } },
.{ "__builtin_bswap16", .{ .name = "bswap16", .tag = .byte_swap } },
.{ "__builtin_bswap32", .{ .name = "bswap32", .tag = .byte_swap } },
.{ "__builtin_bswap64", .{ .name = "bswap64", .tag = .byte_swap } },
.{ "__builtin_ceilf", .{ .name = "ceilf", .tag = .ceil } },
.{ "__builtin_ceil", .{ .name = "ceil", .tag = .ceil } },
.{ "__builtin_clz", .{ .name = "clz" } },
.{ "__builtin_constant_p", .{ .name = "constant_p" } },
.{ "__builtin_cosf", .{ .name = "cosf", .tag = .cos } },
.{ "__builtin_cos", .{ .name = "cos", .tag = .cos } },
.{ "__builtin_ctz", .{ .name = "ctz" } },
.{ "__builtin_exp2f", .{ .name = "exp2f", .tag = .exp2 } },
.{ "__builtin_exp2", .{ .name = "exp2", .tag = .exp2 } },
.{ "__builtin_expf", .{ .name = "expf", .tag = .exp } },
.{ "__builtin_exp", .{ .name = "exp", .tag = .exp } },
.{ "__builtin_expect", .{ .name = "expect" } },
.{ "__builtin_fabsf", .{ .name = "fabsf", .tag = .abs } },
.{ "__builtin_fabs", .{ .name = "fabs", .tag = .abs } },
.{ "__builtin_floorf", .{ .name = "floorf", .tag = .floor } },
.{ "__builtin_floor", .{ .name = "floor", .tag = .floor } },
.{ "__builtin_huge_valf", .{ .name = "huge_valf" } },
.{ "__builtin_inff", .{ .name = "inff" } },
.{ "__builtin_isinf_sign", .{ .name = "isinf_sign" } },
.{ "__builtin_isinf", .{ .name = "isinf" } },
.{ "__builtin_isnan", .{ .name = "isnan" } },
.{ "__builtin_labs", .{ .name = "labs" } },
.{ "__builtin_llabs", .{ .name = "llabs" } },
.{ "__builtin_log10f", .{ .name = "log10f", .tag = .log10 } },
.{ "__builtin_log10", .{ .name = "log10", .tag = .log10 } },
.{ "__builtin_log2f", .{ .name = "log2f", .tag = .log2 } },
.{ "__builtin_log2", .{ .name = "log2", .tag = .log2 } },
.{ "__builtin_logf", .{ .name = "logf", .tag = .log } },
.{ "__builtin_log", .{ .name = "log", .tag = .log } },
.{ "__builtin___memcpy_chk", .{ .name = "memcpy_chk" } },
.{ "__builtin_memcpy", .{ .name = "memcpy" } },
.{ "__builtin___memset_chk", .{ .name = "memset_chk" } },
.{ "__builtin_memset", .{ .name = "memset" } },
.{ "__builtin_mul_overflow", .{ .name = "mul_overflow" } },
.{ "__builtin_nanf", .{ .name = "nanf" } },
.{ "__builtin_object_size", .{ .name = "object_size" } },
.{ "__builtin_popcount", .{ .name = "popcount" } },
.{ "__builtin_roundf", .{ .name = "roundf", .tag = .round } },
.{ "__builtin_round", .{ .name = "round", .tag = .round } },
.{ "__builtin_signbitf", .{ .name = "signbitf" } },
.{ "__builtin_signbit", .{ .name = "signbit" } },
.{ "__builtin_sinf", .{ .name = "sinf", .tag = .sin } },
.{ "__builtin_sin", .{ .name = "sin", .tag = .sin } },
.{ "__builtin_sqrtf", .{ .name = "sqrtf", .tag = .sqrt } },
.{ "__builtin_sqrt", .{ .name = "sqrt", .tag = .sqrt } },
.{ "__builtin_strcmp", .{ .name = "strcmp" } },
.{ "__builtin_strlen", .{ .name = "strlen" } },
.{ "__builtin_truncf", .{ .name = "truncf", .tag = .trunc } },
.{ "__builtin_trunc", .{ .name = "trunc", .tag = .trunc } },
.{ "__builtin_unreachable", .{ .name = "unreachable", .tag = .@"unreachable" } },
.{ "__has_builtin", .{ .name = "has_builtin" } },
// __builtin_alloca_with_align is not currently implemented.
// It is used in a run and a translate test to ensure that non-implemented
// builtins are correctly demoted. If you implement __builtin_alloca_with_align,
// please update the tests to use a different non-implemented builtin.
});

View File

@ -0,0 +1,327 @@
const std = @import("std");
const builtin = @import("builtin");
const testing = std.testing;
const math = std.math;
const helpers = @import("helpers");
const cast = helpers.cast;
test cast {
var i = @as(i64, 10);
try testing.expect(cast(*u8, 16) == @as(*u8, @ptrFromInt(16)));
try testing.expect(cast(*u64, &i).* == @as(u64, 10));
try testing.expect(cast(*i64, @as(?*align(1) i64, &i)) == &i);
try testing.expect(cast(?*u8, 2) == @as(*u8, @ptrFromInt(2)));
try testing.expect(cast(?*i64, @as(*align(1) i64, &i)) == &i);
try testing.expect(cast(?*i64, @as(?*align(1) i64, &i)) == &i);
try testing.expectEqual(@as(u32, 4), cast(u32, @as(*u32, @ptrFromInt(4))));
try testing.expectEqual(@as(u32, 4), cast(u32, @as(?*u32, @ptrFromInt(4))));
try testing.expectEqual(@as(u32, 10), cast(u32, @as(u64, 10)));
try testing.expectEqual(@as(i32, @bitCast(@as(u32, 0x8000_0000))), cast(i32, @as(u32, 0x8000_0000)));
try testing.expectEqual(@as(*u8, @ptrFromInt(2)), cast(*u8, @as(*const u8, @ptrFromInt(2))));
try testing.expectEqual(@as(*u8, @ptrFromInt(2)), cast(*u8, @as(*volatile u8, @ptrFromInt(2))));
try testing.expectEqual(@as(?*anyopaque, @ptrFromInt(2)), cast(?*anyopaque, @as(*u8, @ptrFromInt(2))));
var foo: c_int = -1;
_ = &foo;
try testing.expect(cast(*anyopaque, -1) == @as(*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1))))));
try testing.expect(cast(*anyopaque, foo) == @as(*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1))))));
try testing.expect(cast(?*anyopaque, -1) == @as(?*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1))))));
try testing.expect(cast(?*anyopaque, foo) == @as(?*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1))))));
const FnPtr = ?*align(1) const fn (*anyopaque) void;
try testing.expect(cast(FnPtr, 0) == @as(FnPtr, @ptrFromInt(@as(usize, 0))));
try testing.expect(cast(FnPtr, foo) == @as(FnPtr, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1))))));
const complexFunction = struct {
fn f(_: ?*anyopaque, _: c_uint, _: ?*const fn (?*anyopaque) callconv(.c) c_uint, _: ?*anyopaque, _: c_uint, _: [*c]c_uint) callconv(.c) usize {
return 0;
}
}.f;
const SDL_FunctionPointer = ?*const fn () callconv(.c) void;
const fn_ptr = cast(SDL_FunctionPointer, complexFunction);
try testing.expect(fn_ptr != null);
}
const sizeof = helpers.sizeof;
test sizeof {
const S = extern struct { a: u32 };
const ptr_size = @sizeOf(*anyopaque);
try testing.expect(sizeof(u32) == 4);
try testing.expect(sizeof(@as(u32, 2)) == 4);
try testing.expect(sizeof(2) == @sizeOf(c_int));
try testing.expect(sizeof(2.0) == @sizeOf(f64));
try testing.expect(sizeof(S) == 4);
try testing.expect(sizeof([_]u32{ 4, 5, 6 }) == 12);
try testing.expect(sizeof([3]u32) == 12);
try testing.expect(sizeof([3:0]u32) == 16);
try testing.expect(sizeof(&[_]u32{ 4, 5, 6 }) == ptr_size);
try testing.expect(sizeof(*u32) == ptr_size);
try testing.expect(sizeof([*]u32) == ptr_size);
try testing.expect(sizeof([*c]u32) == ptr_size);
try testing.expect(sizeof(?*u32) == ptr_size);
try testing.expect(sizeof(?[*]u32) == ptr_size);
try testing.expect(sizeof(*anyopaque) == ptr_size);
try testing.expect(sizeof(*void) == ptr_size);
try testing.expect(sizeof(null) == ptr_size);
try testing.expect(sizeof("foobar") == 7);
try testing.expect(sizeof(&[_:0]u16{ 'f', 'o', 'o', 'b', 'a', 'r' }) == 14);
try testing.expect(sizeof(*const [4:0]u8) == 5);
try testing.expect(sizeof(*[4:0]u8) == ptr_size);
try testing.expect(sizeof([*]const [4:0]u8) == ptr_size);
try testing.expect(sizeof(*const *const [4:0]u8) == ptr_size);
try testing.expect(sizeof(*const [4]u8) == ptr_size);
if (false) { // TODO
try testing.expect(sizeof(&sizeof) == @sizeOf(@TypeOf(&sizeof)));
try testing.expect(sizeof(sizeof) == 1);
}
try testing.expect(sizeof(void) == 1);
try testing.expect(sizeof(anyopaque) == 1);
}
const promoteIntLiteral = helpers.promoteIntLiteral;
test promoteIntLiteral {
const signed_hex = promoteIntLiteral(c_int, math.maxInt(c_int) + 1, .hex);
try testing.expectEqual(c_uint, @TypeOf(signed_hex));
if (math.maxInt(c_longlong) == math.maxInt(c_int)) return;
const signed_decimal = promoteIntLiteral(c_int, math.maxInt(c_int) + 1, .decimal);
const unsigned = promoteIntLiteral(c_uint, math.maxInt(c_uint) + 1, .hex);
if (math.maxInt(c_long) > math.maxInt(c_int)) {
try testing.expectEqual(c_long, @TypeOf(signed_decimal));
try testing.expectEqual(c_ulong, @TypeOf(unsigned));
} else {
try testing.expectEqual(c_longlong, @TypeOf(signed_decimal));
try testing.expectEqual(c_ulonglong, @TypeOf(unsigned));
}
}
const shuffleVectorIndex = helpers.shuffleVectorIndex;
test shuffleVectorIndex {
const vector_len: usize = 4;
_ = shuffleVectorIndex(-1, vector_len);
try testing.expect(shuffleVectorIndex(0, vector_len) == 0);
try testing.expect(shuffleVectorIndex(1, vector_len) == 1);
try testing.expect(shuffleVectorIndex(2, vector_len) == 2);
try testing.expect(shuffleVectorIndex(3, vector_len) == 3);
try testing.expect(shuffleVectorIndex(4, vector_len) == -1);
try testing.expect(shuffleVectorIndex(5, vector_len) == -2);
try testing.expect(shuffleVectorIndex(6, vector_len) == -3);
try testing.expect(shuffleVectorIndex(7, vector_len) == -4);
}
const FlexibleArrayType = helpers.FlexibleArrayType;
test FlexibleArrayType {
const Container = extern struct {
size: usize,
};
try testing.expectEqual(FlexibleArrayType(*Container, c_int), [*c]c_int);
try testing.expectEqual(FlexibleArrayType(*const Container, c_int), [*c]const c_int);
try testing.expectEqual(FlexibleArrayType(*volatile Container, c_int), [*c]volatile c_int);
try testing.expectEqual(FlexibleArrayType(*const volatile Container, c_int), [*c]const volatile c_int);
}
const signedRemainder = helpers.signedRemainder;
test signedRemainder {
// TODO add test
return error.SkipZigTest;
}
const ArithmeticConversion = helpers.ArithmeticConversion;
test ArithmeticConversion {
// Promotions not necessarily the same for other platforms
if (builtin.target.cpu.arch != .x86_64 or builtin.target.os.tag != .linux) return error.SkipZigTest;
const Test = struct {
/// Order of operands should not matter for arithmetic conversions
fn checkPromotion(comptime A: type, comptime B: type, comptime Expected: type) !void {
try std.testing.expect(ArithmeticConversion(A, B) == Expected);
try std.testing.expect(ArithmeticConversion(B, A) == Expected);
}
};
try Test.checkPromotion(c_longdouble, c_int, c_longdouble);
try Test.checkPromotion(c_int, f64, f64);
try Test.checkPromotion(f32, bool, f32);
try Test.checkPromotion(bool, c_short, c_int);
try Test.checkPromotion(c_int, c_int, c_int);
try Test.checkPromotion(c_short, c_int, c_int);
try Test.checkPromotion(c_int, c_long, c_long);
try Test.checkPromotion(c_ulonglong, c_uint, c_ulonglong);
try Test.checkPromotion(c_uint, c_int, c_uint);
try Test.checkPromotion(c_uint, c_long, c_long);
try Test.checkPromotion(c_ulong, c_longlong, c_ulonglong);
// stdint.h
try Test.checkPromotion(u8, i8, c_int);
try Test.checkPromotion(u16, i16, c_int);
try Test.checkPromotion(i32, c_int, c_int);
try Test.checkPromotion(u32, c_int, c_uint);
try Test.checkPromotion(i64, c_int, c_long);
try Test.checkPromotion(u64, c_int, c_ulong);
try Test.checkPromotion(isize, c_int, c_long);
try Test.checkPromotion(usize, c_int, c_ulong);
}
const F_SUFFIX = helpers.F_SUFFIX;
test F_SUFFIX {
try testing.expect(@TypeOf(F_SUFFIX(1)) == f32);
}
const U_SUFFIX = helpers.U_SUFFIX;
test U_SUFFIX {
try testing.expect(@TypeOf(U_SUFFIX(1)) == c_uint);
if (math.maxInt(c_ulong) > math.maxInt(c_uint)) {
try testing.expect(@TypeOf(U_SUFFIX(math.maxInt(c_uint) + 1)) == c_ulong);
}
if (math.maxInt(c_ulonglong) > math.maxInt(c_ulong)) {
try testing.expect(@TypeOf(U_SUFFIX(math.maxInt(c_ulong) + 1)) == c_ulonglong);
}
}
const L_SUFFIX = helpers.L_SUFFIX;
test L_SUFFIX {
try testing.expect(@TypeOf(L_SUFFIX(1)) == c_long);
if (math.maxInt(c_long) > math.maxInt(c_int)) {
try testing.expect(@TypeOf(L_SUFFIX(math.maxInt(c_int) + 1)) == c_long);
}
if (math.maxInt(c_longlong) > math.maxInt(c_long)) {
try testing.expect(@TypeOf(L_SUFFIX(math.maxInt(c_long) + 1)) == c_longlong);
}
}
const UL_SUFFIX = helpers.UL_SUFFIX;
test UL_SUFFIX {
try testing.expect(@TypeOf(UL_SUFFIX(1)) == c_ulong);
if (math.maxInt(c_ulonglong) > math.maxInt(c_ulong)) {
try testing.expect(@TypeOf(UL_SUFFIX(math.maxInt(c_ulong) + 1)) == c_ulonglong);
}
}
const LL_SUFFIX = helpers.LL_SUFFIX;
test LL_SUFFIX {
try testing.expect(@TypeOf(LL_SUFFIX(1)) == c_longlong);
}
const ULL_SUFFIX = helpers.ULL_SUFFIX;
test ULL_SUFFIX {
try testing.expect(@TypeOf(ULL_SUFFIX(1)) == c_ulonglong);
}
test "Extended C ABI casting" {
if (math.maxInt(c_long) > math.maxInt(c_char)) {
try testing.expect(@TypeOf(L_SUFFIX(@as(c_char, math.maxInt(c_char) - 1))) == c_long); // c_char
}
if (math.maxInt(c_long) > math.maxInt(c_short)) {
try testing.expect(@TypeOf(L_SUFFIX(@as(c_short, math.maxInt(c_short) - 1))) == c_long); // c_short
}
if (math.maxInt(c_long) > math.maxInt(c_ushort)) {
try testing.expect(@TypeOf(L_SUFFIX(@as(c_ushort, math.maxInt(c_ushort) - 1))) == c_long); //c_ushort
}
if (math.maxInt(c_long) > math.maxInt(c_int)) {
try testing.expect(@TypeOf(L_SUFFIX(@as(c_int, math.maxInt(c_int) - 1))) == c_long); // c_int
}
if (math.maxInt(c_long) > math.maxInt(c_uint)) {
try testing.expect(@TypeOf(L_SUFFIX(@as(c_uint, math.maxInt(c_uint) - 1))) == c_long); // c_uint
try testing.expect(@TypeOf(L_SUFFIX(math.maxInt(c_uint) + 1)) == c_long); // comptime_int -> c_long
}
if (math.maxInt(c_longlong) > math.maxInt(c_long)) {
try testing.expect(@TypeOf(L_SUFFIX(@as(c_long, math.maxInt(c_long) - 1))) == c_long); // c_long
try testing.expect(@TypeOf(L_SUFFIX(math.maxInt(c_long) + 1)) == c_longlong); // comptime_int -> c_longlong
}
}
const WL_CONTAINER_OF = helpers.WL_CONTAINER_OF;
test WL_CONTAINER_OF {
const S = struct {
a: u32 = 0,
b: u32 = 0,
};
const x = S{};
const y = S{};
const ptr = WL_CONTAINER_OF(&x.b, &y, "b");
try testing.expectEqual(&x, ptr);
}
const CAST_OR_CALL = helpers.CAST_OR_CALL;
test "CAST_OR_CALL casting" {
const arg: c_int = 1000;
const casted = CAST_OR_CALL(u8, arg);
try testing.expectEqual(cast(u8, arg), casted);
const S = struct {
x: u32 = 0,
};
var s: S = .{};
const casted_ptr = CAST_OR_CALL(*u8, &s);
try testing.expectEqual(cast(*u8, &s), casted_ptr);
}
test "CAST_OR_CALL calling" {
const Helper = struct {
var last_val: bool = false;
fn returnsVoid(val: bool) void {
last_val = val;
}
fn returnsBool(f: f32) bool {
return f > 0;
}
fn identity(self: c_uint) c_uint {
return self;
}
};
CAST_OR_CALL(Helper.returnsVoid, true);
try testing.expectEqual(true, Helper.last_val);
CAST_OR_CALL(Helper.returnsVoid, false);
try testing.expectEqual(false, Helper.last_val);
try testing.expectEqual(Helper.returnsBool(1), CAST_OR_CALL(Helper.returnsBool, @as(f32, 1)));
try testing.expectEqual(Helper.returnsBool(-1), CAST_OR_CALL(Helper.returnsBool, @as(f32, -1)));
try testing.expectEqual(Helper.identity(@as(c_uint, 100)), CAST_OR_CALL(Helper.identity, @as(c_uint, 100)));
}

View File

@ -0,0 +1,222 @@
const std = @import("std");
const assert = std.debug.assert;
const mem = std.mem;
const process = std.process;
const aro = @import("aro");
const Translator = @import("Translator.zig");
const fast_exit = @import("builtin").mode != .Debug;
var general_purpose_allocator: std.heap.GeneralPurposeAllocator(.{}) = .init;
pub fn main() u8 {
const gpa = general_purpose_allocator.allocator();
defer _ = general_purpose_allocator.deinit();
var arena_instance = std.heap.ArenaAllocator.init(std.heap.page_allocator);
defer arena_instance.deinit();
const arena = arena_instance.allocator();
const args = process.argsAlloc(arena) catch {
std.debug.print("ran out of memory allocating arguments\n", .{});
if (fast_exit) process.exit(1);
return 1;
};
var stderr_buf: [1024]u8 = undefined;
var stderr = std.fs.File.stderr().writer(&stderr_buf);
var diagnostics: aro.Diagnostics = .{
.output = .{ .to_writer = .{
.color = .detect(stderr.file),
.writer = &stderr.interface,
} },
};
var comp = aro.Compilation.initDefault(gpa, arena, &diagnostics, std.fs.cwd()) catch |err| switch (err) {
error.OutOfMemory => {
std.debug.print("ran out of memory initializing C compilation\n", .{});
if (fast_exit) process.exit(1);
return 1;
},
};
defer comp.deinit();
var driver: aro.Driver = .{ .comp = &comp, .diagnostics = &diagnostics, .aro_name = "aro" };
defer driver.deinit();
var toolchain: aro.Toolchain = .{ .driver = &driver, .filesystem = .{ .real = comp.cwd } };
defer toolchain.deinit();
translate(&driver, &toolchain, args) catch |err| switch (err) {
error.OutOfMemory => {
std.debug.print("ran out of memory translating\n", .{});
if (fast_exit) process.exit(1);
return 1;
},
error.FatalError => {
if (fast_exit) process.exit(1);
return 1;
},
error.WriteFailed => {
std.debug.print("unable to write to stdout\n", .{});
if (fast_exit) process.exit(1);
return 1;
},
};
if (fast_exit) process.exit(@intFromBool(comp.diagnostics.errors != 0));
return @intFromBool(comp.diagnostics.errors != 0);
}
pub const usage =
\\Usage {s}: [options] file [CC options]
\\
\\Options:
\\ --help Print this message
\\ --version Print translate-c version
\\ -fmodule-libs Import libraries as modules
\\ -fno-module-libs (default) Install libraries next to output file
\\
\\
;
fn translate(d: *aro.Driver, tc: *aro.Toolchain, args: [][:0]u8) !void {
const gpa = d.comp.gpa;
const aro_args = args: {
var i: usize = 0;
for (args) |arg| {
args[i] = arg;
if (mem.eql(u8, arg, "--help")) {
var stdout_buf: [512]u8 = undefined;
var stdout = std.fs.File.stdout().writer(&stdout_buf);
try stdout.interface.print(usage, .{args[0]});
try stdout.interface.flush();
return;
} else if (mem.eql(u8, arg, "--version")) {
var stdout_buf: [512]u8 = undefined;
var stdout = std.fs.File.stdout().writer(&stdout_buf);
// TODO add version
try stdout.interface.writeAll("0.0.0-dev\n");
try stdout.interface.flush();
return;
} else {
i += 1;
}
}
break :args args[0..i];
};
const user_macros = macros: {
var macro_buf: std.ArrayListUnmanaged(u8) = .empty;
defer macro_buf.deinit(gpa);
var discard_buf: [256]u8 = undefined;
var discarding: std.Io.Writer.Discarding = .init(&discard_buf);
assert(!try d.parseArgs(&discarding.writer, &macro_buf, aro_args));
if (macro_buf.items.len > std.math.maxInt(u32)) {
return d.fatal("user provided macro source exceeded max size", .{});
}
const content = try macro_buf.toOwnedSlice(gpa);
errdefer gpa.free(content);
break :macros try d.comp.addSourceFromOwnedBuffer("<command line>", content, .user);
};
if (d.inputs.items.len != 1) {
return d.fatal("expected exactly one input file", .{});
}
const source = d.inputs.items[0];
tc.discover() catch |er| switch (er) {
error.OutOfMemory => return error.OutOfMemory,
error.TooManyMultilibs => return d.fatal("found more than one multilib with the same priority", .{}),
};
tc.defineSystemIncludes() catch |er| switch (er) {
error.OutOfMemory => return error.OutOfMemory,
error.AroIncludeNotFound => return d.fatal("unable to find Aro builtin headers", .{}),
};
const builtin_macros = d.comp.generateBuiltinMacros(.include_system_defines) catch |err| switch (err) {
error.FileTooBig => return d.fatal("builtin macro source exceeded max size", .{}),
else => |e| return e,
};
var pp = try aro.Preprocessor.initDefault(d.comp);
defer pp.deinit();
var name_buf: [std.fs.max_name_bytes]u8 = undefined;
// Omit the source file from the dep file so that it can be tracked separately.
// In the Zig compiler we want to omit it from the cache hash since it will
// be written to a tmp file then renamed into place, meaning the path will be
// wrong as soon as the work is done.
var opt_dep_file = try d.initDepFile(source, &name_buf, true);
defer if (opt_dep_file) |*dep_file| dep_file.deinit(gpa);
if (opt_dep_file) |*dep_file| pp.dep_file = dep_file;
try pp.preprocessSources(&.{ source, builtin_macros, user_macros });
var c_tree = try pp.parse();
defer c_tree.deinit();
if (d.diagnostics.errors != 0) {
if (fast_exit) process.exit(1);
return error.FatalError;
}
var out_buf: [4096]u8 = undefined;
if (opt_dep_file) |dep_file| {
const dep_file_name = try d.getDepFileName(source, out_buf[0..std.fs.max_name_bytes]);
const file = if (dep_file_name) |path|
d.comp.cwd.createFile(path, .{}) catch |er|
return d.fatal("unable to create dependency file '{s}': {s}", .{ path, aro.Driver.errorDescription(er) })
else
std.fs.File.stdout();
defer if (dep_file_name != null) file.close();
var file_writer = file.writer(&out_buf);
dep_file.write(&file_writer.interface) catch
return d.fatal("unable to write dependency file: {s}", .{aro.Driver.errorDescription(file_writer.err.?)});
}
const rendered_zig = try Translator.translate(.{
.gpa = gpa,
.comp = d.comp,
.pp = &pp,
.tree = &c_tree,
});
defer gpa.free(rendered_zig);
var close_out_file = false;
var out_file_path: []const u8 = "<stdout>";
var out_file: std.fs.File = .stdout();
defer if (close_out_file) out_file.close();
if (d.output_name) |path| blk: {
if (std.mem.eql(u8, path, "-")) break :blk;
if (std.fs.path.dirname(path)) |dirname| {
std.fs.cwd().makePath(dirname) catch |err|
return d.fatal("failed to create path to '{s}': {s}", .{ path, aro.Driver.errorDescription(err) });
}
out_file = std.fs.cwd().createFile(path, .{}) catch |err| {
return d.fatal("failed to create output file '{s}': {s}", .{ path, aro.Driver.errorDescription(err) });
};
close_out_file = true;
out_file_path = path;
}
var out_writer = out_file.writer(&out_buf);
out_writer.interface.writeAll(rendered_zig) catch {};
out_writer.interface.flush() catch {};
if (out_writer.err) |write_err|
return d.fatal("failed to write result to '{s}': {s}", .{ out_file_path, aro.Driver.errorDescription(write_err) });
if (fast_exit) process.exit(0);
}
test {
_ = Translator;
_ = @import("helpers.zig");
_ = @import("PatternList.zig");
}

View File

@ -459,9 +459,9 @@ pub const Manifest = struct {
}
}
pub fn addDepFile(self: *Manifest, dir: fs.Dir, dep_file_basename: []const u8) !void {
pub fn addDepFile(self: *Manifest, dir: fs.Dir, dep_file_sub_path: []const u8) !void {
assert(self.manifest_file == null);
return self.addDepFileMaybePost(dir, dep_file_basename);
return self.addDepFileMaybePost(dir, dep_file_sub_path);
}
pub const HitError = error{
@ -1049,14 +1049,14 @@ pub const Manifest = struct {
self.hash.hasher.update(&new_file.bin_digest);
}
pub fn addDepFilePost(self: *Manifest, dir: fs.Dir, dep_file_basename: []const u8) !void {
pub fn addDepFilePost(self: *Manifest, dir: fs.Dir, dep_file_sub_path: []const u8) !void {
assert(self.manifest_file != null);
return self.addDepFileMaybePost(dir, dep_file_basename);
return self.addDepFileMaybePost(dir, dep_file_sub_path);
}
fn addDepFileMaybePost(self: *Manifest, dir: fs.Dir, dep_file_basename: []const u8) !void {
fn addDepFileMaybePost(self: *Manifest, dir: fs.Dir, dep_file_sub_path: []const u8) !void {
const gpa = self.cache.gpa;
const dep_file_contents = try dir.readFileAlloc(dep_file_basename, gpa, .limited(manifest_file_size_max));
const dep_file_contents = try dir.readFileAlloc(dep_file_sub_path, gpa, .limited(manifest_file_size_max));
defer gpa.free(dep_file_contents);
var error_buf: std.ArrayListUnmanaged(u8) = .empty;
@ -1083,7 +1083,7 @@ pub const Manifest = struct {
},
else => |err| {
try err.printError(gpa, &error_buf);
log.err("failed parsing {s}: {s}", .{ dep_file_basename, error_buf.items });
log.err("failed parsing {s}: {s}", .{ dep_file_sub_path, error_buf.items });
return error.InvalidDepFile;
},
}

View File

@ -36,9 +36,10 @@ pub const ParsedCharLiteral = string_literal.ParsedCharLiteral;
pub const parseCharLiteral = string_literal.parseCharLiteral;
pub const parseNumberLiteral = number_literal.parseNumberLiteral;
// Files needed by translate-c.
pub const c_builtins = @import("zig/c_builtins.zig");
pub const c_translation = @import("zig/c_translation.zig");
pub const c_translation = struct {
pub const builtins = @import("zig/c_translation/builtins.zig");
pub const helpers = @import("zig/c_translation/helpers.zig");
};
pub const SrcHasher = std.crypto.hash.Blake3;
pub const SrcHash = [16]u8;

View File

@ -1,699 +0,0 @@
const std = @import("std");
const builtin = @import("builtin");
const testing = std.testing;
const math = std.math;
const mem = std.mem;
/// Given a type and value, cast the value to the type as c would.
pub fn cast(comptime DestType: type, target: anytype) DestType {
// this function should behave like transCCast in translate-c, except it's for macros
const SourceType = @TypeOf(target);
switch (@typeInfo(DestType)) {
.@"fn" => return castToPtr(*const DestType, SourceType, target),
.pointer => return castToPtr(DestType, SourceType, target),
.optional => |dest_opt| {
if (@typeInfo(dest_opt.child) == .pointer) {
return castToPtr(DestType, SourceType, target);
} else if (@typeInfo(dest_opt.child) == .@"fn") {
return castToPtr(?*const dest_opt.child, SourceType, target);
}
},
.int => {
switch (@typeInfo(SourceType)) {
.pointer => {
return castInt(DestType, @intFromPtr(target));
},
.optional => |opt| {
if (@typeInfo(opt.child) == .pointer) {
return castInt(DestType, @intFromPtr(target));
}
},
.int => {
return castInt(DestType, target);
},
.@"fn" => {
return castInt(DestType, @intFromPtr(&target));
},
.bool => {
return @intFromBool(target);
},
else => {},
}
},
.float => {
switch (@typeInfo(SourceType)) {
.int => return @as(DestType, @floatFromInt(target)),
.float => return @as(DestType, @floatCast(target)),
.bool => return @as(DestType, @floatFromInt(@intFromBool(target))),
else => {},
}
},
.@"union" => |info| {
inline for (info.fields) |field| {
if (field.type == SourceType) return @unionInit(DestType, field.name, target);
}
@compileError("cast to union type '" ++ @typeName(DestType) ++ "' from type '" ++ @typeName(SourceType) ++ "' which is not present in union");
},
.bool => return cast(usize, target) != 0,
else => {},
}
return @as(DestType, target);
}
fn castInt(comptime DestType: type, target: anytype) DestType {
const dest = @typeInfo(DestType).int;
const source = @typeInfo(@TypeOf(target)).int;
if (dest.bits < source.bits)
return @as(DestType, @bitCast(@as(std.meta.Int(source.signedness, dest.bits), @truncate(target))))
else
return @as(DestType, @bitCast(@as(std.meta.Int(source.signedness, dest.bits), target)));
}
fn castPtr(comptime DestType: type, target: anytype) DestType {
return @ptrCast(@alignCast(@constCast(@volatileCast(target))));
}
fn castToPtr(comptime DestType: type, comptime SourceType: type, target: anytype) DestType {
switch (@typeInfo(SourceType)) {
.int => {
return @as(DestType, @ptrFromInt(castInt(usize, target)));
},
.comptime_int => {
if (target < 0)
return @as(DestType, @ptrFromInt(@as(usize, @bitCast(@as(isize, @intCast(target))))))
else
return @as(DestType, @ptrFromInt(@as(usize, @intCast(target))));
},
.pointer => {
return castPtr(DestType, target);
},
.@"fn" => {
return castPtr(DestType, &target);
},
.optional => |target_opt| {
if (@typeInfo(target_opt.child) == .pointer) {
return castPtr(DestType, target);
}
},
else => {},
}
return @as(DestType, target);
}
fn ptrInfo(comptime PtrType: type) std.builtin.Type.Pointer {
return switch (@typeInfo(PtrType)) {
.optional => |opt_info| @typeInfo(opt_info.child).pointer,
.pointer => |ptr_info| ptr_info,
else => unreachable,
};
}
test "cast" {
var i = @as(i64, 10);
try testing.expect(cast(*u8, 16) == @as(*u8, @ptrFromInt(16)));
try testing.expect(cast(*u64, &i).* == @as(u64, 10));
try testing.expect(cast(*i64, @as(?*align(1) i64, &i)) == &i);
try testing.expect(cast(?*u8, 2) == @as(*u8, @ptrFromInt(2)));
try testing.expect(cast(?*i64, @as(*align(1) i64, &i)) == &i);
try testing.expect(cast(?*i64, @as(?*align(1) i64, &i)) == &i);
try testing.expectEqual(@as(u32, 4), cast(u32, @as(*u32, @ptrFromInt(4))));
try testing.expectEqual(@as(u32, 4), cast(u32, @as(?*u32, @ptrFromInt(4))));
try testing.expectEqual(@as(u32, 10), cast(u32, @as(u64, 10)));
try testing.expectEqual(@as(i32, @bitCast(@as(u32, 0x8000_0000))), cast(i32, @as(u32, 0x8000_0000)));
try testing.expectEqual(@as(*u8, @ptrFromInt(2)), cast(*u8, @as(*const u8, @ptrFromInt(2))));
try testing.expectEqual(@as(*u8, @ptrFromInt(2)), cast(*u8, @as(*volatile u8, @ptrFromInt(2))));
try testing.expectEqual(@as(?*anyopaque, @ptrFromInt(2)), cast(?*anyopaque, @as(*u8, @ptrFromInt(2))));
var foo: c_int = -1;
_ = &foo;
try testing.expect(cast(*anyopaque, -1) == @as(*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1))))));
try testing.expect(cast(*anyopaque, foo) == @as(*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1))))));
try testing.expect(cast(?*anyopaque, -1) == @as(?*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1))))));
try testing.expect(cast(?*anyopaque, foo) == @as(?*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1))))));
const FnPtr = ?*align(1) const fn (*anyopaque) void;
try testing.expect(cast(FnPtr, 0) == @as(FnPtr, @ptrFromInt(@as(usize, 0))));
try testing.expect(cast(FnPtr, foo) == @as(FnPtr, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1))))));
}
/// Given a value returns its size as C's sizeof operator would.
pub fn sizeof(target: anytype) usize {
const T: type = if (@TypeOf(target) == type) target else @TypeOf(target);
switch (@typeInfo(T)) {
.float, .int, .@"struct", .@"union", .array, .bool, .vector => return @sizeOf(T),
.@"fn" => {
// sizeof(main) in C returns 1
return 1;
},
.null => return @sizeOf(*anyopaque),
.void => {
// Note: sizeof(void) is 1 on clang/gcc and 0 on MSVC.
return 1;
},
.@"opaque" => {
if (T == anyopaque) {
// Note: sizeof(void) is 1 on clang/gcc and 0 on MSVC.
return 1;
} else {
@compileError("Cannot use C sizeof on opaque type " ++ @typeName(T));
}
},
.optional => |opt| {
if (@typeInfo(opt.child) == .pointer) {
return sizeof(opt.child);
} else {
@compileError("Cannot use C sizeof on non-pointer optional " ++ @typeName(T));
}
},
.pointer => |ptr| {
if (ptr.size == .slice) {
@compileError("Cannot use C sizeof on slice type " ++ @typeName(T));
}
// for strings, sizeof("a") returns 2.
// normal pointer decay scenarios from C are handled
// in the .array case above, but strings remain literals
// and are therefore always pointers, so they need to be
// specially handled here.
if (ptr.size == .one and ptr.is_const and @typeInfo(ptr.child) == .array) {
const array_info = @typeInfo(ptr.child).array;
if ((array_info.child == u8 or array_info.child == u16) and array_info.sentinel() == 0) {
// length of the string plus one for the null terminator.
return (array_info.len + 1) * @sizeOf(array_info.child);
}
}
// When zero sized pointers are removed, this case will no
// longer be reachable and can be deleted.
if (@sizeOf(T) == 0) {
return @sizeOf(*anyopaque);
}
return @sizeOf(T);
},
.comptime_float => return @sizeOf(f64), // TODO c_double #3999
.comptime_int => {
// TODO to get the correct result we have to translate
// `1073741824 * 4` as `int(1073741824) *% int(4)` since
// sizeof(1073741824 * 4) != sizeof(4294967296).
// TODO test if target fits in int, long or long long
return @sizeOf(c_int);
},
else => @compileError("std.meta.sizeof does not support type " ++ @typeName(T)),
}
}
test "sizeof" {
const S = extern struct { a: u32 };
const ptr_size = @sizeOf(*anyopaque);
try testing.expect(sizeof(u32) == 4);
try testing.expect(sizeof(@as(u32, 2)) == 4);
try testing.expect(sizeof(2) == @sizeOf(c_int));
try testing.expect(sizeof(2.0) == @sizeOf(f64));
try testing.expect(sizeof(S) == 4);
try testing.expect(sizeof([_]u32{ 4, 5, 6 }) == 12);
try testing.expect(sizeof([3]u32) == 12);
try testing.expect(sizeof([3:0]u32) == 16);
try testing.expect(sizeof(&[_]u32{ 4, 5, 6 }) == ptr_size);
try testing.expect(sizeof(*u32) == ptr_size);
try testing.expect(sizeof([*]u32) == ptr_size);
try testing.expect(sizeof([*c]u32) == ptr_size);
try testing.expect(sizeof(?*u32) == ptr_size);
try testing.expect(sizeof(?[*]u32) == ptr_size);
try testing.expect(sizeof(*anyopaque) == ptr_size);
try testing.expect(sizeof(*void) == ptr_size);
try testing.expect(sizeof(null) == ptr_size);
try testing.expect(sizeof("foobar") == 7);
try testing.expect(sizeof(&[_:0]u16{ 'f', 'o', 'o', 'b', 'a', 'r' }) == 14);
try testing.expect(sizeof(*const [4:0]u8) == 5);
try testing.expect(sizeof(*[4:0]u8) == ptr_size);
try testing.expect(sizeof([*]const [4:0]u8) == ptr_size);
try testing.expect(sizeof(*const *const [4:0]u8) == ptr_size);
try testing.expect(sizeof(*const [4]u8) == ptr_size);
if (false) { // TODO
try testing.expect(sizeof(&sizeof) == @sizeOf(@TypeOf(&sizeof)));
try testing.expect(sizeof(sizeof) == 1);
}
try testing.expect(sizeof(void) == 1);
try testing.expect(sizeof(anyopaque) == 1);
}
pub const CIntLiteralBase = enum { decimal, octal, hex };
fn PromoteIntLiteralReturnType(comptime SuffixType: type, comptime number: comptime_int, comptime base: CIntLiteralBase) type {
const signed_decimal = [_]type{ c_int, c_long, c_longlong, c_ulonglong };
const signed_oct_hex = [_]type{ c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong };
const unsigned = [_]type{ c_uint, c_ulong, c_ulonglong };
const list: []const type = if (@typeInfo(SuffixType).int.signedness == .unsigned)
&unsigned
else if (base == .decimal)
&signed_decimal
else
&signed_oct_hex;
var pos = mem.indexOfScalar(type, list, SuffixType).?;
while (pos < list.len) : (pos += 1) {
if (number >= math.minInt(list[pos]) and number <= math.maxInt(list[pos])) {
return list[pos];
}
}
@compileError("Integer literal is too large");
}
/// Promote the type of an integer literal until it fits as C would.
pub fn promoteIntLiteral(
comptime SuffixType: type,
comptime number: comptime_int,
comptime base: CIntLiteralBase,
) PromoteIntLiteralReturnType(SuffixType, number, base) {
return number;
}
test "promoteIntLiteral" {
const signed_hex = promoteIntLiteral(c_int, math.maxInt(c_int) + 1, .hex);
try testing.expectEqual(c_uint, @TypeOf(signed_hex));
if (math.maxInt(c_longlong) == math.maxInt(c_int)) return;
const signed_decimal = promoteIntLiteral(c_int, math.maxInt(c_int) + 1, .decimal);
const unsigned = promoteIntLiteral(c_uint, math.maxInt(c_uint) + 1, .hex);
if (math.maxInt(c_long) > math.maxInt(c_int)) {
try testing.expectEqual(c_long, @TypeOf(signed_decimal));
try testing.expectEqual(c_ulong, @TypeOf(unsigned));
} else {
try testing.expectEqual(c_longlong, @TypeOf(signed_decimal));
try testing.expectEqual(c_ulonglong, @TypeOf(unsigned));
}
}
/// Convert from clang __builtin_shufflevector index to Zig @shuffle index
/// clang requires __builtin_shufflevector index arguments to be integer constants.
/// negative values for `this_index` indicate "don't care".
/// clang enforces that `this_index` is less than the total number of vector elements
/// See https://ziglang.org/documentation/master/#shuffle
/// See https://clang.llvm.org/docs/LanguageExtensions.html#langext-builtin-shufflevector
pub fn shuffleVectorIndex(comptime this_index: c_int, comptime source_vector_len: usize) i32 {
const positive_index = std.math.cast(usize, this_index) orelse return undefined;
if (positive_index < source_vector_len) return @as(i32, @intCast(this_index));
const b_index = positive_index - source_vector_len;
return ~@as(i32, @intCast(b_index));
}
test "shuffleVectorIndex" {
const vector_len: usize = 4;
_ = shuffleVectorIndex(-1, vector_len);
try testing.expect(shuffleVectorIndex(0, vector_len) == 0);
try testing.expect(shuffleVectorIndex(1, vector_len) == 1);
try testing.expect(shuffleVectorIndex(2, vector_len) == 2);
try testing.expect(shuffleVectorIndex(3, vector_len) == 3);
try testing.expect(shuffleVectorIndex(4, vector_len) == -1);
try testing.expect(shuffleVectorIndex(5, vector_len) == -2);
try testing.expect(shuffleVectorIndex(6, vector_len) == -3);
try testing.expect(shuffleVectorIndex(7, vector_len) == -4);
}
/// Constructs a [*c] pointer with the const and volatile annotations
/// from SelfType for pointing to a C flexible array of ElementType.
pub fn FlexibleArrayType(comptime SelfType: type, comptime ElementType: type) type {
switch (@typeInfo(SelfType)) {
.pointer => |ptr| {
return @Type(.{ .pointer = .{
.size = .c,
.is_const = ptr.is_const,
.is_volatile = ptr.is_volatile,
.alignment = @alignOf(ElementType),
.address_space = .generic,
.child = ElementType,
.is_allowzero = true,
.sentinel_ptr = null,
} });
},
else => |info| @compileError("Invalid self type \"" ++ @tagName(info) ++ "\" for flexible array getter: " ++ @typeName(SelfType)),
}
}
test "Flexible Array Type" {
const Container = extern struct {
size: usize,
};
try testing.expectEqual(FlexibleArrayType(*Container, c_int), [*c]c_int);
try testing.expectEqual(FlexibleArrayType(*const Container, c_int), [*c]const c_int);
try testing.expectEqual(FlexibleArrayType(*volatile Container, c_int), [*c]volatile c_int);
try testing.expectEqual(FlexibleArrayType(*const volatile Container, c_int), [*c]const volatile c_int);
}
/// C `%` operator for signed integers
/// C standard states: "If the quotient a/b is representable, the expression (a/b)*b + a%b shall equal a"
/// The quotient is not representable if denominator is zero, or if numerator is the minimum integer for
/// the type and denominator is -1. C has undefined behavior for those two cases; this function has safety
/// checked undefined behavior
pub fn signedRemainder(numerator: anytype, denominator: anytype) @TypeOf(numerator, denominator) {
std.debug.assert(@typeInfo(@TypeOf(numerator, denominator)).int.signedness == .signed);
if (denominator > 0) return @rem(numerator, denominator);
return numerator - @divTrunc(numerator, denominator) * denominator;
}
pub const Macros = struct {
pub fn U_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_uint, n, .decimal)) {
return promoteIntLiteral(c_uint, n, .decimal);
}
fn L_SUFFIX_ReturnType(comptime number: anytype) type {
switch (@typeInfo(@TypeOf(number))) {
.int, .comptime_int => return @TypeOf(promoteIntLiteral(c_long, number, .decimal)),
.float, .comptime_float => return c_longdouble,
else => @compileError("Invalid value for L suffix"),
}
}
pub fn L_SUFFIX(comptime number: anytype) L_SUFFIX_ReturnType(number) {
switch (@typeInfo(@TypeOf(number))) {
.int, .comptime_int => return promoteIntLiteral(c_long, number, .decimal),
.float, .comptime_float => @compileError("TODO: c_longdouble initialization from comptime_float not supported"),
else => @compileError("Invalid value for L suffix"),
}
}
pub fn UL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_ulong, n, .decimal)) {
return promoteIntLiteral(c_ulong, n, .decimal);
}
pub fn LL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_longlong, n, .decimal)) {
return promoteIntLiteral(c_longlong, n, .decimal);
}
pub fn ULL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_ulonglong, n, .decimal)) {
return promoteIntLiteral(c_ulonglong, n, .decimal);
}
pub fn F_SUFFIX(comptime f: comptime_float) f32 {
return @as(f32, f);
}
pub fn WL_CONTAINER_OF(ptr: anytype, sample: anytype, comptime member: []const u8) @TypeOf(sample) {
return @fieldParentPtr(member, ptr);
}
/// A 2-argument function-like macro defined as #define FOO(A, B) (A)(B)
/// could be either: cast B to A, or call A with the value B.
pub fn CAST_OR_CALL(a: anytype, b: anytype) switch (@typeInfo(@TypeOf(a))) {
.type => a,
.@"fn" => |fn_info| fn_info.return_type orelse void,
else => |info| @compileError("Unexpected argument type: " ++ @tagName(info)),
} {
switch (@typeInfo(@TypeOf(a))) {
.type => return cast(a, b),
.@"fn" => return a(b),
else => unreachable, // return type will be a compile error otherwise
}
}
pub inline fn DISCARD(x: anytype) void {
_ = x;
}
};
/// Integer promotion described in C11 6.3.1.1.2
fn PromotedIntType(comptime T: type) type {
return switch (T) {
bool, c_short => c_int,
c_ushort => if (@sizeOf(c_ushort) == @sizeOf(c_int)) c_uint else c_int,
c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong => T,
else => switch (@typeInfo(T)) {
.comptime_int => @compileError("Cannot promote `" ++ @typeName(T) ++ "`; a fixed-size number type is required"),
// promote to c_int if it can represent all values of T
.int => |int_info| if (int_info.bits < @bitSizeOf(c_int))
c_int
// otherwise, restore the original C type
else if (int_info.bits == @bitSizeOf(c_int))
if (int_info.signedness == .unsigned) c_uint else c_int
else if (int_info.bits <= @bitSizeOf(c_long))
if (int_info.signedness == .unsigned) c_ulong else c_long
else if (int_info.bits <= @bitSizeOf(c_longlong))
if (int_info.signedness == .unsigned) c_ulonglong else c_longlong
else
@compileError("Cannot promote `" ++ @typeName(T) ++ "`; a C ABI type is required"),
else => @compileError("Attempted to promote invalid type `" ++ @typeName(T) ++ "`"),
},
};
}
/// C11 6.3.1.1.1
fn integerRank(comptime T: type) u8 {
return switch (T) {
bool => 0,
u8, i8 => 1,
c_short, c_ushort => 2,
c_int, c_uint => 3,
c_long, c_ulong => 4,
c_longlong, c_ulonglong => 5,
else => @compileError("integer rank not supported for `" ++ @typeName(T) ++ "`"),
};
}
fn ToUnsigned(comptime T: type) type {
return switch (T) {
c_int => c_uint,
c_long => c_ulong,
c_longlong => c_ulonglong,
else => @compileError("Cannot convert `" ++ @typeName(T) ++ "` to unsigned"),
};
}
/// "Usual arithmetic conversions" from C11 standard 6.3.1.8
fn ArithmeticConversion(comptime A: type, comptime B: type) type {
if (A == c_longdouble or B == c_longdouble) return c_longdouble;
if (A == f80 or B == f80) return f80;
if (A == f64 or B == f64) return f64;
if (A == f32 or B == f32) return f32;
const A_Promoted = PromotedIntType(A);
const B_Promoted = PromotedIntType(B);
comptime {
std.debug.assert(integerRank(A_Promoted) >= integerRank(c_int));
std.debug.assert(integerRank(B_Promoted) >= integerRank(c_int));
}
if (A_Promoted == B_Promoted) return A_Promoted;
const a_signed = @typeInfo(A_Promoted).int.signedness == .signed;
const b_signed = @typeInfo(B_Promoted).int.signedness == .signed;
if (a_signed == b_signed) {
return if (integerRank(A_Promoted) > integerRank(B_Promoted)) A_Promoted else B_Promoted;
}
const SignedType = if (a_signed) A_Promoted else B_Promoted;
const UnsignedType = if (!a_signed) A_Promoted else B_Promoted;
if (integerRank(UnsignedType) >= integerRank(SignedType)) return UnsignedType;
if (std.math.maxInt(SignedType) >= std.math.maxInt(UnsignedType)) return SignedType;
return ToUnsigned(SignedType);
}
test "ArithmeticConversion" {
// Promotions not necessarily the same for other platforms
if (builtin.target.cpu.arch != .x86_64 or builtin.target.os.tag != .linux) return error.SkipZigTest;
const Test = struct {
/// Order of operands should not matter for arithmetic conversions
fn checkPromotion(comptime A: type, comptime B: type, comptime Expected: type) !void {
try std.testing.expect(ArithmeticConversion(A, B) == Expected);
try std.testing.expect(ArithmeticConversion(B, A) == Expected);
}
};
try Test.checkPromotion(c_longdouble, c_int, c_longdouble);
try Test.checkPromotion(c_int, f64, f64);
try Test.checkPromotion(f32, bool, f32);
try Test.checkPromotion(bool, c_short, c_int);
try Test.checkPromotion(c_int, c_int, c_int);
try Test.checkPromotion(c_short, c_int, c_int);
try Test.checkPromotion(c_int, c_long, c_long);
try Test.checkPromotion(c_ulonglong, c_uint, c_ulonglong);
try Test.checkPromotion(c_uint, c_int, c_uint);
try Test.checkPromotion(c_uint, c_long, c_long);
try Test.checkPromotion(c_ulong, c_longlong, c_ulonglong);
// stdint.h
try Test.checkPromotion(u8, i8, c_int);
try Test.checkPromotion(u16, i16, c_int);
try Test.checkPromotion(i32, c_int, c_int);
try Test.checkPromotion(u32, c_int, c_uint);
try Test.checkPromotion(i64, c_int, c_long);
try Test.checkPromotion(u64, c_int, c_ulong);
try Test.checkPromotion(isize, c_int, c_long);
try Test.checkPromotion(usize, c_int, c_ulong);
}
pub const MacroArithmetic = struct {
pub fn div(a: anytype, b: anytype) ArithmeticConversion(@TypeOf(a), @TypeOf(b)) {
const ResType = ArithmeticConversion(@TypeOf(a), @TypeOf(b));
const a_casted = cast(ResType, a);
const b_casted = cast(ResType, b);
switch (@typeInfo(ResType)) {
.float => return a_casted / b_casted,
.int => return @divTrunc(a_casted, b_casted),
else => unreachable,
}
}
pub fn rem(a: anytype, b: anytype) ArithmeticConversion(@TypeOf(a), @TypeOf(b)) {
const ResType = ArithmeticConversion(@TypeOf(a), @TypeOf(b));
const a_casted = cast(ResType, a);
const b_casted = cast(ResType, b);
switch (@typeInfo(ResType)) {
.int => {
if (@typeInfo(ResType).int.signedness == .signed) {
return signedRemainder(a_casted, b_casted);
} else {
return a_casted % b_casted;
}
},
else => unreachable,
}
}
};
test "Macro suffix functions" {
try testing.expect(@TypeOf(Macros.F_SUFFIX(1)) == f32);
try testing.expect(@TypeOf(Macros.U_SUFFIX(1)) == c_uint);
if (math.maxInt(c_ulong) > math.maxInt(c_uint)) {
try testing.expect(@TypeOf(Macros.U_SUFFIX(math.maxInt(c_uint) + 1)) == c_ulong);
}
if (math.maxInt(c_ulonglong) > math.maxInt(c_ulong)) {
try testing.expect(@TypeOf(Macros.U_SUFFIX(math.maxInt(c_ulong) + 1)) == c_ulonglong);
}
try testing.expect(@TypeOf(Macros.L_SUFFIX(1)) == c_long);
if (math.maxInt(c_long) > math.maxInt(c_int)) {
try testing.expect(@TypeOf(Macros.L_SUFFIX(math.maxInt(c_int) + 1)) == c_long);
}
if (math.maxInt(c_longlong) > math.maxInt(c_long)) {
try testing.expect(@TypeOf(Macros.L_SUFFIX(math.maxInt(c_long) + 1)) == c_longlong);
}
try testing.expect(@TypeOf(Macros.UL_SUFFIX(1)) == c_ulong);
if (math.maxInt(c_ulonglong) > math.maxInt(c_ulong)) {
try testing.expect(@TypeOf(Macros.UL_SUFFIX(math.maxInt(c_ulong) + 1)) == c_ulonglong);
}
try testing.expect(@TypeOf(Macros.LL_SUFFIX(1)) == c_longlong);
try testing.expect(@TypeOf(Macros.ULL_SUFFIX(1)) == c_ulonglong);
}
test "WL_CONTAINER_OF" {
const S = struct {
a: u32 = 0,
b: u32 = 0,
};
const x = S{};
const y = S{};
const ptr = Macros.WL_CONTAINER_OF(&x.b, &y, "b");
try testing.expectEqual(&x, ptr);
}
test "CAST_OR_CALL casting" {
const arg: c_int = 1000;
const casted = Macros.CAST_OR_CALL(u8, arg);
try testing.expectEqual(cast(u8, arg), casted);
const S = struct {
x: u32 = 0,
};
var s: S = .{};
const casted_ptr = Macros.CAST_OR_CALL(*u8, &s);
try testing.expectEqual(cast(*u8, &s), casted_ptr);
}
test "CAST_OR_CALL calling" {
const Helper = struct {
var last_val: bool = false;
fn returnsVoid(val: bool) void {
last_val = val;
}
fn returnsBool(f: f32) bool {
return f > 0;
}
fn identity(self: c_uint) c_uint {
return self;
}
};
Macros.CAST_OR_CALL(Helper.returnsVoid, true);
try testing.expectEqual(true, Helper.last_val);
Macros.CAST_OR_CALL(Helper.returnsVoid, false);
try testing.expectEqual(false, Helper.last_val);
try testing.expectEqual(Helper.returnsBool(1), Macros.CAST_OR_CALL(Helper.returnsBool, @as(f32, 1)));
try testing.expectEqual(Helper.returnsBool(-1), Macros.CAST_OR_CALL(Helper.returnsBool, @as(f32, -1)));
try testing.expectEqual(Helper.identity(@as(c_uint, 100)), Macros.CAST_OR_CALL(Helper.identity, @as(c_uint, 100)));
}
test "Extended C ABI casting" {
if (math.maxInt(c_long) > math.maxInt(c_char)) {
try testing.expect(@TypeOf(Macros.L_SUFFIX(@as(c_char, math.maxInt(c_char) - 1))) == c_long); // c_char
}
if (math.maxInt(c_long) > math.maxInt(c_short)) {
try testing.expect(@TypeOf(Macros.L_SUFFIX(@as(c_short, math.maxInt(c_short) - 1))) == c_long); // c_short
}
if (math.maxInt(c_long) > math.maxInt(c_ushort)) {
try testing.expect(@TypeOf(Macros.L_SUFFIX(@as(c_ushort, math.maxInt(c_ushort) - 1))) == c_long); //c_ushort
}
if (math.maxInt(c_long) > math.maxInt(c_int)) {
try testing.expect(@TypeOf(Macros.L_SUFFIX(@as(c_int, math.maxInt(c_int) - 1))) == c_long); // c_int
}
if (math.maxInt(c_long) > math.maxInt(c_uint)) {
try testing.expect(@TypeOf(Macros.L_SUFFIX(@as(c_uint, math.maxInt(c_uint) - 1))) == c_long); // c_uint
try testing.expect(@TypeOf(Macros.L_SUFFIX(math.maxInt(c_uint) + 1)) == c_long); // comptime_int -> c_long
}
if (math.maxInt(c_longlong) > math.maxInt(c_long)) {
try testing.expect(@TypeOf(Macros.L_SUFFIX(@as(c_long, math.maxInt(c_long) - 1))) == c_long); // c_long
try testing.expect(@TypeOf(Macros.L_SUFFIX(math.maxInt(c_long) + 1)) == c_longlong); // comptime_int -> c_longlong
}
}
// Function with complex signature for testing the SDL case
fn complexFunction(_: ?*anyopaque, _: c_uint, _: ?*const fn (?*anyopaque) callconv(.c) c_uint, _: ?*anyopaque, _: c_uint, _: [*c]c_uint) callconv(.c) usize {
return 0;
}
test "function pointer casting" {
const SDL_FunctionPointer = ?*const fn () callconv(.c) void;
const fn_ptr = cast(SDL_FunctionPointer, complexFunction);
try testing.expect(fn_ptr != null);
}

View File

@ -1,182 +1,176 @@
const std = @import("std");
pub inline fn __builtin_bswap16(val: u16) u16 {
return @byteSwap(val);
/// Standard C Library bug: The absolute value of the most negative integer remains negative.
pub inline fn abs(val: c_int) c_int {
return if (val == std.math.minInt(c_int)) val else @intCast(@abs(val));
}
pub inline fn __builtin_bswap32(val: u32) u32 {
return @byteSwap(val);
pub inline fn assume(cond: bool) void {
if (!cond) unreachable;
}
pub inline fn __builtin_bswap64(val: u64) u64 {
pub inline fn bswap16(val: u16) u16 {
return @byteSwap(val);
}
pub inline fn __builtin_signbit(val: f64) c_int {
return @intFromBool(std.math.signbit(val));
}
pub inline fn __builtin_signbitf(val: f32) c_int {
return @intFromBool(std.math.signbit(val));
pub inline fn bswap32(val: u32) u32 {
return @byteSwap(val);
}
pub inline fn __builtin_popcount(val: c_uint) c_int {
// popcount of a c_uint will never exceed the capacity of a c_int
@setRuntimeSafety(false);
return @as(c_int, @bitCast(@as(c_uint, @popCount(val))));
pub inline fn bswap64(val: u64) u64 {
return @byteSwap(val);
}
pub inline fn __builtin_ctz(val: c_uint) c_int {
// Returns the number of trailing 0-bits in val, starting at the least significant bit position.
// In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint
@setRuntimeSafety(false);
return @as(c_int, @bitCast(@as(c_uint, @ctz(val))));
pub inline fn ceilf(val: f32) f32 {
return @ceil(val);
}
pub inline fn __builtin_clz(val: c_uint) c_int {
// Returns the number of leading 0-bits in x, starting at the most significant bit position.
// In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint
pub inline fn ceil(val: f64) f64 {
return @ceil(val);
}
/// Returns the number of leading 0-bits in x, starting at the most significant bit position.
/// In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint
pub inline fn clz(val: c_uint) c_int {
@setRuntimeSafety(false);
return @as(c_int, @bitCast(@as(c_uint, @clz(val))));
}
pub inline fn __builtin_sqrt(val: f64) f64 {
return @sqrt(val);
}
pub inline fn __builtin_sqrtf(val: f32) f32 {
return @sqrt(val);
pub inline fn constant_p(expr: anytype) c_int {
_ = expr;
return @intFromBool(false);
}
pub inline fn __builtin_sin(val: f64) f64 {
return @sin(val);
}
pub inline fn __builtin_sinf(val: f32) f32 {
return @sin(val);
}
pub inline fn __builtin_cos(val: f64) f64 {
return @cos(val);
}
pub inline fn __builtin_cosf(val: f32) f32 {
pub inline fn cosf(val: f32) f32 {
return @cos(val);
}
pub inline fn __builtin_exp(val: f64) f64 {
return @exp(val);
}
pub inline fn __builtin_expf(val: f32) f32 {
return @exp(val);
}
pub inline fn __builtin_exp2(val: f64) f64 {
return @exp2(val);
}
pub inline fn __builtin_exp2f(val: f32) f32 {
return @exp2(val);
}
pub inline fn __builtin_log(val: f64) f64 {
return @log(val);
}
pub inline fn __builtin_logf(val: f32) f32 {
return @log(val);
}
pub inline fn __builtin_log2(val: f64) f64 {
return @log2(val);
}
pub inline fn __builtin_log2f(val: f32) f32 {
return @log2(val);
}
pub inline fn __builtin_log10(val: f64) f64 {
return @log10(val);
}
pub inline fn __builtin_log10f(val: f32) f32 {
return @log10(val);
pub inline fn cos(val: f64) f64 {
return @cos(val);
}
// Standard C Library bug: The absolute value of the most negative integer remains negative.
pub inline fn __builtin_abs(val: c_int) c_int {
return if (val == std.math.minInt(c_int)) val else @intCast(@abs(val));
/// Returns the number of trailing 0-bits in val, starting at the least significant bit position.
/// In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint
pub inline fn ctz(val: c_uint) c_int {
@setRuntimeSafety(false);
return @as(c_int, @bitCast(@as(c_uint, @ctz(val))));
}
pub inline fn __builtin_labs(val: c_long) c_long {
pub inline fn exp2f(val: f32) f32 {
return @exp2(val);
}
pub inline fn exp2(val: f64) f64 {
return @exp2(val);
}
pub inline fn expf(val: f32) f32 {
return @exp(val);
}
pub inline fn exp(val: f64) f64 {
return @exp(val);
}
/// The return value of __builtin_expect is `expr`. `c` is the expected value
/// of `expr` and is used as a hint to the compiler in C. Here it is unused.
pub inline fn expect(expr: c_long, c: c_long) c_long {
_ = c;
return expr;
}
pub inline fn fabsf(val: f32) f32 {
return @abs(val);
}
pub inline fn fabs(val: f64) f64 {
return @abs(val);
}
pub inline fn floorf(val: f32) f32 {
return @floor(val);
}
pub inline fn floor(val: f64) f64 {
return @floor(val);
}
pub inline fn has_builtin(func: anytype) c_int {
_ = func;
return @intFromBool(true);
}
pub inline fn huge_valf() f32 {
return std.math.inf(f32);
}
pub inline fn inff() f32 {
return std.math.inf(f32);
}
/// Similar to isinf, except the return value is -1 for an argument of -Inf and 1 for an argument of +Inf.
pub inline fn isinf_sign(x: anytype) c_int {
if (!std.math.isInf(x)) return 0;
return if (std.math.isPositiveInf(x)) 1 else -1;
}
pub inline fn isinf(x: anytype) c_int {
return @intFromBool(std.math.isInf(x));
}
pub inline fn isnan(x: anytype) c_int {
return @intFromBool(std.math.isNan(x));
}
/// Standard C Library bug: The absolute value of the most negative integer remains negative.
pub inline fn labs(val: c_long) c_long {
return if (val == std.math.minInt(c_long)) val else @intCast(@abs(val));
}
pub inline fn __builtin_llabs(val: c_longlong) c_longlong {
/// Standard C Library bug: The absolute value of the most negative integer remains negative.
pub inline fn llabs(val: c_longlong) c_longlong {
return if (val == std.math.minInt(c_longlong)) val else @intCast(@abs(val));
}
pub inline fn __builtin_fabs(val: f64) f64 {
return @abs(val);
}
pub inline fn __builtin_fabsf(val: f32) f32 {
return @abs(val);
pub inline fn log10f(val: f32) f32 {
return @log10(val);
}
pub inline fn __builtin_floor(val: f64) f64 {
return @floor(val);
}
pub inline fn __builtin_floorf(val: f32) f32 {
return @floor(val);
}
pub inline fn __builtin_ceil(val: f64) f64 {
return @ceil(val);
}
pub inline fn __builtin_ceilf(val: f32) f32 {
return @ceil(val);
}
pub inline fn __builtin_trunc(val: f64) f64 {
return @trunc(val);
}
pub inline fn __builtin_truncf(val: f32) f32 {
return @trunc(val);
}
pub inline fn __builtin_round(val: f64) f64 {
return @round(val);
}
pub inline fn __builtin_roundf(val: f32) f32 {
return @round(val);
pub inline fn log10(val: f64) f64 {
return @log10(val);
}
pub inline fn __builtin_strlen(s: [*c]const u8) usize {
return std.mem.sliceTo(s, 0).len;
}
pub inline fn __builtin_strcmp(s1: [*c]const u8, s2: [*c]const u8) c_int {
return switch (std.mem.orderZ(u8, s1, s2)) {
.lt => -1,
.eq => 0,
.gt => 1,
};
pub inline fn log2f(val: f32) f32 {
return @log2(val);
}
pub inline fn __builtin_object_size(ptr: ?*const anyopaque, ty: c_int) usize {
_ = ptr;
// clang semantics match gcc's: https://gcc.gnu.org/onlinedocs/gcc/Object-Size-Checking.html
// If it is not possible to determine which objects ptr points to at compile time,
// __builtin_object_size should return (size_t) -1 for type 0 or 1 and (size_t) 0
// for type 2 or 3.
if (ty == 0 or ty == 1) return @as(usize, @bitCast(-@as(isize, 1)));
if (ty == 2 or ty == 3) return 0;
unreachable;
pub inline fn log2(val: f64) f64 {
return @log2(val);
}
pub inline fn __builtin___memset_chk(
dst: ?*anyopaque,
val: c_int,
len: usize,
remaining: usize,
) ?*anyopaque {
if (len > remaining) @panic("std.c.builtins.memset_chk called with len > remaining");
return __builtin_memset(dst, val, len);
pub inline fn logf(val: f32) f32 {
return @log(val);
}
pub inline fn __builtin_memset(dst: ?*anyopaque, val: c_int, len: usize) ?*anyopaque {
const dst_cast = @as([*c]u8, @ptrCast(dst));
@memset(dst_cast[0..len], @as(u8, @bitCast(@as(i8, @truncate(val)))));
return dst;
pub inline fn log(val: f64) f64 {
return @log(val);
}
pub inline fn __builtin___memcpy_chk(
pub inline fn memcpy_chk(
noalias dst: ?*anyopaque,
noalias src: ?*const anyopaque,
len: usize,
remaining: usize,
) ?*anyopaque {
if (len > remaining) @panic("std.c.builtins.memcpy_chk called with len > remaining");
return __builtin_memcpy(dst, src, len);
if (len > remaining) @panic("__builtin___memcpy_chk called with len > remaining");
if (len > 0) @memcpy(
@as([*]u8, @ptrCast(dst.?))[0..len],
@as([*]const u8, @ptrCast(src.?)),
);
return dst;
}
pub inline fn __builtin_memcpy(
pub inline fn memcpy(
noalias dst: ?*anyopaque,
noalias src: ?*const anyopaque,
len: usize,
@ -188,16 +182,33 @@ pub inline fn __builtin_memcpy(
return dst;
}
/// The return value of __builtin_expect is `expr`. `c` is the expected value
/// of `expr` and is used as a hint to the compiler in C. Here it is unused.
pub inline fn __builtin_expect(expr: c_long, c: c_long) c_long {
_ = c;
return expr;
pub inline fn memset_chk(
dst: ?*anyopaque,
val: c_int,
len: usize,
remaining: usize,
) ?*anyopaque {
if (len > remaining) @panic("__builtin___memset_chk called with len > remaining");
const dst_cast = @as([*c]u8, @ptrCast(dst));
@memset(dst_cast[0..len], @as(u8, @bitCast(@as(i8, @truncate(val)))));
return dst;
}
pub inline fn memset(dst: ?*anyopaque, val: c_int, len: usize) ?*anyopaque {
const dst_cast = @as([*c]u8, @ptrCast(dst));
@memset(dst_cast[0..len], @as(u8, @bitCast(@as(i8, @truncate(val)))));
return dst;
}
pub fn mul_overflow(a: anytype, b: anytype, result: *@TypeOf(a, b)) c_int {
const res = @mulWithOverflow(a, b);
result.* = res[0];
return res[1];
}
/// returns a quiet NaN. Quiet NaNs have many representations; tagp is used to select one in an
/// implementation-defined way.
/// This implementation is based on the description for __builtin_nan provided in the GCC docs at
/// This implementation is based on the description for nan provided in the GCC docs at
/// https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#index-_005f_005fbuiltin_005fnan
/// Comment is reproduced below:
/// Since ISO C99 defines this function in terms of strtod, which we do not implement, a description
@ -210,59 +221,81 @@ pub inline fn __builtin_expect(expr: c_long, c: c_long) c_long {
///
/// If tagp contains any non-numeric characters, the function returns a NaN whose significand is zero.
/// If tagp is empty, the function returns a NaN whose significand is zero.
pub inline fn __builtin_nanf(tagp: []const u8) f32 {
pub inline fn nanf(tagp: []const u8) f32 {
const parsed = std.fmt.parseUnsigned(c_ulong, tagp, 0) catch 0;
const bits: u23 = @truncate(parsed); // single-precision float trailing significand is 23 bits
return @bitCast(@as(u32, bits) | @as(u32, @bitCast(std.math.nan(f32))));
}
pub inline fn __builtin_huge_valf() f32 {
return std.math.inf(f32);
}
pub inline fn __builtin_inff() f32 {
return std.math.inf(f32);
}
pub inline fn __builtin_isnan(x: anytype) c_int {
return @intFromBool(std.math.isNan(x));
}
pub inline fn __builtin_isinf(x: anytype) c_int {
return @intFromBool(std.math.isInf(x));
}
/// Similar to isinf, except the return value is -1 for an argument of -Inf and 1 for an argument of +Inf.
pub inline fn __builtin_isinf_sign(x: anytype) c_int {
if (!std.math.isInf(x)) return 0;
return if (std.math.isPositiveInf(x)) 1 else -1;
}
pub inline fn __has_builtin(func: anytype) c_int {
_ = func;
return @intFromBool(true);
}
pub inline fn __builtin_assume(cond: bool) void {
if (!cond) unreachable;
}
pub inline fn __builtin_unreachable() noreturn {
pub inline fn object_size(ptr: ?*const anyopaque, ty: c_int) usize {
_ = ptr;
// clang semantics match gcc's: https://gcc.gnu.org/onlinedocs/gcc/Object-Size-Checking.html
// If it is not possible to determine which objects ptr points to at compile time,
// object_size should return (size_t) -1 for type 0 or 1 and (size_t) 0
// for type 2 or 3.
if (ty == 0 or ty == 1) return @as(usize, @bitCast(-@as(isize, 1)));
if (ty == 2 or ty == 3) return 0;
unreachable;
}
pub inline fn __builtin_constant_p(expr: anytype) c_int {
_ = expr;
return @intFromBool(false);
}
pub fn __builtin_mul_overflow(a: anytype, b: anytype, result: *@TypeOf(a, b)) c_int {
const res = @mulWithOverflow(a, b);
result.* = res[0];
return res[1];
/// popcount of a c_uint will never exceed the capacity of a c_int
pub inline fn popcount(val: c_uint) c_int {
@setRuntimeSafety(false);
return @as(c_int, @bitCast(@as(c_uint, @popCount(val))));
}
// __builtin_alloca_with_align is not currently implemented.
// It is used in a run-translated-c test and a test-translate-c test to ensure that non-implemented
// builtins are correctly demoted. If you implement __builtin_alloca_with_align, please update the
// run-translated-c test and the test-translate-c test to use a different non-implemented builtin.
// pub inline fn __builtin_alloca_with_align(size: usize, alignment: usize) *anyopaque {}
pub inline fn roundf(val: f32) f32 {
return @round(val);
}
pub inline fn round(val: f64) f64 {
return @round(val);
}
pub inline fn signbitf(val: f32) c_int {
return @intFromBool(std.math.signbit(val));
}
pub inline fn signbit(val: f64) c_int {
return @intFromBool(std.math.signbit(val));
}
pub inline fn sinf(val: f32) f32 {
return @sin(val);
}
pub inline fn sin(val: f64) f64 {
return @sin(val);
}
pub inline fn sqrtf(val: f32) f32 {
return @sqrt(val);
}
pub inline fn sqrt(val: f64) f64 {
return @sqrt(val);
}
pub inline fn strcmp(s1: [*c]const u8, s2: [*c]const u8) c_int {
return switch (std.mem.orderZ(u8, s1, s2)) {
.lt => -1,
.eq => 0,
.gt => 1,
};
}
pub inline fn strlen(s: [*c]const u8) usize {
return std.mem.sliceTo(s, 0).len;
}
pub inline fn truncf(val: f32) f32 {
return @trunc(val);
}
pub inline fn trunc(val: f64) f64 {
return @trunc(val);
}
pub inline fn @"unreachable"() noreturn {
unreachable;
}

View File

@ -0,0 +1,413 @@
const std = @import("std");
/// "Usual arithmetic conversions" from C11 standard 6.3.1.8
pub fn ArithmeticConversion(comptime A: type, comptime B: type) type {
if (A == c_longdouble or B == c_longdouble) return c_longdouble;
if (A == f80 or B == f80) return f80;
if (A == f64 or B == f64) return f64;
if (A == f32 or B == f32) return f32;
const A_Promoted = PromotedIntType(A);
const B_Promoted = PromotedIntType(B);
comptime {
std.debug.assert(integerRank(A_Promoted) >= integerRank(c_int));
std.debug.assert(integerRank(B_Promoted) >= integerRank(c_int));
}
if (A_Promoted == B_Promoted) return A_Promoted;
const a_signed = @typeInfo(A_Promoted).int.signedness == .signed;
const b_signed = @typeInfo(B_Promoted).int.signedness == .signed;
if (a_signed == b_signed) {
return if (integerRank(A_Promoted) > integerRank(B_Promoted)) A_Promoted else B_Promoted;
}
const SignedType = if (a_signed) A_Promoted else B_Promoted;
const UnsignedType = if (!a_signed) A_Promoted else B_Promoted;
if (integerRank(UnsignedType) >= integerRank(SignedType)) return UnsignedType;
if (std.math.maxInt(SignedType) >= std.math.maxInt(UnsignedType)) return SignedType;
return ToUnsigned(SignedType);
}
/// Integer promotion described in C11 6.3.1.1.2
fn PromotedIntType(comptime T: type) type {
return switch (T) {
bool, c_short => c_int,
c_ushort => if (@sizeOf(c_ushort) == @sizeOf(c_int)) c_uint else c_int,
c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong => T,
else => switch (@typeInfo(T)) {
.comptime_int => @compileError("Cannot promote `" ++ @typeName(T) ++ "`; a fixed-size number type is required"),
// promote to c_int if it can represent all values of T
.int => |int_info| if (int_info.bits < @bitSizeOf(c_int))
c_int
// otherwise, restore the original C type
else if (int_info.bits == @bitSizeOf(c_int))
if (int_info.signedness == .unsigned) c_uint else c_int
else if (int_info.bits <= @bitSizeOf(c_long))
if (int_info.signedness == .unsigned) c_ulong else c_long
else if (int_info.bits <= @bitSizeOf(c_longlong))
if (int_info.signedness == .unsigned) c_ulonglong else c_longlong
else
@compileError("Cannot promote `" ++ @typeName(T) ++ "`; a C ABI type is required"),
else => @compileError("Attempted to promote invalid type `" ++ @typeName(T) ++ "`"),
},
};
}
/// C11 6.3.1.1.1
fn integerRank(comptime T: type) u8 {
return switch (T) {
bool => 0,
u8, i8 => 1,
c_short, c_ushort => 2,
c_int, c_uint => 3,
c_long, c_ulong => 4,
c_longlong, c_ulonglong => 5,
else => @compileError("integer rank not supported for `" ++ @typeName(T) ++ "`"),
};
}
fn ToUnsigned(comptime T: type) type {
return switch (T) {
c_int => c_uint,
c_long => c_ulong,
c_longlong => c_ulonglong,
else => @compileError("Cannot convert `" ++ @typeName(T) ++ "` to unsigned"),
};
}
/// Constructs a [*c] pointer with the const and volatile annotations
/// from SelfType for pointing to a C flexible array of ElementType.
pub fn FlexibleArrayType(comptime SelfType: type, comptime ElementType: type) type {
switch (@typeInfo(SelfType)) {
.pointer => |ptr| {
return @Type(.{ .pointer = .{
.size = .c,
.is_const = ptr.is_const,
.is_volatile = ptr.is_volatile,
.alignment = @alignOf(ElementType),
.address_space = .generic,
.child = ElementType,
.is_allowzero = true,
.sentinel_ptr = null,
} });
},
else => |info| @compileError("Invalid self type \"" ++ @tagName(info) ++ "\" for flexible array getter: " ++ @typeName(SelfType)),
}
}
/// Promote the type of an integer literal until it fits as C would.
pub fn promoteIntLiteral(
comptime SuffixType: type,
comptime number: comptime_int,
comptime base: CIntLiteralBase,
) PromoteIntLiteralReturnType(SuffixType, number, base) {
return number;
}
const CIntLiteralBase = enum { decimal, octal, hex };
fn PromoteIntLiteralReturnType(comptime SuffixType: type, comptime number: comptime_int, comptime base: CIntLiteralBase) type {
const signed_decimal = [_]type{ c_int, c_long, c_longlong, c_ulonglong };
const signed_oct_hex = [_]type{ c_int, c_uint, c_long, c_ulong, c_longlong, c_ulonglong };
const unsigned = [_]type{ c_uint, c_ulong, c_ulonglong };
const list: []const type = if (@typeInfo(SuffixType).int.signedness == .unsigned)
&unsigned
else if (base == .decimal)
&signed_decimal
else
&signed_oct_hex;
var pos = std.mem.indexOfScalar(type, list, SuffixType).?;
while (pos < list.len) : (pos += 1) {
if (number >= std.math.minInt(list[pos]) and number <= std.math.maxInt(list[pos])) {
return list[pos];
}
}
@compileError("Integer literal is too large");
}
/// Convert from clang __builtin_shufflevector index to Zig @shuffle index
/// clang requires __builtin_shufflevector index arguments to be integer constants.
/// negative values for `this_index` indicate "don't care".
/// clang enforces that `this_index` is less than the total number of vector elements
/// See https://ziglang.org/documentation/master/#shuffle
/// See https://clang.llvm.org/docs/LanguageExtensions.html#langext-builtin-shufflevector
pub fn shuffleVectorIndex(comptime this_index: c_int, comptime source_vector_len: usize) i32 {
const positive_index = std.math.cast(usize, this_index) orelse return undefined;
if (positive_index < source_vector_len) return @as(i32, @intCast(this_index));
const b_index = positive_index - source_vector_len;
return ~@as(i32, @intCast(b_index));
}
/// C `%` operator for signed integers
/// C standard states: "If the quotient a/b is representable, the expression (a/b)*b + a%b shall equal a"
/// The quotient is not representable if denominator is zero, or if numerator is the minimum integer for
/// the type and denominator is -1. C has undefined behavior for those two cases; this function has safety
/// checked undefined behavior
pub fn signedRemainder(numerator: anytype, denominator: anytype) @TypeOf(numerator, denominator) {
std.debug.assert(@typeInfo(@TypeOf(numerator, denominator)).int.signedness == .signed);
if (denominator > 0) return @rem(numerator, denominator);
return numerator - @divTrunc(numerator, denominator) * denominator;
}
/// Given a type and value, cast the value to the type as c would.
pub fn cast(comptime DestType: type, target: anytype) DestType {
// this function should behave like transCCast in translate-c, except it's for macros
const SourceType = @TypeOf(target);
switch (@typeInfo(DestType)) {
.@"fn" => return castToPtr(*const DestType, SourceType, target),
.pointer => return castToPtr(DestType, SourceType, target),
.optional => |dest_opt| {
if (@typeInfo(dest_opt.child) == .pointer) {
return castToPtr(DestType, SourceType, target);
} else if (@typeInfo(dest_opt.child) == .@"fn") {
return castToPtr(?*const dest_opt.child, SourceType, target);
}
},
.int => {
switch (@typeInfo(SourceType)) {
.pointer => {
return castInt(DestType, @intFromPtr(target));
},
.optional => |opt| {
if (@typeInfo(opt.child) == .pointer) {
return castInt(DestType, @intFromPtr(target));
}
},
.int => {
return castInt(DestType, target);
},
.@"fn" => {
return castInt(DestType, @intFromPtr(&target));
},
.bool => {
return @intFromBool(target);
},
else => {},
}
},
.float => {
switch (@typeInfo(SourceType)) {
.int => return @as(DestType, @floatFromInt(target)),
.float => return @as(DestType, @floatCast(target)),
.bool => return @as(DestType, @floatFromInt(@intFromBool(target))),
else => {},
}
},
.@"union" => |info| {
inline for (info.fields) |field| {
if (field.type == SourceType) return @unionInit(DestType, field.name, target);
}
@compileError("cast to union type '" ++ @typeName(DestType) ++ "' from type '" ++ @typeName(SourceType) ++ "' which is not present in union");
},
.bool => return cast(usize, target) != 0,
else => {},
}
return @as(DestType, target);
}
fn castInt(comptime DestType: type, target: anytype) DestType {
const dest = @typeInfo(DestType).int;
const source = @typeInfo(@TypeOf(target)).int;
const Int = @Type(.{ .int = .{ .bits = dest.bits, .signedness = source.signedness } });
if (dest.bits < source.bits)
return @as(DestType, @bitCast(@as(Int, @truncate(target))))
else
return @as(DestType, @bitCast(@as(Int, target)));
}
fn castPtr(comptime DestType: type, target: anytype) DestType {
return @ptrCast(@alignCast(@constCast(@volatileCast(target))));
}
fn castToPtr(comptime DestType: type, comptime SourceType: type, target: anytype) DestType {
switch (@typeInfo(SourceType)) {
.int => {
return @as(DestType, @ptrFromInt(castInt(usize, target)));
},
.comptime_int => {
if (target < 0)
return @as(DestType, @ptrFromInt(@as(usize, @bitCast(@as(isize, @intCast(target))))))
else
return @as(DestType, @ptrFromInt(@as(usize, @intCast(target))));
},
.pointer => {
return castPtr(DestType, target);
},
.@"fn" => {
return castPtr(DestType, &target);
},
.optional => |target_opt| {
if (@typeInfo(target_opt.child) == .pointer) {
return castPtr(DestType, target);
}
},
else => {},
}
return @as(DestType, target);
}
/// Given a value returns its size as C's sizeof operator would.
pub fn sizeof(target: anytype) usize {
const T: type = if (@TypeOf(target) == type) target else @TypeOf(target);
switch (@typeInfo(T)) {
.float, .int, .@"struct", .@"union", .array, .bool, .vector => return @sizeOf(T),
.@"fn" => {
// sizeof(main) in C returns 1
return 1;
},
.null => return @sizeOf(*anyopaque),
.void => {
// Note: sizeof(void) is 1 on clang/gcc and 0 on MSVC.
return 1;
},
.@"opaque" => {
if (T == anyopaque) {
// Note: sizeof(void) is 1 on clang/gcc and 0 on MSVC.
return 1;
} else {
@compileError("Cannot use C sizeof on opaque type " ++ @typeName(T));
}
},
.optional => |opt| {
if (@typeInfo(opt.child) == .pointer) {
return sizeof(opt.child);
} else {
@compileError("Cannot use C sizeof on non-pointer optional " ++ @typeName(T));
}
},
.pointer => |ptr| {
if (ptr.size == .slice) {
@compileError("Cannot use C sizeof on slice type " ++ @typeName(T));
}
// for strings, sizeof("a") returns 2.
// normal pointer decay scenarios from C are handled
// in the .array case above, but strings remain literals
// and are therefore always pointers, so they need to be
// specially handled here.
if (ptr.size == .one and ptr.is_const and @typeInfo(ptr.child) == .array) {
const array_info = @typeInfo(ptr.child).array;
if ((array_info.child == u8 or array_info.child == u16) and array_info.sentinel() == 0) {
// length of the string plus one for the null terminator.
return (array_info.len + 1) * @sizeOf(array_info.child);
}
}
// When zero sized pointers are removed, this case will no
// longer be reachable and can be deleted.
if (@sizeOf(T) == 0) {
return @sizeOf(*anyopaque);
}
return @sizeOf(T);
},
.comptime_float => return @sizeOf(f64), // TODO c_double #3999
.comptime_int => {
// TODO to get the correct result we have to translate
// `1073741824 * 4` as `int(1073741824) *% int(4)` since
// sizeof(1073741824 * 4) != sizeof(4294967296).
// TODO test if target fits in int, long or long long
return @sizeOf(c_int);
},
else => @compileError("__helpers.sizeof does not support type " ++ @typeName(T)),
}
}
pub fn div(a: anytype, b: anytype) ArithmeticConversion(@TypeOf(a), @TypeOf(b)) {
const ResType = ArithmeticConversion(@TypeOf(a), @TypeOf(b));
const a_casted = cast(ResType, a);
const b_casted = cast(ResType, b);
switch (@typeInfo(ResType)) {
.float => return a_casted / b_casted,
.int => return @divTrunc(a_casted, b_casted),
else => unreachable,
}
}
pub fn rem(a: anytype, b: anytype) ArithmeticConversion(@TypeOf(a), @TypeOf(b)) {
const ResType = ArithmeticConversion(@TypeOf(a), @TypeOf(b));
const a_casted = cast(ResType, a);
const b_casted = cast(ResType, b);
switch (@typeInfo(ResType)) {
.int => {
if (@typeInfo(ResType).int.signedness == .signed) {
return signedRemainder(a_casted, b_casted);
} else {
return a_casted % b_casted;
}
},
else => unreachable,
}
}
/// A 2-argument function-like macro defined as #define FOO(A, B) (A)(B)
/// could be either: cast B to A, or call A with the value B.
pub fn CAST_OR_CALL(a: anytype, b: anytype) switch (@typeInfo(@TypeOf(a))) {
.type => a,
.@"fn" => |fn_info| fn_info.return_type orelse void,
else => |info| @compileError("Unexpected argument type: " ++ @tagName(info)),
} {
switch (@typeInfo(@TypeOf(a))) {
.type => return cast(a, b),
.@"fn" => return a(b),
else => unreachable, // return type will be a compile error otherwise
}
}
pub inline fn DISCARD(x: anytype) void {
_ = x;
}
pub fn F_SUFFIX(comptime f: comptime_float) f32 {
return @as(f32, f);
}
fn L_SUFFIX_ReturnType(comptime number: anytype) type {
switch (@typeInfo(@TypeOf(number))) {
.int, .comptime_int => return @TypeOf(promoteIntLiteral(c_long, number, .decimal)),
.float, .comptime_float => return c_longdouble,
else => @compileError("Invalid value for L suffix"),
}
}
pub fn L_SUFFIX(comptime number: anytype) L_SUFFIX_ReturnType(number) {
switch (@typeInfo(@TypeOf(number))) {
.int, .comptime_int => return promoteIntLiteral(c_long, number, .decimal),
.float, .comptime_float => @compileError("TODO: c_longdouble initialization from comptime_float not supported"),
else => @compileError("Invalid value for L suffix"),
}
}
pub fn LL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_longlong, n, .decimal)) {
return promoteIntLiteral(c_longlong, n, .decimal);
}
pub fn U_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_uint, n, .decimal)) {
return promoteIntLiteral(c_uint, n, .decimal);
}
pub fn UL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_ulong, n, .decimal)) {
return promoteIntLiteral(c_ulong, n, .decimal);
}
pub fn ULL_SUFFIX(comptime n: comptime_int) @TypeOf(promoteIntLiteral(c_ulonglong, n, .decimal)) {
return promoteIntLiteral(c_ulonglong, n, .decimal);
}
pub fn WL_CONTAINER_OF(ptr: anytype, sample: anytype, comptime member: []const u8) @TypeOf(sample) {
return @fieldParentPtr(member, ptr);
}

View File

@ -5655,115 +5655,60 @@ pub const CImportResult = struct {
};
/// Caller owns returned memory.
pub fn cImport(comp: *Compilation, c_src: []const u8, owner_mod: *Package.Module) !CImportResult {
pub fn cImport(
comp: *Compilation,
c_src: []const u8,
owner_mod: *Package.Module,
prog_node: std.Progress.Node,
) !CImportResult {
dev.check(.translate_c_command);
const tracy_trace = trace(@src());
defer tracy_trace.end();
const cimport_zig_basename = "cimport.zig";
const cimport_basename = "cimport.h";
const translated_basename = "cimport.zig";
var man = comp.obtainCObjectCacheManifest(owner_mod);
defer man.deinit();
man.hash.add(@as(u16, 0xb945)); // Random number to distinguish translate-c from compiling C objects
man.hash.add(@as(u16, 0x7dd9)); // Random number to distinguish translate-c from compiling C objects
man.hash.addBytes(c_src);
man.hash.add(comp.config.c_frontend);
// If the previous invocation resulted in clang errors, we will see a hit
// here with 0 files in the manifest, in which case it is actually a miss.
// We need to "unhit" in this case, to keep the digests matching.
const prev_hash_state = man.hash.peekBin();
const actual_hit = hit: {
_ = try man.hit();
if (man.files.entries.len == 0) {
man.unhit(prev_hash_state, 0);
break :hit false;
}
break :hit true;
};
const digest = if (!actual_hit) digest: {
const digest, const is_hit = if (try man.hit()) .{ man.finalBin(), true } else digest: {
var arena_allocator = std.heap.ArenaAllocator.init(comp.gpa);
defer arena_allocator.deinit();
const arena = arena_allocator.allocator();
const tmp_digest = man.hash.peek();
const tmp_dir_sub_path = try fs.path.join(arena, &[_][]const u8{ "o", &tmp_digest });
var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.makeOpenPath(tmp_dir_sub_path, .{});
defer zig_cache_tmp_dir.close();
const cimport_basename = "cimport.h";
const out_h_path = try comp.dirs.local_cache.join(arena, &[_][]const u8{
tmp_dir_sub_path, cimport_basename,
});
const tmp_basename = std.fmt.hex(std.crypto.random.int(u64));
const tmp_sub_path = "tmp" ++ fs.path.sep_str ++ tmp_basename;
const cache_dir = comp.dirs.local_cache.handle;
const out_h_sub_path = tmp_sub_path ++ fs.path.sep_str ++ cimport_basename;
try cache_dir.makePath(tmp_sub_path);
const out_h_path = try comp.dirs.local_cache.join(arena, &.{out_h_sub_path});
const translated_path = try comp.dirs.local_cache.join(arena, &.{ tmp_sub_path, translated_basename });
const out_dep_path = try std.fmt.allocPrint(arena, "{s}.d", .{out_h_path});
try zig_cache_tmp_dir.writeFile(.{ .sub_path = cimport_basename, .data = c_src });
if (comp.verbose_cimport) {
log.info("C import source: {s}", .{out_h_path});
}
if (comp.verbose_cimport) log.info("writing C import source to {s}", .{out_h_path});
try cache_dir.writeFile(.{ .sub_path = out_h_sub_path, .data = c_src });
var argv = std.array_list.Managed([]const u8).init(comp.gpa);
defer argv.deinit();
try argv.append(@tagName(comp.config.c_frontend)); // argv[0] is program name, actual args start at [1]
try comp.addTranslateCCArgs(arena, &argv, .c, out_dep_path, owner_mod);
try argv.appendSlice(&.{ out_h_path, "-o", translated_path });
try argv.append(out_h_path);
if (comp.verbose_cc) dump_argv(argv.items);
var stdout: []u8 = undefined;
try @import("main.zig").translateC(comp.gpa, arena, argv.items, prog_node, &stdout);
if (comp.verbose_cimport and stdout.len != 0) log.info("unexpected stdout: {s}", .{stdout});
if (comp.verbose_cc) {
dump_argv(argv.items);
}
var tree = switch (comp.config.c_frontend) {
.aro => tree: {
if (true) @panic("TODO");
break :tree undefined;
},
.clang => tree: {
if (!build_options.have_llvm) unreachable;
const translate_c = @import("translate_c.zig");
// Convert to null terminated args.
const new_argv_with_sentinel = try arena.alloc(?[*:0]const u8, argv.items.len + 1);
new_argv_with_sentinel[argv.items.len] = null;
const new_argv = new_argv_with_sentinel[0..argv.items.len :null];
for (argv.items, 0..) |arg, i| {
new_argv[i] = try arena.dupeZ(u8, arg);
}
const c_headers_dir_path_z = try comp.dirs.zig_lib.joinZ(arena, &.{"include"});
var errors = std.zig.ErrorBundle.empty;
errdefer errors.deinit(comp.gpa);
break :tree translate_c.translate(
comp.gpa,
new_argv.ptr,
new_argv.ptr + new_argv.len,
&errors,
c_headers_dir_path_z,
) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.SemanticAnalyzeFail => {
return CImportResult{
.digest = undefined,
.cache_hit = actual_hit,
.errors = errors,
};
},
};
},
};
defer tree.deinit(comp.gpa);
if (comp.verbose_cimport) {
log.info("C import .d file: {s}", .{out_dep_path});
}
const dep_basename = fs.path.basename(out_dep_path);
try man.addDepFilePost(zig_cache_tmp_dir, dep_basename);
const dep_sub_path = out_h_sub_path ++ ".d";
if (comp.verbose_cimport) log.info("processing dep file at {s}", .{dep_sub_path});
try man.addDepFilePost(cache_dir, dep_sub_path);
switch (comp.cache_use) {
.whole => |whole| if (whole.cache_manifest) |whole_cache_manifest| {
whole.cache_manifest_mutex.lock();
defer whole.cache_manifest_mutex.unlock();
try whole_cache_manifest.addDepFilePost(zig_cache_tmp_dir, dep_basename);
try whole_cache_manifest.addDepFilePost(cache_dir, dep_sub_path);
},
.incremental, .none => {},
}
@ -5771,19 +5716,12 @@ pub fn cImport(comp: *Compilation, c_src: []const u8, owner_mod: *Package.Module
const bin_digest = man.finalBin();
const hex_digest = Cache.binToHex(bin_digest);
const o_sub_path = "o" ++ fs.path.sep_str ++ hex_digest;
var o_dir = try comp.dirs.local_cache.handle.makeOpenPath(o_sub_path, .{});
defer o_dir.close();
var out_zig_file = try o_dir.createFile(cimport_zig_basename, .{});
defer out_zig_file.close();
if (comp.verbose_cimport) log.info("renaming {s} to {s}", .{ tmp_sub_path, o_sub_path });
try renameTmpIntoCache(comp.dirs.local_cache, tmp_sub_path, o_sub_path);
const formatted = try tree.renderAlloc(comp.gpa);
defer comp.gpa.free(formatted);
try out_zig_file.writeAll(formatted);
break :digest bin_digest;
} else man.finalBin();
break :digest .{ bin_digest, false };
};
if (man.have_exclusive_lock) {
// Write the updated manifest. This is a no-op if the manifest is not dirty. Note that it is
@ -5795,9 +5733,9 @@ pub fn cImport(comp: *Compilation, c_src: []const u8, owner_mod: *Package.Module
};
}
return CImportResult{
return .{
.digest = digest,
.cache_hit = actual_hit,
.cache_hit = is_hit,
.errors = std.zig.ErrorBundle.empty,
};
}
@ -6747,51 +6685,63 @@ pub fn addTranslateCCArgs(
out_dep_path: ?[]const u8,
owner_mod: *Package.Module,
) !void {
const target = &owner_mod.resolved_target.result;
try argv.appendSlice(&.{ "-x", "c" });
try comp.addCCArgs(arena, argv, ext, out_dep_path, owner_mod);
// This gives us access to preprocessing entities, presumably at the cost of performance.
try argv.appendSlice(&.{ "-Xclang", "-detailed-preprocessing-record" });
const resource_path = try comp.dirs.zig_lib.join(arena, &.{"compiler/aro/include"});
try argv.appendSlice(&.{ "-isystem", resource_path });
try comp.addCommonCCArgs(arena, argv, ext, out_dep_path, owner_mod, .aro);
try argv.appendSlice(&[_][]const u8{ "-target", try target.zigTriple(arena) });
const mcpu = mcpu: {
var buf: std.ArrayListUnmanaged(u8) = .empty;
defer buf.deinit(comp.gpa);
try buf.print(comp.gpa, "-mcpu={s}", .{target.cpu.model.name});
// TODO better serialization https://github.com/ziglang/zig/issues/4584
const all_features_list = target.cpu.arch.allFeaturesList();
try argv.ensureUnusedCapacity(all_features_list.len * 4);
for (all_features_list, 0..) |feature, index_usize| {
const index = @as(std.Target.Cpu.Feature.Set.Index, @intCast(index_usize));
const is_enabled = target.cpu.features.isEnabled(index);
const plus_or_minus = "-+"[@intFromBool(is_enabled)];
try buf.print(comp.gpa, "{c}{s}", .{ plus_or_minus, feature.name });
}
break :mcpu try buf.toOwnedSlice(arena);
};
try argv.append(mcpu);
try argv.appendSlice(comp.global_cc_argv);
try argv.appendSlice(owner_mod.cc_argv);
}
/// Add common C compiler args between translate-c and C object compilation.
pub fn addCCArgs(
fn addCommonCCArgs(
comp: *const Compilation,
arena: Allocator,
argv: *std.array_list.Managed([]const u8),
ext: FileExt,
out_dep_path: ?[]const u8,
mod: *Package.Module,
c_frontend: Config.CFrontend,
) !void {
const target = &mod.resolved_target.result;
const is_clang = c_frontend == .clang;
// As of Clang 16.x, it will by default read extra flags from /etc/clang.
// I'm sure the person who implemented this means well, but they have a lot
// to learn about abstractions and where the appropriate boundaries between
// them are. The road to hell is paved with good intentions. Fortunately it
// can be disabled.
try argv.append("--no-default-config");
// We don't ever put `-fcolor-diagnostics` or `-fno-color-diagnostics` because in passthrough mode
// we want Clang to infer it, and in normal mode we always want it off, which will be true since
// clang will detect stderr as a pipe rather than a terminal.
if (!comp.clang_passthrough_mode and ext.clangSupportsDiagnostics()) {
// Make stderr more easily parseable.
try argv.append("-fno-caret-diagnostics");
if (target_util.supports_fpic(target)) {
// PIE needs to go before PIC because Clang interprets `-fno-PIE` to imply `-fno-PIC`, which
// we don't necessarily want.
try argv.append(if (comp.config.pie) "-fPIE" else "-fno-PIE");
try argv.append(if (mod.pic) "-fPIC" else "-fno-PIC");
}
// We never want clang to invoke the system assembler for anything. So we would want
// this option always enabled. However, it only matters for some targets. To avoid
// "unused parameter" warnings, and to keep CLI spam to a minimum, we only put this
// flag on the command line if it is necessary.
if (target_util.clangMightShellOutForAssembly(target)) {
try argv.append("-integrated-as");
}
const llvm_triple = try @import("codegen/llvm.zig").targetTriple(arena, target);
try argv.appendSlice(&[_][]const u8{ "-target", llvm_triple });
switch (target.os.tag) {
.ios, .macos, .tvos, .watchos => |os| {
.ios, .macos, .tvos, .watchos => |os| if (is_clang) {
try argv.ensureUnusedCapacity(2);
// Pass the proper -m<os>-version-min argument for darwin.
const ver = target.os.version_range.semver.min;
@ -6815,78 +6765,6 @@ pub fn addCCArgs(
else => {},
}
const xclang_flag = switch (ext) {
.assembly, .assembly_with_cpp => "-Xclangas",
else => "-Xclang",
};
if (target_util.clangSupportsTargetCpuArg(target)) {
if (target.cpu.model.llvm_name) |llvm_name| {
try argv.appendSlice(&[_][]const u8{
xclang_flag, "-target-cpu", xclang_flag, llvm_name,
});
}
}
// It would be really nice if there was a more compact way to communicate this info to Clang.
const all_features_list = target.cpu.arch.allFeaturesList();
try argv.ensureUnusedCapacity(all_features_list.len * 4);
for (all_features_list, 0..) |feature, index_usize| {
const index = @as(std.Target.Cpu.Feature.Set.Index, @intCast(index_usize));
const is_enabled = target.cpu.features.isEnabled(index);
if (feature.llvm_name) |llvm_name| {
// We communicate float ABI to Clang through the dedicated options.
if (std.mem.startsWith(u8, llvm_name, "soft-float") or
std.mem.startsWith(u8, llvm_name, "hard-float"))
continue;
// Ignore these until we figure out how to handle the concept of omitting features.
// See https://github.com/ziglang/zig/issues/23539
if (target_util.isDynamicAMDGCNFeature(target, feature)) continue;
argv.appendSliceAssumeCapacity(&[_][]const u8{ xclang_flag, "-target-feature", xclang_flag });
const plus_or_minus = "-+"[@intFromBool(is_enabled)];
const arg = try std.fmt.allocPrint(arena, "{c}{s}", .{ plus_or_minus, llvm_name });
argv.appendAssumeCapacity(arg);
}
}
if (target.cpu.arch.isThumb()) {
try argv.append(switch (ext) {
.assembly, .assembly_with_cpp => "-Wa,-mthumb",
else => "-mthumb",
});
}
if (target_util.llvmMachineAbi(target)) |mabi| {
// Clang's integrated Arm assembler doesn't support `-mabi` yet...
// Clang's FreeBSD driver doesn't support `-mabi` on PPC64 (ELFv2 is used anyway).
if (!(target.cpu.arch.isArm() and (ext == .assembly or ext == .assembly_with_cpp)) and
!(target.cpu.arch.isPowerPC64() and target.os.tag == .freebsd))
{
try argv.append(try std.fmt.allocPrint(arena, "-mabi={s}", .{mabi}));
}
}
// We might want to support -mfloat-abi=softfp for Arm and CSKY here in the future.
if (target_util.clangSupportsFloatAbiArg(target)) {
const fabi = @tagName(target.abi.float());
try argv.append(switch (target.cpu.arch) {
// For whatever reason, Clang doesn't support `-mfloat-abi` for s390x.
.s390x => try std.fmt.allocPrint(arena, "-m{s}-float", .{fabi}),
else => try std.fmt.allocPrint(arena, "-mfloat-abi={s}", .{fabi}),
});
}
if (target_util.supports_fpic(target)) {
// PIE needs to go before PIC because Clang interprets `-fno-PIE` to imply `-fno-PIC`, which
// we don't necessarily want.
try argv.append(if (comp.config.pie) "-fPIE" else "-fno-PIE");
try argv.append(if (mod.pic) "-fPIC" else "-fno-PIC");
}
if (comp.mingw_unicode_entry_point) {
try argv.append("-municode");
}
@ -6923,45 +6801,6 @@ pub fn addCCArgs(
if (ext != .assembly) {
try argv.append(if (target.os.tag == .freestanding) "-ffreestanding" else "-fhosted");
if (target_util.clangSupportsNoImplicitFloatArg(target) and target.abi.float() == .soft) {
try argv.append("-mno-implicit-float");
}
if (target_util.hasRedZone(target)) {
try argv.append(if (mod.red_zone) "-mred-zone" else "-mno-red-zone");
}
try argv.append(if (mod.omit_frame_pointer) "-fomit-frame-pointer" else "-fno-omit-frame-pointer");
const ssp_buf_size = mod.stack_protector;
if (ssp_buf_size != 0) {
try argv.appendSlice(&[_][]const u8{
"-fstack-protector-strong",
"--param",
try std.fmt.allocPrint(arena, "ssp-buffer-size={d}", .{ssp_buf_size}),
});
} else {
try argv.append("-fno-stack-protector");
}
try argv.append(if (mod.no_builtin) "-fno-builtin" else "-fbuiltin");
try argv.append(if (comp.function_sections) "-ffunction-sections" else "-fno-function-sections");
try argv.append(if (comp.data_sections) "-fdata-sections" else "-fno-data-sections");
switch (mod.unwind_tables) {
.none => {
try argv.append("-fno-unwind-tables");
try argv.append("-fno-asynchronous-unwind-tables");
},
.sync => {
// Need to override Clang's convoluted default logic.
try argv.append("-fno-asynchronous-unwind-tables");
try argv.append("-funwind-tables");
},
.async => try argv.append("-fasynchronous-unwind-tables"),
}
try argv.append("-nostdinc");
if (ext == .cpp or ext == .hpp) {
@ -7085,12 +6924,14 @@ pub fn addCCArgs(
.mm,
.hmm,
=> {
try argv.append("-fno-spell-checking");
if (is_clang) {
try argv.append("-fno-spell-checking");
if (target.os.tag == .windows and target.abi.isGnu()) {
// windows.h has files such as pshpack1.h which do #pragma packing,
// triggering a clang warning. So for this target, we disable this warning.
try argv.append("-Wno-pragma-pack");
if (target.os.tag == .windows and target.abi.isGnu()) {
// windows.h has files such as pshpack1.h which do #pragma packing,
// triggering a clang warning. So for this target, we disable this warning.
try argv.append("-Wno-pragma-pack");
}
}
if (mod.optimize_mode != .Debug) {
@ -7100,80 +6941,6 @@ pub fn addCCArgs(
else => {},
}
// Only assembly files support these flags.
switch (ext) {
.assembly,
.assembly_with_cpp,
=> {
// The Clang assembler does not accept the list of CPU features like the
// compiler frontend does. Therefore we must hard-code the -m flags for
// all CPU features here.
switch (target.cpu.arch) {
.riscv32, .riscv32be, .riscv64, .riscv64be => {
const RvArchFeat = struct { char: u8, feat: std.Target.riscv.Feature };
const letters = [_]RvArchFeat{
.{ .char = 'm', .feat = .m },
.{ .char = 'a', .feat = .a },
.{ .char = 'f', .feat = .f },
.{ .char = 'd', .feat = .d },
.{ .char = 'c', .feat = .c },
};
const prefix: []const u8 = if (target.cpu.arch == .riscv64) "rv64" else "rv32";
const prefix_len = 4;
assert(prefix.len == prefix_len);
var march_buf: [prefix_len + letters.len + 1]u8 = undefined;
var march_index: usize = prefix_len;
@memcpy(march_buf[0..prefix.len], prefix);
if (target.cpu.has(.riscv, .e)) {
march_buf[march_index] = 'e';
} else {
march_buf[march_index] = 'i';
}
march_index += 1;
for (letters) |letter| {
if (target.cpu.has(.riscv, letter.feat)) {
march_buf[march_index] = letter.char;
march_index += 1;
}
}
const march_arg = try std.fmt.allocPrint(arena, "-march={s}", .{
march_buf[0..march_index],
});
try argv.append(march_arg);
if (target.cpu.has(.riscv, .relax)) {
try argv.append("-mrelax");
} else {
try argv.append("-mno-relax");
}
if (target.cpu.has(.riscv, .save_restore)) {
try argv.append("-msave-restore");
} else {
try argv.append("-mno-save-restore");
}
},
.mips, .mipsel, .mips64, .mips64el => {
if (target.cpu.model.llvm_name) |llvm_name| {
try argv.append(try std.fmt.allocPrint(arena, "-march={s}", .{llvm_name}));
}
},
else => {
// TODO
},
}
if (target_util.clangAssemblerSupportsMcpuArg(target)) {
if (target.cpu.model.llvm_name) |llvm_name| {
try argv.append(try std.fmt.allocPrint(arena, "-mcpu={s}", .{llvm_name}));
}
}
},
else => {},
}
// Only compiled files support these flags.
switch (ext) {
.c,
@ -7191,7 +6958,7 @@ pub fn addCCArgs(
try argv.append(try std.fmt.allocPrint(arena, "-mcmodel={s}", .{@tagName(mod.code_model)}));
}
{
if (is_clang) {
var san_arg: std.ArrayListUnmanaged(u8) = .empty;
const prefix = "-fsanitize=";
if (mod.sanitize_c != .off) {
@ -7272,6 +7039,243 @@ pub fn addCCArgs(
},
else => {},
}
}
/// Add common C compiler args and Clang specific args.
pub fn addCCArgs(
comp: *const Compilation,
arena: Allocator,
argv: *std.array_list.Managed([]const u8),
ext: FileExt,
out_dep_path: ?[]const u8,
mod: *Package.Module,
) !void {
const target = &mod.resolved_target.result;
// As of Clang 16.x, it will by default read extra flags from /etc/clang.
// I'm sure the person who implemented this means well, but they have a lot
// to learn about abstractions and where the appropriate boundaries between
// them are. The road to hell is paved with good intentions. Fortunately it
// can be disabled.
try argv.append("--no-default-config");
// We don't ever put `-fcolor-diagnostics` or `-fno-color-diagnostics` because in passthrough mode
// we want Clang to infer it, and in normal mode we always want it off, which will be true since
// clang will detect stderr as a pipe rather than a terminal.
if (!comp.clang_passthrough_mode and ext.clangSupportsDiagnostics()) {
// Make stderr more easily parseable.
try argv.append("-fno-caret-diagnostics");
}
// We never want clang to invoke the system assembler for anything. So we would want
// this option always enabled. However, it only matters for some targets. To avoid
// "unused parameter" warnings, and to keep CLI spam to a minimum, we only put this
// flag on the command line if it is necessary.
if (target_util.clangMightShellOutForAssembly(target)) {
try argv.append("-integrated-as");
}
const llvm_triple = try @import("codegen/llvm.zig").targetTriple(arena, target);
try argv.appendSlice(&[_][]const u8{ "-target", llvm_triple });
if (target.cpu.arch.isThumb()) {
try argv.append(switch (ext) {
.assembly, .assembly_with_cpp => "-Wa,-mthumb",
else => "-mthumb",
});
}
if (target_util.llvmMachineAbi(target)) |mabi| {
// Clang's integrated Arm assembler doesn't support `-mabi` yet...
// Clang's FreeBSD driver doesn't support `-mabi` on PPC64 (ELFv2 is used anyway).
if (!(target.cpu.arch.isArm() and (ext == .assembly or ext == .assembly_with_cpp)) and
!(target.cpu.arch.isPowerPC64() and target.os.tag == .freebsd))
{
try argv.append(try std.fmt.allocPrint(arena, "-mabi={s}", .{mabi}));
}
}
// We might want to support -mfloat-abi=softfp for Arm and CSKY here in the future.
if (target_util.clangSupportsFloatAbiArg(target)) {
const fabi = @tagName(target.abi.float());
try argv.append(switch (target.cpu.arch) {
// For whatever reason, Clang doesn't support `-mfloat-abi` for s390x.
.s390x => try std.fmt.allocPrint(arena, "-m{s}-float", .{fabi}),
else => try std.fmt.allocPrint(arena, "-mfloat-abi={s}", .{fabi}),
});
}
try comp.addCommonCCArgs(arena, argv, ext, out_dep_path, mod, comp.config.c_frontend);
// Only assembly files support these flags.
switch (ext) {
.assembly,
.assembly_with_cpp,
=> {
// The Clang assembler does not accept the list of CPU features like the
// compiler frontend does. Therefore we must hard-code the -m flags for
// all CPU features here.
switch (target.cpu.arch) {
.riscv32, .riscv32be, .riscv64, .riscv64be => {
const RvArchFeat = struct { char: u8, feat: std.Target.riscv.Feature };
const letters = [_]RvArchFeat{
.{ .char = 'm', .feat = .m },
.{ .char = 'a', .feat = .a },
.{ .char = 'f', .feat = .f },
.{ .char = 'd', .feat = .d },
.{ .char = 'c', .feat = .c },
};
const prefix: []const u8 = if (target.cpu.arch == .riscv64) "rv64" else "rv32";
const prefix_len = 4;
assert(prefix.len == prefix_len);
var march_buf: [prefix_len + letters.len + 1]u8 = undefined;
var march_index: usize = prefix_len;
@memcpy(march_buf[0..prefix.len], prefix);
if (target.cpu.has(.riscv, .e)) {
march_buf[march_index] = 'e';
} else {
march_buf[march_index] = 'i';
}
march_index += 1;
for (letters) |letter| {
if (target.cpu.has(.riscv, letter.feat)) {
march_buf[march_index] = letter.char;
march_index += 1;
}
}
const march_arg = try std.fmt.allocPrint(arena, "-march={s}", .{
march_buf[0..march_index],
});
try argv.append(march_arg);
if (target.cpu.has(.riscv, .relax)) {
try argv.append("-mrelax");
} else {
try argv.append("-mno-relax");
}
if (target.cpu.has(.riscv, .save_restore)) {
try argv.append("-msave-restore");
} else {
try argv.append("-mno-save-restore");
}
},
.mips, .mipsel, .mips64, .mips64el => {
if (target.cpu.model.llvm_name) |llvm_name| {
try argv.append(try std.fmt.allocPrint(arena, "-march={s}", .{llvm_name}));
}
},
else => {
// TODO
},
}
if (target_util.clangAssemblerSupportsMcpuArg(target)) {
if (target.cpu.model.llvm_name) |llvm_name| {
try argv.append(try std.fmt.allocPrint(arena, "-mcpu={s}", .{llvm_name}));
}
}
},
else => {},
}
// Non-preprocessed assembly files don't support these flags.
if (ext != .assembly) {
if (target_util.clangSupportsNoImplicitFloatArg(target) and target.abi.float() == .soft) {
try argv.append("-mno-implicit-float");
}
if (target_util.hasRedZone(target)) {
try argv.append(if (mod.red_zone) "-mred-zone" else "-mno-red-zone");
}
try argv.append(if (mod.omit_frame_pointer) "-fomit-frame-pointer" else "-fno-omit-frame-pointer");
const ssp_buf_size = mod.stack_protector;
if (ssp_buf_size != 0) {
try argv.appendSlice(&[_][]const u8{
"-fstack-protector-strong",
"--param",
try std.fmt.allocPrint(arena, "ssp-buffer-size={d}", .{ssp_buf_size}),
});
} else {
try argv.append("-fno-stack-protector");
}
try argv.append(if (mod.no_builtin) "-fno-builtin" else "-fbuiltin");
try argv.append(if (comp.function_sections) "-ffunction-sections" else "-fno-function-sections");
try argv.append(if (comp.data_sections) "-fdata-sections" else "-fno-data-sections");
switch (mod.unwind_tables) {
.none => {
try argv.append("-fno-unwind-tables");
try argv.append("-fno-asynchronous-unwind-tables");
},
.sync => {
// Need to override Clang's convoluted default logic.
try argv.append("-fno-asynchronous-unwind-tables");
try argv.append("-funwind-tables");
},
.async => try argv.append("-fasynchronous-unwind-tables"),
}
}
// Only compiled files support these flags.
switch (ext) {
.c,
.h,
.cpp,
.hpp,
.m,
.hm,
.mm,
.hmm,
.ll,
.bc,
=> {
const xclang_flag = switch (ext) {
.assembly, .assembly_with_cpp => "-Xclangas",
else => "-Xclang",
};
if (target_util.clangSupportsTargetCpuArg(target)) {
if (target.cpu.model.llvm_name) |llvm_name| {
try argv.appendSlice(&[_][]const u8{
xclang_flag, "-target-cpu", xclang_flag, llvm_name,
});
}
}
// It would be really nice if there was a more compact way to communicate this info to Clang.
const all_features_list = target.cpu.arch.allFeaturesList();
try argv.ensureUnusedCapacity(all_features_list.len * 4);
for (all_features_list, 0..) |feature, index_usize| {
const index = @as(std.Target.Cpu.Feature.Set.Index, @intCast(index_usize));
const is_enabled = target.cpu.features.isEnabled(index);
if (feature.llvm_name) |llvm_name| {
// We communicate float ABI to Clang through the dedicated options.
if (std.mem.startsWith(u8, llvm_name, "soft-float") or
std.mem.startsWith(u8, llvm_name, "hard-float"))
continue;
// Ignore these until we figure out how to handle the concept of omitting features.
// See https://github.com/ziglang/zig/issues/23539
if (target_util.isDynamicAMDGCNFeature(target, feature)) continue;
argv.appendSliceAssumeCapacity(&[_][]const u8{ xclang_flag, "-target-feature", xclang_flag });
const plus_or_minus = "-+"[@intFromBool(is_enabled)];
const arg = try std.fmt.allocPrint(arena, "{c}{s}", .{ plus_or_minus, llvm_name });
argv.appendAssumeCapacity(arg);
}
}
},
else => {},
}
try argv.appendSlice(comp.global_cc_argv);
try argv.appendSlice(mod.cc_argv);

View File

@ -6925,10 +6925,7 @@ pub fn deactivate(ip: *const InternPool) void {
/// For debugger access only.
const debug_state = struct {
const enable = switch (builtin.zig_backend) {
else => false,
.stage2_x86_64 => !builtin.strip_debug_info,
};
const enable = false;
const enable_checks = enable and !builtin.single_threaded;
threadlocal var intern_pool: ?*const InternPool = null;
};

View File

@ -5713,10 +5713,6 @@ fn zirCImport(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileEr
const extra = sema.code.extraData(Zir.Inst.Block, pl_node.payload_index);
const body = sema.code.bodySlice(extra.end, extra.data.body_len);
// we check this here to avoid undefined symbols
if (!build_options.have_llvm)
return sema.fail(parent_block, src, "C import unavailable; Zig compiler built without LLVM extensions", .{});
var c_import_buf = std.array_list.Managed(u8).init(gpa);
defer c_import_buf.deinit();
@ -5741,8 +5737,11 @@ fn zirCImport(sema: *Sema, parent_block: *Block, inst: Zir.Inst.Index) CompileEr
_ = try sema.analyzeInlineBody(&child_block, body, inst);
var c_import_res = comp.cImport(c_import_buf.items, parent_block.ownerModule()) catch |err|
return sema.fail(&child_block, src, "C import failed: {s}", .{@errorName(err)});
const prog_node = zcu.cur_sema_prog_node.start("@cImport", 0);
defer prog_node.end();
var c_import_res = comp.cImport(c_import_buf.items, parent_block.ownerModule(), prog_node) catch |err|
return sema.fail(&child_block, src, "C import failed: {t}", .{err});
defer c_import_res.deinit(gpa);
if (c_import_res.errors.errorMessageCount() != 0) {

View File

@ -32,7 +32,6 @@ const Sema = @import("Sema.zig");
const target_util = @import("target.zig");
const build_options = @import("build_options");
const isUpDir = @import("introspect.zig").isUpDir;
const clang = @import("clang.zig");
const InternPool = @import("InternPool.zig");
const Alignment = InternPool.Alignment;
const AnalUnit = InternPool.AnalUnit;

File diff suppressed because it is too large Load Diff

View File

@ -296,7 +296,11 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
});
const aro = @import("aro");
var aro_comp = aro.Compilation.init(gpa, std.fs.cwd());
var diagnostics: aro.Diagnostics = .{
.output = .{ .to_list = .{ .arena = .init(gpa) } },
};
defer diagnostics.deinit();
var aro_comp = aro.Compilation.init(gpa, arena, &diagnostics, std.fs.cwd());
defer aro_comp.deinit();
aro_comp.target = target.*;
@ -316,17 +320,22 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
const builtin_macros = try aro_comp.generateBuiltinMacros(.include_system_defines);
const def_file_source = try aro_comp.addSourceFromPath(def_file_path);
var pp = aro.Preprocessor.init(&aro_comp);
var pp = aro.Preprocessor.init(&aro_comp, .{ .provided = 0 });
defer pp.deinit();
pp.linemarkers = .none;
pp.preserve_whitespace = true;
try pp.preprocessSources(&.{ def_file_source, builtin_macros });
for (aro_comp.diagnostics.list.items) |diagnostic| {
if (diagnostic.kind == .@"fatal error" or diagnostic.kind == .@"error") {
aro.Diagnostics.render(&aro_comp, std.Io.tty.detectConfig(std.fs.File.stderr()));
return error.AroPreprocessorFailed;
if (aro_comp.diagnostics.output.to_list.messages.items.len != 0) {
var buffer: [64]u8 = undefined;
const w = std.debug.lockStderrWriter(&buffer);
defer std.debug.unlockStderrWriter();
for (aro_comp.diagnostics.output.to_list.messages.items) |msg| {
if (msg.kind == .@"fatal error" or msg.kind == .@"error") {
msg.write(w, .detect(std.fs.File.stderr()), true) catch {};
return error.AroPreprocessorFailed;
}
}
}
@ -335,9 +344,9 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
const def_final_file = try o_dir.createFile(final_def_basename, .{ .truncate = true });
defer def_final_file.close();
var buffer: [1024]u8 = undefined;
var def_final_file_writer = def_final_file.writer(&buffer);
try pp.prettyPrintTokens(&def_final_file_writer.interface, .result_only);
try def_final_file_writer.interface.flush();
var file_writer = def_final_file.writer(&buffer);
try pp.prettyPrintTokens(&file_writer.interface, .result_only);
try file_writer.interface.flush();
}
const lib_final_path = try std.fs.path.join(gpa, &.{ "o", &digest, final_lib_basename });

View File

@ -204,17 +204,6 @@ pub fn main() anyerror!void {
return mainArgs(gpa, arena, args);
}
/// Check that LLVM and Clang have been linked properly so that they are using the same
/// libc++ and can safely share objects with pointers to static variables in libc++
fn verifyLibcxxCorrectlyLinked() void {
if (build_options.have_llvm and ZigClangIsLLVMUsingSeparateLibcxx()) {
fatal(
\\Zig was built/linked incorrectly: LLVM and Clang have separate copies of libc++
\\ If you are dynamically linking LLVM, make sure you dynamically link libc++ too
, .{});
}
}
fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
const tr = tracy.trace(@src());
defer tr.end();
@ -350,13 +339,9 @@ fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
} else if (mem.eql(u8, cmd, "version")) {
dev.check(.version_command);
try fs.File.stdout().writeAll(build_options.version ++ "\n");
// Check libc++ linkage to make sure Zig was built correctly, but only
// for "env" and "version" to avoid affecting the startup time for
// build-critical commands (check takes about ~10 μs)
return verifyLibcxxCorrectlyLinked();
return;
} else if (mem.eql(u8, cmd, "env")) {
dev.check(.env_command);
verifyLibcxxCorrectlyLinked();
var stdout_writer = fs.File.stdout().writer(&stdout_buffer);
try @import("print_env.zig").cmdEnv(
arena,
@ -4551,179 +4536,64 @@ fn cmdTranslateC(
prog_node: std.Progress.Node,
) !void {
dev.check(.translate_c_command);
_ = file_system_inputs;
_ = fancy_output;
const color: Color = .auto;
assert(comp.c_source_files.len == 1);
const c_source_file = comp.c_source_files[0];
const translated_zig_basename = try std.fmt.allocPrint(arena, "{s}.zig", .{comp.root_name});
var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.makeOpenPath("tmp", .{});
defer zig_cache_tmp_dir.close();
var man: Cache.Manifest = comp.obtainCObjectCacheManifest(comp.root_mod);
man.want_shared_lock = false;
defer man.deinit();
man.hash.add(@as(u16, 0xb945)); // Random number to distinguish translate-c from compiling C objects
man.hash.add(comp.config.c_frontend);
Compilation.cache_helpers.hashCSource(&man, c_source_file) catch |err| {
fatal("unable to process '{s}': {s}", .{ c_source_file.src_path, @errorName(err) });
const ext = Compilation.classifyFileExt(c_source_file.src_path);
const out_dep_path: ?[]const u8 = blk: {
if (comp.disable_c_depfile) break :blk null;
const c_src_basename = fs.path.basename(c_source_file.src_path);
const dep_basename = try std.fmt.allocPrint(arena, "{s}.d", .{c_src_basename});
const out_dep_path = try comp.tmpFilePath(arena, dep_basename);
break :blk out_dep_path;
};
if (fancy_output) |p| p.cache_hit = true;
const bin_digest, const hex_digest = if (try man.hit()) digest: {
if (file_system_inputs) |buf| try man.populateFileSystemInputs(buf);
const bin_digest = man.finalBin();
const hex_digest = Cache.binToHex(bin_digest);
break :digest .{ bin_digest, hex_digest };
} else digest: {
if (fancy_output) |p| p.cache_hit = false;
var argv = std.array_list.Managed([]const u8).init(arena);
switch (comp.config.c_frontend) {
.aro => {},
.clang => {
// argv[0] is program name, actual args start at [1]
try argv.append(@tagName(comp.config.c_frontend));
},
}
var argv = std.array_list.Managed([]const u8).init(arena);
try comp.addTranslateCCArgs(arena, &argv, ext, out_dep_path, comp.root_mod);
try argv.append(c_source_file.src_path);
if (comp.verbose_cc) Compilation.dump_argv(argv.items);
var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.makeOpenPath("tmp", .{});
defer zig_cache_tmp_dir.close();
try translateC(comp.gpa, arena, argv.items, prog_node, null);
const ext = Compilation.classifyFileExt(c_source_file.src_path);
const out_dep_path: ?[]const u8 = blk: {
if (comp.config.c_frontend == .aro or comp.disable_c_depfile or !ext.clangSupportsDepFile())
break :blk null;
const c_src_basename = fs.path.basename(c_source_file.src_path);
const dep_basename = try std.fmt.allocPrint(arena, "{s}.d", .{c_src_basename});
const out_dep_path = try comp.tmpFilePath(arena, dep_basename);
break :blk out_dep_path;
if (out_dep_path) |dep_file_path| {
const dep_basename = fs.path.basename(dep_file_path);
// Add the files depended on to the cache system.
//man.addDepFilePost(zig_cache_tmp_dir, dep_basename) catch |err| switch (err) {
// error.FileNotFound => {
// // Clang didn't emit the dep file; nothing to add to the manifest.
// break :add_deps;
// },
// else => |e| return e,
//};
// Just to save disk space, we delete the file because it is never needed again.
zig_cache_tmp_dir.deleteFile(dep_basename) catch |err| {
warn("failed to delete '{s}': {t}", .{ dep_file_path, err });
};
// TODO
if (comp.config.c_frontend != .aro)
try comp.addTranslateCCArgs(arena, &argv, ext, out_dep_path, comp.root_mod);
try argv.append(c_source_file.src_path);
if (comp.verbose_cc) {
Compilation.dump_argv(argv.items);
}
const Result = union(enum) {
success: []const u8,
error_bundle: std.zig.ErrorBundle,
};
const result: Result = switch (comp.config.c_frontend) {
.aro => f: {
var stdout: []u8 = undefined;
try jitCmd(comp.gpa, arena, argv.items, .{
.cmd_name = "aro_translate_c",
.root_src_path = "aro_translate_c.zig",
.depend_on_aro = true,
.capture = &stdout,
.progress_node = prog_node,
});
break :f .{ .success = stdout };
},
.clang => f: {
if (!build_options.have_llvm) unreachable;
const translate_c = @import("translate_c.zig");
// Convert to null terminated args.
const clang_args_len = argv.items.len + c_source_file.extra_flags.len;
const new_argv_with_sentinel = try arena.alloc(?[*:0]const u8, clang_args_len + 1);
new_argv_with_sentinel[clang_args_len] = null;
const new_argv = new_argv_with_sentinel[0..clang_args_len :null];
for (argv.items, 0..) |arg, i| {
new_argv[i] = try arena.dupeZ(u8, arg);
}
for (c_source_file.extra_flags, 0..) |arg, i| {
new_argv[argv.items.len + i] = try arena.dupeZ(u8, arg);
}
const c_headers_dir_path_z = try comp.dirs.zig_lib.joinZ(arena, &.{"include"});
var errors = std.zig.ErrorBundle.empty;
var tree = translate_c.translate(
comp.gpa,
new_argv.ptr,
new_argv.ptr + new_argv.len,
&errors,
c_headers_dir_path_z,
) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
error.SemanticAnalyzeFail => break :f .{ .error_bundle = errors },
};
defer tree.deinit(comp.gpa);
break :f .{ .success = try tree.renderAlloc(arena) };
},
};
if (out_dep_path) |dep_file_path| add_deps: {
const dep_basename = fs.path.basename(dep_file_path);
// Add the files depended on to the cache system.
man.addDepFilePost(zig_cache_tmp_dir, dep_basename) catch |err| switch (err) {
error.FileNotFound => {
// Clang didn't emit the dep file; nothing to add to the manifest.
break :add_deps;
},
else => |e| return e,
};
// Just to save disk space, we delete the file because it is never needed again.
zig_cache_tmp_dir.deleteFile(dep_basename) catch |err| {
warn("failed to delete '{s}': {s}", .{ dep_file_path, @errorName(err) });
};
}
const formatted = switch (result) {
.success => |formatted| formatted,
.error_bundle => |eb| {
if (file_system_inputs) |buf| try man.populateFileSystemInputs(buf);
if (fancy_output) |p| {
p.errors = eb;
return;
} else {
eb.renderToStdErr(color.renderOptions());
process.exit(1);
}
},
};
const bin_digest = man.finalBin();
const hex_digest = Cache.binToHex(bin_digest);
const o_sub_path = try fs.path.join(arena, &[_][]const u8{ "o", &hex_digest });
var o_dir = try comp.dirs.local_cache.handle.makeOpenPath(o_sub_path, .{});
defer o_dir.close();
var zig_file = try o_dir.createFile(translated_zig_basename, .{});
defer zig_file.close();
try zig_file.writeAll(formatted);
man.writeManifest() catch |err| warn("failed to write cache manifest: {t}", .{err});
if (file_system_inputs) |buf| try man.populateFileSystemInputs(buf);
break :digest .{ bin_digest, hex_digest };
};
if (fancy_output) |p| {
p.digest = bin_digest;
p.errors = std.zig.ErrorBundle.empty;
} else {
const out_zig_path = try fs.path.join(arena, &.{ "o", &hex_digest, translated_zig_basename });
const zig_file = comp.dirs.local_cache.handle.openFile(out_zig_path, .{}) catch |err| {
const path = comp.dirs.local_cache.path orelse ".";
fatal("unable to open cached translated zig file '{s}{s}{s}': {s}", .{ path, fs.path.sep_str, out_zig_path, @errorName(err) });
};
defer zig_file.close();
var stdout_writer = fs.File.stdout().writer(&stdout_buffer);
var file_reader = zig_file.reader(&.{});
_ = try stdout_writer.interface.sendFileAll(&file_reader, .unlimited);
try stdout_writer.interface.flush();
return cleanExit();
}
return cleanExit();
}
pub fn translateC(
gpa: Allocator,
arena: Allocator,
argv: []const []const u8,
prog_node: std.Progress.Node,
capture: ?*[]u8,
) !void {
try jitCmd(gpa, arena, argv, .{
.cmd_name = "translate-c",
.root_src_path = "translate-c/main.zig",
.depend_on_aro = true,
.progress_node = prog_node,
.capture = capture,
});
}
const usage_init =
@ -5682,12 +5552,13 @@ fn jitCmd(
child_argv.appendSliceAssumeCapacity(args);
if (process.can_execv and options.capture == null) {
if (EnvVar.ZIG_DEBUG_CMD.isSet()) {
const cmd = try std.mem.join(arena, " ", child_argv.items);
std.debug.print("{s}\n", .{cmd});
}
const err = process.execv(gpa, child_argv.items);
const cmd = try std.mem.join(arena, " ", child_argv.items);
fatal("the following command failed to execve with '{s}':\n{s}", .{
@errorName(err),
cmd,
});
fatal("the following command failed to execve with '{t}':\n{s}", .{ err, cmd });
}
if (!process.can_spawn) {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -885,28 +885,6 @@ test "vector @reduce comptime" {
try expect(is_all_true == false);
}
test "mask parameter of @shuffle is comptime scope" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest;
const __v4hi = @Vector(4, i16);
var v4_a = __v4hi{ 1, 2, 3, 4 };
var v4_b = __v4hi{ 5, 6, 7, 8 };
_ = .{ &v4_a, &v4_b };
const shuffled: __v4hi = @shuffle(i16, v4_a, v4_b, @Vector(4, i32){
std.zig.c_translation.shuffleVectorIndex(0, @typeInfo(@TypeOf(v4_a)).vector.len),
std.zig.c_translation.shuffleVectorIndex(2, @typeInfo(@TypeOf(v4_a)).vector.len),
std.zig.c_translation.shuffleVectorIndex(4, @typeInfo(@TypeOf(v4_a)).vector.len),
std.zig.c_translation.shuffleVectorIndex(6, @typeInfo(@TypeOf(v4_a)).vector.len),
});
try expect(shuffled[0] == 1);
try expect(shuffled[1] == 3);
try expect(shuffled[2] == 5);
try expect(shuffled[3] == 7);
}
test "saturating add" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO

View File

@ -1,4 +0,0 @@
test {
_ = @import("c_import/c_char_signedness.zig");
_ = @import("c_import/macros.zig");
}

View File

@ -1,13 +0,0 @@
const std = @import("std");
const builtin = @import("builtin");
const expectEqual = std.testing.expectEqual;
const c = @cImport({
@cInclude("limits.h");
});
test "c_char signedness" {
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
try expectEqual(@as(c_char, c.CHAR_MIN), std.math.minInt(c_char));
try expectEqual(@as(c_char, c.CHAR_MAX), std.math.maxInt(c_char));
}

View File

@ -1,70 +0,0 @@
// initializer list expression
typedef struct Color {
unsigned char r;
unsigned char g;
unsigned char b;
unsigned char a;
} Color;
#define CLITERAL(type) (type)
#define LIGHTGRAY CLITERAL(Color){ 200, 200, 200, 255 } // Light Gray
#define MY_SIZEOF(x) ((int)sizeof(x))
#define MY_SIZEOF2(x) ((int)sizeof x)
struct Foo {
int a;
};
union U {
long l;
double d;
};
#define SIZE_OF_FOO sizeof(struct Foo)
#define MAP_FAILED ((void *) -1)
#define IGNORE_ME_1(x) ((void)(x))
#define IGNORE_ME_2(x) ((const void)(x))
#define IGNORE_ME_3(x) ((volatile void)(x))
#define IGNORE_ME_4(x) ((const volatile void)(x))
#define IGNORE_ME_5(x) ((volatile const void)(x))
#define IGNORE_ME_6(x) (void)(x)
#define IGNORE_ME_7(x) (const void)(x)
#define IGNORE_ME_8(x) (volatile void)(x)
#define IGNORE_ME_9(x) (const volatile void)(x)
#define IGNORE_ME_10(x) (volatile const void)(x)
#define UNION_CAST(X) (union U)(X)
#define CAST_OR_CALL_WITH_PARENS(type_or_fn, val) ((type_or_fn)(val))
#define NESTED_COMMA_OPERATOR (1, (2, 3))
#define NESTED_COMMA_OPERATOR_LHS (1, 2), 3
#include <stdint.h>
#if !defined(__UINTPTR_MAX__)
typedef _Bool uintptr_t;
#endif
#define CAST_TO_BOOL(X) (_Bool)(X)
#define CAST_TO_UINTPTR(X) (uintptr_t)(X)
#define LARGE_INT 18446744073709550592
#define EMBEDDED_TAB "hello "
#define DIVIDE_CONSTANT(version) (version / 1000)
#define DIVIDE_ARGS(A, B) (A / B)
#define REMAINDER_CONSTANT(version) (version % 10000)
#define REMAINDER_ARGS(A, B) (A % B)
#define LONG(x) x##L
#define X LONG(10)
#define BLANK_MACRO
#define BLANK_CHILD_MACRO BLANK_MACRO BLANK_MACRO BLANK_MACRO
#define MACRO_VALUE 0
typedef long def_type;
#define BLANK_MACRO_CAST (BLANK_CHILD_MACRO def_type BLANK_CHILD_MACRO)MACRO_VALUE

View File

@ -1,239 +0,0 @@
const builtin = @import("builtin");
const std = @import("std");
const expect = std.testing.expect;
const expectEqual = std.testing.expectEqual;
const expectEqualStrings = std.testing.expectEqualStrings;
const h = @cImport(@cInclude("macros.h"));
const latin1 = @cImport(@cInclude("macros_not_utf8.h"));
test "casting to void with a macro" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
h.IGNORE_ME_1(42);
h.IGNORE_ME_2(42);
h.IGNORE_ME_3(42);
h.IGNORE_ME_4(42);
h.IGNORE_ME_5(42);
h.IGNORE_ME_6(42);
h.IGNORE_ME_7(42);
h.IGNORE_ME_8(42);
h.IGNORE_ME_9(42);
h.IGNORE_ME_10(42);
}
test "initializer list expression" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
try expectEqual(h.Color{
.r = 200,
.g = 200,
.b = 200,
.a = 255,
}, h.LIGHTGRAY);
}
test "sizeof in macros" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
try expect(@as(c_int, @sizeOf(u32)) == h.MY_SIZEOF(u32));
try expect(@as(c_int, @sizeOf(u32)) == h.MY_SIZEOF2(u32));
}
test "reference to a struct type" {
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
try expect(@sizeOf(h.struct_Foo) == h.SIZE_OF_FOO);
}
test "cast negative integer to pointer" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
try expectEqual(@as(?*anyopaque, @ptrFromInt(@as(usize, @bitCast(@as(isize, -1))))), h.MAP_FAILED);
}
test "casting to union with a macro" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
const l: c_long = 42;
const d: f64 = 2.0;
var casted = h.UNION_CAST(l);
try expect(l == casted.l);
casted = h.UNION_CAST(d);
try expect(d == casted.d);
}
test "casting or calling a value with a paren-surrounded macro" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
const l: c_long = 42;
const casted = h.CAST_OR_CALL_WITH_PARENS(c_int, l);
try expect(casted == @as(c_int, @intCast(l)));
const Helper = struct {
fn foo(n: c_int) !void {
try expect(n == 42);
}
};
try h.CAST_OR_CALL_WITH_PARENS(Helper.foo, 42);
}
test "nested comma operator" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
try expectEqual(@as(c_int, 3), h.NESTED_COMMA_OPERATOR);
try expectEqual(@as(c_int, 3), h.NESTED_COMMA_OPERATOR_LHS);
}
test "cast functions" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
const S = struct {
fn foo() void {}
};
try expectEqual(true, h.CAST_TO_BOOL(S.foo));
try expect(h.CAST_TO_UINTPTR(S.foo) != 0);
}
test "large integer macro" {
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
try expectEqual(@as(c_ulonglong, 18446744073709550592), h.LARGE_INT);
}
test "string literal macro with embedded tab character" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
try expectEqualStrings("hello\t", h.EMBEDDED_TAB);
}
test "string and char literals that are not UTF-8 encoded. Issue #12784" {
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
try expectEqual(@as(u8, '\xA9'), latin1.UNPRINTABLE_CHAR);
try expectEqualStrings("\xA9\xA9\xA9", latin1.UNPRINTABLE_STRING);
}
test "Macro that uses division operator. Issue #13162" {
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_x86_64 and builtin.target.ofmt != .elf and builtin.target.ofmt != .macho) return error.SkipZigTest;
try expectEqual(@as(c_int, 42), h.DIVIDE_CONSTANT(@as(c_int, 42_000)));
try expectEqual(@as(c_uint, 42), h.DIVIDE_CONSTANT(@as(c_uint, 42_000)));
try expectEqual(
@as(f64, 42.0),
h.DIVIDE_ARGS(
@as(f64, 42.0),
true,
),
);
try expectEqual(
@as(c_int, 21),
h.DIVIDE_ARGS(
@as(i8, 42),
@as(i8, 2),
),
);
try expectEqual(
@as(c_uint, 21),
h.DIVIDE_ARGS(
@as(u32, 42),
@as(u32, 2),
),
);
try expectEqual(
@as(c_int, 21),
h.DIVIDE_ARGS(
@as(c_ushort, 42),
@as(c_ushort, 2),
),
);
}
test "Macro that uses remainder operator. Issue #13346" {
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
try expectEqual(@as(c_int, 2_010), h.REMAINDER_CONSTANT(@as(c_int, 42_010)));
try expectEqual(@as(c_uint, 2_030), h.REMAINDER_CONSTANT(@as(c_uint, 42_030)));
try expectEqual(
@as(c_int, 7),
h.REMAINDER_ARGS(
@as(i8, 17),
@as(i8, 10),
),
);
try expectEqual(
@as(c_int, 5),
h.REMAINDER_ARGS(
@as(c_ushort, 25),
@as(c_ushort, 20),
),
);
try expectEqual(
@as(c_int, 1),
h.REMAINDER_ARGS(
@as(c_int, 5),
@as(c_int, -2),
),
);
}
test "@typeInfo on @cImport result" {
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
try expect(@typeInfo(h).@"struct".decls.len > 1);
}
test "Macro that uses Long type concatenation casting" {
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
try expect((@TypeOf(h.X)) == c_long);
try expectEqual(h.X, @as(c_long, 10));
}
test "Blank macros" {
if (builtin.zig_backend == .stage2_spirv) return error.SkipZigTest;
try expectEqual(h.BLANK_MACRO, "");
try expectEqual(h.BLANK_CHILD_MACRO, "");
try expect(@TypeOf(h.BLANK_MACRO_CAST) == h.def_type);
try expectEqual(h.BLANK_MACRO_CAST, @as(c_long, 0));
}

View File

@ -1,5 +0,0 @@
// Note: This file is encoded with ISO/IEC 8859-1 (latin1), not UTF-8.
// Do not change the encoding
#define UNPRINTABLE_STRING "©©©"
#define UNPRINTABLE_CHAR '©'

Some files were not shown because too many files have changed in this diff Show More