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. # These are planned to stay even when we are self-hosted.
src/zig_llvm.cpp src/zig_llvm.cpp
src/zig_llvm-ar.cpp src/zig_llvm-ar.cpp
src/zig_clang.cpp
src/zig_clang_driver.cpp src/zig_clang_driver.cpp
src/zig_clang_cc1_main.cpp src/zig_clang_cc1_main.cpp
src/zig_clang_cc1as_main.cpp src/zig_clang_cc1as_main.cpp
@ -502,7 +501,6 @@ set(ZIG_STAGE2_SOURCES
lib/std/zig/Server.zig lib/std/zig/Server.zig
lib/std/zig/WindowsSdk.zig lib/std/zig/WindowsSdk.zig
lib/std/zig/Zir.zig lib/std/zig/Zir.zig
lib/std/zig/c_builtins.zig
lib/std/zig/string_literal.zig lib/std/zig/string_literal.zig
lib/std/zig/system.zig lib/std/zig/system.zig
lib/std/zig/system/NativePaths.zig lib/std/zig/system/NativePaths.zig
@ -537,7 +535,6 @@ set(ZIG_STAGE2_SOURCES
src/Value.zig src/Value.zig
src/Zcu.zig src/Zcu.zig
src/Zcu/PerThread.zig src/Zcu/PerThread.zig
src/clang.zig
src/clang_options.zig src/clang_options.zig
src/clang_options_data.zig src/clang_options_data.zig
src/codegen.zig src/codegen.zig
@ -641,7 +638,6 @@ set(ZIG_STAGE2_SOURCES
src/register_manager.zig src/register_manager.zig
src/target.zig src/target.zig
src/tracy.zig src/tracy.zig
src/translate_c.zig
src/libs/wasi_libc.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_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_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_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_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_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; 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); test_step.dependOn(check_fmt);
const test_cases_step = b.step("test-cases", "Run the main compiler test cases"); 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_filters = test_filters,
.test_target_filters = test_target_filters, .test_target_filters = test_target_filters,
.skip_compile_errors = skip_compile_errors, .skip_compile_errors = skip_compile_errors,
@ -428,9 +426,6 @@ pub fn build(b: *std.Build) !void {
.skip_linux = skip_linux, .skip_linux = skip_linux,
.skip_llvm = skip_llvm, .skip_llvm = skip_llvm,
.skip_libc = skip_libc, .skip_libc = skip_libc,
}, .{
.skip_translate_c = skip_translate_c,
.skip_run_translated_c = skip_run_translated_c,
}, .{ }, .{
.enable_llvm = enable_llvm, .enable_llvm = enable_llvm,
.llvm_has_m68k = llvm_has_m68k, .llvm_has_m68k = llvm_has_m68k,
@ -465,28 +460,6 @@ pub fn build(b: *std.Build) !void {
.max_rss = 4000000000, .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_modules_step.dependOn(tests.addModuleTests(b, .{
.test_filters = test_filters, .test_filters = test_filters,
.test_target_filters = test_target_filters, .test_target_filters = test_target_filters,
@ -577,7 +550,6 @@ pub fn build(b: *std.Build) !void {
enable_macos_sdk, enable_macos_sdk,
enable_ios_sdk, enable_ios_sdk,
enable_symlinks_windows, enable_symlinks_windows,
skip_translate_c,
)); ));
test_step.dependOn(tests.addCAbiTests(b, .{ test_step.dependOn(tests.addCAbiTests(b, .{
.test_target_filters = test_target_filters, .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"), .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", aro_mod);
compiler_mod.addImport("aro_translate_c", aro_translate_c_mod);
return compiler_mod; return compiler_mod;
} }
@ -1158,7 +1124,6 @@ fn toNativePathSep(b: *std.Build, s: []const u8) []u8 {
const zig_cpp_sources = [_][]const u8{ const zig_cpp_sources = [_][]const u8{
// These are planned to stay even when we are self-hosted. // These are planned to stay even when we are self-hosted.
"src/zig_llvm.cpp", "src/zig_llvm.cpp",
"src/zig_clang.cpp",
"src/zig_llvm-ar.cpp", "src/zig_llvm-ar.cpp",
"src/zig_clang_driver.cpp", "src/zig_clang_driver.cpp",
"src/zig_clang_cc1_main.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 Parser = @import("aro/Parser.zig");
pub const Preprocessor = @import("aro/Preprocessor.zig"); pub const Preprocessor = @import("aro/Preprocessor.zig");
pub const Source = @import("aro/Source.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 Tokenizer = @import("aro/Tokenizer.zig");
pub const Toolchain = @import("aro/Toolchain.zig"); pub const Toolchain = @import("aro/Toolchain.zig");
pub const Tree = @import("aro/Tree.zig"); pub const Tree = @import("aro/Tree.zig");
pub const Type = @import("aro/Type.zig"); pub const TypeStore = @import("aro/TypeStore.zig");
pub const TypeMapper = @import("aro/StringInterner.zig").TypeMapper; pub const QualType = TypeStore.QualType;
pub const target_util = @import("aro/target.zig"); pub const Type = TypeStore.Type;
pub const Value = @import("aro/Value.zig"); pub const Value = @import("aro/Value.zig");
const backend = @import("backend.zig"); const backend = @import("backend.zig");
@ -18,6 +20,7 @@ pub const Interner = backend.Interner;
pub const Ir = backend.Ir; pub const Ir = backend.Ir;
pub const Object = backend.Object; pub const Object = backend.Object;
pub const CallingConvention = backend.CallingConvention; pub const CallingConvention = backend.CallingConvention;
pub const Assembly = backend.Assembly;
pub const version_str = backend.version_str; pub const version_str = backend.version_str;
pub const version = backend.version; pub const version = backend.version;
@ -34,6 +37,5 @@ test {
_ = @import("aro/Preprocessor.zig"); _ = @import("aro/Preprocessor.zig");
_ = @import("aro/target.zig"); _ = @import("aro/target.zig");
_ = @import("aro/Tokenizer.zig"); _ = @import("aro/Tokenizer.zig");
_ = @import("aro/toolchains/Linux.zig");
_ = @import("aro/Value.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 std = @import("std");
const Compilation = @import("Compilation.zig"); 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 LangOpts = @import("LangOpts.zig");
const Parser = @import("Parser.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"); const Properties = @import("Builtins/Properties.zig");
pub const Builtin = @import("Builtins/Builtin.zig").with(Properties); pub const Builtin = @import("Builtins/Builtin.zig").with(Properties);
const Expanded = struct { const Expanded = struct {
ty: Type, qt: QualType,
builtin: Builtin, builtin: Builtin,
}; };
const NameToTypeMap = std.StringHashMapUnmanaged(Type); const NameToTypeMap = std.StringHashMapUnmanaged(QualType);
const Builtins = @This(); const Builtins = @This();
@ -25,38 +27,38 @@ pub fn deinit(b: *Builtins, gpa: std.mem.Allocator) void {
b._name_to_type_map.deinit(gpa); b._name_to_type_map.deinit(gpa);
} }
fn specForSize(comp: *const Compilation, size_bits: u32) Type.Builder.Specifier { fn specForSize(comp: *const Compilation, size_bits: u32) TypeStore.Builder.Specifier {
var ty = Type{ .specifier = .short }; var qt: QualType = .short;
if (ty.sizeof(comp).? * 8 == size_bits) return .short; if (qt.bitSizeof(comp) == size_bits) return .short;
ty.specifier = .int; qt = .int;
if (ty.sizeof(comp).? * 8 == size_bits) return .int; if (qt.bitSizeof(comp) == size_bits) return .int;
ty.specifier = .long; qt = .long;
if (ty.sizeof(comp).? * 8 == size_bits) return .long; if (qt.bitSizeof(comp) == size_bits) return .long;
ty.specifier = .long_long; qt = .long_long;
if (ty.sizeof(comp).? * 8 == size_bits) return .long_long; if (qt.bitSizeof(comp) == size_bits) return .long_long;
unreachable; unreachable;
} }
fn createType(desc: TypeDescription, it: *TypeDescription.TypeIterator, comp: *const Compilation, allocator: std.mem.Allocator) !Type { fn createType(desc: TypeDescription, it: *TypeDescription.TypeIterator, comp: *Compilation) !QualType {
var builder: Type.Builder = .{ .error_on_invalid = true }; 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_int32 = false;
var require_native_int64 = false; var require_native_int64 = false;
for (desc.prefix) |prefix| { for (desc.prefix) |prefix| {
switch (prefix) { switch (prefix) {
.L => builder.combine(undefined, .long, 0) catch unreachable, .L => builder.combine(.long, 0) catch unreachable,
.LL => { .LL => builder.combine(.long_long, 0) catch unreachable,
builder.combine(undefined, .long, 0) catch unreachable;
builder.combine(undefined, .long, 0) catch unreachable;
},
.LLL => { .LLL => {
switch (builder.specifier) { switch (builder.type) {
.none => builder.specifier = .int128, .none => builder.type = .int128,
.signed => builder.specifier = .sint128, .signed => builder.type = .sint128,
.unsigned => builder.specifier = .uint128, .unsigned => builder.type = .uint128,
else => unreachable, else => unreachable,
} }
}, },
@ -65,239 +67,226 @@ fn createType(desc: TypeDescription, it: *TypeDescription.TypeIterator, comp: *c
.N => { .N => {
std.debug.assert(desc.spec == .i); std.debug.assert(desc.spec == .i);
if (!target_util.isLP64(comp.target)) { if (!target_util.isLP64(comp.target)) {
builder.combine(undefined, .long, 0) catch unreachable; builder.combine(.long, 0) catch unreachable;
} }
}, },
.O => { .O => {
builder.combine(undefined, .long, 0) catch unreachable; builder.combine(.long, 0) catch unreachable;
if (comp.target.os.tag != .opencl) { 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, .S => builder.combine(.signed, 0) catch unreachable,
.U => builder.combine(undefined, .unsigned, 0) catch unreachable, .U => builder.combine(.unsigned, 0) catch unreachable,
.I => { .I => {
// Todo: compile-time constant integer // Todo: compile-time constant integer
}, },
} }
} }
switch (desc.spec) { switch (desc.spec) {
.v => builder.combine(undefined, .void, 0) catch unreachable, .v => builder.combine(.void, 0) catch unreachable,
.b => builder.combine(undefined, .bool, 0) catch unreachable, .b => builder.combine(.bool, 0) catch unreachable,
.c => builder.combine(undefined, .char, 0) catch unreachable, .c => builder.combine(.char, 0) catch unreachable,
.s => builder.combine(undefined, .short, 0) catch unreachable, .s => builder.combine(.short, 0) catch unreachable,
.i => { .i => {
if (require_native_int32) { if (require_native_int32) {
builder.specifier = specForSize(comp, 32); builder.type = specForSize(comp, 32);
} else if (require_native_int64) { } else if (require_native_int64) {
builder.specifier = specForSize(comp, 64); builder.type = specForSize(comp, 64);
} else { } else {
switch (builder.specifier) { switch (builder.type) {
.int128, .sint128, .uint128 => {}, .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, .h => builder.combine(.fp16, 0) catch unreachable,
.x => builder.combine(undefined, .float16, 0) catch unreachable, .x => builder.combine(.float16, 0) catch unreachable,
.y => { .y => {
// Todo: __bf16 // Todo: __bf16
return .{ .specifier = .invalid }; return .invalid;
}, },
.f => builder.combine(undefined, .float, 0) catch unreachable, .f => builder.combine(.float, 0) catch unreachable,
.d => { .d => {
if (builder.specifier == .long_long) { if (builder.type == .long_long) {
builder.specifier = .float128; builder.type = .float128;
} else { } else {
builder.combine(undefined, .double, 0) catch unreachable; builder.combine(.double, 0) catch unreachable;
} }
}, },
.z => { .z => {
std.debug.assert(builder.specifier == .none); std.debug.assert(builder.type == .none);
builder.specifier = Type.Builder.fromType(comp.types.size); builder.type = Builder.fromType(comp, comp.type_store.size);
}, },
.w => { .w => {
std.debug.assert(builder.specifier == .none); std.debug.assert(builder.type == .none);
builder.specifier = Type.Builder.fromType(comp.types.wchar); builder.type = Builder.fromType(comp, comp.type_store.wchar);
}, },
.F => { .F => {
std.debug.assert(builder.specifier == .none); std.debug.assert(builder.type == .none);
builder.specifier = Type.Builder.fromType(comp.types.ns_constant_string.ty); builder.type = Builder.fromType(comp, comp.type_store.ns_constant_string);
}, },
.G => { .G => {
// Todo: id // Todo: id
return .{ .specifier = .invalid }; return .invalid;
}, },
.H => { .H => {
// Todo: SEL // Todo: SEL
return .{ .specifier = .invalid }; return .invalid;
}, },
.M => { .M => {
// Todo: struct objc_super // Todo: struct objc_super
return .{ .specifier = .invalid }; return .invalid;
}, },
.a => { .a => {
std.debug.assert(builder.specifier == .none); std.debug.assert(builder.type == .none);
std.debug.assert(desc.suffix.len == 0); 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 => { .A => {
std.debug.assert(builder.specifier == .none); std.debug.assert(builder.type == .none);
std.debug.assert(desc.suffix.len == 0); std.debug.assert(desc.suffix.len == 0);
var va_list = comp.types.va_list; var va_list = comp.type_store.va_list;
if (va_list.isArray()) va_list.decayArray(); std.debug.assert(!va_list.is(comp, .array));
builder.specifier = Type.Builder.fromType(va_list); builder.type = Builder.fromType(comp, va_list);
}, },
.V => |element_count| { .V => |element_count| {
std.debug.assert(desc.suffix.len == 0); std.debug.assert(desc.suffix.len == 0);
const child_desc = it.next().?; const child_desc = it.next().?;
const child_ty = try createType(child_desc, undefined, comp, allocator); const elem_qt = try createType(child_desc, undefined, comp);
const arr_ty = try allocator.create(Type.Array); const vector_qt = try comp.type_store.put(comp.gpa, .{ .vector = .{
arr_ty.* = .{ .elem = elem_qt,
.len = element_count, .len = element_count,
.elem = child_ty, } });
}; builder.type = .{ .other = vector_qt };
const vector_ty: Type = .{ .specifier = .vector, .data = .{ .array = arr_ty } };
builder.specifier = Type.Builder.fromType(vector_ty);
}, },
.q => { .q => {
// Todo: scalable vector // Todo: scalable vector
return .{ .specifier = .invalid }; return .invalid;
}, },
.E => { .E => {
// Todo: ext_vector (OpenCL vector) // Todo: ext_vector (OpenCL vector)
return .{ .specifier = .invalid }; return .invalid;
}, },
.X => |child| { .X => |child| {
builder.combine(undefined, .complex, 0) catch unreachable; builder.combine(.complex, 0) catch unreachable;
switch (child) { switch (child) {
.float => builder.combine(undefined, .float, 0) catch unreachable, .float => builder.combine(.float, 0) catch unreachable,
.double => builder.combine(undefined, .double, 0) catch unreachable, .double => builder.combine(.double, 0) catch unreachable,
.longdouble => { .longdouble => {
builder.combine(undefined, .long, 0) catch unreachable; builder.combine(.long, 0) catch unreachable;
builder.combine(undefined, .double, 0) catch unreachable; builder.combine(.double, 0) catch unreachable;
}, },
} }
}, },
.Y => { .Y => {
std.debug.assert(builder.specifier == .none); std.debug.assert(builder.type == .none);
std.debug.assert(desc.suffix.len == 0); 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 => { .P => {
std.debug.assert(builder.specifier == .none); std.debug.assert(builder.type == .none);
if (comp.types.file.specifier == .invalid) { if (comp.type_store.file.isInvalid()) {
return comp.types.file; return comp.type_store.file;
} }
builder.specifier = Type.Builder.fromType(comp.types.file); builder.type = Builder.fromType(comp, comp.type_store.file);
}, },
.J => { .J => {
std.debug.assert(builder.specifier == .none); std.debug.assert(builder.type == .none);
std.debug.assert(desc.suffix.len == 0); std.debug.assert(desc.suffix.len == 0);
if (comp.types.jmp_buf.specifier == .invalid) { if (comp.type_store.jmp_buf.isInvalid()) {
return comp.types.jmp_buf; 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 => { .SJ => {
std.debug.assert(builder.specifier == .none); std.debug.assert(builder.type == .none);
std.debug.assert(desc.suffix.len == 0); std.debug.assert(desc.suffix.len == 0);
if (comp.types.sigjmp_buf.specifier == .invalid) { if (comp.type_store.sigjmp_buf.isInvalid()) {
return comp.types.sigjmp_buf; 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 => { .K => {
std.debug.assert(builder.specifier == .none); std.debug.assert(builder.type == .none);
if (comp.types.ucontext_t.specifier == .invalid) { if (comp.type_store.ucontext_t.isInvalid()) {
return comp.types.ucontext_t; 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 => { .p => {
std.debug.assert(builder.specifier == .none); std.debug.assert(builder.type == .none);
std.debug.assert(desc.suffix.len == 0); 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| { for (desc.suffix) |suffix| {
switch (suffix) { switch (suffix) {
.@"*" => |address_space| { .@"*" => |address_space| {
_ = address_space; // TODO: handle address space _ = address_space; // TODO: handle address space
const elem_ty = try allocator.create(Type); const pointer_qt = try comp.type_store.put(comp.gpa, .{ .pointer = .{
elem_ty.* = builder.finish(undefined) catch unreachable; .child = builder.finish() catch unreachable,
const ty = Type{ .decayed = null,
.specifier = .pointer, } });
.data = .{ .sub_type = elem_ty },
}; builder.@"const" = null;
builder.qual = .{}; builder.@"volatile" = null;
builder.specifier = Type.Builder.fromType(ty); builder.restrict = null;
builder.type = .{ .other = pointer_qt };
}, },
.C => builder.qual.@"const" = 0, .C => builder.@"const" = 0,
.D => builder.qual.@"volatile" = 0, .D => builder.@"volatile" = 0,
.R => builder.qual.restrict = 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); var it = TypeDescription.TypeIterator.init(builtin.properties.param_str);
const ret_ty_desc = it.next().?; const ret_ty_desc = it.next().?;
if (ret_ty_desc.spec == .@"!") { if (ret_ty_desc.spec == .@"!") {
// Todo: handle target-dependent definition // 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 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) { 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]); return comp.type_store.put(comp.gpa, .{ .func = .{
const func = try type_arena.create(Type.Func);
func.* = .{
.return_type = ret_ty, .return_type = ret_ty,
.params = duped_params, .kind = if (builtin.properties.isVarArgs()) .variadic else .normal,
}; .params = params[0..param_count],
return .{ } });
.specifier = if (builtin.properties.isVarArgs()) .var_args_func else .func,
.data = .{ .func = func },
};
} }
/// Asserts that the builtin has already been created /// Asserts that the builtin has already been created
pub fn lookup(b: *const Builtins, name: []const u8) Expanded { pub fn lookup(b: *const Builtins, name: []const u8) Expanded {
const builtin = Builtin.fromName(name).?; const builtin = Builtin.fromName(name).?;
const ty = b._name_to_type_map.get(name).?; const qt = b._name_to_type_map.get(name).?;
return .{ return .{ .builtin = builtin, .qt = qt };
.builtin = builtin,
.ty = ty,
};
} }
pub fn getOrCreate(b: *Builtins, comp: *Compilation, name: []const u8, type_arena: std.mem.Allocator) !?Expanded { pub fn getOrCreate(b: *Builtins, comp: *Compilation, name: []const u8) !?Expanded {
const ty = b._name_to_type_map.get(name) orelse { const qt = b._name_to_type_map.get(name) orelse {
const builtin = Builtin.fromName(name) orelse return null; const builtin = Builtin.fromName(name) orelse return null;
if (!comp.hasBuiltinFunction(builtin)) return null; if (!comp.hasBuiltinFunction(builtin)) return null;
try b._name_to_type_map.ensureUnusedCapacity(comp.gpa, 1); try b._name_to_type_map.ensureUnusedCapacity(comp.gpa, 1);
const ty = try createBuiltin(comp, builtin, type_arena); const qt = try createBuiltin(comp, builtin);
b._name_to_type_map.putAssumeCapacity(name, ty); b._name_to_type_map.putAssumeCapacity(name, qt);
return .{ return .{
.builtin = builtin, .builtin = builtin,
.ty = ty, .qt = qt,
}; };
}; };
const builtin = Builtin.fromName(name).?; const builtin = Builtin.fromName(name).?;
return .{ return .{ .builtin = builtin, .qt = qt };
.builtin = builtin,
.ty = ty,
};
} }
pub const Iterator = struct { pub const Iterator = struct {
@ -323,12 +312,13 @@ pub const Iterator = struct {
}; };
test Iterator { test Iterator {
const gpa = std.testing.allocator;
var it = Iterator{}; var it = Iterator{};
var seen = std.StringHashMap(Builtin).init(std.testing.allocator); var seen: std.StringHashMapUnmanaged(Builtin) = .empty;
defer seen.deinit(); 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(); defer arena_state.deinit();
const arena = arena_state.allocator(); const arena = arena_state.allocator();
@ -344,25 +334,27 @@ test Iterator {
std.debug.print("previous data: {}\n", .{seen.get(entry.name).?}); std.debug.print("previous data: {}\n", .{seen.get(entry.name).?});
return error.TestExpectedUniqueEntries; 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()); try std.testing.expectEqual(@as(usize, Builtin.data.len), seen.count());
} }
test "All builtins" { test "All builtins" {
var comp = Compilation.init(std.testing.allocator, std.fs.cwd()); var arena_state: std.heap.ArenaAllocator = .init(std.testing.allocator);
defer comp.deinit(); defer arena_state.deinit();
_ = try comp.generateBuiltinMacros(.include_system_defines); const arena = arena_state.allocator();
var arena = std.heap.ArenaAllocator.init(std.testing.allocator);
defer arena.deinit();
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{}; var builtin_it = Iterator{};
while (builtin_it.next()) |entry| { while (builtin_it.next()) |entry| {
const name = try type_arena.dupe(u8, entry.name); const name = try arena.dupe(u8, entry.name);
if (try comp.builtins.getOrCreate(&comp, name, type_arena)) |func_ty| { if (try comp.builtins.getOrCreate(&comp, name)) |func_ty| {
const get_again = (try comp.builtins.getOrCreate(&comp, name, std.testing.failing_allocator)).?; const get_again = (try comp.builtins.getOrCreate(&comp, name)).?;
const found_by_lookup = comp.builtins.lookup(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, get_again.builtin.tag);
try std.testing.expectEqual(func_ty.builtin.tag, found_by_lookup.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" { test "Allocation failures" {
const Test = struct { const Test = struct {
fn testOne(allocator: std.mem.Allocator) !void { 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(); defer comp.deinit();
_ = try comp.generateBuiltinMacros(.include_system_defines); _ = 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; const num_builtins = 40;
var builtin_it = Iterator{}; var builtin_it = Iterator{};
for (0..num_builtins) |_| { for (0..num_builtins) |_| {
const entry = builtin_it.next().?; 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 Builtin = Builtins.Builtin;
const Parser = @import("../Parser.zig"); const Parser = @import("../Parser.zig");
const Tree = @import("../Tree.zig"); const Tree = @import("../Tree.zig");
const NodeIndex = Tree.NodeIndex; const TypeStore = @import("../TypeStore.zig");
const Type = @import("../Type.zig"); const Type = TypeStore.Type;
const QualType = TypeStore.QualType;
const Value = @import("../Value.zig"); const Value = @import("../Value.zig");
fn makeNan(comptime T: type, str: []const u8) T { 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)))); 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); const builtin = Builtin.fromTag(tag);
if (!builtin.properties.attributes.const_evaluable) return .{}; if (!builtin.properties.attributes.const_evaluable) return .{};
switch (tag) { switch (tag) {
Builtin.tagFromName("__builtin_inff").?, .__builtin_inff,
Builtin.tagFromName("__builtin_inf").?, .__builtin_inf,
Builtin.tagFromName("__builtin_infl").?, .__builtin_infl,
=> { => {
const ty: Type = switch (tag) { const qt: QualType = switch (tag) {
Builtin.tagFromName("__builtin_inff").? => .{ .specifier = .float }, .__builtin_inff => .float,
Builtin.tagFromName("__builtin_inf").? => .{ .specifier = .double }, .__builtin_inf => .double,
Builtin.tagFromName("__builtin_infl").? => .{ .specifier = .long_double }, .__builtin_infl => .long_double,
else => unreachable, 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) }, 32 => .{ .f32 = std.math.inf(f32) },
64 => .{ .f64 = std.math.inf(f64) }, 64 => .{ .f64 = std.math.inf(f64) },
80 => .{ .f80 = std.math.inf(f80) }, 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 }); return Value.intern(p.comp, .{ .float = f });
}, },
Builtin.tagFromName("__builtin_isinf").? => blk: { .__builtin_isinf => blk: {
if (args.len == 0) break :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)); return Value.fromBool(val.isInf(p.comp));
}, },
Builtin.tagFromName("__builtin_isinf_sign").? => blk: { .__builtin_isinf_sign => blk: {
if (args.len == 0) break :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)) { switch (val.isInfSign(p.comp)) {
.unknown => {}, .unknown => {},
.finite => return Value.zero, .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), .negative => return Value.int(@as(i64, -1), p.comp),
} }
}, },
Builtin.tagFromName("__builtin_isnan").? => blk: { .__builtin_isnan => blk: {
if (args.len == 0) break :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)); return Value.fromBool(val.isNan(p.comp));
}, },
Builtin.tagFromName("__builtin_nan").? => blk: { .__builtin_nan => blk: {
if (args.len == 0) break :blk; if (args.len == 0) break :blk;
const val = p.getDecayedStringLiteral(args[0]) orelse break :blk; const val = p.getDecayedStringLiteral(args[0]) orelse break :blk;
const bytes = p.comp.interner.get(val.ref()).bytes; 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) }, 32 => .{ .f32 = makeNan(f32, bytes) },
64 => .{ .f64 = makeNan(f64, bytes) }, 64 => .{ .f64 = makeNan(f64, bytes) },
80 => .{ .f80 = makeNan(f80, 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) { pub const Filesystem = union(enum) {
real: void, real: std.fs.Dir,
fake: []const Entry, fake: []const Entry,
const Entry = struct { const Entry = struct {
@ -172,8 +172,8 @@ pub const Filesystem = union(enum) {
pub fn exists(fs: Filesystem, path: []const u8) bool { pub fn exists(fs: Filesystem, path: []const u8) bool {
switch (fs) { switch (fs) {
.real => { .real => |cwd| {
std.fs.cwd().access(path, .{}) catch return false; cwd.access(path, .{}) catch return false;
return true; return true;
}, },
.fake => |paths| return existsFake(paths, path), .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 /// 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 { pub fn readFile(fs: Filesystem, path: []const u8, buf: []u8) ?[]const u8 {
return switch (fs) { return switch (fs) {
.real => { .real => |cwd| {
const file = std.fs.cwd().openFile(path, .{}) catch return null; const file = cwd.openFile(path, .{}) catch return null;
defer file.close(); defer file.close();
const bytes_read = file.readAll(buf) catch return null; 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 { pub fn openDir(fs: Filesystem, dir_name: []const u8) std.fs.Dir.OpenError!Dir {
return switch (fs) { 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 } }, .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 good = bad;
var it = mem.splitScalar(u8, text, '.'); var it = mem.splitScalar(u8, text, '.');
const first = it.next().?; const first = it.first();
const second = it.next() orelse ""; const second = it.next() orelse "";
const rest = it.next() orelse ""; const rest = it.next() orelse "";

View File

@ -1,47 +1,50 @@
const std = @import("std"); const std = @import("std");
const Filesystem = @import("Filesystem.zig").Filesystem; 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. /// Large enough for GCCDetector for Linux; may need to be increased to support other toolchains.
const max_multilibs = 4; const max_multilibs = 4;
const MultilibArray = std.BoundedArray(Multilib, max_multilibs);
pub const Detected = struct { pub const Detected = struct {
multilibs: MultilibArray = .{}, multilib_buf: [max_multilibs]Multilib = undefined,
multilib_count: u8 = 0,
selected: Multilib = .{}, selected: Multilib = .{},
biarch_sibling: ?Multilib = null, biarch_sibling: ?Multilib = null,
pub fn filter(self: *Detected, multilib_filter: Filter, fs: Filesystem) void { pub fn filter(d: *Detected, multilib_filter: Filter, fs: Filesystem) void {
var found_count: usize = 0; var found_count: u8 = 0;
for (self.multilibs.constSlice()) |multilib| { for (d.multilibs()) |multilib| {
if (multilib_filter.exists(multilib, fs)) { if (multilib_filter.exists(multilib, fs)) {
self.multilibs.set(found_count, multilib); d.multilib_buf[found_count] = multilib;
found_count += 1; found_count += 1;
} }
} }
self.multilibs.resize(found_count) catch unreachable; d.multilib_count = found_count;
} }
pub fn select(self: *Detected, flags: Flags) !bool { pub fn select(d: *Detected, check_flags: []const []const u8) !bool {
var filtered: MultilibArray = .{}; var selected: ?Multilib = null;
for (self.multilibs.constSlice()) |multilib| {
for (multilib.flags.constSlice()) |multilib_flag| { for (d.multilibs()) |multilib| {
const matched = for (flags.constSlice()) |arg_flag| { 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; if (std.mem.eql(u8, arg_flag[1..], multilib_flag[1..])) break arg_flag;
} else multilib_flag; } else multilib_flag;
if (matched[0] != multilib_flag[0]) break; if (matched[0] != multilib_flag[0]) break;
} else if (selected != null) {
return error.TooManyMultilibs;
} else { } else {
filtered.appendAssumeCapacity(multilib); selected = multilib;
} }
} }
if (filtered.len == 0) return false; if (selected) |multilib| {
if (filtered.len == 1) { d.selected = multilib;
self.selected = filtered.get(0);
return true; 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 = "", gcc_suffix: []const u8 = "",
os_suffix: []const u8 = "", os_suffix: []const u8 = "",
include_suffix: []const u8 = "", include_suffix: []const u8 = "",
flags: Flags = .{}, flag_buf: [6][]const u8 = undefined,
flag_count: u8 = 0,
priority: u32 = 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 = .{ var self: Multilib = .{
.gcc_suffix = gcc_suffix, .gcc_suffix = gcc_suffix,
.os_suffix = os_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; 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 std = @import("std");
const mem = std.mem; const mem = std.mem;
const Allocator = mem.Allocator; const Allocator = mem.Allocator;
const Source = @import("Source.zig");
const Compilation = @import("Compilation.zig"); const Compilation = @import("Compilation.zig");
const Source = @import("Source.zig");
const Tokenizer = @import("Tokenizer.zig"); const Tokenizer = @import("Tokenizer.zig");
pub const Hideset = @This(); 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 /// Used for computing union/intersection of two lists; stored here so that allocations can be retained
/// until hideset is deinit'ed /// until hideset is deinit'ed
tmp_map: std.AutoHashMapUnmanaged(Identifier, void) = .empty, tmp_map: std.AutoHashMapUnmanaged(Identifier, void) = .{},
linked_list: Item.List = .{}, linked_list: Item.List = .{},
comp: *const Compilation, comp: *const Compilation,

View File

@ -3,17 +3,16 @@
const std = @import("std"); const std = @import("std");
const Allocator = std.mem.Allocator; const Allocator = std.mem.Allocator;
const testing = std.testing; const testing = std.testing;
const Diagnostics = @import("Diagnostics.zig");
const Parser = @import("Parser.zig");
const Tree = @import("Tree.zig"); const Tree = @import("Tree.zig");
const Token = Tree.Token; const Token = Tree.Token;
const TokenIndex = Tree.TokenIndex; const TokenIndex = Tree.TokenIndex;
const NodeIndex = Tree.NodeIndex; const Node = Tree.Node;
const Type = @import("Type.zig");
const Diagnostics = @import("Diagnostics.zig");
const NodeList = std.array_list.Managed(NodeIndex);
const Parser = @import("Parser.zig");
const Item = struct { const Item = struct {
list: InitList = .{}, list: InitList,
index: u64, index: u64,
fn order(_: void, a: Item, b: Item) std.math.Order { fn order(_: void, a: Item, b: Item) std.math.Order {
@ -23,8 +22,8 @@ const Item = struct {
const InitList = @This(); const InitList = @This();
list: std.ArrayListUnmanaged(Item) = .empty, list: std.ArrayList(Item) = .empty,
node: NodeIndex = .none, node: Node.OptIndex = .null,
tok: TokenIndex = 0, tok: TokenIndex = 0,
/// Deinitialize freeing all memory. /// Deinitialize freeing all memory.
@ -34,50 +33,6 @@ pub fn deinit(il: *InitList, gpa: Allocator) void {
il.* = undefined; 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. /// Find item at index, create new if one does not exist.
pub fn find(il: *InitList, gpa: Allocator, index: u64) !*InitList { pub fn find(il: *InitList, gpa: Allocator, index: u64) !*InitList {
const items = il.list.items; const items = il.list.items;
@ -85,13 +40,21 @@ pub fn find(il: *InitList, gpa: Allocator, index: u64) !*InitList {
var right: usize = items.len; var right: usize = items.len;
// Append new value to empty list // Append new value to empty list
if (left == right) { if (il.list.items.len == 0) {
const item = try il.list.addOne(gpa); const item = try il.list.addOne(gpa);
item.* = .{ item.* = .{
.list = .{ .node = .none, .tok = 0 }, .list = .{},
.index = index, .index = index,
}; };
return &item.list; 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) { 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. // Insert a new value into a sorted position.
try il.list.insert(gpa, left, .{ try il.list.insert(gpa, left, .{
.list = .{ .node = .none, .tok = 0 }, .list = .{},
.index = index, .index = index,
}); });
return &il.list.items[left].list; return &il.list.items[left].list;
@ -118,22 +81,6 @@ test "basic usage" {
var il: InitList = .{}; var il: InitList = .{};
defer il.deinit(gpa); 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 item = try il.find(gpa, 0);
var i: usize = 1; var i: usize = 1;

View File

@ -1,11 +1,20 @@
const std = @import("std"); const std = @import("std");
const DiagnosticTag = @import("Diagnostics.zig").Tag;
const char_info = @import("char_info.zig"); const char_info = @import("char_info.zig");
const DiagnosticTag = @import("Diagnostics.zig").Tag;
pub const Compiler = enum { pub const Compiler = enum {
clang, clang,
gcc, gcc,
msvc, 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 /// 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 /// 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 /// Encoded as major * 10,000 + minor * 100 + patch
/// e.g. 4.2.1 == 40201 /// 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 { pub fn setStandard(self: *LangOpts, name: []const u8) error{InvalidStandard}!void {
self.standard = Standard.NameMap.get(name) orelse return error.InvalidStandard; self.standard = Standard.NameMap.get(name) orelse return error.InvalidStandard;
} }
pub fn enableMSExtensions(self: *LangOpts) void { pub fn setMSExtensions(self: *LangOpts, enabled: bool) void {
self.declspec_attrs = true; self.declspec_attrs = enabled;
self.ms_extensions = true; self.ms_extensions = enabled;
}
pub fn disableMSExtensions(self: *LangOpts) void {
self.declspec_attrs = false;
self.ms_extensions = true;
} }
pub fn hasChar8_T(self: *const LangOpts) bool { 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 { pub fn setEmulatedCompiler(self: *LangOpts, compiler: Compiler) void {
self.emulate = compiler; self.emulate = compiler;
if (compiler == .msvc) self.enableMSExtensions(); self.setMSExtensions(compiler == .msvc);
} }
pub fn setFpEvalMethod(self: *LangOpts, fp_eval_method: FPEvalMethod) void { 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 std = @import("std");
const Compilation = @import("Compilation.zig"); const Compilation = @import("Compilation.zig");
const Preprocessor = @import("Preprocessor.zig"); const Diagnostics = @import("Diagnostics.zig");
const Parser = @import("Parser.zig"); const Parser = @import("Parser.zig");
const Preprocessor = @import("Preprocessor.zig");
const TokenIndex = @import("Tree.zig").TokenIndex; const TokenIndex = @import("Tree.zig").TokenIndex;
pub const Error = Compilation.Error || error{ UnknownPragma, StopPreprocessing }; pub const Error = Compilation.Error || error{ UnknownPragma, StopPreprocessing };
@ -58,7 +60,7 @@ pub fn pasteTokens(pp: *Preprocessor, start_idx: TokenIndex) ![]const u8 {
.string_literal => { .string_literal => {
if (rparen_count != 0) return error.ExpectedStringLiteral; if (rparen_count != 0) return error.ExpectedStringLiteral;
const str = pp.expandedSlice(tok); 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, 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 { pub fn shouldPreserveTokens(self: *Pragma, pp: *Preprocessor, start_idx: TokenIndex) bool {
if (self.preserveTokens) |func| return func(self, pp, start_idx); 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 { 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); defer std.debug.assert(tok_index == p.tok_i);
if (self.parserHandler) |func| return func(self, p, start_idx); 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 { 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; 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(); const Source = @This();
@ -51,9 +66,7 @@ pub fn physicalLine(source: Source, loc: Location) u32 {
return loc.line + source.numSplicesBefore(loc.byte_offset); 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) ExpandedLocation {
pub fn lineCol(source: Source, loc: Location) LineCol {
var start: usize = 0; var start: usize = 0;
// find the start of the line which is either a newline or a splice // 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; 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]; nl = source.splice_locs[splice_index];
} }
return .{ return .{
.path = source.path,
.line = source.buf[start..nl], .line = source.buf[start..nl],
.line_no = loc.line + splice_index, .line_no = loc.line + splice_index,
.col = col, .col = col,
.width = width, .width = width,
.end_with_splice = end_with_splice, .end_with_splice = end_with_splice,
.kind = source.kind,
}; };
} }

View File

@ -2,82 +2,34 @@ const std = @import("std");
const mem = std.mem; const mem = std.mem;
const Compilation = @import("Compilation.zig"); 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(); const StringInterner = @This();
string_table: StringToIdMap = .{}, pub const StringId = enum(u32) {
next_id: StringId = @enumFromInt(@intFromEnum(StringId.empty) + 1), empty = std.math.maxInt(u32),
_,
pub fn deinit(self: *StringInterner, allocator: mem.Allocator) void { pub fn lookup(id: StringId, comp: *const Compilation) []const u8 {
self.string_table.deinit(allocator); 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 { /// Intern externally owned string.
return comp.string_interner.internExtra(comp.gpa, str); pub fn intern(si: *StringInterner, allocator: mem.Allocator, str: []const u8) !StringId {
}
pub fn internExtra(self: *StringInterner, allocator: mem.Allocator, str: []const u8) !StringId {
if (str.len == 0) return .empty; if (str.len == 0) return .empty;
const gop = try self.string_table.getOrPut(allocator, str); const gop = try si.table.getOrPut(allocator, str);
if (gop.found_existing) return gop.value_ptr.*; return @enumFromInt(gop.index);
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 } };
} }

View File

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

View File

@ -1,8 +1,45 @@
const std = @import("std"); const std = @import("std");
const assert = std.debug.assert; const assert = std.debug.assert;
const Compilation = @import("Compilation.zig"); const Compilation = @import("Compilation.zig");
const Source = @import("Source.zig");
const LangOpts = @import("LangOpts.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 { pub const Token = struct {
id: Id, id: Id,
@ -18,7 +55,7 @@ pub const Token = struct {
eof, eof,
/// identifier containing solely basic character set characters /// identifier containing solely basic character set characters
identifier, identifier,
/// identifier with at least one extended character /// identifier with at least one extended character or UCN escape sequence
extended_identifier, extended_identifier,
// string literals with prefixes // string literals with prefixes
@ -147,6 +184,10 @@ pub const Token = struct {
macro_counter, macro_counter,
/// Special token for implementing _Pragma /// Special token for implementing _Pragma
macro_param_pragma_operator, 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__ /// Special identifier for implementing __func__
macro_func, macro_func,
@ -154,6 +195,12 @@ pub const Token = struct {
macro_function, macro_function,
/// Special identifier for implementing __PRETTY_FUNCTION__ /// Special identifier for implementing __PRETTY_FUNCTION__
macro_pretty_func, 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,
keyword_auto_type, keyword_auto_type,
@ -290,13 +337,21 @@ pub const Token = struct {
keyword_thiscall2, keyword_thiscall2,
keyword_vectorcall, keyword_vectorcall,
keyword_vectorcall2, 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 // Type nullability
builtin_choose_expr, keyword_nonnull,
builtin_va_arg, keyword_nullable,
builtin_offsetof, keyword_nullable_result,
builtin_bitoffsetof, keyword_null_unspecified,
builtin_types_compatible_p,
/// Generated by #embed directive /// Generated by #embed directive
/// Decimal value with no prefix or suffix /// Decimal value with no prefix or suffix
@ -323,6 +378,12 @@ pub const Token = struct {
/// A comment token if asked to preserve comments. /// A comment token if asked to preserve comments.
comment, 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. /// Return true if token is identifier or keyword.
pub fn isMacroIdentifier(id: Id) bool { pub fn isMacroIdentifier(id: Id) bool {
switch (id) { switch (id) {
@ -347,6 +408,9 @@ pub const Token = struct {
.macro_func, .macro_func,
.macro_function, .macro_function,
.macro_pretty_func, .macro_pretty_func,
.macro_date,
.macro_time,
.macro_timestamp,
.keyword_auto, .keyword_auto,
.keyword_auto_type, .keyword_auto_type,
.keyword_break, .keyword_break,
@ -409,11 +473,6 @@ pub const Token = struct {
.keyword_restrict2, .keyword_restrict2,
.keyword_alignof1, .keyword_alignof1,
.keyword_alignof2, .keyword_alignof2,
.builtin_choose_expr,
.builtin_va_arg,
.builtin_offsetof,
.builtin_bitoffsetof,
.builtin_types_compatible_p,
.keyword_attribute1, .keyword_attribute1,
.keyword_attribute2, .keyword_attribute2,
.keyword_extension, .keyword_extension,
@ -444,6 +503,19 @@ pub const Token = struct {
.keyword_thiscall2, .keyword_thiscall2,
.keyword_vectorcall, .keyword_vectorcall,
.keyword_vectorcall2, .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_bit_int,
.keyword_c23_alignas, .keyword_c23_alignas,
.keyword_c23_alignof, .keyword_c23_alignof,
@ -547,11 +619,18 @@ pub const Token = struct {
.macro_file, .macro_file,
.macro_line, .macro_line,
.macro_counter, .macro_counter,
.macro_time,
.macro_date,
.macro_timestamp,
.macro_param_pragma_operator, .macro_param_pragma_operator,
.macro_param_ms_identifier,
.macro_param_ms_pragma,
.placemarker, .placemarker,
=> "", => "",
.macro_ws => " ", .macro_ws => " ",
.incomplete_ucn => "\\",
.macro_func => "__func__", .macro_func => "__func__",
.macro_function => "__FUNCTION__", .macro_function => "__FUNCTION__",
.macro_pretty_func => "__PRETTY_FUNCTION__", .macro_pretty_func => "__PRETTY_FUNCTION__",
@ -695,11 +774,6 @@ pub const Token = struct {
.keyword_alignof2 => "__alignof__", .keyword_alignof2 => "__alignof__",
.keyword_typeof1 => "__typeof", .keyword_typeof1 => "__typeof",
.keyword_typeof2 => "__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_attribute1 => "__attribute",
.keyword_attribute2 => "__attribute__", .keyword_attribute2 => "__attribute__",
.keyword_extension => "__extension__", .keyword_extension => "__extension__",
@ -730,6 +804,19 @@ pub const Token = struct {
.keyword_thiscall2 => "_thiscall", .keyword_thiscall2 => "_thiscall",
.keyword_vectorcall => "__vectorcall", .keyword_vectorcall => "__vectorcall",
.keyword_vectorcall2 => "_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_func,
.macro_function, .macro_function,
.macro_pretty_func, .macro_pretty_func,
.builtin_choose_expr,
.builtin_va_arg,
.builtin_offsetof,
.builtin_bitoffsetof,
.builtin_types_compatible_p,
=> "an identifier", => "an identifier",
.string_literal, .string_literal,
.string_literal_utf_16, .string_literal_utf_16,
@ -763,7 +845,7 @@ pub const Token = struct {
.unterminated_char_literal, .unterminated_char_literal,
.empty_char_literal, .empty_char_literal,
=> "a character literal", => "a character literal",
.pp_num, .embed_byte => "A number", .pp_num, .embed_byte => "a number",
else => id.lexeme().?, else => id.lexeme().?,
}; };
} }
@ -871,6 +953,12 @@ pub const Token = struct {
.keyword_stdcall2, .keyword_stdcall2,
.keyword_thiscall2, .keyword_thiscall2,
.keyword_vectorcall2, .keyword_vectorcall2,
.keyword_fastcall2,
.keyword_cdecl2,
.keyword_forceinline,
.keyword_forceinline2,
.keyword_unaligned,
.keyword_unaligned2,
=> if (langopts.ms_extensions) kw else .identifier, => if (langopts.ms_extensions) kw else .identifier,
else => kw, else => kw,
}; };
@ -1013,13 +1101,21 @@ pub const Token = struct {
.{ "_thiscall", .keyword_thiscall2 }, .{ "_thiscall", .keyword_thiscall2 },
.{ "__vectorcall", .keyword_vectorcall }, .{ "__vectorcall", .keyword_vectorcall },
.{ "_vectorcall", .keyword_vectorcall2 }, .{ "_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 // Type nullability
.{ "__builtin_choose_expr", .builtin_choose_expr }, .{ "_Nonnull", .keyword_nonnull },
.{ "__builtin_va_arg", .builtin_va_arg }, .{ "_Nullable", .keyword_nullable },
.{ "__builtin_offsetof", .builtin_offsetof }, .{ "_Nullable_result", .keyword_nullable_result },
.{ "__builtin_bitoffsetof", .builtin_bitoffsetof }, .{ "_Null_unspecified", .keyword_null_unspecified },
.{ "__builtin_types_compatible_p", .builtin_types_compatible_p },
}); });
}; };
@ -1099,6 +1195,26 @@ pub fn next(self: *Tokenizer) Token {
'u' => state = .u, 'u' => state = .u,
'U' => state = .U, 'U' => state = .U,
'L' => state = .L, '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, 'a'...'t', 'v'...'z', 'A'...'K', 'M'...'T', 'V'...'Z', '_' => state = .identifier,
'=' => state = .equal, '=' => state = .equal,
'!' => state = .bang, '!' => state = .bang,
@ -1324,6 +1440,20 @@ pub fn next(self: *Tokenizer) Token {
break; break;
}, },
0x80...0xFF => state = .extended_identifier, 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 => { else => {
id = if (state == .identifier) Token.getTokenId(self.langopts, self.buf[start..self.index]) else .extended_identifier; id = if (state == .identifier) Token.getTokenId(self.langopts, self.buf[start..self.index]) else .extended_identifier;
break; break;
@ -1731,7 +1861,10 @@ pub fn next(self: *Tokenizer) Token {
} }
} else if (self.index == self.buf.len) { } else if (self.index == self.buf.len) {
switch (state) { 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]), .u, .u8, .U, .L, .identifier => id = Token.getTokenId(self.langopts, self.buf[start..self.index]),
.extended_identifier => id = .extended_identifier, .extended_identifier => id = .extended_identifier,
@ -2105,6 +2238,15 @@ test "comments" {
.hash, .hash,
.identifier, .identifier,
}); });
try expectTokensExtra(
\\//foo
\\void
\\//bar
, &.{
.comment, .nl,
.keyword_void, .nl,
.comment,
}, .{ .preserve_comments = true });
} }
test "extended identifiers" { test "extended identifiers" {
@ -2147,16 +2289,51 @@ test "C23 keywords" {
.keyword_c23_thread_local, .keyword_c23_thread_local,
.keyword_nullptr, .keyword_nullptr,
.keyword_typeof_unqual, .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" { test "Tokenizer fuzz test" {
var comp = Compilation.init(std.testing.allocator, std.fs.cwd()); 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(); 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 = .{ var tokenizer: Tokenizer = .{
@ -2170,13 +2347,18 @@ test "Tokenizer fuzz test" {
if (tok.id == .eof) break; if (tok.id == .eof) break;
try std.testing.expect(prev_index < tokenizer.index); // ensure that the tokenizer always makes progress 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 { fn expectTokensExtra(contents: []const u8, expected_tokens: []const Token.Id, langopts: ?LangOpts) !void {
var comp = Compilation.init(std.testing.allocator, std.fs.cwd()); 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(); defer comp.deinit();
if (standard) |provided| { if (langopts) |provided| {
comp.langopts.standard = provided; comp.langopts = provided;
} }
const source = try comp.addSourceFromBuffer("path", contents); const source = try comp.addSourceFromBuffer("path", contents);
var tokenizer = Tokenizer{ var tokenizer = Tokenizer{

View File

@ -1,14 +1,15 @@
const std = @import("std"); const std = @import("std");
const Driver = @import("Driver.zig");
const Compilation = @import("Compilation.zig");
const mem = std.mem; 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 { pub const RuntimeLibKind = enum {
compiler_rt, compiler_rt,
@ -35,22 +36,13 @@ pub const UnwindLibKind = enum {
const Inner = union(enum) { const Inner = union(enum) {
uninitialized, uninitialized,
linux: Linux,
unknown: void, unknown: void,
fn deinit(self: *Inner, allocator: mem.Allocator) void {
switch (self.*) {
.linux => |*linux| linux.deinit(allocator),
.uninitialized, .unknown => {},
}
}
}; };
const Toolchain = @This(); const Toolchain = @This();
filesystem: Filesystem = .{ .real = {} }, filesystem: Filesystem,
driver: *Driver, driver: *Driver,
arena: mem.Allocator,
/// The list of toolchain specific path prefixes to search for libraries. /// The list of toolchain specific path prefixes to search for libraries.
library_paths: PathList = .{}, library_paths: PathList = .{},
@ -72,7 +64,6 @@ pub fn getTarget(tc: *const Toolchain) std.Target {
fn getDefaultLinker(tc: *const Toolchain) []const u8 { fn getDefaultLinker(tc: *const Toolchain) []const u8 {
return switch (tc.inner) { return switch (tc.inner) {
.uninitialized => unreachable, .uninitialized => unreachable,
.linux => |linux| linux.getDefaultLinker(tc.getTarget()),
.unknown => "ld", .unknown => "ld",
}; };
} }
@ -81,36 +72,26 @@ fn getDefaultLinker(tc: *const Toolchain) []const u8 {
pub fn discover(tc: *Toolchain) !void { pub fn discover(tc: *Toolchain) !void {
if (tc.inner != .uninitialized) return; if (tc.inner != .uninitialized) return;
const target = tc.getTarget(); tc.inner = .unknown;
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
};
return switch (tc.inner) { return switch (tc.inner) {
.uninitialized => unreachable, .uninitialized => unreachable,
.linux => |*linux| linux.discover(tc),
.unknown => {}, .unknown => {},
}; };
} }
pub fn deinit(tc: *Toolchain) void { pub fn deinit(tc: *Toolchain) void {
const gpa = tc.driver.comp.gpa; const gpa = tc.driver.comp.gpa;
tc.inner.deinit(gpa);
tc.library_paths.deinit(gpa); tc.library_paths.deinit(gpa);
tc.file_paths.deinit(gpa); tc.file_paths.deinit(gpa);
tc.program_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 /// Write linker path to `buf` and return a slice of it
pub fn getLinkerPath(tc: *const Toolchain, buf: []u8) ![]const u8 { pub fn getLinkerPath(tc: *const Toolchain, buf: []u8) ![]const u8 {
// --ld-path= takes precedence over -fuse-ld= and specifies the executable // --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 // 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. // among -B, COMPILER_PATH and PATH. --ld-path= should be used instead.
if (mem.indexOfScalar(u8, use_linker, '/') != null) { 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)) { if (std.fs.path.isAbsolute(use_linker)) {
@ -157,8 +143,11 @@ pub fn getLinkerPath(tc: *const Toolchain, buf: []u8) ![]const u8 {
return use_linker; return use_linker;
} }
} else { } else {
var linker_name = try std.array_list.Managed(u8).initCapacity(tc.driver.comp.gpa, 5 + use_linker.len); // "ld64." ++ use_linker const gpa = tc.driver.comp.gpa;
defer linker_name.deinit(); 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()) { if (tc.getTarget().os.tag.isDarwin()) {
linker_name.appendSliceAssumeCapacity("ld64."); linker_name.appendSliceAssumeCapacity("ld64.");
} else { } 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 /// TODO: this isn't exactly right since our target names don't necessarily match up
/// with GCC's. /// with GCC's.
/// For example the Zig target `arm-freestanding-eabi` would need the `arm-none-eabi` tools /// 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) { fn possibleProgramNames(
var possible_names: std.BoundedArray([]const u8, 2) = .{}; 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 (raw_triple) |triple| {
if (std.fmt.bufPrint(buf, "{s}-{s}", .{ triple, name })) |res| { if (std.fmt.bufPrint(buf, "{s}-{s}", .{ triple, name })) |res| {
possible_names.appendAssumeCapacity(res); possible_name_buf[i] = res;
i += 1;
} else |_| {} } 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 /// Add toolchain `file_paths` to argv as `-L` arguments
pub fn addFilePathLibArgs(tc: *const Toolchain, argv: *std.array_list.Managed([]const u8)) !void { pub fn addFilePathLibArgs(tc: *const Toolchain, argv: *std.ArrayList([]const u8)) !void {
try argv.ensureUnusedCapacity(tc.file_paths.items.len); try argv.ensureUnusedCapacity(tc.driver.comp.gpa, tc.file_paths.items.len);
var bytes_needed: usize = 0; var bytes_needed: usize = 0;
for (tc.file_paths.items) |path| { for (tc.file_paths.items) |path| {
bytes_needed += path.len + 2; // +2 for `-L` 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; var index: usize = 0;
for (tc.file_paths.items) |path| { for (tc.file_paths.items) |path| {
@memcpy(bytes[index..][0..2], "-L"); @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 fib = std.heap.FixedBufferAllocator.init(&path_buf);
var tool_specific_buf: [64]u8 = undefined; 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| { for (tc.program_paths.items) |program_path| {
defer fib.reset(); 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 path_buf: [std.fs.max_path_bytes]u8 = undefined;
var fib = std.heap.FixedBufferAllocator.init(&path_buf); var fib = std.heap.FixedBufferAllocator.init(&path_buf);
const allocator = fib.allocator(); const allocator = fib.allocator();
const arena = tc.driver.comp.arena;
const sysroot = tc.getSysroot(); 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 aro_dir = std.fs.path.dirname(tc.driver.aro_name) orelse "";
const candidate = try std.fs.path.join(allocator, &.{ aro_dir, "..", name }); const candidate = try std.fs.path.join(allocator, &.{ aro_dir, "..", name });
if (tc.filesystem.exists(candidate)) { 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| { 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| { 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; return name;
@ -299,7 +296,7 @@ const PathKind = enum {
program, 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. /// add it to the specified path list.
pub fn addPathIfExists(tc: *Toolchain, components: []const []const u8, dest_kind: PathKind) !void { pub fn addPathIfExists(tc: *Toolchain, components: []const []const u8, dest_kind: PathKind) !void {
var path_buf: [std.fs.max_path_bytes]u8 = undefined; 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); const candidate = try std.fs.path.join(fib.allocator(), components);
if (tc.filesystem.exists(candidate)) { 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) { const dest = switch (dest_kind) {
.library => &tc.library_paths, .library => &tc.library_paths,
.file => &tc.file_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 /// whether the path actually exists
pub fn addPathFromComponents(tc: *Toolchain, components: []const []const u8, dest_kind: PathKind) !void { 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) { const dest = switch (dest_kind) {
.library => &tc.library_paths, .library => &tc.library_paths,
.file => &tc.file_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); 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 { fn getDefaultRuntimeLibKind(tc: *const Toolchain) RuntimeLibKind {
if (tc.getTarget().abi.isAndroid()) { if (tc.getTarget().abi.isAndroid()) {
return .compiler_rt; return .compiler_rt;
@ -396,7 +383,7 @@ fn getUnwindLibKind(tc: *const Toolchain) !UnwindLibKind {
return .libgcc; return .libgcc;
} else if (mem.eql(u8, libname, "libunwind")) { } else if (mem.eql(u8, libname, "libunwind")) {
if (tc.getRuntimeLibKind() == .libgcc) { if (tc.getRuntimeLibKind() == .libgcc) {
try tc.driver.comp.addDiagnostic(.{ .tag = .incompatible_unwindlib }, &.{}); try tc.driver.err("--rtlib=libgcc requires --unwindlib=libgcc", .{});
} }
return .compiler_rt; return .compiler_rt;
} else { } 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 unw = try tc.getUnwindLibKind();
const target = tc.getTarget(); const target = tc.getTarget();
if ((target.abi.isAndroid() and unw == .libgcc) or 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 lgk = tc.getLibGCCKind();
const as_needed = lgk == .unspecified and !target.abi.isAndroid() and !target_util.isCygwinMinGW(target) and target.os.tag != .aix; 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) { if (as_needed) {
try argv.append(getAsNeededOption(target.os.tag == .solaris, true)); argv.appendAssumeCapacity(getAsNeededOption(target.os.tag == .solaris, true));
} }
switch (unw) { switch (unw) {
.none => return, .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) { .compiler_rt => if (target.os.tag == .aix) {
if (lgk != .static) { if (lgk != .static) {
try argv.append("-lunwind"); argv.appendAssumeCapacity("-lunwind");
} }
} else if (lgk == .static) { } else if (lgk == .static) {
try argv.append("-l:libunwind.a"); argv.appendAssumeCapacity("-l:libunwind.a");
} else if (lgk == .shared) { } else if (lgk == .shared) {
if (target_util.isCygwinMinGW(target)) { if (target_util.isCygwinMinGW(target)) {
try argv.append("-l:libunwind.dll.a"); argv.appendAssumeCapacity("-l:libunwind.dll.a");
} else { } else {
try argv.append("-l:libunwind.so"); argv.appendAssumeCapacity("-l:libunwind.so");
} }
} else { } else {
try argv.append("-lunwind"); argv.appendAssumeCapacity("-lunwind");
}, },
} }
if (as_needed) { 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(); const libgcc_kind = tc.getLibGCCKind();
if (libgcc_kind == .static or libgcc_kind == .unspecified) { if (libgcc_kind == .static or libgcc_kind == .unspecified) {
try argv.append("-lgcc"); try argv.append(gpa, "-lgcc");
} }
try tc.addUnwindLibrary(argv); try tc.addUnwindLibrary(argv);
if (libgcc_kind == .shared) { 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 target = tc.getTarget();
const rlt = tc.getRuntimeLibKind(); const rlt = tc.getRuntimeLibKind();
switch (rlt) { switch (rlt) {
@ -472,7 +462,7 @@ pub fn addRuntimeLibs(tc: *const Toolchain, argv: *std.array_list.Managed([]cons
if (target_util.isKnownWindowsMSVCEnvironment(target)) { if (target_util.isKnownWindowsMSVCEnvironment(target)) {
const rtlib_str = tc.driver.rtlib orelse system_defaults.rtlib; const rtlib_str = tc.driver.rtlib orelse system_defaults.rtlib;
if (!mem.eql(u8, rtlib_str, "platform")) { 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 { } else {
try tc.addLibGCC(argv); 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) { 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 { pub fn defineSystemIncludes(tc: *Toolchain) !void {
return switch (tc.inner) { return switch (tc.inner) {
.uninitialized => unreachable, .uninitialized => unreachable,
.linux => |*linux| linux.defineSystemIncludes(tc),
.unknown => { .unknown => {
if (tc.driver.nostdinc) return; if (tc.driver.nostdinc) return;
const comp = tc.driver.comp; const comp = tc.driver.comp;
if (!tc.driver.nobuiltininc) { 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) { 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 assert = std.debug.assert;
const BigIntConst = std.math.big.int.Const; const BigIntConst = std.math.big.int.Const;
const BigIntMutable = std.math.big.int.Mutable; 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 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 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(); 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 { pub fn ref(v: Value) Interner.Ref {
std.debug.assert(v.opt_ref != .none); std.debug.assert(v.opt_ref != .none);
return @enumFromInt(@intFromEnum(v.opt_ref)); 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 { pub fn is(v: Value, tag: std.meta.Tag(Interner.Key), comp: *const Compilation) bool {
if (v.opt_ref == .none) return false; if (v.opt_ref == .none) return false;
return comp.interner.get(v.ref()) == tag; 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(); defer comp.deinit();
const target_query = try std.Target.Query.parse(.{ .arch_os_abi = "x86_64-linux-gnu" }); const target_query = try std.Target.Query.parse(.{ .arch_os_abi = "x86_64-linux-gnu" });
comp.target = try std.zig.system.resolveTargetQuery(target_query); 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(); defer comp.deinit();
const target_query = try std.Target.Query.parse(.{ .arch_os_abi = "x86_64-linux-gnu" }); const target_query = try std.Target.Query.parse(.{ .arch_os_abi = "x86_64-linux-gnu" });
comp.target = try std.zig.system.resolveTargetQuery(target_query); 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. /// Converts the stored value from a float to an integer.
/// `.none` value remains unchanged. /// `.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; if (v.opt_ref == .none) return .none;
const float_val = v.toFloat(f128, comp); const float_val = v.toFloat(f128, comp);
const was_zero = float_val == 0; const was_zero = float_val == 0;
if (dest_ty.is(.bool)) { if (dest_ty.is(comp, .bool)) {
const was_one = float_val == 1.0; const was_one = float_val == 1.0;
v.* = fromBool(!was_zero); v.* = fromBool(!was_zero);
if (was_zero or was_one) return .none; if (was_zero or was_one) return .none;
return .value_changed; 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; v.* = zero;
return .out_of_range; return .out_of_range;
} else if (!std.math.isFinite(float_val)) {
v.* = .{};
return .overflow;
} }
const signedness = dest_ty.signedness(comp); 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 = .{ var big_int: std.math.big.int.Mutable = .{
.limbs = try comp.gpa.alloc(std.math.big.Limb, @max( .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, .len = undefined,
.positive = undefined, .positive = undefined,
}; };
defer comp.gpa.free(big_int.limbs);
const had_fraction = switch (big_int.setFloat(float_val, .trunc)) { const had_fraction = switch (big_int.setFloat(float_val, .trunc)) {
.inexact => true, .inexact => true,
.exact => false, .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. /// Converts the stored value from an integer to a float.
/// `.none` value remains unchanged. /// `.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 (v.opt_ref == .none) return;
if (dest_ty.isComplex()) { if (dest_ty.is(comp, .complex)) {
const bits = dest_ty.bitSizeof(comp).?; const bits = dest_ty.bitSizeof(comp);
const cf: Interner.Key.Complex = switch (bits) { const cf: Interner.Key.Complex = switch (bits) {
32 => .{ .cf16 = .{ v.toFloat(f16, comp), 0 } }, 32 => .{ .cf16 = .{ v.toFloat(f16, comp), 0 } },
64 => .{ .cf32 = .{ v.toFloat(f32, 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 }); v.* = try intern(comp, .{ .complex = cf });
return; return;
} }
const bits = dest_ty.bitSizeof(comp).?; const bits = dest_ty.bitSizeof(comp);
return switch (comp.interner.get(v.ref()).int) { return switch (comp.interner.get(v.ref()).int) {
inline .u64, .i64 => |data| { inline .u64, .i64 => |data| {
const f: Interner.Key.Float = switch (bits) { const f: Interner.Key.Float = switch (bits) {
@ -232,14 +252,16 @@ pub const IntCastChangeKind = enum {
/// Truncates or extends bits based on type. /// Truncates or extends bits based on type.
/// `.none` value remains unchanged. /// `.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; 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; const dest_signed = dest_ty.signedness(comp) == .signed;
var space: BigIntSpace = undefined; var space: BigIntSpace = undefined;
const big = v.toBigInt(&space, comp); const big = key.toBigInt(&space);
const value_bits = big.bitCountTwosComp(); const value_bits = big.bitCountTwosComp();
// if big is negative, then is signed. // 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 /// Converts the stored value to a float of the specified type
/// `.none` value remains unchanged. /// `.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; if (v.opt_ref == .none) return;
const bits = dest_ty.bitSizeof(comp).?; const bits = dest_ty.bitSizeof(comp);
if (dest_ty.isComplex()) { if (dest_ty.is(comp, .complex)) {
const cf: Interner.Key.Complex = switch (bits) { const cf: Interner.Key.Complex = switch (bits) {
32 => .{ .cf16 = .{ v.toFloat(f16, comp), v.imag(f16, comp) } }, 32 => .{ .cf16 = .{ v.toFloat(f16, comp), v.imag(f16, comp) } },
64 => .{ .cf32 = .{ v.toFloat(f32, comp), v.imag(f32, 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 { fn toBigInt(val: Value, space: *BigIntSpace, comp: *const Compilation) BigIntConst {
return switch (comp.interner.get(val.ref()).int) { return comp.interner.get(val.ref()).toBigInt(space);
inline .u64, .i64 => |x| BigIntMutable.init(&space.limbs, x).toConst(),
.big_int => |b| b,
};
} }
pub fn isZero(v: Value, comp: *const Compilation) bool { 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, inline else => |data| return data[0] == 0.0 and data[1] == 0.0,
}, },
.bytes => return false, .bytes => return false,
.pointer => return false,
else => unreachable, 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 { pub fn toInt(v: Value, comptime T: type, comp: *const Compilation) ?T {
if (v.opt_ref == .none) return null; 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; var space: BigIntSpace = undefined;
const big_int = v.toBigInt(&space, comp); const big_int = key.toBigInt(&space);
return big_int.toInt(T) catch null; 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 { const ComplexOp = enum {
add, add,
sub, 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 { pub fn add(res: *Value, lhs: Value, rhs: Value, qt: QualType, comp: *Compilation) !bool {
const bits: usize = @intCast(ty.bitSizeof(comp).?); const bits: usize = @intCast(qt.bitSizeof(comp));
if (ty.isFloat()) { const scalar_kind = qt.scalarKind(comp);
if (ty.isComplex()) { if (scalar_kind.isFloat()) {
if (scalar_kind == .complex_float) {
res.* = switch (bits) { res.* = switch (bits) {
32 => try complexAddSub(lhs, rhs, f16, .add, comp), 32 => try complexAddSub(lhs, rhs, f16, .add, comp),
64 => try complexAddSub(lhs, rhs, f32, .add, comp), 64 => try complexAddSub(lhs, rhs, f32, .add, comp),
@ -516,11 +544,33 @@ pub fn add(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !b
}; };
res.* = try intern(comp, .{ .float = f }); res.* = try intern(comp, .{ .float = f });
return false; return false;
} else { }
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 lhs_space: BigIntSpace = undefined;
var rhs_space: BigIntSpace = undefined; var rhs_space: BigIntSpace = undefined;
const lhs_bigint = lhs.toBigInt(&lhs_space, comp); const lhs_bigint = lhs_key.toBigInt(&lhs_space);
const rhs_bigint = rhs.toBigInt(&rhs_space, comp); const rhs_bigint = rhs_key.toBigInt(&rhs_space);
const limbs = try comp.gpa.alloc( const limbs = try comp.gpa.alloc(
std.math.big.Limb, std.math.big.Limb,
@ -529,16 +579,25 @@ pub fn add(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !b
defer comp.gpa.free(limbs); defer comp.gpa.free(limbs);
var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
const overflowed = result_bigint.addWrap(lhs_bigint, rhs_bigint, ty.signedness(comp), bits); const overflowed = result_bigint.addWrap(lhs_bigint, rhs_bigint, qt.signedness(comp), bits);
res.* = try intern(comp, .{ .int = .{ .big_int = result_bigint.toConst() } }); res.* = try intern(comp, .{ .int = .{ .big_int = result_bigint.toConst() } });
return overflowed; return overflowed;
}
} }
pub fn sub(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !bool { pub fn negate(res: *Value, val: Value, qt: QualType, comp: *Compilation) !bool {
const bits: usize = @intCast(ty.bitSizeof(comp).?); return res.sub(zero, val, qt, undefined, comp);
if (ty.isFloat()) { }
if (ty.isComplex()) {
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) { res.* = switch (bits) {
32 => try complexAddSub(lhs, rhs, f16, .sub, comp), 32 => try complexAddSub(lhs, rhs, f16, .sub, comp),
64 => try complexAddSub(lhs, rhs, f32, .sub, comp), 64 => try complexAddSub(lhs, rhs, f32, .sub, comp),
@ -559,11 +618,43 @@ pub fn sub(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !b
}; };
res.* = try intern(comp, .{ .float = f }); res.* = try intern(comp, .{ .float = f });
return false; return false;
} else { }
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 lhs_space: BigIntSpace = undefined;
var rhs_space: BigIntSpace = undefined; var rhs_space: BigIntSpace = undefined;
const lhs_bigint = lhs.toBigInt(&lhs_space, comp); const lhs_bigint = lhs_key.toBigInt(&lhs_space);
const rhs_bigint = rhs.toBigInt(&rhs_space, comp); const rhs_bigint = rhs_key.toBigInt(&rhs_space);
const limbs = try comp.gpa.alloc( const limbs = try comp.gpa.alloc(
std.math.big.Limb, std.math.big.Limb,
@ -572,16 +663,16 @@ pub fn sub(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !b
defer comp.gpa.free(limbs); defer comp.gpa.free(limbs);
var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
const overflowed = result_bigint.subWrap(lhs_bigint, rhs_bigint, ty.signedness(comp), bits); const overflowed = result_bigint.subWrap(lhs_bigint, rhs_bigint, qt.signedness(comp), bits);
res.* = try intern(comp, .{ .int = .{ .big_int = result_bigint.toConst() } }); res.* = try intern(comp, .{ .int = .{ .big_int = result_bigint.toConst() } });
return overflowed; return overflowed;
}
} }
pub fn mul(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !bool { pub fn mul(res: *Value, lhs: Value, rhs: Value, qt: QualType, comp: *Compilation) !bool {
const bits: usize = @intCast(ty.bitSizeof(comp).?); const bits: usize = @intCast(qt.bitSizeof(comp));
if (ty.isFloat()) { const scalar_kind = qt.scalarKind(comp);
if (ty.isComplex()) { if (scalar_kind.isFloat()) {
if (scalar_kind == .complex_float) {
const cf: Interner.Key.Complex = switch (bits) { 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)) }, 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)) }, 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); 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); const overflowed = !result_bigint.toConst().fitsInTwosComp(signedness, bits);
if (overflowed) { if (overflowed) {
result_bigint.truncate(result_bigint.toConst(), signedness, bits); 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 /// caller guarantees rhs != 0
pub fn div(res: *Value, lhs: Value, rhs: Value, ty: Type, comp: *Compilation) !bool { pub fn div(res: *Value, lhs: Value, rhs: Value, qt: QualType, comp: *Compilation) !bool {
const bits: usize = @intCast(ty.bitSizeof(comp).?); const bits: usize = @intCast(qt.bitSizeof(comp));
if (ty.isFloat()) { const scalar_kind = qt.scalarKind(comp);
if (ty.isComplex()) { if (scalar_kind.isFloat()) {
if (scalar_kind == .complex_float) {
const cf: Interner.Key.Complex = switch (bits) { 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)) }, 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)) }, 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); result_q.divTrunc(&result_r, lhs_bigint, rhs_bigint, limbs_buffer);
res.* = try intern(comp, .{ .int = .{ .big_int = result_q.toConst() } }); 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 rhs != 0
/// caller guarantees lhs != std.math.minInt(T) OR rhs != -1 /// 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 lhs_space: BigIntSpace = undefined;
var rhs_space: BigIntSpace = undefined; var rhs_space: BigIntSpace = undefined;
const lhs_bigint = lhs.toBigInt(&lhs_space, comp); const lhs_bigint = lhs.toBigInt(&lhs_space, comp);
const rhs_bigint = rhs.toBigInt(&rhs_space, comp); const rhs_bigint = rhs.toBigInt(&rhs_space, comp);
const signedness = ty.signedness(comp); if (qt.signedness(comp) == .signed) {
if (signedness == .signed) {
var spaces: [2]BigIntSpace = undefined; 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 negative = BigIntMutable.init(&spaces[0].limbs, -1).toConst();
const big_one = BigIntMutable.init(&spaces[1].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)) { 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)) { } else if (rhs_bigint.order(big_one).compare(.lt)) {
// lhs - @divTrunc(lhs, rhs) * rhs // lhs - @divTrunc(lhs, rhs) * rhs
var tmp: Value = undefined; var tmp: Value = undefined;
_ = try tmp.div(lhs, rhs, ty, comp); _ = try tmp.div(lhs, rhs, qt, comp);
_ = try tmp.mul(tmp, rhs, ty, comp); _ = try tmp.mul(tmp, rhs, qt, comp);
_ = try tmp.sub(lhs, tmp, ty, comp); _ = try tmp.sub(lhs, tmp, qt, undefined, comp);
return tmp; 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() } }); return intern(comp, .{ .int = .{ .big_int = result_bigint.toConst() } });
} }
pub fn bitNot(val: Value, ty: Type, comp: *Compilation) !Value { pub fn bitNot(val: Value, qt: QualType, comp: *Compilation) !Value {
const bits: usize = @intCast(ty.bitSizeof(comp).?); const bits: usize = @intCast(qt.bitSizeof(comp));
var val_space: Value.BigIntSpace = undefined; var val_space: Value.BigIntSpace = undefined;
const val_bigint = val.toBigInt(&val_space, comp); 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); defer comp.gpa.free(limbs);
var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined }; 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() } }); 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; var lhs_space: Value.BigIntSpace = undefined;
const lhs_bigint = lhs.toBigInt(&lhs_space, comp); const lhs_bigint = lhs.toBigInt(&lhs_space, comp);
const shift = rhs.toInt(usize, comp) orelse std.math.maxInt(usize); 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 (shift > bits) {
if (lhs_bigint.positive) { if (lhs_bigint.positive) {
res.* = try Value.maxInt(ty, comp); res.* = try Value.maxInt(qt, comp);
} else { } else {
res.* = try Value.minInt(ty, comp); res.* = try Value.minInt(qt, comp);
} }
return true; 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 }; var result_bigint = BigIntMutable{ .limbs = limbs, .positive = undefined, .len = undefined };
result_bigint.shiftLeft(lhs_bigint, shift); result_bigint.shiftLeft(lhs_bigint, shift);
const signedness = ty.signedness(comp); const signedness = qt.signedness(comp);
const overflowed = !result_bigint.toConst().fitsInTwosComp(signedness, bits); const overflowed = !result_bigint.toConst().fitsInTwosComp(signedness, bits);
if (overflowed) { if (overflowed) {
result_bigint.truncate(result_bigint.toConst(), signedness, bits); 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; 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; var lhs_space: Value.BigIntSpace = undefined;
const lhs_bigint = lhs.toBigInt(&lhs_space, comp); const lhs_bigint = lhs.toBigInt(&lhs_space, comp);
const shift = rhs.toInt(usize, comp) orelse return zero; 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( const limbs = try comp.gpa.alloc(
std.math.big.Limb, std.math.big.Limb,
std.math.big.int.calcTwosCompLimbCount(bits), 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() } }); return intern(comp, .{ .int = .{ .big_int = result_bigint.toConst() } });
} }
pub fn complexConj(val: Value, ty: Type, comp: *Compilation) !Value { pub fn complexConj(val: Value, qt: QualType, comp: *Compilation) !Value {
const bits = ty.bitSizeof(comp).?; const bits = qt.bitSizeof(comp);
const cf: Interner.Key.Complex = switch (bits) { const cf: Interner.Key.Complex = switch (bits) {
32 => .{ .cf16 = .{ val.toFloat(f16, comp), -val.imag(f16, comp) } }, 32 => .{ .cf16 = .{ val.toFloat(f16, comp), -val.imag(f16, comp) } },
64 => .{ .cf32 = .{ val.toFloat(f32, comp), -val.imag(f32, 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 }); 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) { if (op == .eq) {
return lhs.opt_ref == rhs.opt_ref; return lhs.opt_ref == rhs.opt_ref;
} else if (lhs.opt_ref == rhs.opt_ref) { } else if (lhs.opt_ref == rhs.opt_ref) {
return std.math.Order.eq.compare(op); 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 lhs_key = comp.interner.get(lhs.ref());
const rhs_key = comp.interner.get(rhs.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); return lhs_bigint.order(rhs_bigint).compare(op);
} }
fn twosCompIntLimit(limit: std.math.big.int.TwosCompIntLimit, ty: Type, comp: *Compilation) !Value { /// Returns null for values that cannot be compared at compile time (e.g. `&x < &y`) for globals `x` and `y`.
const signedness = ty.signedness(comp); 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; 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) { switch (mag_bits) {
inline 8, 16, 32, 64 => |bits| { inline 8, 16, 32, 64 => |bits| {
if (limit == .min) return Value.int(@as(i64, std.math.minInt(std.meta.Int(.signed, bits))), comp); 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() } }); return Value.intern(comp, .{ .int = .{ .big_int = result_bigint.toConst() } });
} }
pub fn minInt(ty: Type, comp: *Compilation) !Value { pub fn minInt(qt: QualType, comp: *Compilation) !Value {
return twosCompIntLimit(.min, ty, comp); return twosCompIntLimit(.min, qt, comp);
} }
pub fn maxInt(ty: Type, comp: *Compilation) !Value { pub fn maxInt(qt: QualType, comp: *Compilation) !Value {
return twosCompIntLimit(.max, ty, comp); return twosCompIntLimit(.max, qt, comp);
} }
pub fn print(v: Value, ty: Type, comp: *const Compilation, w: *Writer) Writer.Error!void { const NestedPrint = union(enum) {
if (ty.is(.bool)) { pointer: struct {
return w.writeAll(if (v.isZero(comp)) "false" else "true"); 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()); const key = comp.interner.get(v.ref());
switch (key) { switch (key) {
.null => return w.writeAll("nullptr_t"), .null => try w.writeAll("nullptr_t"),
.int => |repr| switch (repr) { .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) { .float => |repr| switch (repr) {
.f16 => |x| return w.print("{d}", .{@round(@as(f64, @floatCast(x)) * 1000) / 1000}), .f16 => |x| try w.print("{d}", .{@round(@as(f64, @floatCast(x)) * 1000) / 1000}),
.f32 => |x| return w.print("{d}", .{@round(@as(f64, @floatCast(x)) * 1000000) / 1000000}), .f32 => |x| try w.print("{d}", .{@round(@as(f64, @floatCast(x)) * 1000000) / 1000000}),
inline else => |x| return w.print("{d}", .{@as(f64, @floatCast(x))}), 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) { .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 }), .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| return w.print("{d} + {d}i", .{ @as(f64, @floatCast(components[0])), @as(f64, @floatCast(components[1])) }), 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 else => unreachable, // not a value
} }
return null;
} }
pub fn printString(bytes: []const u8, ty: Type, comp: *const Compilation, w: *Writer) Writer.Error!void { 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(ty.elemType().sizeof(comp).?); const size: Compilation.CharUnitSize = @enumFromInt(qt.childType(comp).sizeof(comp));
const without_null = bytes[0 .. bytes.len - @intFromEnum(size)]; const without_null = bytes[0 .. bytes.len - @intFromEnum(size)];
try w.writeByte('"'); try w.writeByte('"');
switch (size) { switch (size) {
.@"1" => try w.print("{f}", .{std.zig.fmtString(without_null)}), .@"1" => try std.zig.stringEscape(without_null, w),
.@"2" => { .@"2" => {
var items: [2]u16 = undefined; var items: [2]u16 = undefined;
var i: usize = 0; 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 /// 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); assert(codepoint > 0x7F);
return switch (codepoint) { return switch (codepoint) {
0x01c3 => '!', // LATIN LETTER RETROFLEX CLICK 0x01c3 => "!", // LATIN LETTER RETROFLEX CLICK
0x037e => ';', // GREEK QUESTION MARK 0x037e => ";", // GREEK QUESTION MARK
0x2212 => '-', // MINUS SIGN 0x2212 => "-", // MINUS SIGN
0x2215 => '/', // DIVISION SLASH 0x2215 => "/", // DIVISION SLASH
0x2216 => '\\', // SET MINUS 0x2216 => "\\", // SET MINUS
0x2217 => '*', // ASTERISK OPERATOR 0x2217 => "*", // ASTERISK OPERATOR
0x2223 => '|', // DIVIDES 0x2223 => "|", // DIVIDES
0x2227 => '^', // LOGICAL AND 0x2227 => "^", // LOGICAL AND
0x2236 => ':', // RATIO 0x2236 => ":", // RATIO
0x223c => '~', // TILDE OPERATOR 0x223c => "~", // TILDE OPERATOR
0xa789 => ':', // MODIFIER LETTER COLON 0xa789 => ":", // MODIFIER LETTER COLON
0xff01 => '!', // FULLWIDTH EXCLAMATION MARK 0xff01 => "!", // FULLWIDTH EXCLAMATION MARK
0xff03 => '#', // FULLWIDTH NUMBER SIGN 0xff03 => "#", // FULLWIDTH NUMBER SIGN
0xff04 => '$', // FULLWIDTH DOLLAR SIGN 0xff04 => "$", // FULLWIDTH DOLLAR SIGN
0xff05 => '%', // FULLWIDTH PERCENT SIGN 0xff05 => "%", // FULLWIDTH PERCENT SIGN
0xff06 => '&', // FULLWIDTH AMPERSAND 0xff06 => "&", // FULLWIDTH AMPERSAND
0xff08 => '(', // FULLWIDTH LEFT PARENTHESIS 0xff08 => "(", // FULLWIDTH LEFT PARENTHESIS
0xff09 => ')', // FULLWIDTH RIGHT PARENTHESIS 0xff09 => ")", // FULLWIDTH RIGHT PARENTHESIS
0xff0a => '*', // FULLWIDTH ASTERISK 0xff0a => "*", // FULLWIDTH ASTERISK
0xff0b => '+', // FULLWIDTH ASTERISK 0xff0b => "+", // FULLWIDTH ASTERISK
0xff0c => ',', // FULLWIDTH COMMA 0xff0c => ",", // FULLWIDTH COMMA
0xff0d => '-', // FULLWIDTH HYPHEN-MINUS 0xff0d => "-", // FULLWIDTH HYPHEN-MINUS
0xff0e => '.', // FULLWIDTH FULL STOP 0xff0e => ".", // FULLWIDTH FULL STOP
0xff0f => '/', // FULLWIDTH SOLIDUS 0xff0f => "/", // FULLWIDTH SOLIDUS
0xff1a => ':', // FULLWIDTH COLON 0xff1a => ":", // FULLWIDTH COLON
0xff1b => ';', // FULLWIDTH SEMICOLON 0xff1b => ";", // FULLWIDTH SEMICOLON
0xff1c => '<', // FULLWIDTH LESS-THAN SIGN 0xff1c => "<", // FULLWIDTH LESS-THAN SIGN
0xff1d => '=', // FULLWIDTH EQUALS SIGN 0xff1d => "=", // FULLWIDTH EQUALS SIGN
0xff1e => '>', // FULLWIDTH GREATER-THAN SIGN 0xff1e => ">", // FULLWIDTH GREATER-THAN SIGN
0xff1f => '?', // FULLWIDTH QUESTION MARK 0xff1f => "?", // FULLWIDTH QUESTION MARK
0xff20 => '@', // FULLWIDTH COMMERCIAL AT 0xff20 => "@", // FULLWIDTH COMMERCIAL AT
0xff3b => '[', // FULLWIDTH LEFT SQUARE BRACKET 0xff3b => "[", // FULLWIDTH LEFT SQUARE BRACKET
0xff3c => '\\', // FULLWIDTH REVERSE SOLIDUS 0xff3c => "\\", // FULLWIDTH REVERSE SOLIDUS
0xff3d => ']', // FULLWIDTH RIGHT SQUARE BRACKET 0xff3d => "]", // FULLWIDTH RIGHT SQUARE BRACKET
0xff3e => '^', // FULLWIDTH CIRCUMFLEX ACCENT 0xff3e => "^", // FULLWIDTH CIRCUMFLEX ACCENT
0xff5b => '{', // FULLWIDTH LEFT CURLY BRACKET 0xff5b => "{", // FULLWIDTH LEFT CURLY BRACKET
0xff5c => '|', // FULLWIDTH VERTICAL LINE 0xff5c => "|", // FULLWIDTH VERTICAL LINE
0xff5d => '}', // FULLWIDTH RIGHT CURLY BRACKET 0xff5d => "}", // FULLWIDTH RIGHT CURLY BRACKET
0xff5e => '~', // FULLWIDTH TILDE 0xff5e => "~", // FULLWIDTH TILDE
else => null, else => null,
}; };
} }

View File

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

View File

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

View File

@ -1,12 +1,13 @@
const std = @import("std"); const std = @import("std");
const mem = std.mem; const mem = std.mem;
const Compilation = @import("../Compilation.zig"); const Compilation = @import("../Compilation.zig");
const Pragma = @import("../Pragma.zig");
const Diagnostics = @import("../Diagnostics.zig"); const Diagnostics = @import("../Diagnostics.zig");
const Preprocessor = @import("../Preprocessor.zig");
const Parser = @import("../Parser.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 Source = @import("../Source.zig");
const TokenIndex = @import("../Tree.zig").TokenIndex;
const Message = @This(); 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 { 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) { const str = Pragma.pasteTokens(pp, start_idx + 1) catch |err| switch (err) {
error.ExpectedStringLiteral => { error.ExpectedStringLiteral => {
return pp.comp.addDiagnostic(.{ return Pragma.err(pp, start_idx, .pragma_requires_string_literal, .{"message"});
.tag = .pragma_requires_string_literal,
.loc = message_tok.loc,
.extra = .{ .str = "message" },
}, message_expansion_locs);
}, },
else => |e| return e, 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) const loc = if (message_expansion_locs.len != 0)
message_expansion_locs[message_expansion_locs.len - 1] message_expansion_locs[message_expansion_locs.len - 1]
else else
message_tok.loc; 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 std = @import("std");
const mem = std.mem; const mem = std.mem;
const Compilation = @import("../Compilation.zig"); const Compilation = @import("../Compilation.zig");
const Pragma = @import("../Pragma.zig");
const Diagnostics = @import("../Diagnostics.zig"); const Diagnostics = @import("../Diagnostics.zig");
const Preprocessor = @import("../Preprocessor.zig");
const Parser = @import("../Parser.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 Source = @import("../Source.zig");
const TokenIndex = @import("../Tree.zig").TokenIndex;
const Once = @This(); const Once = @This();
@ -14,15 +15,14 @@ pragma: Pragma = .{
.afterParse = afterParse, .afterParse = afterParse,
.deinit = deinit, .deinit = deinit,
.preprocessorHandler = preprocessorHandler, .preprocessorHandler = preprocessorHandler,
.preserveTokens = preserveTokens,
}, },
pragma_once: std.AutoHashMap(Source.Id, void), pragma_once: std.AutoHashMapUnmanaged(Source.Id, void) = .empty,
preprocess_count: u32 = 0, preprocess_count: u32 = 0,
pub fn init(allocator: mem.Allocator) !*Pragma { pub fn init(allocator: mem.Allocator) !*Pragma {
var once = try allocator.create(Once); var once = try allocator.create(Once);
once.* = .{ once.* = .{};
.pragma_once = std.AutoHashMap(Source.Id, void).init(allocator),
};
return &once.pragma; return &once.pragma;
} }
@ -33,8 +33,9 @@ fn afterParse(pragma: *Pragma, _: *Compilation) void {
fn deinit(pragma: *Pragma, comp: *Compilation) void { fn deinit(pragma: *Pragma, comp: *Compilation) void {
var self: *Once = @fieldParentPtr("pragma", pragma); var self: *Once = @fieldParentPtr("pragma", pragma);
self.pragma_once.deinit(); self.pragma_once.deinit(comp.gpa);
comp.gpa.destroy(self); comp.gpa.destroy(self);
pragma.* = undefined;
} }
fn preprocessorHandler(pragma: *Pragma, pp: *Preprocessor, start_idx: TokenIndex) Pragma.Error!void { 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 name_tok = pp.tokens.get(start_idx);
const next = pp.tokens.get(start_idx + 1); const next = pp.tokens.get(start_idx + 1);
if (next.id != .nl) { if (next.id != .nl) {
try pp.comp.addDiagnostic(.{ const diagnostic: Preprocessor.Diagnostic = .extra_tokens_directive_end;
.tag = .extra_tokens_directive_end, return pp.diagnostics.addWithLocation(pp.comp, .{
.loc = name_tok.loc, .text = diagnostic.fmt,
}, pp.expansionSlice(start_idx + 1)); .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 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) { if (prev != null and !seen) {
return error.StopPreprocessing; return error.StopPreprocessing;
} }
self.preprocess_count = pp.preprocess_count; 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 std = @import("std");
const mem = std.mem; const mem = std.mem;
const Compilation = @import("../Compilation.zig"); const Compilation = @import("../Compilation.zig");
const Pragma = @import("../Pragma.zig");
const Diagnostics = @import("../Diagnostics.zig"); const Diagnostics = @import("../Diagnostics.zig");
const Preprocessor = @import("../Preprocessor.zig");
const Parser = @import("../Parser.zig"); const Parser = @import("../Parser.zig");
const Pragma = @import("../Pragma.zig");
const Preprocessor = @import("../Preprocessor.zig");
const Tree = @import("../Tree.zig"); const Tree = @import("../Tree.zig");
const TokenIndex = Tree.TokenIndex; const TokenIndex = Tree.TokenIndex;
@ -13,9 +14,8 @@ const Pack = @This();
pragma: Pragma = .{ pragma: Pragma = .{
.deinit = deinit, .deinit = deinit,
.parserHandler = parserHandler, .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 { pub fn init(allocator: mem.Allocator) !*Pragma {
var pack = try allocator.create(Pack); 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; var idx = start_idx + 1;
const l_paren = p.pp.tokens.get(idx); const l_paren = p.pp.tokens.get(idx);
if (l_paren.id != .l_paren) { if (l_paren.id != .l_paren) {
return p.comp.addDiagnostic(.{ return Pragma.err(p.pp, idx, .pragma_pack_lparen, .{});
.tag = .pragma_pack_lparen,
.loc = l_paren.loc,
}, p.pp.expansionSlice(idx));
} }
idx += 1; idx += 1;
@ -54,11 +51,11 @@ fn parserHandler(pragma: *Pragma, p: *Parser, start_idx: TokenIndex) Compilation
pop, pop,
}; };
const action = std.meta.stringToEnum(Action, p.tokSlice(arg)) orelse { 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) { switch (action) {
.show => { .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 => { .push, .pop => {
var new_val: ?u8 = null; var new_val: ?u8 = null;
@ -75,21 +72,23 @@ fn parserHandler(pragma: *Pragma, p: *Parser, start_idx: TokenIndex) Compilation
idx += 1; idx += 1;
const int = idx; const int = idx;
idx += 1; 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; 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) { 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 { } else {
pack.pop(p, label); pack.pop(p, label);
if (new_val != null) { 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) { } 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| { if (new_val) |some| {
@ -115,14 +114,14 @@ fn parserHandler(pragma: *Pragma, p: *Parser, start_idx: TokenIndex) Compilation
} }
if (tok_ids[idx] != .r_paren) { 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 { fn packInt(p: *Parser, tok_i: TokenIndex) Compilation.Error!?u8 {
const res = p.parseNumberToken(tok_i) catch |err| switch (err) { const res = p.parseNumberToken(tok_i) catch |err| switch (err) {
error.ParsingFailed => { error.ParsingFailed => {
try p.errTok(.pragma_pack_int, tok_i); try Pragma.err(p.pp, tok_i, .pragma_pack_int, .{});
return null; return null;
}, },
else => |e| return e, else => |e| return e,
@ -131,7 +130,7 @@ fn packInt(p: *Parser, tok_i: TokenIndex) Compilation.Error!?u8 {
switch (int) { switch (int) {
1, 2, 4, 8, 16 => return @intCast(int), 1, 2, 4, 8, 16 => return @intCast(int),
else => { else => {
try p.errTok(.pragma_pack_int, tok_i); try Pragma.err(p.pp, tok_i, .pragma_pack_int, .{});
return null; return null;
}, },
} }
@ -156,9 +155,3 @@ fn pop(pack: *Pack, p: *Parser, maybe_label: ?[]const u8) void {
p.pragma_pack = prev.val; 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 //! Licensed under MIT license: https://github.com/mahkoh/repr-c/tree/master/repc/facade
const std = @import("std"); const std = @import("std");
const Type = @import("Type.zig");
const Attribute = @import("Attribute.zig"); const Attribute = @import("Attribute.zig");
const Compilation = @import("Compilation.zig"); const Compilation = @import("Compilation.zig");
const Parser = @import("Parser.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 Record = Type.Record;
const Field = Record.Field; const Field = Record.Field;
const TypeLayout = Type.TypeLayout; const RecordLayout = Type.Record.Layout;
const FieldLayout = Type.FieldLayout; const FieldLayout = Type.Record.Field.Layout;
const target_util = @import("target.zig");
const BITS_PER_BYTE = 8; const BITS_PER_BYTE = 8;
@ -42,36 +45,33 @@ const SysVContext = struct {
comp: *const Compilation, 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 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{ return SysVContext{
.attr_packed = ty.hasAttribute(.@"packed"), .attr_packed = qt.hasAttribute(comp, .@"packed"),
.max_field_align_bits = pack_value, .max_field_align_bits = pack_value,
.aligned_bits = req_align, .aligned_bits = req_align,
.is_union = ty.is(.@"union"), .is_union = qt.is(comp, .@"union"),
.size_bits = 0, .size_bits = 0,
.comp = comp, .comp = comp,
.ongoing_bitfield = null, .ongoing_bitfield = null,
}; };
} }
fn layoutFields(self: *SysVContext, rec: *const Record) !void { fn layoutFields(self: *SysVContext, fields: []Type.Record.Field) !void {
for (rec.fields, 0..) |*fld, fld_indx| { for (fields) |*field| {
if (fld.ty.specifier == .invalid) continue; if (field.qt.isInvalid()) continue;
const type_layout = computeLayout(fld.ty, self.comp); const type_layout = computeLayout(field.qt, self.comp);
var field_attrs: ?[]const Attribute = null; const attributes = field.attributes(self.comp);
if (rec.field_attributes) |attrs| {
field_attrs = attrs[fld_indx];
}
if (self.comp.target.isMinGW()) { 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 { } else {
if (fld.isRegularField()) { if (field.bit_width.unpack()) |bit_width| {
fld.layout = try self.layoutRegularField(field_attrs, type_layout); field.layout = try self.layoutBitField(attributes, type_layout, field.name_tok != 0, bit_width);
} else { } 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 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 /// - the field is a zero-sized bit-field and the previous field was not a non-zero-sized bit-field
/// See test case 0068. /// 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 (is_attr_packed) return true;
if (bit_width) |width| { if (bit_width) |width| {
if (ongoing_bitfield) |ongoing| { if (ongoing_bitfield) |ongoing| {
@ -98,12 +98,12 @@ const SysVContext = struct {
fn layoutMinGWField( fn layoutMinGWField(
self: *SysVContext, self: *SysVContext,
field: *const Field, field: *const Field,
field_attrs: ?[]const Attribute, field_attrs: []const Attribute,
field_layout: TypeLayout, field_layout: RecordLayout,
) !FieldLayout { ) !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 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; var field_alignment_bits: u64 = field_layout.field_alignment_bits;
if (ignore_type_alignment) { if (ignore_type_alignment) {
@ -120,16 +120,16 @@ const SysVContext = struct {
// - the field is a non-zero-width bit-field and not packed. // - the field is a non-zero-width bit-field and not packed.
// See test case 0069. // See test case 0069.
const update_record_alignment = const update_record_alignment =
field.isRegularField() or field.bit_width == .null or
(field.specifiedBitWidth() == 0 and self.ongoing_bitfield != null) or (field.bit_width.unpack().? == 0 and self.ongoing_bitfield != null) or
(field.specifiedBitWidth() != 0 and !is_attr_packed); (field.bit_width.unpack().? != 0 and !is_attr_packed);
// If a field affects the alignment of a record, the alignment is calculated in the // 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. // usual way except that __attribute__((packed)) is ignored on a zero-width bit-field.
// See test case 0068. // See test case 0068.
if (update_record_alignment) { if (update_record_alignment) {
var ty_alignment_bits = field_layout.field_alignment_bits; 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 = BITS_PER_BYTE;
} }
ty_alignment_bits = @max(ty_alignment_bits, annotation_alignment_bits); 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, // @attr_packed _ { size: 64, alignment: 64 }long long:0,
// { offset: 8, size: 8 }d { size: 8, alignment: 8 }char, // { offset: 8, size: 8 }d { size: 8, alignment: 8 }char,
// } // }
if (field.isRegularField()) { if (field.bit_width.unpack()) |bit_width| {
return self.layoutRegularFieldMinGW(field_layout.size_bits, field_alignment_bits); return self.layoutBitFieldMinGW(field_layout.size_bits, field_alignment_bits, field.name_tok != 0, bit_width);
} else { } 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( fn layoutRegularField(
self: *SysVContext, self: *SysVContext,
fld_attrs: ?[]const Attribute, fld_attrs: []const Attribute,
fld_layout: TypeLayout, fld_layout: RecordLayout,
) !FieldLayout { ) !FieldLayout {
var fld_align_bits = fld_layout.field_alignment_bits; 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 // The field alignment can be increased by __attribute__((aligned)) annotations on the
// field. See test case 0085. // 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); fld_align_bits = @max(fld_align_bits, @as(u32, anno) * BITS_PER_BYTE);
} }
@ -268,8 +268,8 @@ const SysVContext = struct {
fn layoutBitField( fn layoutBitField(
self: *SysVContext, self: *SysVContext,
fld_attrs: ?[]const Attribute, fld_attrs: []const Attribute,
fld_layout: TypeLayout, fld_layout: RecordLayout,
is_named: bool, is_named: bool,
bit_width: u64, bit_width: u64,
) !FieldLayout { ) !FieldLayout {
@ -302,7 +302,7 @@ const SysVContext = struct {
const attr_packed = self.attr_packed or isPacked(fld_attrs); const attr_packed = self.attr_packed or isPacked(fld_attrs);
const has_packing_annotation = attr_packed or self.max_field_align_bits != null; 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; const first_unused_bit: u64 = if (self.is_union) 0 else self.size_bits;
var field_align_bits: u64 = 1; var field_align_bits: u64 = 1;
@ -403,9 +403,9 @@ const MsvcContext = struct {
is_union: bool, is_union: bool,
comp: *const Compilation, 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; 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. // __attribute__((packed)) behaves like #pragma pack(1) in clang. See test case 0056.
pack_value = BITS_PER_BYTE; pack_value = BITS_PER_BYTE;
} }
@ -420,8 +420,8 @@ const MsvcContext = struct {
// The required alignment can be increased by adding a __declspec(align) // The required alignment can be increased by adding a __declspec(align)
// annotation. See test case 0023. // annotation. See test case 0023.
const must_align = @as(u32, (ty.requestedAlignment(comp) orelse 1)) * BITS_PER_BYTE; const must_align = @as(u32, (qt.requestedAlignment(comp) orelse 1)) * BITS_PER_BYTE;
return MsvcContext{ return .{
.req_align_bits = must_align, .req_align_bits = must_align,
.pointer_align_bits = must_align, .pointer_align_bits = must_align,
.field_align_bits = must_align, .field_align_bits = must_align,
@ -429,26 +429,26 @@ const MsvcContext = struct {
.max_field_align_bits = pack_value, .max_field_align_bits = pack_value,
.ongoing_bitfield = null, .ongoing_bitfield = null,
.contains_non_bitfield = false, .contains_non_bitfield = false,
.is_union = ty.is(.@"union"), .is_union = qt.is(comp, .@"union"),
.comp = comp, .comp = comp,
}; };
} }
fn layoutField(self: *MsvcContext, fld: *const Field, fld_attrs: ?[]const Attribute) !FieldLayout { fn layoutField(self: *MsvcContext, fld: *const Field, fld_attrs: []const Attribute) !FieldLayout {
const type_layout = computeLayout(fld.ty, self.comp); const type_layout = computeLayout(fld.qt, self.comp);
// The required alignment of the field is the maximum of the required alignment of the // 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. // underlying type and the __declspec(align) annotation on the field itself.
// See test case 0028. // See test case 0028.
var req_align = type_layout.required_alignment_bits; 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); 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 // 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. // fields except that the required alignment of bitfields is ignored.
// See test case 0029. // See test case 0029.
if (fld.isRegularField()) { if (fld.bit_width == .null) {
self.req_align_bits = @max(self.req_align_bits, req_align); 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); fld_align_bits = @min(fld_align_bits, max_align);
} }
// check the requested alignment of the field type. // 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); 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 // __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. // pack(1) had been applied only to this field. See test case 0057.
fld_align_bits = @max(fld_align_bits, req_align); fld_align_bits = @max(fld_align_bits, req_align);
if (fld.isRegularField()) { if (fld.bit_width.unpack()) |bit_width| {
return self.layoutRegularField(type_layout.size_bits, fld_align_bits); return self.layoutBitField(type_layout.size_bits, fld_align_bits, bit_width);
} else { } 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) { switch (comp.langopts.emulate) {
.gcc, .clang => { .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); context.size_bits = try alignForward(context.size_bits, context.aligned_bits);
rec.type_layout = .{ return .{
.size_bits = context.size_bits, .size_bits = context.size_bits,
.field_alignment_bits = context.aligned_bits, .field_alignment_bits = context.aligned_bits,
.pointer_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 => { .msvc => {
var context = MsvcContext.init(ty, comp, pragma_pack); var context = MsvcContext.init(qt, comp, pragma_pack);
for (rec.fields, 0..) |*fld, fld_indx| { for (fields) |*field| {
if (fld.ty.specifier == .invalid) continue; if (field.qt.isInvalid()) continue;
var field_attrs: ?[]const Attribute = null; field.layout = try context.layoutField(field, field.attributes(comp));
if (rec.field_attributes) |attrs| {
field_attrs = attrs[fld_indx];
}
fld.layout = try context.layoutField(fld, field_attrs);
} }
if (context.size_bits == 0) { if (context.size_bits == 0) {
// As an extension, MSVC allows records that only contain zero-sized bitfields and empty // 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.handleZeroSizedRecord();
} }
context.size_bits = try alignForward(context.size_bits, context.pointer_align_bits); context.size_bits = try alignForward(context.size_bits, context.pointer_align_bits);
rec.type_layout = .{ return .{
.size_bits = context.size_bits, .size_bits = context.size_bits,
.field_alignment_bits = context.field_align_bits, .field_alignment_bits = context.field_align_bits,
.pointer_alignment_bits = context.pointer_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 { fn computeLayout(qt: QualType, comp: *const Compilation) RecordLayout {
if (ty.getRecord()) |rec| { switch (qt.base(comp).type) {
const requested = BITS_PER_BYTE * (ty.requestedAlignment(comp) orelse 0); .@"struct", .@"union" => |record| {
const requested = BITS_PER_BYTE * (qt.requestedAlignment(comp) orelse 0);
return .{ return .{
.size_bits = rec.type_layout.size_bits, .size_bits = record.layout.?.size_bits,
.pointer_alignment_bits = @max(requested, rec.type_layout.pointer_alignment_bits), .pointer_alignment_bits = @max(requested, record.layout.?.pointer_alignment_bits),
.field_alignment_bits = @max(requested, rec.type_layout.field_alignment_bits), .field_alignment_bits = @max(requested, record.layout.?.field_alignment_bits),
.required_alignment_bits = rec.type_layout.required_alignment_bits, .required_alignment_bits = record.layout.?.required_alignment_bits,
}; };
} else { },
const type_align = ty.alignof(comp) * BITS_PER_BYTE; else => {
const type_align = qt.alignof(comp) * BITS_PER_BYTE;
return .{ return .{
.size_bits = ty.bitSizeof(comp) orelse 0, .size_bits = qt.bitSizeofOrNull(comp) orelse 0,
.pointer_alignment_bits = type_align, .pointer_alignment_bits = type_align,
.field_alignment_bits = type_align, .field_alignment_bits = type_align,
.required_alignment_bits = BITS_PER_BYTE, .required_alignment_bits = BITS_PER_BYTE,
}; };
},
} }
} }

View File

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

View File

@ -1,11 +1,13 @@
//! Parsing and classification of string and character literals //! Parsing and classification of string and character literals
const std = @import("std"); const std = @import("std");
const mem = std.mem;
const Compilation = @import("Compilation.zig"); const Compilation = @import("Compilation.zig");
const Type = @import("Type.zig");
const Diagnostics = @import("Diagnostics.zig"); const Diagnostics = @import("Diagnostics.zig");
const Tokenizer = @import("Tokenizer.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) { pub const Item = union(enum) {
/// decoded hex or character escape /// decoded hex or character escape
@ -18,11 +20,6 @@ pub const Item = union(enum) {
utf8_text: std.unicode.Utf8View, utf8_text: std.unicode.Utf8View,
}; };
const CharDiagnostic = struct {
tag: Diagnostics.Tag,
extra: Diagnostics.Message.Extra,
};
pub const Kind = enum { pub const Kind = enum {
char, char,
wide, wide,
@ -91,13 +88,13 @@ pub const Kind = enum {
} }
/// The C type of a character literal of this kind /// 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) { return switch (kind) {
.char => Type.int, .char => .int,
.wide => comp.types.wchar, .wide => comp.type_store.wchar,
.utf_8 => .{ .specifier = .uchar }, .utf_8 => .uchar,
.utf_16 => comp.types.uint_least16_t, .utf_16 => comp.type_store.uint_least16_t,
.utf_32 => comp.types.uint_least32_t, .utf_32 => comp.type_store.uint_least32_t,
.unterminated => unreachable, .unterminated => unreachable,
}; };
} }
@ -120,7 +117,7 @@ pub const Kind = enum {
pub fn charUnitSize(kind: Kind, comp: *const Compilation) Compilation.CharUnitSize { pub fn charUnitSize(kind: Kind, comp: *const Compilation) Compilation.CharUnitSize {
return switch (kind) { return switch (kind) {
.char => .@"1", .char => .@"1",
.wide => switch (comp.types.wchar.sizeof(comp).?) { .wide => switch (comp.type_store.wchar.sizeof(comp)) {
2 => .@"2", 2 => .@"2",
4 => .@"4", 4 => .@"4",
else => unreachable, else => unreachable,
@ -140,37 +137,52 @@ pub const Kind = enum {
} }
/// The C type of an element of a string literal of this kind /// 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) { return switch (kind) {
.unterminated => unreachable, .unterminated => unreachable,
.char => .{ .specifier = .char }, .char => .char,
.utf_8 => if (comp.langopts.hasChar8_T()) .{ .specifier = .uchar } else .{ .specifier = .char }, .utf_8 => if (comp.langopts.hasChar8_T()) .uchar else .char,
else => kind.charLiteralType(comp), 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 { pub const Parser = struct {
comp: *const Compilation,
literal: []const u8, literal: []const u8,
i: usize = 0, i: usize = 0,
kind: Kind, kind: Kind,
max_codepoint: u21, 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 /// We only want to issue a max of 1 error per char literal
errored: bool = false, errored: bool = false,
errors_buffer: [4]CharDiagnostic, /// Makes incorrect encoding always an error.
errors_len: usize, /// Used when concatenating string literals.
comp: *const Compilation, incorrect_encoding_is_error: bool = false,
/// If this is false, do not issue any diagnostics for incorrect character encoding
pub fn init(literal: []const u8, kind: Kind, max_codepoint: u21, comp: *const Compilation) Parser { /// Incorrect encoding is allowed if we are unescaping an identifier in the preprocessor
return .{ diagnose_incorrect_encoding: bool = true,
.literal = literal,
.comp = comp,
.kind = kind,
.max_codepoint = max_codepoint,
.errors_buffer = undefined,
.errors_len = 0,
};
}
fn prefixLen(self: *const Parser) usize { fn prefixLen(self: *const Parser) usize {
return switch (self.kind) { return switch (self.kind) {
@ -181,65 +193,204 @@ pub const Parser = struct {
}; };
} }
pub fn errors(p: *Parser) []CharDiagnostic { const Diagnostic = struct {
return p.errors_buffer[0..p.errors_len]; 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 { pub fn warn(p: *Parser, diagnostic: Diagnostic, args: anytype) Compilation.Error!void {
if (self.errored) return; defer p.offset = 0;
self.errored = true; if (p.errored) return;
const diagnostic: CharDiagnostic = .{ .tag = tag, .extra = extra }; if (p.comp.diagnostics.effectiveKind(diagnostic) == .off) return;
if (self.errors_len == self.errors_buffer.len) {
self.errors_buffer[self.errors_buffer.len - 1] = diagnostic; var sf = std.heap.stackFallback(1024, p.comp.gpa);
} else { var allocating: std.Io.Writer.Allocating = .init(sf.get());
self.errors_buffer[self.errors_len] = diagnostic; defer allocating.deinit();
self.errors_len += 1;
} 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);
} }
pub fn warn(self: *Parser, tag: Diagnostics.Tag, extra: Diagnostics.Message.Extra) void { fn formatArgs(w: *std.Io.Writer, fmt: []const u8, args: anytype) !void {
if (self.errored) return; var i: usize = 0;
if (self.errors_len < self.errors_buffer.len) { inline for (std.meta.fields(@TypeOf(args))) |arg_info| {
self.errors_buffer[self.errors_len] = .{ .tag = tag, .extra = extra }; const arg = @field(args, arg_info.name);
self.errors_len += 1; 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 next(self: *Parser) ?Item { pub fn next(p: *Parser) !?Item {
if (self.i >= self.literal.len) return null; if (p.i >= p.literal.len) return null;
const start = self.i; const start = p.i;
if (self.literal[start] != '\\') { if (p.literal[start] != '\\') {
self.i = mem.indexOfScalarPos(u8, self.literal, start + 1, '\\') orelse self.literal.len; p.i = mem.indexOfScalarPos(u8, p.literal, start + 1, '\\') orelse p.literal.len;
const unescaped_slice = self.literal[start..self.i]; const unescaped_slice = p.literal[start..p.i];
const view = std.unicode.Utf8View.init(unescaped_slice) catch { const view = std.unicode.Utf8View.init(unescaped_slice) catch {
if (self.kind != .char) { if (!p.diagnose_incorrect_encoding) {
self.err(.illegal_char_encoding_error, .{ .none = {} }); 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; return null;
} }
self.warn(.illegal_char_encoding_warning, .{ .none = {} }); try p.warn(.illegal_char_encoding_warning, .{});
return .{ .improperly_encoded = self.literal[start..self.i] }; return .{ .improperly_encoded = p.literal[start..p.i] };
}; };
return .{ .utf8_text = view }; return .{ .utf8_text = view };
} }
switch (self.literal[start + 1]) { switch (p.literal[start + 1]) {
'u', 'U' => return self.parseUnicodeEscape(), 'u', 'U' => return try p.parseUnicodeEscape(),
else => return self.parseEscapedChar(), else => return try p.parseEscapedChar(),
} }
} }
fn parseUnicodeEscape(self: *Parser) ?Item { fn parseUnicodeEscape(p: *Parser) !?Item {
const start = self.i; 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'); std.debug.assert(kind == 'u' or kind == 'U');
self.i += 2; p.i += 2;
if (self.i >= self.literal.len or !std.ascii.isHex(self.literal[self.i])) { if (p.i >= p.literal.len or !std.ascii.isHex(p.literal[p.i])) {
self.err(.missing_hex_escape, .{ .ascii = @intCast(kind) }); try p.err(.missing_hex_escape, .{Ascii.init(kind)});
return null; return null;
} }
const expected_len: usize = if (kind == 'u') 4 else 8; const expected_len: usize = if (kind == 'u') 4 else 8;
@ -247,66 +398,66 @@ pub const Parser = struct {
var count: usize = 0; var count: usize = 0;
var val: u32 = 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; if (i == expected_len) break;
const char = std.fmt.charToDigit(c, 16) catch { const char = std.fmt.charToDigit(c, 16) catch break;
break;
};
val, const overflow = @shlWithOverflow(val, 4); val, const overflow = @shlWithOverflow(val, 4);
overflowed = overflowed or overflow != 0; overflowed = overflowed or overflow != 0;
val |= char; val |= char;
count += 1; count += 1;
} }
self.i += expected_len; p.i += expected_len;
if (overflowed) { 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; return null;
} }
if (count != expected_len) { if (count != expected_len) {
self.err(.incomplete_universal_character, .{ .none = {} }); try p.err(.incomplete_universal_character, .{});
return null; return null;
} }
if (val > std.math.maxInt(u21) or !std.unicode.utf8ValidCodepoint(@intCast(val))) { 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; return null;
} }
if (val > self.max_codepoint) { if (val > p.max_codepoint) {
self.err(.char_too_large, .{ .none = {} }); try p.err(.char_too_large, .{});
return null; return null;
} }
if (val < 0xA0 and (val != '$' and val != '@' and val != '`')) { 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 (val >= 0x20 and val <= 0x7F) {
if (is_error) { if (is_error) {
self.err(.ucn_basic_char_error, .{ .ascii = @intCast(val) }); try p.err(.ucn_basic_char_error, .{Ascii.init(val)});
} else { } else if (!p.comp.langopts.standard.atLeast(.c23)) {
self.warn(.ucn_basic_char_warning, .{ .ascii = @intCast(val) }); try p.warn(.ucn_basic_char_warning, .{Ascii.init(val)});
} }
} else { } else {
if (is_error) { if (is_error) {
self.err(.ucn_control_char_error, .{ .none = {} }); try p.err(.ucn_control_char_error, .{});
} else { } else if (!p.comp.langopts.standard.atLeast(.c23)) {
self.warn(.ucn_control_char_warning, .{ .none = {} }); 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) }; return .{ .codepoint = @intCast(val) };
} }
fn parseEscapedChar(self: *Parser) Item { fn parseEscapedChar(p: *Parser) !Item {
self.i += 1; p.i += 1;
const c = self.literal[self.i]; const c = p.literal[p.i];
defer if (c != 'x' and (c < '0' or c > '7')) { defer if (c != 'x' and (c < '0' or c > '7')) {
self.i += 1; p.i += 1;
}; };
switch (c) { switch (c) {
@ -319,36 +470,40 @@ pub const Parser = struct {
'a' => return .{ .value = 0x07 }, 'a' => return .{ .value = 0x07 },
'b' => return .{ .value = 0x08 }, 'b' => return .{ .value = 0x08 },
'e', 'E' => { '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 }; 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 }; return .{ .value = c };
}, },
'f' => return .{ .value = 0x0C }, 'f' => return .{ .value = 0x0C },
'v' => return .{ .value = 0x0B }, 'v' => return .{ .value = 0x0B },
'x' => return .{ .value = self.parseNumberEscape(.hex) }, 'x' => return .{ .value = try p.parseNumberEscape(.hex) },
'0'...'7' => return .{ .value = self.parseNumberEscape(.octal) }, '0'...'7' => return .{ .value = try p.parseNumberEscape(.octal) },
'u', 'U' => unreachable, // handled by parseUnicodeEscape 'u', 'U' => unreachable, // handled by parseUnicodeEscape
else => { 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 }; return .{ .value = c };
}, },
} }
} }
fn parseNumberEscape(self: *Parser, base: EscapeBase) u32 { fn parseNumberEscape(p: *Parser, base: EscapeBase) !u32 {
var val: u32 = 0; var val: u32 = 0;
var count: usize = 0; var count: usize = 0;
var overflowed = false; var overflowed = false;
const start = self.i; const start = p.i;
defer self.i += count; defer p.i += count;
const slice = switch (base) { 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: { .hex => blk: {
self.i += 1; p.i += 1;
break :blk self.literal[self.i..]; // skip over 'x'; could have an arbitrary number of chars break :blk p.literal[p.i..]; // skip over 'x'; could have an arbitrary number of chars
}, },
}; };
for (slice) |c| { for (slice) |c| {
@ -358,13 +513,14 @@ pub const Parser = struct {
val += char; val += char;
count += 1; count += 1;
} }
if (overflowed or val > self.kind.maxInt(self.comp)) { if (overflowed or val > p.kind.maxInt(p.comp)) {
self.err(.escape_sequence_overflow, .{ .offset = start + self.prefixLen() }); p.offset += @intCast(start + p.prefixLen());
try p.err(.escape_sequence_overflow, .{});
return 0; return 0;
} }
if (count == 0) { if (count == 0) {
std.debug.assert(base == .hex); std.debug.assert(base == .hex);
self.err(.missing_hex_escape, .{ .ascii = 'x' }); try p.err(.missing_hex_escape, .{Ascii.init('x')});
} }
return val; 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 Interner = @import("backend/Interner.zig");
pub const Ir = @import("backend/Ir.zig"); pub const Ir = @import("backend/Ir.zig");
pub const Object = @import("backend/Object.zig"); pub const Object = @import("backend/Object.zig");
pub const CallingConvention = enum { pub const CallingConvention = enum {
C, c,
stdcall, stdcall,
thiscall, thiscall,
vectorcall, 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"; 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 { items: std.MultiArrayList(struct {
tag: Tag, tag: Tag,
data: u32, data: u32,
}) = .{}, }) = .empty,
extra: std.ArrayListUnmanaged(u32) = .empty, extra: std.ArrayList(u32) = .empty,
limbs: std.ArrayListUnmanaged(Limb) = .empty, limbs: std.ArrayList(Limb) = .empty,
strings: std.ArrayListUnmanaged(u8) = .empty, strings: std.ArrayList(u8) = .empty,
const KeyAdapter = struct { const KeyAdapter = struct {
interner: *const Interner, interner: *const Interner,
@ -65,6 +65,7 @@ pub const Key = union(enum) {
float: Float, float: Float,
complex: Complex, complex: Complex,
bytes: []const u8, bytes: []const u8,
pointer: Pointer,
pub const Float = union(enum) { pub const Float = union(enum) {
f16: f16, f16: f16,
@ -80,6 +81,12 @@ pub const Key = union(enum) {
cf80: [2]f80, cf80: [2]f80,
cf128: [2]f128, 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 { pub fn hash(key: Key) u32 {
var hasher = Hash.init(0); var hasher = Hash.init(0);
@ -199,6 +206,10 @@ pub const Key = union(enum) {
} }
return null; return null;
} }
pub fn toBigInt(key: Key, space: *Tag.Int.BigIntSpace) BigIntConst {
return key.int.toBigInt(space);
}
}; };
pub const Ref = enum(u32) { pub const Ref = enum(u32) {
@ -303,6 +314,8 @@ pub const Tag = enum(u8) {
bytes, bytes,
/// `data` is `Record` /// `data` is `Record`
record_ty, record_ty,
/// `data` is Pointer
pointer,
pub const Array = struct { pub const Array = struct {
len0: u32, len0: u32,
@ -322,6 +335,11 @@ pub const Tag = enum(u8) {
child: Ref, child: Ref,
}; };
pub const Pointer = struct {
node: u32,
offset: Ref,
};
pub const Int = struct { pub const Int = struct {
limbs_index: u32, limbs_index: u32,
limbs_len: 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: { .int => |repr| int: {
var space: Tag.Int.BigIntSpace = undefined; var space: Tag.Int.BigIntSpace = undefined;
const big = repr.toBigInt(&space); const big = repr.toBigInt(&space);
@ -792,6 +819,13 @@ pub fn get(i: *const Interner, ref: Ref) Key {
.child = vector_ty.child, .child = vector_ty.child,
} }; } };
}, },
.pointer => {
const pointer = i.extraData(Tag.Pointer, data);
return .{ .pointer = .{
.node = pointer.node,
.offset = pointer.offset,
} };
},
.u32 => .{ .int = .{ .u64 = data } }, .u32 => .{ .int = .{ .u64 = data } },
.i32 => .{ .int = .{ .i64 = @as(i32, @bitCast(data)) } }, .i32 => .{ .int = .{ .i64 = @as(i32, @bitCast(data)) } },
.int_positive, .int_negative => { .int_positive, .int_negative => {

View File

@ -11,7 +11,7 @@ decls: std.StringArrayHashMapUnmanaged(Decl),
pub const Decl = struct { pub const Decl = struct {
instructions: std.MultiArrayList(Inst), instructions: std.MultiArrayList(Inst),
body: std.ArrayListUnmanaged(Ref), body: std.ArrayList(Ref),
arena: std.heap.ArenaAllocator.State, arena: std.heap.ArenaAllocator.State,
pub fn deinit(decl: *Decl, gpa: Allocator) void { pub fn deinit(decl: *Decl, gpa: Allocator) void {
@ -27,8 +27,8 @@ pub const Builder = struct {
interner: *Interner, interner: *Interner,
decls: std.StringArrayHashMapUnmanaged(Decl) = .empty, decls: std.StringArrayHashMapUnmanaged(Decl) = .empty,
instructions: std.MultiArrayList(Ir.Inst) = .{}, instructions: std.MultiArrayList(Ir.Inst) = .empty,
body: std.ArrayListUnmanaged(Ref) = .empty, body: std.ArrayList(Ref) = .empty,
alloc_count: u32 = 0, alloc_count: u32 = 0,
arg_count: u32 = 0, arg_count: u32 = 0,
current_label: Ref = undefined, 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 LITERAL = std.Io.tty.Color.bright_green;
const ATTRIBUTE = std.Io.tty.Color.bright_yellow; 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| { for (ir.decls.keys(), ir.decls.values()) |name, *decl| {
try ir.dumpDecl(decl, gpa, name, config, w); 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 tags = decl.instructions.items(.tag);
const data = decl.instructions.items(.data); const data = decl.instructions.items(.data);
var ref_map = RefMap.init(gpa); var ref_map: RefMap = .empty;
defer ref_map.deinit(); defer ref_map.deinit(gpa);
var label_map = RefMap.init(gpa); var label_map: RefMap = .empty;
defer label_map.deinit(); defer label_map.deinit(gpa);
const ret_inst = decl.body.items[decl.body.items.len - 1]; const ret_inst = decl.body.items[decl.body.items.len - 1];
const ret_operand = data[@intFromEnum(ret_inst)].un; 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]; const ref = decl.body.items[arg_count];
if (tags[@intFromEnum(ref)] != .arg) break; if (tags[@intFromEnum(ref)] != .arg) break;
if (arg_count != 0) try w.writeAll(", "); 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 ir.writeRef(decl, &ref_map, ref, config, w);
try config.setColor(w, .reset); try config.setColor(w, .reset);
} }
try w.writeAll(") {\n"); try w.writeAll(") {\n");
for (decl.body.items[arg_count..]) |ref| { for (decl.body.items[arg_count..]) |ref| {
switch (tags[@intFromEnum(ref)]) { switch (tags[@intFromEnum(ref)]) {
.label => try label_map.put(ref, {}), .label => try label_map.put(gpa, ref, {}),
else => {}, else => {},
} }
} }
@ -460,7 +461,7 @@ fn dumpDecl(ir: *const Ir, decl: *const Decl, gpa: Allocator, name: []const u8,
}, },
.select => { .select => {
const br = data[i].branch; 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 w.writeAll("select ");
try ir.writeRef(decl, &ref_map, br.cond, config, w); try ir.writeRef(decl, &ref_map, br.cond, config, w);
try config.setColor(w, .reset); try config.setColor(w, .reset);
@ -500,7 +501,7 @@ fn dumpDecl(ir: *const Ir, decl: *const Decl, gpa: Allocator, name: []const u8,
}, },
.call => { .call => {
const call = data[i].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 w.writeAll("call ");
try ir.writeRef(decl, &ref_map, call.func, config, w); try ir.writeRef(decl, &ref_map, call.func, config, w);
try config.setColor(w, .reset); try config.setColor(w, .reset);
@ -514,7 +515,7 @@ fn dumpDecl(ir: *const Ir, decl: *const Decl, gpa: Allocator, name: []const u8,
}, },
.alloc => { .alloc => {
const alloc = data[i].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 w.writeAll("alloc ");
try config.setColor(w, ATTRIBUTE); try config.setColor(w, ATTRIBUTE);
try w.writeAll("size "); 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'); try w.writeByte('\n');
}, },
.phi => { .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 w.writeAll("phi");
try config.setColor(w, .reset); try config.setColor(w, .reset);
try w.writeAll(" {"); try w.writeAll(" {");
@ -559,7 +560,7 @@ fn dumpDecl(ir: *const Ir, decl: *const Decl, gpa: Allocator, name: []const u8,
try w.writeByte('\n'); try w.writeByte('\n');
}, },
.load => { .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 w.writeAll("load ");
try ir.writeRef(decl, &ref_map, data[i].un, config, w); try ir.writeRef(decl, &ref_map, data[i].un, config, w);
try w.writeByte('\n'); try w.writeByte('\n');
@ -582,7 +583,7 @@ fn dumpDecl(ir: *const Ir, decl: *const Decl, gpa: Allocator, name: []const u8,
.mod, .mod,
=> { => {
const bin = data[i].bin; 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 w.print("{s} ", .{@tagName(tag)});
try ir.writeRef(decl, &ref_map, bin.lhs, config, w); try ir.writeRef(decl, &ref_map, bin.lhs, config, w);
try config.setColor(w, .reset); try config.setColor(w, .reset);
@ -597,7 +598,7 @@ fn dumpDecl(ir: *const Ir, decl: *const Decl, gpa: Allocator, name: []const u8,
.sext, .sext,
=> { => {
const un = data[i].un; 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 w.print("{s} ", .{@tagName(tag)});
try ir.writeRef(decl, &ref_map, un, config, w); try ir.writeRef(decl, &ref_map, un, config, w);
try w.writeByte('\n'); 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"); 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); const ty = ir.interner.get(ty_ref);
try config.setColor(w, TYPE); try config.setColor(w, TYPE);
switch (ty) { 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); try config.setColor(w, LITERAL);
const key = ir.interner.get(val); const key = ir.interner.get(val);
switch (key) { switch (key) {
@ -650,12 +651,12 @@ fn writeValue(ir: Ir, val: Interner.Ref, config: std.Io.tty.Config, w: anytype)
.float => |repr| switch (repr) { .float => |repr| switch (repr) {
inline else => |x| return w.print("{d}", .{@as(f64, @floatCast(x))}), 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 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); assert(ref != .none);
const index = @intFromEnum(ref); const index = @intFromEnum(ref);
const ty_ref = decl.instructions.items(.ty)[index]; 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}); 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 { 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(ref, {}); try ref_map.put(gpa, ref, {});
try w.writeAll(" "); try w.writeAll(" ");
try ir.writeRef(decl, ref_map, ref, config, w); try ir.writeRef(decl, ref_map, ref, config, w);
try config.setColor(w, .reset); 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); 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); assert(ref != .none);
const index = @intFromEnum(ref); const index = @intFromEnum(ref);
const label = decl.instructions.items(.data)[index].label; const label = decl.instructions.items(.data)[index].label;

View File

@ -30,7 +30,7 @@ pub const Section = union(enum) {
custom: []const u8, 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) { switch (obj.format) {
.elf => return @as(*Elf, @alignCast(@fieldParentPtr("obj", obj))).getSection(section), .elf => return @as(*Elf, @alignCast(@fieldParentPtr("obj", obj))).getSection(section),
else => unreachable, 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) { 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, else => unreachable,
} }
} }

View File

@ -4,8 +4,8 @@ const Target = std.Target;
const Object = @import("../Object.zig"); const Object = @import("../Object.zig");
const Section = struct { const Section = struct {
data: std.array_list.Managed(u8), data: std.ArrayList(u8) = .empty,
relocations: std.ArrayListUnmanaged(Relocation) = .empty, relocations: std.ArrayList(Relocation) = .empty,
flags: u64, flags: u64,
type: u32, type: u32,
index: u16 = undefined, index: u16 = undefined,
@ -58,7 +58,7 @@ pub fn deinit(elf: *Elf) void {
{ {
var it = elf.sections.valueIterator(); var it = elf.sections.valueIterator();
while (it.next()) |sect| { while (it.next()) |sect| {
sect.*.data.deinit(); sect.*.data.deinit(gpa);
sect.*.relocations.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_name = sectionString(section_kind);
const section = elf.sections.get(section_name) orelse blk: { const section = elf.sections.get(section_name) orelse blk: {
const section = try elf.arena.allocator().create(Section); const section = try elf.arena.allocator().create(Section);
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, .type = std.elf.SHT_PROGBITS,
.flags = switch (section_kind) { .flags = switch (section_kind) {
.func, .custom => std.elf.SHF_ALLOC + std.elf.SHF_EXECINSTR, .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 /// relocations
/// strtab /// strtab
/// section headers /// section headers
pub fn finish(elf: *Elf, file: std.fs.File) !void { pub fn finish(elf: *Elf, w: *std.Io.Writer) !void {
var file_buffer: [1024]u8 = undefined; var num_sections: std.elf.Half = additional_sections;
var file_writer = file.writer(&file_buffer);
const w = &file_writer.interface;
var num_sections: std.elf.Elf64_Half = additional_sections;
var relocations_len: std.elf.Elf64_Off = 0; var relocations_len: std.elf.Elf64_Off = 0;
var sections_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 strtab_offset = rela_offset + relocations_len;
const sh_offset = strtab_offset + elf.strtab_len; const sh_offset = strtab_offset + elf.strtab_len;
const sh_offset_aligned = std.mem.alignForward(u64, sh_offset, 16); 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_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_type = std.elf.ET.REL, // we only produce relocatables
.e_machine = elf.obj.target.toElfMachine(), .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_shnum = num_sections,
.e_shstrndx = strtab_index, .e_shstrndx = strtab_index,
}; };
try w.writeStruct(elf_header); try w.writeStruct(elf_header, endian);
// write contents of sections // write contents of sections
{ {
@ -222,13 +219,13 @@ pub fn finish(elf: *Elf, file: std.fs.File) !void {
} }
// pad to 8 bytes // 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; var name_offset: u32 = strtab_default.len;
// write symbols // write symbols
{ {
// first symbol must be null // 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 sym_index: u16 = 1;
var it = elf.local_symbols.iterator(); 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_shndx = if (sym.section) |some| some.index else 0,
.st_value = sym.offset, .st_value = sym.offset,
.st_size = sym.size, .st_size = sym.size,
}); }, endian);
sym.index = sym_index; sym.index = sym_index;
sym_index += 1; sym_index += 1;
name_offset += @intCast(entry.key_ptr.len + 1); // +1 for null byte 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_shndx = if (sym.section) |some| some.index else 0,
.st_value = sym.offset, .st_value = sym.offset,
.st_size = sym.size, .st_size = sym.size,
}); }, endian);
sym.index = sym_index; sym.index = sym_index;
sym_index += 1; sym_index += 1;
name_offset += @intCast(entry.key_ptr.len + 1); // +1 for null byte 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_offset = rela.offset,
.r_addend = rela.addend, .r_addend = rela.addend,
.r_info = (@as(u64, rela.symbol.index) << 32) | rela.type, .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 // 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 // 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 // write strtab section header
{ {
const sect_header = std.elf.Elf64_Shdr{ const sect_header: std.elf.Elf64_Shdr = .{
.sh_name = strtab_name, .sh_name = strtab_name,
.sh_type = std.elf.SHT_STRTAB, .sh_type = std.elf.SHT_STRTAB,
.sh_flags = 0, .sh_flags = 0,
@ -312,12 +309,12 @@ pub fn finish(elf: *Elf, file: std.fs.File) !void {
.sh_addralign = 1, .sh_addralign = 1,
.sh_entsize = 0, .sh_entsize = 0,
}; };
try w.writeStruct(sect_header); try w.writeStruct(sect_header, endian);
} }
// write symtab section header // write symtab section header
{ {
const sect_header = std.elf.Elf64_Shdr{ const sect_header: std.elf.Elf64_Shdr = .{
.sh_name = symtab_name, .sh_name = symtab_name,
.sh_type = std.elf.SHT_SYMTAB, .sh_type = std.elf.SHT_SYMTAB,
.sh_flags = 0, .sh_flags = 0,
@ -329,7 +326,7 @@ pub fn finish(elf: *Elf, file: std.fs.File) !void {
.sh_addralign = 8, .sh_addralign = 8,
.sh_entsize = @sizeOf(std.elf.Elf64_Sym), .sh_entsize = @sizeOf(std.elf.Elf64_Sym),
}; };
try w.writeStruct(sect_header); try w.writeStruct(sect_header, endian);
} }
// remaining section headers // remaining section headers
@ -352,7 +349,7 @@ pub fn finish(elf: *Elf, file: std.fs.File) !void {
.sh_info = 0, .sh_info = 0,
.sh_addralign = if (sect.flags & std.elf.SHF_EXECINSTR != 0) 16 else 1, .sh_addralign = if (sect.flags & std.elf.SHF_EXECINSTR != 0) 16 else 1,
.sh_entsize = 0, .sh_entsize = 0,
}); }, endian);
if (rela_count != 0) { if (rela_count != 0) {
const size = rela_count * @sizeOf(std.elf.Elf64_Rela); 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_info = sect.index,
.sh_addralign = 8, .sh_addralign = 8,
.sh_entsize = @sizeOf(std.elf.Elf64_Rela), .sh_entsize = @sizeOf(std.elf.Elf64_Rela),
}); }, endian);
rela_sect_offset += size; 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(); defer aro_arena_state.deinit();
const aro_arena = aro_arena_state.allocator(); 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(); defer comp.deinit();
var argv: std.ArrayList([]const u8) = .empty; 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) { preprocess.preprocess(&comp, &preprocessed_buf.writer, argv.items, maybe_dependencies) catch |err| switch (err) {
error.GeneratedSourceError => { 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); std.process.exit(1);
}, },
// ArgError can occur if e.g. the .rc file is not found // ArgError can occur if e.g. the .rc file is not found
error.ArgError, error.PreprocessError => { 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); std.process.exit(1);
}, },
error.StreamTooLong => { error.FileTooBig => {
try error_handler.emitMessage(allocator, .err, "failed during preprocessing: maximum file size exceeded", .{}); try error_handler.emitMessage(allocator, .err, "failed during preprocessing: maximum file size exceeded", .{});
std.process.exit(1); 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, error.OutOfMemory => |e| return e,
}; };
@ -660,11 +677,10 @@ const ErrorHandler = union(enum) {
try server.serveErrorBundle(error_bundle); try server.serveErrorBundle(error_bundle);
}, },
.tty => { .tty => {
// extra newline to separate this line from the aro errors // aro errors have already been emitted
const stderr = std.debug.lockStderrWriter(&.{}); const stderr = std.debug.lockStderrWriter(&.{});
defer std.debug.unlockStderrWriter(); defer std.debug.unlockStderrWriter();
try renderErrorMessage(stderr, self.tty, .err, "{s}\n", .{fail_msg}); try renderErrorMessage(stderr, self.tty, .err, "{s}", .{fail_msg});
aro.Diagnostics.render(comp, self.tty);
}, },
} }
} }
@ -883,12 +899,10 @@ fn aroDiagnosticsToErrorBundle(
.msg = try bundle.addString(fail_msg), .msg = try bundle.addString(fail_msg),
}); });
var msg_writer = MsgWriter.init(gpa);
defer msg_writer.deinit();
var cur_err: ?ErrorBundle.ErrorMessage = null; var cur_err: ?ErrorBundle.ErrorMessage = null;
var cur_notes: std.ArrayList(ErrorBundle.ErrorMessage) = .empty; var cur_notes: std.ArrayList(ErrorBundle.ErrorMessage) = .empty;
defer cur_notes.deinit(gpa); defer cur_notes.deinit(gpa);
for (comp.diagnostics.list.items) |msg| { for (comp.diagnostics.output.to_list.messages.items) |msg| {
switch (msg.kind) { switch (msg.kind) {
// Clear the current error so that notes don't bleed into unassociated errors // Clear the current error so that notes don't bleed into unassociated errors
.off, .warning => { .off, .warning => {
@ -897,28 +911,19 @@ fn aroDiagnosticsToErrorBundle(
}, },
.note => if (cur_err == null) continue, .note => if (cur_err == null) continue,
.@"fatal error", .@"error" => {}, .@"fatal error", .@"error" => {},
.default => unreachable,
} }
msg_writer.resetRetainingCapacity();
aro.Diagnostics.renderMessage(comp, &msg_writer, msg);
const src_loc = src_loc: { const src_loc = src_loc: {
if (msg_writer.path) |src_path| { if (msg.location) |location| {
var src_loc: ErrorBundle.SourceLocation = .{ break :src_loc try bundle.addSourceLocation(.{
.src_path = try bundle.addString(src_path), .src_path = try bundle.addString(location.path),
.line = msg_writer.line - 1, // 1-based -> 0-based .line = location.line_no - 1, // 1-based -> 0-based
.column = msg_writer.col - 1, // 1-based -> 0-based .column = location.col - 1, // 1-based -> 0-based
.span_start = 0, .span_start = location.width,
.span_main = 0, .span_main = location.width,
.span_end = 0, .span_end = location.width,
}; .source_line = try bundle.addString(location.line),
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);
} }
break :src_loc ErrorBundle.SourceLocationIndex.none; break :src_loc ErrorBundle.SourceLocationIndex.none;
}; };
@ -929,7 +934,7 @@ fn aroDiagnosticsToErrorBundle(
try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items); try flushErrorMessageIntoBundle(&bundle, err, cur_notes.items);
} }
cur_err = .{ cur_err = .{
.msg = try bundle.addString(msg_writer.buf.items), .msg = try bundle.addString(msg.text),
.src_loc = src_loc, .src_loc = src_loc,
}; };
cur_notes.clearRetainingCapacity(); cur_notes.clearRetainingCapacity();
@ -937,11 +942,11 @@ fn aroDiagnosticsToErrorBundle(
.note => { .note => {
cur_err.?.notes_len += 1; cur_err.?.notes_len += 1;
try cur_notes.append(gpa, .{ try cur_notes.append(gpa, .{
.msg = try bundle.addString(msg_writer.buf.items), .msg = try bundle.addString(msg.text),
.src_loc = src_loc, .src_loc = src_loc,
}); });
}, },
.off, .warning, .default => unreachable, .off, .warning => unreachable,
} }
} }
if (cur_err) |err| { if (cur_err) |err| {
@ -950,63 +955,3 @@ fn aroDiagnosticsToErrorBundle(
return try bundle.toOwnedBundle(""); 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 Dependencies = @import("compile.zig").Dependencies;
const aro = @import("aro"); const aro = @import("aro");
const PreprocessError = error{ ArgError, GeneratedSourceError, PreprocessError, StreamTooLong, OutOfMemory }; const PreprocessError = error{ ArgError, GeneratedSourceError, PreprocessError, FileTooBig, OutOfMemory, WriteFailed };
pub fn preprocess( pub fn preprocess(
comp: *aro.Compilation, comp: *aro.Compilation,
@ -16,18 +16,18 @@ pub fn preprocess(
) PreprocessError!void { ) PreprocessError!void {
try comp.addDefaultPragmaHandlers(); 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(); defer driver.deinit();
var macro_buf: std.Io.Writer.Allocating = .init(comp.gpa); var macro_buf: std.ArrayListUnmanaged(u8) = .empty;
defer macro_buf.deinit(); defer macro_buf.deinit(comp.gpa);
var trash: [64]u8 = undefined; var discard_buffer: [64]u8 = undefined;
var discarding: std.Io.Writer.Discarding = .init(&trash); var discarding: std.Io.Writer.Discarding = .init(&discard_buffer);
_ = driver.parseArgs(&discarding.writer, &macro_buf.writer, argv) catch |err| switch (err) { _ = driver.parseArgs(&discarding.writer, &macro_buf, argv) catch |err| switch (err) {
error.FatalError => return error.ArgError, error.FatalError => return error.ArgError,
error.OutOfMemory => |e| return e, error.OutOfMemory => |e| return e,
error.WriteFailed => return error.OutOfMemory, error.WriteFailed => unreachable,
}; };
if (hasAnyErrors(comp)) return error.ArgError; if (hasAnyErrors(comp)) return error.ArgError;
@ -37,7 +37,7 @@ pub fn preprocess(
error.FatalError => return error.GeneratedSourceError, error.FatalError => return error.GeneratedSourceError,
else => |e| return e, 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, error.FatalError => return error.GeneratedSourceError,
else => |e| return e, else => |e| return e,
}; };
@ -46,7 +46,10 @@ pub fn preprocess(
if (hasAnyErrors(comp)) return error.GeneratedSourceError; if (hasAnyErrors(comp)) return error.GeneratedSourceError;
comp.generated_buf.items.len = 0; 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(); defer pp.deinit();
if (comp.langopts.ms_extensions) { if (comp.langopts.ms_extensions) {
@ -79,16 +82,7 @@ pub fn preprocess(
} }
fn hasAnyErrors(comp: *aro.Compilation) bool { fn hasAnyErrors(comp: *aro.Compilation) bool {
// In theory we could just check Diagnostics.errors != 0, but that only return comp.diagnostics.errors != 0;
// 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;
} }
/// `arena` is used for temporary -D argument strings and the INCLUDE environment variable. /// `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", "-E",
"--comments", "--comments",
"-fuse-line-directives", "-fuse-line-directives",
"-fgnuc-version=4.2.1",
"--target=x86_64-windows-msvc", "--target=x86_64-windows-msvc",
"--emulate=msvc", "--emulate=msvc",
"-nostdinc", "-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); assert(self.manifest_file == null);
return self.addDepFileMaybePost(dir, dep_file_basename); return self.addDepFileMaybePost(dir, dep_file_sub_path);
} }
pub const HitError = error{ pub const HitError = error{
@ -1049,14 +1049,14 @@ pub const Manifest = struct {
self.hash.hasher.update(&new_file.bin_digest); 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); 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 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); defer gpa.free(dep_file_contents);
var error_buf: std.ArrayListUnmanaged(u8) = .empty; var error_buf: std.ArrayListUnmanaged(u8) = .empty;
@ -1083,7 +1083,7 @@ pub const Manifest = struct {
}, },
else => |err| { else => |err| {
try err.printError(gpa, &error_buf); 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; return error.InvalidDepFile;
}, },
} }

View File

@ -36,9 +36,10 @@ pub const ParsedCharLiteral = string_literal.ParsedCharLiteral;
pub const parseCharLiteral = string_literal.parseCharLiteral; pub const parseCharLiteral = string_literal.parseCharLiteral;
pub const parseNumberLiteral = number_literal.parseNumberLiteral; pub const parseNumberLiteral = number_literal.parseNumberLiteral;
// Files needed by translate-c. pub const c_translation = struct {
pub const c_builtins = @import("zig/c_builtins.zig"); pub const builtins = @import("zig/c_translation/builtins.zig");
pub const c_translation = @import("zig/c_translation.zig"); pub const helpers = @import("zig/c_translation/helpers.zig");
};
pub const SrcHasher = std.crypto.hash.Blake3; pub const SrcHasher = std.crypto.hash.Blake3;
pub const SrcHash = [16]u8; 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"); const std = @import("std");
pub inline fn __builtin_bswap16(val: u16) u16 { /// Standard C Library bug: The absolute value of the most negative integer remains negative.
return @byteSwap(val); 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); return @byteSwap(val);
} }
pub inline fn __builtin_signbit(val: f64) c_int { pub inline fn bswap32(val: u32) u32 {
return @intFromBool(std.math.signbit(val)); return @byteSwap(val);
}
pub inline fn __builtin_signbitf(val: f32) c_int {
return @intFromBool(std.math.signbit(val));
} }
pub inline fn __builtin_popcount(val: c_uint) c_int { pub inline fn bswap64(val: u64) u64 {
// popcount of a c_uint will never exceed the capacity of a c_int return @byteSwap(val);
@setRuntimeSafety(false);
return @as(c_int, @bitCast(@as(c_uint, @popCount(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. pub inline fn ceilf(val: f32) f32 {
// In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint return @ceil(val);
@setRuntimeSafety(false);
return @as(c_int, @bitCast(@as(c_uint, @ctz(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. pub inline fn ceil(val: f64) f64 {
// In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint 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); @setRuntimeSafety(false);
return @as(c_int, @bitCast(@as(c_uint, @clz(val)))); return @as(c_int, @bitCast(@as(c_uint, @clz(val))));
} }
pub inline fn __builtin_sqrt(val: f64) f64 { pub inline fn constant_p(expr: anytype) c_int {
return @sqrt(val); _ = expr;
} return @intFromBool(false);
pub inline fn __builtin_sqrtf(val: f32) f32 {
return @sqrt(val);
} }
pub inline fn __builtin_sin(val: f64) f64 { pub inline fn cosf(val: f32) f32 {
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 {
return @cos(val); return @cos(val);
} }
pub inline fn __builtin_exp(val: f64) f64 { pub inline fn cos(val: f64) f64 {
return @exp(val); return @cos(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);
} }
// Standard C Library bug: The absolute value of the most negative integer remains negative. /// Returns the number of trailing 0-bits in val, starting at the least significant bit position.
pub inline fn __builtin_abs(val: c_int) c_int { /// In C if `val` is 0, the result is undefined; in zig it's the number of bits in a c_uint
return if (val == std.math.minInt(c_int)) val else @intCast(@abs(val)); 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)); 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)); 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 log10f(val: f32) f32 {
} return @log10(val);
pub inline fn __builtin_fabsf(val: f32) f32 {
return @abs(val);
} }
pub inline fn __builtin_floor(val: f64) f64 { pub inline fn log10(val: f64) f64 {
return @floor(val); return @log10(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 __builtin_strlen(s: [*c]const u8) usize { pub inline fn log2f(val: f32) f32 {
return std.mem.sliceTo(s, 0).len; return @log2(val);
}
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 __builtin_object_size(ptr: ?*const anyopaque, ty: c_int) usize { pub inline fn log2(val: f64) f64 {
_ = ptr; return @log2(val);
// 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 __builtin___memset_chk( pub inline fn logf(val: f32) f32 {
dst: ?*anyopaque, return @log(val);
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 __builtin_memset(dst: ?*anyopaque, val: c_int, len: usize) ?*anyopaque { pub inline fn log(val: f64) f64 {
const dst_cast = @as([*c]u8, @ptrCast(dst)); return @log(val);
@memset(dst_cast[0..len], @as(u8, @bitCast(@as(i8, @truncate(val)))));
return dst;
} }
pub inline fn __builtin___memcpy_chk( pub inline fn memcpy_chk(
noalias dst: ?*anyopaque, noalias dst: ?*anyopaque,
noalias src: ?*const anyopaque, noalias src: ?*const anyopaque,
len: usize, len: usize,
remaining: usize, remaining: usize,
) ?*anyopaque { ) ?*anyopaque {
if (len > remaining) @panic("std.c.builtins.memcpy_chk called with len > remaining"); if (len > remaining) @panic("__builtin___memcpy_chk called with len > remaining");
return __builtin_memcpy(dst, src, len); 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 dst: ?*anyopaque,
noalias src: ?*const anyopaque, noalias src: ?*const anyopaque,
len: usize, len: usize,
@ -188,16 +182,33 @@ pub inline fn __builtin_memcpy(
return dst; return dst;
} }
/// The return value of __builtin_expect is `expr`. `c` is the expected value pub inline fn memset_chk(
/// of `expr` and is used as a hint to the compiler in C. Here it is unused. dst: ?*anyopaque,
pub inline fn __builtin_expect(expr: c_long, c: c_long) c_long { val: c_int,
_ = c; len: usize,
return expr; 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 /// returns a quiet NaN. Quiet NaNs have many representations; tagp is used to select one in an
/// implementation-defined way. /// 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 /// https://gcc.gnu.org/onlinedocs/gcc/Other-Builtins.html#index-_005f_005fbuiltin_005fnan
/// Comment is reproduced below: /// Comment is reproduced below:
/// Since ISO C99 defines this function in terms of strtod, which we do not implement, a description /// 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 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. /// 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 parsed = std.fmt.parseUnsigned(c_ulong, tagp, 0) catch 0;
const bits: u23 = @truncate(parsed); // single-precision float trailing significand is 23 bits 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)))); return @bitCast(@as(u32, bits) | @as(u32, @bitCast(std.math.nan(f32))));
} }
pub inline fn __builtin_huge_valf() f32 { pub inline fn object_size(ptr: ?*const anyopaque, ty: c_int) usize {
return std.math.inf(f32); _ = 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,
pub inline fn __builtin_inff() f32 { // object_size should return (size_t) -1 for type 0 or 1 and (size_t) 0
return std.math.inf(f32); // 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;
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 {
unreachable; unreachable;
} }
pub inline fn __builtin_constant_p(expr: anytype) c_int { /// popcount of a c_uint will never exceed the capacity of a c_int
_ = expr; pub inline fn popcount(val: c_uint) c_int {
return @intFromBool(false); @setRuntimeSafety(false);
} return @as(c_int, @bitCast(@as(c_uint, @popCount(val))));
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];
} }
// __builtin_alloca_with_align is not currently implemented. pub inline fn roundf(val: f32) f32 {
// It is used in a run-translated-c test and a test-translate-c test to ensure that non-implemented return @round(val);
// 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 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. /// 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); dev.check(.translate_c_command);
const tracy_trace = trace(@src()); const cimport_basename = "cimport.h";
defer tracy_trace.end(); const translated_basename = "cimport.zig";
const cimport_zig_basename = "cimport.zig";
var man = comp.obtainCObjectCacheManifest(owner_mod); var man = comp.obtainCObjectCacheManifest(owner_mod);
defer man.deinit(); 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.addBytes(c_src);
man.hash.add(comp.config.c_frontend);
// If the previous invocation resulted in clang errors, we will see a hit const digest, const is_hit = if (try man.hit()) .{ man.finalBin(), true } else digest: {
// 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: {
var arena_allocator = std.heap.ArenaAllocator.init(comp.gpa); var arena_allocator = std.heap.ArenaAllocator.init(comp.gpa);
defer arena_allocator.deinit(); defer arena_allocator.deinit();
const arena = arena_allocator.allocator(); const arena = arena_allocator.allocator();
const tmp_digest = man.hash.peek(); const tmp_basename = std.fmt.hex(std.crypto.random.int(u64));
const tmp_dir_sub_path = try fs.path.join(arena, &[_][]const u8{ "o", &tmp_digest }); const tmp_sub_path = "tmp" ++ fs.path.sep_str ++ tmp_basename;
var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.makeOpenPath(tmp_dir_sub_path, .{}); const cache_dir = comp.dirs.local_cache.handle;
defer zig_cache_tmp_dir.close(); const out_h_sub_path = tmp_sub_path ++ fs.path.sep_str ++ cimport_basename;
const cimport_basename = "cimport.h";
const out_h_path = try comp.dirs.local_cache.join(arena, &[_][]const u8{ try cache_dir.makePath(tmp_sub_path);
tmp_dir_sub_path, cimport_basename,
}); 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}); 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("writing C import source to {s}", .{out_h_path});
if (comp.verbose_cimport) { try cache_dir.writeFile(.{ .sub_path = out_h_sub_path, .data = c_src });
log.info("C import source: {s}", .{out_h_path});
}
var argv = std.array_list.Managed([]const u8).init(comp.gpa); var argv = std.array_list.Managed([]const u8).init(comp.gpa);
defer argv.deinit(); 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 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) { const dep_sub_path = out_h_sub_path ++ ".d";
dump_argv(argv.items); if (comp.verbose_cimport) log.info("processing dep file at {s}", .{dep_sub_path});
} try man.addDepFilePost(cache_dir, dep_sub_path);
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);
switch (comp.cache_use) { switch (comp.cache_use) {
.whole => |whole| if (whole.cache_manifest) |whole_cache_manifest| { .whole => |whole| if (whole.cache_manifest) |whole_cache_manifest| {
whole.cache_manifest_mutex.lock(); whole.cache_manifest_mutex.lock();
defer whole.cache_manifest_mutex.unlock(); 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 => {}, .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 bin_digest = man.finalBin();
const hex_digest = Cache.binToHex(bin_digest); const hex_digest = Cache.binToHex(bin_digest);
const o_sub_path = "o" ++ fs.path.sep_str ++ hex_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, .{}); if (comp.verbose_cimport) log.info("renaming {s} to {s}", .{ tmp_sub_path, o_sub_path });
defer out_zig_file.close(); try renameTmpIntoCache(comp.dirs.local_cache, tmp_sub_path, o_sub_path);
const formatted = try tree.renderAlloc(comp.gpa); break :digest .{ bin_digest, false };
defer comp.gpa.free(formatted); };
try out_zig_file.writeAll(formatted);
break :digest bin_digest;
} else man.finalBin();
if (man.have_exclusive_lock) { if (man.have_exclusive_lock) {
// Write the updated manifest. This is a no-op if the manifest is not dirty. Note that it is // 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, .digest = digest,
.cache_hit = actual_hit, .cache_hit = is_hit,
.errors = std.zig.ErrorBundle.empty, .errors = std.zig.ErrorBundle.empty,
}; };
} }
@ -6747,51 +6685,63 @@ pub fn addTranslateCCArgs(
out_dep_path: ?[]const u8, out_dep_path: ?[]const u8,
owner_mod: *Package.Module, owner_mod: *Package.Module,
) !void { ) !void {
const target = &owner_mod.resolved_target.result;
try argv.appendSlice(&.{ "-x", "c" }); 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. const resource_path = try comp.dirs.zig_lib.join(arena, &.{"compiler/aro/include"});
try argv.appendSlice(&.{ "-Xclang", "-detailed-preprocessing-record" }); 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. /// Add common C compiler args between translate-c and C object compilation.
pub fn addCCArgs( fn addCommonCCArgs(
comp: *const Compilation, comp: *const Compilation,
arena: Allocator, arena: Allocator,
argv: *std.array_list.Managed([]const u8), argv: *std.array_list.Managed([]const u8),
ext: FileExt, ext: FileExt,
out_dep_path: ?[]const u8, out_dep_path: ?[]const u8,
mod: *Package.Module, mod: *Package.Module,
c_frontend: Config.CFrontend,
) !void { ) !void {
const target = &mod.resolved_target.result; 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. if (target_util.supports_fpic(target)) {
// I'm sure the person who implemented this means well, but they have a lot // PIE needs to go before PIC because Clang interprets `-fno-PIE` to imply `-fno-PIC`, which
// to learn about abstractions and where the appropriate boundaries between // we don't necessarily want.
// them are. The road to hell is paved with good intentions. Fortunately it try argv.append(if (comp.config.pie) "-fPIE" else "-fno-PIE");
// can be disabled. try argv.append(if (mod.pic) "-fPIC" else "-fno-PIC");
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 });
switch (target.os.tag) { switch (target.os.tag) {
.ios, .macos, .tvos, .watchos => |os| { .ios, .macos, .tvos, .watchos => |os| if (is_clang) {
try argv.ensureUnusedCapacity(2); try argv.ensureUnusedCapacity(2);
// Pass the proper -m<os>-version-min argument for darwin. // Pass the proper -m<os>-version-min argument for darwin.
const ver = target.os.version_range.semver.min; const ver = target.os.version_range.semver.min;
@ -6815,78 +6765,6 @@ pub fn addCCArgs(
else => {}, 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) { if (comp.mingw_unicode_entry_point) {
try argv.append("-municode"); try argv.append("-municode");
} }
@ -6923,45 +6801,6 @@ pub fn addCCArgs(
if (ext != .assembly) { if (ext != .assembly) {
try argv.append(if (target.os.tag == .freestanding) "-ffreestanding" else "-fhosted"); 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"); try argv.append("-nostdinc");
if (ext == .cpp or ext == .hpp) { if (ext == .cpp or ext == .hpp) {
@ -7085,6 +6924,7 @@ pub fn addCCArgs(
.mm, .mm,
.hmm, .hmm,
=> { => {
if (is_clang) {
try argv.append("-fno-spell-checking"); try argv.append("-fno-spell-checking");
if (target.os.tag == .windows and target.abi.isGnu()) { if (target.os.tag == .windows and target.abi.isGnu()) {
@ -7092,6 +6932,7 @@ pub fn addCCArgs(
// triggering a clang warning. So for this target, we disable this warning. // triggering a clang warning. So for this target, we disable this warning.
try argv.append("-Wno-pragma-pack"); try argv.append("-Wno-pragma-pack");
} }
}
if (mod.optimize_mode != .Debug) { if (mod.optimize_mode != .Debug) {
try argv.append("-Werror=date-time"); try argv.append("-Werror=date-time");
@ -7100,80 +6941,6 @@ pub fn addCCArgs(
else => {}, 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. // Only compiled files support these flags.
switch (ext) { switch (ext) {
.c, .c,
@ -7191,7 +6958,7 @@ pub fn addCCArgs(
try argv.append(try std.fmt.allocPrint(arena, "-mcmodel={s}", .{@tagName(mod.code_model)})); try argv.append(try std.fmt.allocPrint(arena, "-mcmodel={s}", .{@tagName(mod.code_model)}));
} }
{ if (is_clang) {
var san_arg: std.ArrayListUnmanaged(u8) = .empty; var san_arg: std.ArrayListUnmanaged(u8) = .empty;
const prefix = "-fsanitize="; const prefix = "-fsanitize=";
if (mod.sanitize_c != .off) { if (mod.sanitize_c != .off) {
@ -7272,6 +7039,243 @@ pub fn addCCArgs(
}, },
else => {}, 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(comp.global_cc_argv);
try argv.appendSlice(mod.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. /// For debugger access only.
const debug_state = struct { const debug_state = struct {
const enable = switch (builtin.zig_backend) { const enable = false;
else => false,
.stage2_x86_64 => !builtin.strip_debug_info,
};
const enable_checks = enable and !builtin.single_threaded; const enable_checks = enable and !builtin.single_threaded;
threadlocal var intern_pool: ?*const InternPool = null; 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 extra = sema.code.extraData(Zir.Inst.Block, pl_node.payload_index);
const body = sema.code.bodySlice(extra.end, extra.data.body_len); 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); var c_import_buf = std.array_list.Managed(u8).init(gpa);
defer c_import_buf.deinit(); 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); _ = try sema.analyzeInlineBody(&child_block, body, inst);
var c_import_res = comp.cImport(c_import_buf.items, parent_block.ownerModule()) catch |err| const prog_node = zcu.cur_sema_prog_node.start("@cImport", 0);
return sema.fail(&child_block, src, "C import failed: {s}", .{@errorName(err)}); 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); defer c_import_res.deinit(gpa);
if (c_import_res.errors.errorMessageCount() != 0) { 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 target_util = @import("target.zig");
const build_options = @import("build_options"); const build_options = @import("build_options");
const isUpDir = @import("introspect.zig").isUpDir; const isUpDir = @import("introspect.zig").isUpDir;
const clang = @import("clang.zig");
const InternPool = @import("InternPool.zig"); const InternPool = @import("InternPool.zig");
const Alignment = InternPool.Alignment; const Alignment = InternPool.Alignment;
const AnalUnit = InternPool.AnalUnit; 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"); 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(); defer aro_comp.deinit();
aro_comp.target = target.*; aro_comp.target = target.*;
@ -316,28 +320,33 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void {
const builtin_macros = try aro_comp.generateBuiltinMacros(.include_system_defines); const builtin_macros = try aro_comp.generateBuiltinMacros(.include_system_defines);
const def_file_source = try aro_comp.addSourceFromPath(def_file_path); 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(); defer pp.deinit();
pp.linemarkers = .none; pp.linemarkers = .none;
pp.preserve_whitespace = true; pp.preserve_whitespace = true;
try pp.preprocessSources(&.{ def_file_source, builtin_macros }); try pp.preprocessSources(&.{ def_file_source, builtin_macros });
for (aro_comp.diagnostics.list.items) |diagnostic| { if (aro_comp.diagnostics.output.to_list.messages.items.len != 0) {
if (diagnostic.kind == .@"fatal error" or diagnostic.kind == .@"error") { var buffer: [64]u8 = undefined;
aro.Diagnostics.render(&aro_comp, std.Io.tty.detectConfig(std.fs.File.stderr())); 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; return error.AroPreprocessorFailed;
} }
} }
}
{ {
// new scope to ensure definition file is written before passing the path to WriteImportLibrary // new scope to ensure definition file is written before passing the path to WriteImportLibrary
const def_final_file = try o_dir.createFile(final_def_basename, .{ .truncate = true }); const def_final_file = try o_dir.createFile(final_def_basename, .{ .truncate = true });
defer def_final_file.close(); defer def_final_file.close();
var buffer: [1024]u8 = undefined; var buffer: [1024]u8 = undefined;
var def_final_file_writer = def_final_file.writer(&buffer); var file_writer = def_final_file.writer(&buffer);
try pp.prettyPrintTokens(&def_final_file_writer.interface, .result_only); try pp.prettyPrintTokens(&file_writer.interface, .result_only);
try def_final_file_writer.interface.flush(); try file_writer.interface.flush();
} }
const lib_final_path = try std.fs.path.join(gpa, &.{ "o", &digest, final_lib_basename }); 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); 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 { fn mainArgs(gpa: Allocator, arena: Allocator, args: []const []const u8) !void {
const tr = tracy.trace(@src()); const tr = tracy.trace(@src());
defer tr.end(); 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")) { } else if (mem.eql(u8, cmd, "version")) {
dev.check(.version_command); dev.check(.version_command);
try fs.File.stdout().writeAll(build_options.version ++ "\n"); try fs.File.stdout().writeAll(build_options.version ++ "\n");
// Check libc++ linkage to make sure Zig was built correctly, but only return;
// for "env" and "version" to avoid affecting the startup time for
// build-critical commands (check takes about ~10 μs)
return verifyLibcxxCorrectlyLinked();
} else if (mem.eql(u8, cmd, "env")) { } else if (mem.eql(u8, cmd, "env")) {
dev.check(.env_command); dev.check(.env_command);
verifyLibcxxCorrectlyLinked();
var stdout_writer = fs.File.stdout().writer(&stdout_buffer); var stdout_writer = fs.File.stdout().writer(&stdout_buffer);
try @import("print_env.zig").cmdEnv( try @import("print_env.zig").cmdEnv(
arena, arena,
@ -4551,179 +4536,64 @@ fn cmdTranslateC(
prog_node: std.Progress.Node, prog_node: std.Progress.Node,
) !void { ) !void {
dev.check(.translate_c_command); dev.check(.translate_c_command);
_ = file_system_inputs;
_ = fancy_output;
const color: Color = .auto;
assert(comp.c_source_files.len == 1); assert(comp.c_source_files.len == 1);
const c_source_file = comp.c_source_files[0]; const c_source_file = comp.c_source_files[0];
const translated_zig_basename = try std.fmt.allocPrint(arena, "{s}.zig", .{comp.root_name});
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) });
};
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 zig_cache_tmp_dir = try comp.dirs.local_cache.handle.makeOpenPath("tmp", .{}); var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.makeOpenPath("tmp", .{});
defer zig_cache_tmp_dir.close(); defer zig_cache_tmp_dir.close();
const ext = Compilation.classifyFileExt(c_source_file.src_path); const ext = Compilation.classifyFileExt(c_source_file.src_path);
const out_dep_path: ?[]const u8 = blk: { const out_dep_path: ?[]const u8 = blk: {
if (comp.config.c_frontend == .aro or comp.disable_c_depfile or !ext.clangSupportsDepFile()) if (comp.disable_c_depfile) break :blk null;
break :blk null;
const c_src_basename = fs.path.basename(c_source_file.src_path); 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 dep_basename = try std.fmt.allocPrint(arena, "{s}.d", .{c_src_basename});
const out_dep_path = try comp.tmpFilePath(arena, dep_basename); const out_dep_path = try comp.tmpFilePath(arena, dep_basename);
break :blk out_dep_path; break :blk out_dep_path;
}; };
// TODO var argv = std.array_list.Managed([]const u8).init(arena);
if (comp.config.c_frontend != .aro)
try comp.addTranslateCCArgs(arena, &argv, ext, out_dep_path, comp.root_mod); try comp.addTranslateCCArgs(arena, &argv, ext, out_dep_path, comp.root_mod);
try argv.append(c_source_file.src_path); try argv.append(c_source_file.src_path);
if (comp.verbose_cc) Compilation.dump_argv(argv.items);
if (comp.verbose_cc) { try translateC(comp.gpa, arena, argv.items, prog_node, null);
Compilation.dump_argv(argv.items);
}
const Result = union(enum) { if (out_dep_path) |dep_file_path| {
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); const dep_basename = fs.path.basename(dep_file_path);
// Add the files depended on to the cache system. // Add the files depended on to the cache system.
man.addDepFilePost(zig_cache_tmp_dir, dep_basename) catch |err| switch (err) { //man.addDepFilePost(zig_cache_tmp_dir, dep_basename) catch |err| switch (err) {
error.FileNotFound => { // error.FileNotFound => {
// Clang didn't emit the dep file; nothing to add to the manifest. // // Clang didn't emit the dep file; nothing to add to the manifest.
break :add_deps; // break :add_deps;
}, // },
else => |e| return e, // else => |e| return e,
}; //};
// Just to save disk space, we delete the file because it is never needed again. // Just to save disk space, we delete the file because it is never needed again.
zig_cache_tmp_dir.deleteFile(dep_basename) catch |err| { zig_cache_tmp_dir.deleteFile(dep_basename) catch |err| {
warn("failed to delete '{s}': {s}", .{ dep_file_path, @errorName(err) }); warn("failed to delete '{s}': {t}", .{ dep_file_path, 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 = const usage_init =
@ -5682,12 +5552,13 @@ fn jitCmd(
child_argv.appendSliceAssumeCapacity(args); child_argv.appendSliceAssumeCapacity(args);
if (process.can_execv and options.capture == null) { 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 err = process.execv(gpa, child_argv.items);
const cmd = try std.mem.join(arena, " ", child_argv.items); const cmd = try std.mem.join(arena, " ", child_argv.items);
fatal("the following command failed to execve with '{s}':\n{s}", .{ fatal("the following command failed to execve with '{t}':\n{s}", .{ err, cmd });
@errorName(err),
cmd,
});
} }
if (!process.can_spawn) { 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); 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" { test "saturating add" {
if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO 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