mirror of
https://github.com/ziglang/zig.git
synced 2025-12-06 06:13:07 +00:00
1542 lines
58 KiB
Zig
Vendored
1542 lines
58 KiB
Zig
Vendored
const std = @import("std");
|
|
const assert = std.debug.assert;
|
|
const mem = std.mem;
|
|
const Allocator = mem.Allocator;
|
|
const EpochSeconds = std.time.epoch.EpochSeconds;
|
|
const Builtins = @import("Builtins.zig");
|
|
const Diagnostics = @import("Diagnostics.zig");
|
|
const LangOpts = @import("LangOpts.zig");
|
|
const Source = @import("Source.zig");
|
|
const Tokenizer = @import("Tokenizer.zig");
|
|
const Token = Tokenizer.Token;
|
|
const Type = @import("Type.zig");
|
|
const Pragma = @import("Pragma.zig");
|
|
const StringInterner = @import("StringInterner.zig");
|
|
const record_layout = @import("record_layout.zig");
|
|
const target_util = @import("target.zig");
|
|
const BuiltinFunction = @import("builtins/BuiltinFunction.zig");
|
|
|
|
const Compilation = @This();
|
|
|
|
pub const Error = error{
|
|
/// A fatal error has ocurred and compilation has stopped.
|
|
FatalError,
|
|
} || Allocator.Error;
|
|
|
|
pub const bit_int_max_bits = 128;
|
|
const path_buf_stack_limit = 1024;
|
|
|
|
/// Environment variables used during compilation / linking.
|
|
pub const Environment = struct {
|
|
/// Directory to use for temporary files
|
|
/// TODO: not implemented yet
|
|
tmpdir: ?[]const u8 = null,
|
|
|
|
/// PATH environment variable used to search for programs
|
|
path: ?[]const u8 = null,
|
|
|
|
/// Directories to try when searching for subprograms.
|
|
/// TODO: not implemented yet
|
|
compiler_path: ?[]const u8 = null,
|
|
|
|
/// Directories to try when searching for special linker files, if compiling for the native target
|
|
/// TODO: not implemented yet
|
|
library_path: ?[]const u8 = null,
|
|
|
|
/// List of directories to be searched as if specified with -I, but after any paths given with -I options on the command line
|
|
/// Used regardless of the language being compiled
|
|
/// TODO: not implemented yet
|
|
cpath: ?[]const u8 = null,
|
|
|
|
/// List of directories to be searched as if specified with -I, but after any paths given with -I options on the command line
|
|
/// Used if the language being compiled is C
|
|
/// TODO: not implemented yet
|
|
c_include_path: ?[]const u8 = null,
|
|
|
|
/// UNIX timestamp to be used instead of the current date and time in the __DATE__ and __TIME__ macros
|
|
source_date_epoch: ?[]const u8 = null,
|
|
|
|
/// Load all of the environment variables using the std.process API. Do not use if using Aro as a shared library on Linux without libc
|
|
/// See https://github.com/ziglang/zig/issues/4524
|
|
/// Assumes that `self` has been default-initialized
|
|
pub fn loadAll(self: *Environment, allocator: std.mem.Allocator) !void {
|
|
errdefer self.deinit(allocator);
|
|
|
|
inline for (@typeInfo(@TypeOf(self.*)).Struct.fields) |field| {
|
|
std.debug.assert(@field(self, field.name) == null);
|
|
|
|
var env_var_buf: [field.name.len]u8 = undefined;
|
|
const env_var_name = std.ascii.upperString(&env_var_buf, field.name);
|
|
const val: ?[]const u8 = std.process.getEnvVarOwned(allocator, env_var_name) catch |err| switch (err) {
|
|
error.OutOfMemory => |e| return e,
|
|
error.EnvironmentVariableNotFound => null,
|
|
error.InvalidUtf8 => null,
|
|
};
|
|
@field(self, field.name) = val;
|
|
}
|
|
}
|
|
|
|
/// Use this only if environment slices were allocated with `allocator` (such as via `loadAll`)
|
|
pub fn deinit(self: *Environment, allocator: std.mem.Allocator) void {
|
|
inline for (@typeInfo(@TypeOf(self.*)).Struct.fields) |field| {
|
|
if (@field(self, field.name)) |slice| {
|
|
allocator.free(slice);
|
|
}
|
|
}
|
|
self.* = undefined;
|
|
}
|
|
};
|
|
|
|
gpa: Allocator,
|
|
environment: Environment = .{},
|
|
sources: std.StringArrayHashMap(Source),
|
|
diag: Diagnostics,
|
|
include_dirs: std.ArrayList([]const u8),
|
|
system_include_dirs: std.ArrayList([]const u8),
|
|
target: std.Target = @import("builtin").target,
|
|
pragma_handlers: std.StringArrayHashMap(*Pragma),
|
|
langopts: LangOpts = .{},
|
|
generated_buf: std.ArrayList(u8),
|
|
builtins: Builtins = .{},
|
|
types: struct {
|
|
wchar: Type = undefined,
|
|
uint_least16_t: Type = undefined,
|
|
uint_least32_t: Type = undefined,
|
|
ptrdiff: Type = undefined,
|
|
size: Type = undefined,
|
|
va_list: Type = undefined,
|
|
pid_t: Type = undefined,
|
|
ns_constant_string: struct {
|
|
ty: Type = undefined,
|
|
record: Type.Record = undefined,
|
|
fields: [4]Type.Record.Field = undefined,
|
|
int_ty: Type = .{ .specifier = .int, .qual = .{ .@"const" = true } },
|
|
char_ty: Type = .{ .specifier = .char, .qual = .{ .@"const" = true } },
|
|
} = .{},
|
|
file: Type = .{ .specifier = .invalid },
|
|
jmp_buf: Type = .{ .specifier = .invalid },
|
|
sigjmp_buf: Type = .{ .specifier = .invalid },
|
|
ucontext_t: Type = .{ .specifier = .invalid },
|
|
intmax: Type = .{ .specifier = .invalid },
|
|
intptr: Type = .{ .specifier = .invalid },
|
|
int16: Type = .{ .specifier = .invalid },
|
|
int64: Type = .{ .specifier = .invalid },
|
|
} = .{},
|
|
string_interner: StringInterner = .{},
|
|
ms_cwd_source_id: ?Source.Id = null,
|
|
|
|
pub fn init(gpa: Allocator) Compilation {
|
|
return .{
|
|
.gpa = gpa,
|
|
.sources = std.StringArrayHashMap(Source).init(gpa),
|
|
.diag = Diagnostics.init(gpa),
|
|
.include_dirs = std.ArrayList([]const u8).init(gpa),
|
|
.system_include_dirs = std.ArrayList([]const u8).init(gpa),
|
|
.pragma_handlers = std.StringArrayHashMap(*Pragma).init(gpa),
|
|
.generated_buf = std.ArrayList(u8).init(gpa),
|
|
};
|
|
}
|
|
|
|
pub fn deinit(comp: *Compilation) void {
|
|
for (comp.pragma_handlers.values()) |pragma| {
|
|
pragma.deinit(pragma, comp);
|
|
}
|
|
for (comp.sources.values()) |source| {
|
|
comp.gpa.free(source.path);
|
|
comp.gpa.free(source.buf);
|
|
comp.gpa.free(source.splice_locs);
|
|
}
|
|
comp.sources.deinit();
|
|
comp.diag.deinit();
|
|
comp.include_dirs.deinit();
|
|
for (comp.system_include_dirs.items) |path| comp.gpa.free(path);
|
|
comp.system_include_dirs.deinit();
|
|
comp.pragma_handlers.deinit();
|
|
comp.generated_buf.deinit();
|
|
comp.builtins.deinit(comp.gpa);
|
|
comp.string_interner.deinit(comp.gpa);
|
|
}
|
|
|
|
pub fn intern(comp: *Compilation, str: []const u8) !StringInterner.StringId {
|
|
return comp.string_interner.intern(comp.gpa, str);
|
|
}
|
|
|
|
pub fn getSourceEpoch(self: *const Compilation, max: i64) !?i64 {
|
|
const provided = self.environment.source_date_epoch orelse return null;
|
|
const parsed = std.fmt.parseInt(i64, provided, 10) catch return error.InvalidEpoch;
|
|
if (parsed < 0 or parsed > max) return error.InvalidEpoch;
|
|
return parsed;
|
|
}
|
|
|
|
/// Dec 31 9999 23:59:59
|
|
const max_timestamp = 253402300799;
|
|
|
|
fn getTimestamp(comp: *Compilation) !u47 {
|
|
const provided: ?i64 = comp.getSourceEpoch(max_timestamp) catch blk: {
|
|
try comp.diag.add(.{
|
|
.tag = .invalid_source_epoch,
|
|
.loc = .{ .id = .unused, .byte_offset = 0, .line = 0 },
|
|
}, &.{});
|
|
break :blk null;
|
|
};
|
|
const timestamp = provided orelse std.time.timestamp();
|
|
return @intCast(std.math.clamp(timestamp, 0, max_timestamp));
|
|
}
|
|
|
|
fn generateDateAndTime(w: anytype, timestamp: u47) !void {
|
|
const epoch_seconds = EpochSeconds{ .secs = timestamp };
|
|
const epoch_day = epoch_seconds.getEpochDay();
|
|
const day_seconds = epoch_seconds.getDaySeconds();
|
|
const year_day = epoch_day.calculateYearDay();
|
|
const month_day = year_day.calculateMonthDay();
|
|
|
|
const month_names = [_][]const u8{ "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };
|
|
std.debug.assert(std.time.epoch.Month.jan.numeric() == 1);
|
|
|
|
const month_name = month_names[month_day.month.numeric() - 1];
|
|
try w.print("#define __DATE__ \"{s} {d: >2} {d}\"\n", .{
|
|
month_name,
|
|
month_day.day_index + 1,
|
|
year_day.year,
|
|
});
|
|
try w.print("#define __TIME__ \"{d:0>2}:{d:0>2}:{d:0>2}\"\n", .{
|
|
day_seconds.getHoursIntoDay(),
|
|
day_seconds.getMinutesIntoHour(),
|
|
day_seconds.getSecondsIntoMinute(),
|
|
});
|
|
|
|
const day_names = [_][]const u8{ "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun" };
|
|
// days since Thu Oct 1 1970
|
|
const day_name = day_names[@intCast((epoch_day.day + 3) % 7)];
|
|
try w.print("#define __TIMESTAMP__ \"{s} {s} {d: >2} {d:0>2}:{d:0>2}:{d:0>2} {d}\"\n", .{
|
|
day_name,
|
|
month_name,
|
|
month_day.day_index + 1,
|
|
day_seconds.getHoursIntoDay(),
|
|
day_seconds.getMinutesIntoHour(),
|
|
day_seconds.getSecondsIntoMinute(),
|
|
year_day.year,
|
|
});
|
|
}
|
|
|
|
/// Generate builtin macros that will be available to each source file.
|
|
pub fn generateBuiltinMacros(comp: *Compilation) !Source {
|
|
try comp.generateBuiltinTypes();
|
|
|
|
var buf = std.ArrayList(u8).init(comp.gpa);
|
|
defer buf.deinit();
|
|
const w = buf.writer();
|
|
|
|
// standard macros
|
|
try w.writeAll(
|
|
\\#define __VERSION__ "Aro
|
|
++ @import("lib.zig").version_str ++ "\"\n" ++
|
|
\\#define __Aro__
|
|
\\#define __STDC__ 1
|
|
\\#define __STDC_HOSTED__ 1
|
|
\\#define __STDC_NO_ATOMICS__ 1
|
|
\\#define __STDC_NO_COMPLEX__ 1
|
|
\\#define __STDC_NO_THREADS__ 1
|
|
\\#define __STDC_NO_VLA__ 1
|
|
\\
|
|
);
|
|
if (comp.langopts.standard.StdCVersionMacro()) |stdc_version| {
|
|
try w.print("#define __STDC_VERSION__ {s}\n", .{stdc_version});
|
|
}
|
|
const ptr_width = comp.target.ptrBitWidth();
|
|
|
|
// os macros
|
|
switch (comp.target.os.tag) {
|
|
.linux => try w.writeAll(
|
|
\\#define linux 1
|
|
\\#define __linux 1
|
|
\\#define __linux__ 1
|
|
\\
|
|
),
|
|
.windows => if (ptr_width == 32) try w.writeAll(
|
|
\\#define WIN32 1
|
|
\\#define _WIN32 1
|
|
\\#define __WIN32 1
|
|
\\#define __WIN32__ 1
|
|
\\
|
|
) else try w.writeAll(
|
|
\\#define WIN32 1
|
|
\\#define WIN64 1
|
|
\\#define _WIN32 1
|
|
\\#define _WIN64 1
|
|
\\#define __WIN32 1
|
|
\\#define __WIN64 1
|
|
\\#define __WIN32__ 1
|
|
\\#define __WIN64__ 1
|
|
\\
|
|
),
|
|
.freebsd => try w.print("#define __FreeBSD__ {d}\n", .{comp.target.os.version_range.semver.min.major}),
|
|
.netbsd => try w.writeAll("#define __NetBSD__ 1\n"),
|
|
.openbsd => try w.writeAll("#define __OpenBSD__ 1\n"),
|
|
.dragonfly => try w.writeAll("#define __DragonFly__ 1\n"),
|
|
.solaris => try w.writeAll(
|
|
\\#define sun 1
|
|
\\#define __sun 1
|
|
\\
|
|
),
|
|
.macos => try w.writeAll(
|
|
\\#define __APPLE__ 1
|
|
\\#define __MACH__ 1
|
|
\\
|
|
),
|
|
else => {},
|
|
}
|
|
|
|
// unix and other additional os macros
|
|
switch (comp.target.os.tag) {
|
|
.freebsd,
|
|
.netbsd,
|
|
.openbsd,
|
|
.dragonfly,
|
|
.linux,
|
|
=> try w.writeAll(
|
|
\\#define unix 1
|
|
\\#define __unix 1
|
|
\\#define __unix__ 1
|
|
\\
|
|
),
|
|
else => {},
|
|
}
|
|
if (comp.target.abi == .android) {
|
|
try w.writeAll("#define __ANDROID__ 1\n");
|
|
}
|
|
|
|
// architecture macros
|
|
switch (comp.target.cpu.arch) {
|
|
.x86_64 => try w.writeAll(
|
|
\\#define __amd64__ 1
|
|
\\#define __amd64 1
|
|
\\#define __x86_64 1
|
|
\\#define __x86_64__ 1
|
|
\\
|
|
),
|
|
.x86 => try w.writeAll(
|
|
\\#define i386 1
|
|
\\#define __i386 1
|
|
\\#define __i386__ 1
|
|
\\
|
|
),
|
|
.mips,
|
|
.mipsel,
|
|
.mips64,
|
|
.mips64el,
|
|
=> try w.writeAll(
|
|
\\#define __mips__ 1
|
|
\\#define mips 1
|
|
\\
|
|
),
|
|
.powerpc,
|
|
.powerpcle,
|
|
=> try w.writeAll(
|
|
\\#define __powerpc__ 1
|
|
\\#define __POWERPC__ 1
|
|
\\#define __ppc__ 1
|
|
\\#define __PPC__ 1
|
|
\\#define _ARCH_PPC 1
|
|
\\
|
|
),
|
|
.powerpc64,
|
|
.powerpc64le,
|
|
=> try w.writeAll(
|
|
\\#define __powerpc 1
|
|
\\#define __powerpc__ 1
|
|
\\#define __powerpc64__ 1
|
|
\\#define __POWERPC__ 1
|
|
\\#define __ppc__ 1
|
|
\\#define __ppc64__ 1
|
|
\\#define __PPC__ 1
|
|
\\#define __PPC64__ 1
|
|
\\#define _ARCH_PPC 1
|
|
\\#define _ARCH_PPC64 1
|
|
\\
|
|
),
|
|
.sparc64 => try w.writeAll(
|
|
\\#define __sparc__ 1
|
|
\\#define __sparc 1
|
|
\\#define __sparc_v9__ 1
|
|
\\
|
|
),
|
|
.sparc, .sparcel => try w.writeAll(
|
|
\\#define __sparc__ 1
|
|
\\#define __sparc 1
|
|
\\
|
|
),
|
|
.arm, .armeb => try w.writeAll(
|
|
\\#define __arm__ 1
|
|
\\#define __arm 1
|
|
\\
|
|
),
|
|
.thumb, .thumbeb => try w.writeAll(
|
|
\\#define __arm__ 1
|
|
\\#define __arm 1
|
|
\\#define __thumb__ 1
|
|
\\
|
|
),
|
|
.aarch64, .aarch64_be => try w.writeAll("#define __aarch64__ 1\n"),
|
|
.msp430 => try w.writeAll(
|
|
\\#define MSP430 1
|
|
\\#define __MSP430__ 1
|
|
\\
|
|
),
|
|
else => {},
|
|
}
|
|
|
|
if (comp.target.os.tag != .windows) switch (ptr_width) {
|
|
64 => try w.writeAll(
|
|
\\#define _LP64 1
|
|
\\#define __LP64__ 1
|
|
\\
|
|
),
|
|
32 => try w.writeAll("#define _ILP32 1\n"),
|
|
else => {},
|
|
};
|
|
|
|
try w.writeAll(
|
|
\\#define __ORDER_LITTLE_ENDIAN__ 1234
|
|
\\#define __ORDER_BIG_ENDIAN__ 4321
|
|
\\#define __ORDER_PDP_ENDIAN__ 3412
|
|
\\
|
|
);
|
|
if (comp.target.cpu.arch.endian() == .little) try w.writeAll(
|
|
\\#define __BYTE_ORDER__ __ORDER_LITTLE_ENDIAN__
|
|
\\#define __LITTLE_ENDIAN__ 1
|
|
\\
|
|
) else try w.writeAll(
|
|
\\#define __BYTE_ORDER__ __ORDER_BIG_ENDIAN__
|
|
\\#define __BIG_ENDIAN__ 1
|
|
\\
|
|
);
|
|
|
|
// timestamps
|
|
const timestamp = try comp.getTimestamp();
|
|
try generateDateAndTime(w, timestamp);
|
|
|
|
// types
|
|
if (comp.getCharSignedness() == .unsigned) try w.writeAll("#define __CHAR_UNSIGNED__ 1\n");
|
|
try w.writeAll("#define __CHAR_BIT__ 8\n");
|
|
|
|
// int maxs
|
|
try comp.generateIntWidth(w, "BOOL", .{ .specifier = .bool });
|
|
try comp.generateIntMaxAndWidth(w, "SCHAR", .{ .specifier = .schar });
|
|
try comp.generateIntMaxAndWidth(w, "SHRT", .{ .specifier = .short });
|
|
try comp.generateIntMaxAndWidth(w, "INT", .{ .specifier = .int });
|
|
try comp.generateIntMaxAndWidth(w, "LONG", .{ .specifier = .long });
|
|
try comp.generateIntMaxAndWidth(w, "LONG_LONG", .{ .specifier = .long_long });
|
|
try comp.generateIntMaxAndWidth(w, "WCHAR", comp.types.wchar);
|
|
// try comp.generateIntMax(w, "WINT", comp.types.wchar);
|
|
try comp.generateIntMaxAndWidth(w, "INTMAX", comp.types.intmax);
|
|
try comp.generateIntMaxAndWidth(w, "SIZE", comp.types.size);
|
|
try comp.generateIntMaxAndWidth(w, "UINTMAX", comp.types.intmax.makeIntegerUnsigned());
|
|
try comp.generateIntMaxAndWidth(w, "PTRDIFF", comp.types.ptrdiff);
|
|
try comp.generateIntMaxAndWidth(w, "INTPTR", comp.types.intptr);
|
|
try comp.generateIntMaxAndWidth(w, "UINTPTR", comp.types.intptr.makeIntegerUnsigned());
|
|
|
|
// int widths
|
|
try w.print("#define __BITINT_MAXWIDTH__ {d}\n", .{bit_int_max_bits});
|
|
|
|
// sizeof types
|
|
try comp.generateSizeofType(w, "__SIZEOF_FLOAT__", .{ .specifier = .float });
|
|
try comp.generateSizeofType(w, "__SIZEOF_DOUBLE__", .{ .specifier = .double });
|
|
try comp.generateSizeofType(w, "__SIZEOF_LONG_DOUBLE__", .{ .specifier = .long_double });
|
|
try comp.generateSizeofType(w, "__SIZEOF_SHORT__", .{ .specifier = .short });
|
|
try comp.generateSizeofType(w, "__SIZEOF_INT__", .{ .specifier = .int });
|
|
try comp.generateSizeofType(w, "__SIZEOF_LONG__", .{ .specifier = .long });
|
|
try comp.generateSizeofType(w, "__SIZEOF_LONG_LONG__", .{ .specifier = .long_long });
|
|
try comp.generateSizeofType(w, "__SIZEOF_POINTER__", .{ .specifier = .pointer });
|
|
try comp.generateSizeofType(w, "__SIZEOF_PTRDIFF_T__", comp.types.ptrdiff);
|
|
try comp.generateSizeofType(w, "__SIZEOF_SIZE_T__", comp.types.size);
|
|
try comp.generateSizeofType(w, "__SIZEOF_WCHAR_T__", comp.types.wchar);
|
|
// try comp.generateSizeofType(w, "__SIZEOF_WINT_T__", .{ .specifier = .pointer });
|
|
|
|
// various int types
|
|
const mapper = comp.string_interner.getSlowTypeMapper();
|
|
try generateTypeMacro(w, mapper, "__INTPTR_TYPE__", comp.types.intptr, comp.langopts);
|
|
try generateTypeMacro(w, mapper, "__UINTPTR_TYPE__", comp.types.intptr.makeIntegerUnsigned(), comp.langopts);
|
|
|
|
try generateTypeMacro(w, mapper, "__INTMAX_TYPE__", comp.types.intmax, comp.langopts);
|
|
try comp.generateSuffixMacro("__INTMAX", w, comp.types.intptr);
|
|
|
|
try generateTypeMacro(w, mapper, "__UINTMAX_TYPE__", comp.types.intmax.makeIntegerUnsigned(), comp.langopts);
|
|
try comp.generateSuffixMacro("__UINTMAX", w, comp.types.intptr.makeIntegerUnsigned());
|
|
|
|
try generateTypeMacro(w, mapper, "__PTRDIFF_TYPE__", comp.types.ptrdiff, comp.langopts);
|
|
try generateTypeMacro(w, mapper, "__SIZE_TYPE__", comp.types.size, comp.langopts);
|
|
try generateTypeMacro(w, mapper, "__WCHAR_TYPE__", comp.types.wchar, comp.langopts);
|
|
|
|
try comp.generateExactWidthTypes(w, mapper);
|
|
|
|
if (target_util.FPSemantics.halfPrecisionType(comp.target)) |half| {
|
|
try generateFloatMacros(w, "FLT16", half, "F16");
|
|
}
|
|
try generateFloatMacros(w, "FLT", target_util.FPSemantics.forType(.float, comp.target), "F");
|
|
try generateFloatMacros(w, "DBL", target_util.FPSemantics.forType(.double, comp.target), "");
|
|
try generateFloatMacros(w, "LDBL", target_util.FPSemantics.forType(.longdouble, comp.target), "L");
|
|
|
|
// TODO: clang treats __FLT_EVAL_METHOD__ as a special-cased macro because evaluating it within a scope
|
|
// where `#pragma clang fp eval_method(X)` has been called produces an error diagnostic.
|
|
const flt_eval_method = comp.langopts.fp_eval_method orelse target_util.defaultFpEvalMethod(comp.target);
|
|
try w.print("#define __FLT_EVAL_METHOD__ {d}\n", .{@intFromEnum(flt_eval_method)});
|
|
|
|
try w.writeAll(
|
|
\\#define __FLT_RADIX__ 2
|
|
\\#define __DECIMAL_DIG__ __LDBL_DECIMAL_DIG__
|
|
\\
|
|
);
|
|
|
|
return comp.addSourceFromBuffer("<builtin>", buf.items);
|
|
}
|
|
|
|
fn generateFloatMacros(w: anytype, prefix: []const u8, semantics: target_util.FPSemantics, ext: []const u8) !void {
|
|
const denormMin = semantics.chooseValue(
|
|
[]const u8,
|
|
.{
|
|
"5.9604644775390625e-8",
|
|
"1.40129846e-45",
|
|
"4.9406564584124654e-324",
|
|
"3.64519953188247460253e-4951",
|
|
"4.94065645841246544176568792868221e-324",
|
|
"6.47517511943802511092443895822764655e-4966",
|
|
},
|
|
);
|
|
const digits = semantics.chooseValue(i32, .{ 3, 6, 15, 18, 31, 33 });
|
|
const decimalDigits = semantics.chooseValue(i32, .{ 5, 9, 17, 21, 33, 36 });
|
|
const epsilon = semantics.chooseValue(
|
|
[]const u8,
|
|
.{
|
|
"9.765625e-4",
|
|
"1.19209290e-7",
|
|
"2.2204460492503131e-16",
|
|
"1.08420217248550443401e-19",
|
|
"4.94065645841246544176568792868221e-324",
|
|
"1.92592994438723585305597794258492732e-34",
|
|
},
|
|
);
|
|
const mantissaDigits = semantics.chooseValue(i32, .{ 11, 24, 53, 64, 106, 113 });
|
|
|
|
const min10Exp = semantics.chooseValue(i32, .{ -4, -37, -307, -4931, -291, -4931 });
|
|
const max10Exp = semantics.chooseValue(i32, .{ 4, 38, 308, 4932, 308, 4932 });
|
|
|
|
const minExp = semantics.chooseValue(i32, .{ -13, -125, -1021, -16381, -968, -16381 });
|
|
const maxExp = semantics.chooseValue(i32, .{ 16, 128, 1024, 16384, 1024, 16384 });
|
|
|
|
const min = semantics.chooseValue(
|
|
[]const u8,
|
|
.{
|
|
"6.103515625e-5",
|
|
"1.17549435e-38",
|
|
"2.2250738585072014e-308",
|
|
"3.36210314311209350626e-4932",
|
|
"2.00416836000897277799610805135016e-292",
|
|
"3.36210314311209350626267781732175260e-4932",
|
|
},
|
|
);
|
|
const max = semantics.chooseValue(
|
|
[]const u8,
|
|
.{
|
|
"6.5504e+4",
|
|
"3.40282347e+38",
|
|
"1.7976931348623157e+308",
|
|
"1.18973149535723176502e+4932",
|
|
"1.79769313486231580793728971405301e+308",
|
|
"1.18973149535723176508575932662800702e+4932",
|
|
},
|
|
);
|
|
|
|
var defPrefix = std.BoundedArray(u8, 32).init(0) catch unreachable;
|
|
defPrefix.writer().print("__{s}_", .{prefix}) catch return error.OutOfMemory;
|
|
|
|
const prefix_slice = defPrefix.constSlice();
|
|
|
|
try w.print("#define {s}DENORM_MIN__ {s}{s}\n", .{ prefix_slice, denormMin, ext });
|
|
try w.print("#define {s}HAS_DENORM__\n", .{prefix_slice});
|
|
try w.print("#define {s}DIG__ {d}\n", .{ prefix_slice, digits });
|
|
try w.print("#define {s}DECIMAL_DIG__ {d}\n", .{ prefix_slice, decimalDigits });
|
|
|
|
try w.print("#define {s}EPSILON__ {s}{s}\n", .{ prefix_slice, epsilon, ext });
|
|
try w.print("#define {s}HAS_INFINITY__\n", .{prefix_slice});
|
|
try w.print("#define {s}HAS_QUIET_NAN__\n", .{prefix_slice});
|
|
try w.print("#define {s}MANT_DIG__ {d}\n", .{ prefix_slice, mantissaDigits });
|
|
|
|
try w.print("#define {s}MAX_10_EXP__ {d}\n", .{ prefix_slice, max10Exp });
|
|
try w.print("#define {s}MAX_EXP__ {d}\n", .{ prefix_slice, maxExp });
|
|
try w.print("#define {s}MAX__ {s}{s}\n", .{ prefix_slice, max, ext });
|
|
|
|
try w.print("#define {s}MIN_10_EXP__ ({d})\n", .{ prefix_slice, min10Exp });
|
|
try w.print("#define {s}MIN_EXP__ ({d})\n", .{ prefix_slice, minExp });
|
|
try w.print("#define {s}MIN__ {s}{s}\n", .{ prefix_slice, min, ext });
|
|
}
|
|
|
|
fn generateTypeMacro(w: anytype, mapper: StringInterner.TypeMapper, name: []const u8, ty: Type, langopts: LangOpts) !void {
|
|
try w.print("#define {s} ", .{name});
|
|
try ty.print(mapper, langopts, w);
|
|
try w.writeByte('\n');
|
|
}
|
|
|
|
fn generateBuiltinTypes(comp: *Compilation) !void {
|
|
const os = comp.target.os.tag;
|
|
const wchar: Type = switch (comp.target.cpu.arch) {
|
|
.xcore => .{ .specifier = .uchar },
|
|
.ve, .msp430 => .{ .specifier = .uint },
|
|
.arm, .armeb, .thumb, .thumbeb => .{
|
|
.specifier = if (os != .windows and os != .netbsd and os != .openbsd) .uint else .int,
|
|
},
|
|
.aarch64, .aarch64_be, .aarch64_32 => .{
|
|
.specifier = if (!os.isDarwin() and os != .netbsd) .uint else .int,
|
|
},
|
|
.x86_64, .x86 => .{ .specifier = if (os == .windows) .ushort else .int },
|
|
else => .{ .specifier = .int },
|
|
};
|
|
|
|
const ptr_width = comp.target.ptrBitWidth();
|
|
const ptrdiff = if (os == .windows and ptr_width == 64)
|
|
Type{ .specifier = .long_long }
|
|
else switch (ptr_width) {
|
|
16 => Type{ .specifier = .int },
|
|
32 => Type{ .specifier = .int },
|
|
64 => Type{ .specifier = .long },
|
|
else => unreachable,
|
|
};
|
|
|
|
const size = if (os == .windows and ptr_width == 64)
|
|
Type{ .specifier = .ulong_long }
|
|
else switch (ptr_width) {
|
|
16 => Type{ .specifier = .uint },
|
|
32 => Type{ .specifier = .uint },
|
|
64 => Type{ .specifier = .ulong },
|
|
else => unreachable,
|
|
};
|
|
|
|
const va_list = try comp.generateVaListType();
|
|
|
|
const pid_t: Type = switch (os) {
|
|
.haiku => .{ .specifier = .long },
|
|
// Todo: pid_t is required to "a signed integer type"; are there any systems
|
|
// on which it is `short int`?
|
|
else => .{ .specifier = .int },
|
|
};
|
|
|
|
const intmax = target_util.intMaxType(comp.target);
|
|
const intptr = target_util.intPtrType(comp.target);
|
|
const int16 = target_util.int16Type(comp.target);
|
|
const int64 = target_util.int64Type(comp.target);
|
|
|
|
comp.types = .{
|
|
.wchar = wchar,
|
|
.ptrdiff = ptrdiff,
|
|
.size = size,
|
|
.va_list = va_list,
|
|
.pid_t = pid_t,
|
|
.intmax = intmax,
|
|
.intptr = intptr,
|
|
.int16 = int16,
|
|
.int64 = int64,
|
|
.uint_least16_t = comp.intLeastN(16, .unsigned),
|
|
.uint_least32_t = comp.intLeastN(32, .unsigned),
|
|
};
|
|
|
|
try comp.generateNsConstantStringType();
|
|
}
|
|
|
|
/// Smallest integer type with at least N bits
|
|
fn intLeastN(comp: *const Compilation, bits: usize, signedness: std.builtin.Signedness) Type {
|
|
const candidates = switch (signedness) {
|
|
.signed => &[_]Type.Specifier{ .schar, .short, .int, .long, .long_long },
|
|
.unsigned => &[_]Type.Specifier{ .uchar, .ushort, .uint, .ulong, .ulong_long },
|
|
};
|
|
for (candidates) |specifier| {
|
|
const ty: Type = .{ .specifier = specifier };
|
|
if (ty.sizeof(comp).? * 8 >= bits) return ty;
|
|
} else unreachable;
|
|
}
|
|
|
|
fn intSize(comp: *const Compilation, specifier: Type.Specifier) u64 {
|
|
const ty = Type{ .specifier = specifier };
|
|
return ty.sizeof(comp).?;
|
|
}
|
|
|
|
fn generateExactWidthTypes(comp: *const Compilation, w: anytype, mapper: StringInterner.TypeMapper) !void {
|
|
try comp.generateExactWidthType(w, mapper, .schar);
|
|
|
|
if (comp.intSize(.short) > comp.intSize(.char)) {
|
|
try comp.generateExactWidthType(w, mapper, .short);
|
|
}
|
|
|
|
if (comp.intSize(.int) > comp.intSize(.short)) {
|
|
try comp.generateExactWidthType(w, mapper, .int);
|
|
}
|
|
|
|
if (comp.intSize(.long) > comp.intSize(.int)) {
|
|
try comp.generateExactWidthType(w, mapper, .long);
|
|
}
|
|
|
|
if (comp.intSize(.long_long) > comp.intSize(.long)) {
|
|
try comp.generateExactWidthType(w, mapper, .long_long);
|
|
}
|
|
|
|
try comp.generateExactWidthType(w, mapper, .uchar);
|
|
try comp.generateExactWidthIntMax(w, .uchar);
|
|
try comp.generateExactWidthIntMax(w, .schar);
|
|
|
|
if (comp.intSize(.short) > comp.intSize(.char)) {
|
|
try comp.generateExactWidthType(w, mapper, .ushort);
|
|
try comp.generateExactWidthIntMax(w, .ushort);
|
|
try comp.generateExactWidthIntMax(w, .short);
|
|
}
|
|
|
|
if (comp.intSize(.int) > comp.intSize(.short)) {
|
|
try comp.generateExactWidthType(w, mapper, .uint);
|
|
try comp.generateExactWidthIntMax(w, .uint);
|
|
try comp.generateExactWidthIntMax(w, .int);
|
|
}
|
|
|
|
if (comp.intSize(.long) > comp.intSize(.int)) {
|
|
try comp.generateExactWidthType(w, mapper, .ulong);
|
|
try comp.generateExactWidthIntMax(w, .ulong);
|
|
try comp.generateExactWidthIntMax(w, .long);
|
|
}
|
|
|
|
if (comp.intSize(.long_long) > comp.intSize(.long)) {
|
|
try comp.generateExactWidthType(w, mapper, .ulong_long);
|
|
try comp.generateExactWidthIntMax(w, .ulong_long);
|
|
try comp.generateExactWidthIntMax(w, .long_long);
|
|
}
|
|
}
|
|
|
|
fn generateFmt(comp: *const Compilation, prefix: []const u8, w: anytype, ty: Type) !void {
|
|
const unsigned = ty.isUnsignedInt(comp);
|
|
const modifier = ty.formatModifier();
|
|
const formats = if (unsigned) "ouxX" else "di";
|
|
for (formats) |c| {
|
|
try w.print("#define {s}_FMT{c}__ \"{s}{c}\"\n", .{ prefix, c, modifier, c });
|
|
}
|
|
}
|
|
|
|
fn generateSuffixMacro(comp: *const Compilation, prefix: []const u8, w: anytype, ty: Type) !void {
|
|
return w.print("#define {s}_C_SUFFIX__ {s}\n", .{ prefix, ty.intValueSuffix(comp) });
|
|
}
|
|
|
|
/// Generate the following for ty:
|
|
/// Name macro (e.g. #define __UINT32_TYPE__ unsigned int)
|
|
/// Format strings (e.g. #define __UINT32_FMTu__ "u")
|
|
/// Suffix macro (e.g. #define __UINT32_C_SUFFIX__ U)
|
|
fn generateExactWidthType(comp: *const Compilation, w: anytype, mapper: StringInterner.TypeMapper, specifier: Type.Specifier) !void {
|
|
var ty = Type{ .specifier = specifier };
|
|
const width = 8 * ty.sizeof(comp).?;
|
|
const unsigned = ty.isUnsignedInt(comp);
|
|
|
|
if (width == 16) {
|
|
ty = if (unsigned) comp.types.int16.makeIntegerUnsigned() else comp.types.int16;
|
|
} else if (width == 64) {
|
|
ty = if (unsigned) comp.types.int64.makeIntegerUnsigned() else comp.types.int64;
|
|
}
|
|
|
|
var prefix = std.BoundedArray(u8, 16).init(0) catch unreachable;
|
|
prefix.writer().print("{s}{d}", .{ if (unsigned) "__UINT" else "__INT", width }) catch return error.OutOfMemory;
|
|
|
|
{
|
|
const len = prefix.len;
|
|
defer prefix.resize(len) catch unreachable; // restoring previous size
|
|
prefix.appendSliceAssumeCapacity("_TYPE__");
|
|
try generateTypeMacro(w, mapper, prefix.constSlice(), ty, comp.langopts);
|
|
}
|
|
|
|
try comp.generateFmt(prefix.constSlice(), w, ty);
|
|
try comp.generateSuffixMacro(prefix.constSlice(), w, ty);
|
|
}
|
|
|
|
pub fn hasHalfPrecisionFloatABI(comp: *const Compilation) bool {
|
|
return comp.langopts.allow_half_args_and_returns or target_util.hasHalfPrecisionFloatABI(comp.target);
|
|
}
|
|
|
|
fn generateNsConstantStringType(comp: *Compilation) !void {
|
|
comp.types.ns_constant_string.record = .{
|
|
.name = try comp.intern("__NSConstantString_tag"),
|
|
.fields = &comp.types.ns_constant_string.fields,
|
|
.field_attributes = null,
|
|
.type_layout = undefined,
|
|
};
|
|
const const_int_ptr = Type{ .specifier = .pointer, .data = .{ .sub_type = &comp.types.ns_constant_string.int_ty } };
|
|
const const_char_ptr = Type{ .specifier = .pointer, .data = .{ .sub_type = &comp.types.ns_constant_string.char_ty } };
|
|
|
|
comp.types.ns_constant_string.fields[0] = .{ .name = try comp.intern("isa"), .ty = const_int_ptr };
|
|
comp.types.ns_constant_string.fields[1] = .{ .name = try comp.intern("flags"), .ty = .{ .specifier = .int } };
|
|
comp.types.ns_constant_string.fields[2] = .{ .name = try comp.intern("str"), .ty = const_char_ptr };
|
|
comp.types.ns_constant_string.fields[3] = .{ .name = try comp.intern("length"), .ty = .{ .specifier = .long } };
|
|
comp.types.ns_constant_string.ty = .{ .specifier = .@"struct", .data = .{ .record = &comp.types.ns_constant_string.record } };
|
|
record_layout.compute(&comp.types.ns_constant_string.record, comp.types.ns_constant_string.ty, comp, null);
|
|
}
|
|
|
|
fn generateVaListType(comp: *Compilation) !Type {
|
|
const Kind = enum { char_ptr, void_ptr, aarch64_va_list, x86_64_va_list };
|
|
const kind: Kind = switch (comp.target.cpu.arch) {
|
|
.aarch64 => switch (comp.target.os.tag) {
|
|
.windows => @as(Kind, .char_ptr),
|
|
.ios, .macos, .tvos, .watchos => .char_ptr,
|
|
else => .aarch64_va_list,
|
|
},
|
|
.sparc, .wasm32, .wasm64, .bpfel, .bpfeb, .riscv32, .riscv64, .avr, .spirv32, .spirv64 => .void_ptr,
|
|
.powerpc => switch (comp.target.os.tag) {
|
|
.ios, .macos, .tvos, .watchos, .aix => @as(Kind, .char_ptr),
|
|
else => return Type{ .specifier = .void }, // unknown
|
|
},
|
|
.x86, .msp430 => .char_ptr,
|
|
.x86_64 => switch (comp.target.os.tag) {
|
|
.windows => @as(Kind, .char_ptr),
|
|
else => .x86_64_va_list,
|
|
},
|
|
else => return Type{ .specifier = .void }, // unknown
|
|
};
|
|
|
|
// TODO this might be bad?
|
|
const arena = comp.diag.arena.allocator();
|
|
|
|
var ty: Type = undefined;
|
|
switch (kind) {
|
|
.char_ptr => ty = .{ .specifier = .char },
|
|
.void_ptr => ty = .{ .specifier = .void },
|
|
.aarch64_va_list => {
|
|
const record_ty = try arena.create(Type.Record);
|
|
record_ty.* = .{
|
|
.name = try comp.intern("__va_list_tag"),
|
|
.fields = try arena.alloc(Type.Record.Field, 5),
|
|
.field_attributes = null,
|
|
.type_layout = undefined, // computed below
|
|
};
|
|
const void_ty = try arena.create(Type);
|
|
void_ty.* = .{ .specifier = .void };
|
|
const void_ptr = Type{ .specifier = .pointer, .data = .{ .sub_type = void_ty } };
|
|
record_ty.fields[0] = .{ .name = try comp.intern("__stack"), .ty = void_ptr };
|
|
record_ty.fields[1] = .{ .name = try comp.intern("__gr_top"), .ty = void_ptr };
|
|
record_ty.fields[2] = .{ .name = try comp.intern("__vr_top"), .ty = void_ptr };
|
|
record_ty.fields[3] = .{ .name = try comp.intern("__gr_offs"), .ty = .{ .specifier = .int } };
|
|
record_ty.fields[4] = .{ .name = try comp.intern("__vr_offs"), .ty = .{ .specifier = .int } };
|
|
ty = .{ .specifier = .@"struct", .data = .{ .record = record_ty } };
|
|
record_layout.compute(record_ty, ty, comp, null);
|
|
},
|
|
.x86_64_va_list => {
|
|
const record_ty = try arena.create(Type.Record);
|
|
record_ty.* = .{
|
|
.name = try comp.intern("__va_list_tag"),
|
|
.fields = try arena.alloc(Type.Record.Field, 4),
|
|
.field_attributes = null,
|
|
.type_layout = undefined, // computed below
|
|
};
|
|
const void_ty = try arena.create(Type);
|
|
void_ty.* = .{ .specifier = .void };
|
|
const void_ptr = Type{ .specifier = .pointer, .data = .{ .sub_type = void_ty } };
|
|
record_ty.fields[0] = .{ .name = try comp.intern("gp_offset"), .ty = .{ .specifier = .uint } };
|
|
record_ty.fields[1] = .{ .name = try comp.intern("fp_offset"), .ty = .{ .specifier = .uint } };
|
|
record_ty.fields[2] = .{ .name = try comp.intern("overflow_arg_area"), .ty = void_ptr };
|
|
record_ty.fields[3] = .{ .name = try comp.intern("reg_save_area"), .ty = void_ptr };
|
|
ty = .{ .specifier = .@"struct", .data = .{ .record = record_ty } };
|
|
record_layout.compute(record_ty, ty, comp, null);
|
|
},
|
|
}
|
|
if (kind == .char_ptr or kind == .void_ptr) {
|
|
const elem_ty = try arena.create(Type);
|
|
elem_ty.* = ty;
|
|
ty = Type{ .specifier = .pointer, .data = .{ .sub_type = elem_ty } };
|
|
} else {
|
|
const arr_ty = try arena.create(Type.Array);
|
|
arr_ty.* = .{ .len = 1, .elem = ty };
|
|
ty = Type{ .specifier = .array, .data = .{ .array = arr_ty } };
|
|
}
|
|
|
|
return ty;
|
|
}
|
|
|
|
fn generateIntMax(comp: *const Compilation, w: anytype, name: []const u8, ty: Type) !void {
|
|
const bit_count: u8 = @intCast(ty.sizeof(comp).? * 8);
|
|
const unsigned = ty.isUnsignedInt(comp);
|
|
const max = if (bit_count == 128)
|
|
@as(u128, if (unsigned) std.math.maxInt(u128) else std.math.maxInt(u128))
|
|
else
|
|
ty.maxInt(comp);
|
|
try w.print("#define __{s}_MAX__ {d}{s}\n", .{ name, max, ty.intValueSuffix(comp) });
|
|
}
|
|
|
|
fn generateExactWidthIntMax(comp: *const Compilation, w: anytype, specifier: Type.Specifier) !void {
|
|
var ty = Type{ .specifier = specifier };
|
|
const bit_count: u8 = @intCast(ty.sizeof(comp).? * 8);
|
|
const unsigned = ty.isUnsignedInt(comp);
|
|
|
|
if (bit_count == 64) {
|
|
ty = if (unsigned) comp.types.int64.makeIntegerUnsigned() else comp.types.int64;
|
|
}
|
|
|
|
var name = std.BoundedArray(u8, 6).init(0) catch unreachable;
|
|
name.writer().print("{s}{d}", .{ if (unsigned) "UINT" else "INT", bit_count }) catch return error.OutOfMemory;
|
|
|
|
return comp.generateIntMax(w, name.constSlice(), ty);
|
|
}
|
|
|
|
fn generateIntWidth(comp: *Compilation, w: anytype, name: []const u8, ty: Type) !void {
|
|
try w.print("#define __{s}_WIDTH__ {d}\n", .{ name, 8 * ty.sizeof(comp).? });
|
|
}
|
|
|
|
fn generateIntMaxAndWidth(comp: *Compilation, w: anytype, name: []const u8, ty: Type) !void {
|
|
try comp.generateIntMax(w, name, ty);
|
|
try comp.generateIntWidth(w, name, ty);
|
|
}
|
|
|
|
fn generateSizeofType(comp: *Compilation, w: anytype, name: []const u8, ty: Type) !void {
|
|
try w.print("#define {s} {d}\n", .{ name, ty.sizeof(comp).? });
|
|
}
|
|
|
|
pub fn nextLargestIntSameSign(comp: *const Compilation, ty: Type) ?Type {
|
|
assert(ty.isInt());
|
|
const specifiers = if (ty.isUnsignedInt(comp))
|
|
[_]Type.Specifier{ .short, .int, .long, .long_long }
|
|
else
|
|
[_]Type.Specifier{ .ushort, .uint, .ulong, .ulong_long };
|
|
const size = ty.sizeof(comp).?;
|
|
for (specifiers) |specifier| {
|
|
const candidate = Type{ .specifier = specifier };
|
|
if (candidate.sizeof(comp).? > size) return candidate;
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// If `enum E { ... }` syntax has a fixed underlying integer type regardless of the presence of
|
|
/// __attribute__((packed)) or the range of values of the corresponding enumerator constants,
|
|
/// specify it here.
|
|
/// TODO: likely incomplete
|
|
pub fn fixedEnumTagSpecifier(comp: *const Compilation) ?Type.Specifier {
|
|
switch (comp.langopts.emulate) {
|
|
.msvc => return .int,
|
|
.clang => if (comp.target.os.tag == .windows) return .int,
|
|
.gcc => {},
|
|
}
|
|
return null;
|
|
}
|
|
|
|
pub fn getCharSignedness(comp: *const Compilation) std.builtin.Signedness {
|
|
return comp.langopts.char_signedness_override orelse comp.target.charSignedness();
|
|
}
|
|
|
|
pub fn defineSystemIncludes(comp: *Compilation, aro_dir: []const u8) !void {
|
|
var stack_fallback = std.heap.stackFallback(path_buf_stack_limit, comp.gpa);
|
|
const allocator = stack_fallback.get();
|
|
var search_path = aro_dir;
|
|
while (std.fs.path.dirname(search_path)) |dirname| : (search_path = dirname) {
|
|
var base_dir = std.fs.cwd().openDir(dirname, .{}) catch continue;
|
|
defer base_dir.close();
|
|
|
|
base_dir.access("include/stddef.h", .{}) catch continue;
|
|
const path = try std.fs.path.join(comp.gpa, &.{ dirname, "include" });
|
|
errdefer comp.gpa.free(path);
|
|
try comp.system_include_dirs.append(path);
|
|
break;
|
|
} else return error.AroIncludeNotFound;
|
|
|
|
if (comp.target.os.tag == .linux) {
|
|
const triple_str = try comp.target.linuxTriple(allocator);
|
|
defer allocator.free(triple_str);
|
|
|
|
const multiarch_path = try std.fs.path.join(allocator, &.{ "/usr/include", triple_str });
|
|
defer allocator.free(multiarch_path);
|
|
|
|
if (!std.meta.isError(std.fs.accessAbsolute(multiarch_path, .{}))) {
|
|
const duped = try comp.gpa.dupe(u8, multiarch_path);
|
|
errdefer comp.gpa.free(duped);
|
|
try comp.system_include_dirs.append(duped);
|
|
}
|
|
}
|
|
const usr_include = try comp.gpa.dupe(u8, "/usr/include");
|
|
errdefer comp.gpa.free(usr_include);
|
|
try comp.system_include_dirs.append(usr_include);
|
|
}
|
|
|
|
pub fn getSource(comp: *const Compilation, id: Source.Id) Source {
|
|
if (id == .generated) return .{
|
|
.path = "<scratch space>",
|
|
.buf = comp.generated_buf.items,
|
|
.id = .generated,
|
|
.splice_locs = &.{},
|
|
.kind = .user,
|
|
};
|
|
return comp.sources.values()[@intFromEnum(id) - 2];
|
|
}
|
|
|
|
/// Creates a Source from the contents of `reader` and adds it to the Compilation
|
|
pub fn addSourceFromReader(comp: *Compilation, reader: anytype, path: []const u8, kind: Source.Kind) !Source {
|
|
const contents = try reader.readAllAlloc(comp.gpa, std.math.maxInt(u32));
|
|
errdefer comp.gpa.free(contents);
|
|
return comp.addSourceFromOwnedBuffer(contents, path, kind);
|
|
}
|
|
|
|
/// Creates a Source from `buf` and adds it to the Compilation
|
|
/// Performs newline splicing and line-ending normalization to '\n'
|
|
/// `buf` will be modified and the allocation will be resized if newline splicing
|
|
/// or line-ending changes happen.
|
|
/// caller retains ownership of `path`
|
|
/// To add the contents of an arbitrary reader as a Source, see addSourceFromReader
|
|
/// To add a file's contents given its path, see addSourceFromPath
|
|
pub fn addSourceFromOwnedBuffer(comp: *Compilation, buf: []u8, path: []const u8, kind: Source.Kind) !Source {
|
|
try comp.sources.ensureUnusedCapacity(1);
|
|
|
|
var contents = buf;
|
|
const duped_path = try comp.gpa.dupe(u8, path);
|
|
errdefer comp.gpa.free(duped_path);
|
|
|
|
var splice_list = std.ArrayList(u32).init(comp.gpa);
|
|
defer splice_list.deinit();
|
|
|
|
const source_id: Source.Id = @enumFromInt(comp.sources.count() + 2);
|
|
|
|
var i: u32 = 0;
|
|
var backslash_loc: u32 = undefined;
|
|
var state: enum {
|
|
beginning_of_file,
|
|
bom1,
|
|
bom2,
|
|
start,
|
|
back_slash,
|
|
cr,
|
|
back_slash_cr,
|
|
trailing_ws,
|
|
} = .beginning_of_file;
|
|
var line: u32 = 1;
|
|
|
|
for (contents) |byte| {
|
|
contents[i] = byte;
|
|
|
|
switch (byte) {
|
|
'\r' => {
|
|
switch (state) {
|
|
.start, .cr, .beginning_of_file => {
|
|
state = .start;
|
|
line += 1;
|
|
state = .cr;
|
|
contents[i] = '\n';
|
|
i += 1;
|
|
},
|
|
.back_slash, .trailing_ws, .back_slash_cr => {
|
|
i = backslash_loc;
|
|
try splice_list.append(i);
|
|
if (state == .trailing_ws) {
|
|
try comp.diag.add(.{
|
|
.tag = .backslash_newline_escape,
|
|
.loc = .{ .id = source_id, .byte_offset = i, .line = line },
|
|
}, &.{});
|
|
}
|
|
state = if (state == .back_slash_cr) .cr else .back_slash_cr;
|
|
},
|
|
.bom1, .bom2 => break, // invalid utf-8
|
|
}
|
|
},
|
|
'\n' => {
|
|
switch (state) {
|
|
.start, .beginning_of_file => {
|
|
state = .start;
|
|
line += 1;
|
|
i += 1;
|
|
},
|
|
.cr, .back_slash_cr => {},
|
|
.back_slash, .trailing_ws => {
|
|
i = backslash_loc;
|
|
if (state == .back_slash or state == .trailing_ws) {
|
|
try splice_list.append(i);
|
|
}
|
|
if (state == .trailing_ws) {
|
|
try comp.diag.add(.{
|
|
.tag = .backslash_newline_escape,
|
|
.loc = .{ .id = source_id, .byte_offset = i, .line = line },
|
|
}, &.{});
|
|
}
|
|
},
|
|
.bom1, .bom2 => break,
|
|
}
|
|
state = .start;
|
|
},
|
|
'\\' => {
|
|
backslash_loc = i;
|
|
state = .back_slash;
|
|
i += 1;
|
|
},
|
|
'\t', '\x0B', '\x0C', ' ' => {
|
|
switch (state) {
|
|
.start, .trailing_ws => {},
|
|
.beginning_of_file => state = .start,
|
|
.cr, .back_slash_cr => state = .start,
|
|
.back_slash => state = .trailing_ws,
|
|
.bom1, .bom2 => break,
|
|
}
|
|
i += 1;
|
|
},
|
|
'\xEF' => {
|
|
i += 1;
|
|
state = switch (state) {
|
|
.beginning_of_file => .bom1,
|
|
else => .start,
|
|
};
|
|
},
|
|
'\xBB' => {
|
|
i += 1;
|
|
state = switch (state) {
|
|
.bom1 => .bom2,
|
|
else => .start,
|
|
};
|
|
},
|
|
'\xBF' => {
|
|
switch (state) {
|
|
.bom2 => i = 0, // rewind and overwrite the BOM
|
|
else => i += 1,
|
|
}
|
|
state = .start;
|
|
},
|
|
else => {
|
|
i += 1;
|
|
state = .start;
|
|
},
|
|
}
|
|
}
|
|
|
|
const splice_locs = try splice_list.toOwnedSlice();
|
|
errdefer comp.gpa.free(splice_locs);
|
|
|
|
if (i != contents.len) contents = try comp.gpa.realloc(contents, i);
|
|
errdefer @compileError("errdefers in callers would possibly free the realloced slice using the original len");
|
|
|
|
var source = Source{
|
|
.id = source_id,
|
|
.path = duped_path,
|
|
.buf = contents,
|
|
.splice_locs = splice_locs,
|
|
.kind = kind,
|
|
};
|
|
|
|
comp.sources.putAssumeCapacityNoClobber(duped_path, source);
|
|
return source;
|
|
}
|
|
|
|
/// Caller retains ownership of `path` and `buf`.
|
|
/// Dupes the source buffer; if it is acceptable to modify the source buffer and possibly resize
|
|
/// the allocation, please use `addSourceFromOwnedBuffer`
|
|
pub fn addSourceFromBuffer(comp: *Compilation, path: []const u8, buf: []const u8) !Source {
|
|
if (comp.sources.get(path)) |some| return some;
|
|
if (@as(u64, buf.len) > std.math.maxInt(u32)) return error.StreamTooLong;
|
|
|
|
const contents = try comp.gpa.dupe(u8, buf);
|
|
errdefer comp.gpa.free(contents);
|
|
|
|
return comp.addSourceFromOwnedBuffer(contents, path, .user);
|
|
}
|
|
|
|
/// Caller retains ownership of `path`.
|
|
pub fn addSourceFromPath(comp: *Compilation, path: []const u8) !Source {
|
|
return comp.addSourceFromPathExtra(path, .user);
|
|
}
|
|
|
|
/// Caller retains ownership of `path`.
|
|
fn addSourceFromPathExtra(comp: *Compilation, path: []const u8, kind: Source.Kind) !Source {
|
|
if (comp.sources.get(path)) |some| return some;
|
|
|
|
if (mem.indexOfScalar(u8, path, 0) != null) {
|
|
return error.FileNotFound;
|
|
}
|
|
|
|
const file = try std.fs.cwd().openFile(path, .{});
|
|
defer file.close();
|
|
|
|
const contents = file.readToEndAlloc(comp.gpa, std.math.maxInt(u32)) catch |err| switch (err) {
|
|
error.FileTooBig => return error.StreamTooLong,
|
|
else => |e| return e,
|
|
};
|
|
errdefer comp.gpa.free(contents);
|
|
|
|
return comp.addSourceFromOwnedBuffer(contents, path, kind);
|
|
}
|
|
|
|
pub const IncludeDirIterator = struct {
|
|
comp: *const Compilation,
|
|
cwd_source_id: ?Source.Id,
|
|
include_dirs_idx: usize = 0,
|
|
sys_include_dirs_idx: usize = 0,
|
|
tried_ms_cwd: bool = false,
|
|
|
|
const FoundSource = struct {
|
|
path: []const u8,
|
|
kind: Source.Kind,
|
|
};
|
|
|
|
fn next(self: *IncludeDirIterator) ?FoundSource {
|
|
if (self.cwd_source_id) |source_id| {
|
|
self.cwd_source_id = null;
|
|
const path = self.comp.getSource(source_id).path;
|
|
return .{ .path = std.fs.path.dirname(path) orelse ".", .kind = .user };
|
|
}
|
|
if (self.include_dirs_idx < self.comp.include_dirs.items.len) {
|
|
defer self.include_dirs_idx += 1;
|
|
return .{ .path = self.comp.include_dirs.items[self.include_dirs_idx], .kind = .user };
|
|
}
|
|
if (self.sys_include_dirs_idx < self.comp.system_include_dirs.items.len) {
|
|
defer self.sys_include_dirs_idx += 1;
|
|
return .{ .path = self.comp.system_include_dirs.items[self.sys_include_dirs_idx], .kind = .system };
|
|
}
|
|
if (self.comp.ms_cwd_source_id) |source_id| {
|
|
if (self.tried_ms_cwd) return null;
|
|
self.tried_ms_cwd = true;
|
|
const path = self.comp.getSource(source_id).path;
|
|
return .{ .path = std.fs.path.dirname(path) orelse ".", .kind = .user };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// Returned value's path field must be freed by allocator
|
|
fn nextWithFile(self: *IncludeDirIterator, filename: []const u8, allocator: Allocator) !?FoundSource {
|
|
while (self.next()) |found| {
|
|
const path = try std.fs.path.join(allocator, &.{ found.path, filename });
|
|
if (self.comp.langopts.ms_extensions) {
|
|
for (path) |*c| {
|
|
if (c.* == '\\') c.* = '/';
|
|
}
|
|
}
|
|
return .{ .path = path, .kind = found.kind };
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/// Advance the iterator until it finds an include directory that matches
|
|
/// the directory which contains `source`.
|
|
fn skipUntilDirMatch(self: *IncludeDirIterator, source: Source.Id) void {
|
|
const path = self.comp.getSource(source).path;
|
|
const includer_path = std.fs.path.dirname(path) orelse ".";
|
|
while (self.next()) |found| {
|
|
if (mem.eql(u8, includer_path, found.path)) break;
|
|
}
|
|
}
|
|
};
|
|
|
|
pub fn hasInclude(
|
|
comp: *const Compilation,
|
|
filename: []const u8,
|
|
includer_token_source: Source.Id,
|
|
/// angle bracket vs quotes
|
|
include_type: IncludeType,
|
|
/// __has_include vs __has_include_next
|
|
which: WhichInclude,
|
|
) !bool {
|
|
const cwd = std.fs.cwd();
|
|
if (std.fs.path.isAbsolute(filename)) {
|
|
if (which == .next) return false;
|
|
return !std.meta.isError(cwd.access(filename, .{}));
|
|
}
|
|
|
|
const cwd_source_id = switch (include_type) {
|
|
.quotes => switch (which) {
|
|
.first => includer_token_source,
|
|
.next => null,
|
|
},
|
|
.angle_brackets => null,
|
|
};
|
|
var it = IncludeDirIterator{ .comp = comp, .cwd_source_id = cwd_source_id };
|
|
if (which == .next) {
|
|
it.skipUntilDirMatch(includer_token_source);
|
|
}
|
|
|
|
var stack_fallback = std.heap.stackFallback(path_buf_stack_limit, comp.gpa);
|
|
|
|
while (try it.nextWithFile(filename, stack_fallback.get())) |found| {
|
|
defer stack_fallback.get().free(found.path);
|
|
if (!std.meta.isError(cwd.access(found.path, .{}))) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
pub const WhichInclude = enum {
|
|
first,
|
|
next,
|
|
};
|
|
|
|
pub const IncludeType = enum {
|
|
quotes,
|
|
angle_brackets,
|
|
};
|
|
|
|
fn getFileContents(comp: *Compilation, path: []const u8) ![]const u8 {
|
|
if (mem.indexOfScalar(u8, path, 0) != null) {
|
|
return error.FileNotFound;
|
|
}
|
|
|
|
const file = try std.fs.cwd().openFile(path, .{});
|
|
defer file.close();
|
|
|
|
return file.readToEndAlloc(comp.gpa, std.math.maxInt(u32));
|
|
}
|
|
|
|
pub fn findEmbed(
|
|
comp: *Compilation,
|
|
filename: []const u8,
|
|
includer_token_source: Source.Id,
|
|
/// angle bracket vs quotes
|
|
include_type: IncludeType,
|
|
) !?[]const u8 {
|
|
if (std.fs.path.isAbsolute(filename)) {
|
|
return if (comp.getFileContents(filename)) |some|
|
|
some
|
|
else |err| switch (err) {
|
|
error.OutOfMemory => |e| return e,
|
|
else => null,
|
|
};
|
|
}
|
|
|
|
const cwd_source_id = switch (include_type) {
|
|
.quotes => includer_token_source,
|
|
.angle_brackets => null,
|
|
};
|
|
var it = IncludeDirIterator{ .comp = comp, .cwd_source_id = cwd_source_id };
|
|
var stack_fallback = std.heap.stackFallback(path_buf_stack_limit, comp.gpa);
|
|
|
|
while (try it.nextWithFile(filename, stack_fallback.get())) |found| {
|
|
defer stack_fallback.get().free(found.path);
|
|
if (comp.getFileContents(found.path)) |some|
|
|
return some
|
|
else |err| switch (err) {
|
|
error.OutOfMemory => return error.OutOfMemory,
|
|
else => {},
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
pub fn findInclude(
|
|
comp: *Compilation,
|
|
filename: []const u8,
|
|
includer_token: Token,
|
|
/// angle bracket vs quotes
|
|
include_type: IncludeType,
|
|
/// include vs include_next
|
|
which: WhichInclude,
|
|
) !?Source {
|
|
if (std.fs.path.isAbsolute(filename)) {
|
|
if (which == .next) return null;
|
|
// TODO: classify absolute file as belonging to system includes or not?
|
|
return if (comp.addSourceFromPath(filename)) |some|
|
|
some
|
|
else |err| switch (err) {
|
|
error.OutOfMemory => |e| return e,
|
|
else => null,
|
|
};
|
|
}
|
|
const cwd_source_id = switch (include_type) {
|
|
.quotes => switch (which) {
|
|
.first => includer_token.source,
|
|
.next => null,
|
|
},
|
|
.angle_brackets => null,
|
|
};
|
|
var it = IncludeDirIterator{ .comp = comp, .cwd_source_id = cwd_source_id };
|
|
|
|
if (which == .next) {
|
|
it.skipUntilDirMatch(includer_token.source);
|
|
}
|
|
|
|
var stack_fallback = std.heap.stackFallback(path_buf_stack_limit, comp.gpa);
|
|
while (try it.nextWithFile(filename, stack_fallback.get())) |found| {
|
|
defer stack_fallback.get().free(found.path);
|
|
if (comp.addSourceFromPathExtra(found.path, found.kind)) |some| {
|
|
if (it.tried_ms_cwd) {
|
|
try comp.diag.add(.{
|
|
.tag = .ms_search_rule,
|
|
.extra = .{ .str = some.path },
|
|
.loc = .{
|
|
.id = includer_token.source,
|
|
.byte_offset = includer_token.start,
|
|
.line = includer_token.line,
|
|
},
|
|
}, &.{});
|
|
}
|
|
return some;
|
|
} else |err| switch (err) {
|
|
error.OutOfMemory => return error.OutOfMemory,
|
|
else => {},
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
pub fn addPragmaHandler(comp: *Compilation, name: []const u8, handler: *Pragma) Allocator.Error!void {
|
|
try comp.pragma_handlers.putNoClobber(name, handler);
|
|
}
|
|
|
|
pub fn addDefaultPragmaHandlers(comp: *Compilation) Allocator.Error!void {
|
|
const GCC = @import("pragmas/gcc.zig");
|
|
var gcc = try GCC.init(comp.gpa);
|
|
errdefer gcc.deinit(gcc, comp);
|
|
|
|
const Once = @import("pragmas/once.zig");
|
|
var once = try Once.init(comp.gpa);
|
|
errdefer once.deinit(once, comp);
|
|
|
|
const Message = @import("pragmas/message.zig");
|
|
var message = try Message.init(comp.gpa);
|
|
errdefer message.deinit(message, comp);
|
|
|
|
const Pack = @import("pragmas/pack.zig");
|
|
var pack = try Pack.init(comp.gpa);
|
|
errdefer pack.deinit(pack, comp);
|
|
|
|
try comp.addPragmaHandler("GCC", gcc);
|
|
try comp.addPragmaHandler("once", once);
|
|
try comp.addPragmaHandler("message", message);
|
|
try comp.addPragmaHandler("pack", pack);
|
|
}
|
|
|
|
pub fn getPragma(comp: *Compilation, name: []const u8) ?*Pragma {
|
|
return comp.pragma_handlers.get(name);
|
|
}
|
|
|
|
const PragmaEvent = enum {
|
|
before_preprocess,
|
|
before_parse,
|
|
after_parse,
|
|
};
|
|
|
|
pub fn pragmaEvent(comp: *Compilation, event: PragmaEvent) void {
|
|
for (comp.pragma_handlers.values()) |pragma| {
|
|
const maybe_func = switch (event) {
|
|
.before_preprocess => pragma.beforePreprocess,
|
|
.before_parse => pragma.beforeParse,
|
|
.after_parse => pragma.afterParse,
|
|
};
|
|
if (maybe_func) |func| func(pragma, comp);
|
|
}
|
|
}
|
|
|
|
pub fn hasBuiltin(comp: *const Compilation, name: []const u8) bool {
|
|
if (std.mem.eql(u8, name, "__builtin_va_arg") or
|
|
std.mem.eql(u8, name, "__builtin_choose_expr") or
|
|
std.mem.eql(u8, name, "__builtin_bitoffsetof") or
|
|
std.mem.eql(u8, name, "__builtin_offsetof") or
|
|
std.mem.eql(u8, name, "__builtin_types_compatible_p")) return true;
|
|
|
|
const builtin = BuiltinFunction.fromName(name) orelse return false;
|
|
return comp.hasBuiltinFunction(builtin);
|
|
}
|
|
|
|
pub fn hasBuiltinFunction(comp: *const Compilation, builtin: BuiltinFunction) bool {
|
|
if (!target_util.builtinEnabled(comp.target, builtin.properties.target_set)) return false;
|
|
|
|
switch (builtin.properties.language) {
|
|
.all_languages => return true,
|
|
.all_ms_languages => return comp.langopts.emulate == .msvc,
|
|
.gnu_lang, .all_gnu_languages => return comp.langopts.standard.isGNU(),
|
|
}
|
|
}
|
|
|
|
pub const renderErrors = Diagnostics.render;
|
|
|
|
test "addSourceFromReader" {
|
|
const Test = struct {
|
|
fn addSourceFromReader(str: []const u8, expected: []const u8, warning_count: u32, splices: []const u32) !void {
|
|
var comp = Compilation.init(std.testing.allocator);
|
|
defer comp.deinit();
|
|
|
|
var buf_reader = std.io.fixedBufferStream(str);
|
|
const source = try comp.addSourceFromReader(buf_reader.reader(), "path", .user);
|
|
|
|
try std.testing.expectEqualStrings(expected, source.buf);
|
|
try std.testing.expectEqual(warning_count, @as(u32, @intCast(comp.diag.list.items.len)));
|
|
try std.testing.expectEqualSlices(u32, splices, source.splice_locs);
|
|
}
|
|
|
|
fn withAllocationFailures(allocator: std.mem.Allocator) !void {
|
|
var comp = Compilation.init(allocator);
|
|
defer comp.deinit();
|
|
|
|
_ = try comp.addSourceFromBuffer("path", "spliced\\\nbuffer\n");
|
|
_ = try comp.addSourceFromBuffer("path", "non-spliced buffer\n");
|
|
}
|
|
};
|
|
try Test.addSourceFromReader("ab\\\nc", "abc", 0, &.{2});
|
|
try Test.addSourceFromReader("ab\\\rc", "abc", 0, &.{2});
|
|
try Test.addSourceFromReader("ab\\\r\nc", "abc", 0, &.{2});
|
|
try Test.addSourceFromReader("ab\\ \nc", "abc", 1, &.{2});
|
|
try Test.addSourceFromReader("ab\\\t\nc", "abc", 1, &.{2});
|
|
try Test.addSourceFromReader("ab\\ \t\nc", "abc", 1, &.{2});
|
|
try Test.addSourceFromReader("ab\\\r \nc", "ab \nc", 0, &.{2});
|
|
try Test.addSourceFromReader("ab\\\\\nc", "ab\\c", 0, &.{3});
|
|
try Test.addSourceFromReader("ab\\ \r\nc", "abc", 1, &.{2});
|
|
try Test.addSourceFromReader("ab\\ \\\nc", "ab\\ c", 0, &.{4});
|
|
try Test.addSourceFromReader("ab\\\r\\\nc", "abc", 0, &.{ 2, 2 });
|
|
try Test.addSourceFromReader("ab\\ \rc", "abc", 1, &.{2});
|
|
try Test.addSourceFromReader("ab\\", "ab\\", 0, &.{});
|
|
try Test.addSourceFromReader("ab\\\\", "ab\\\\", 0, &.{});
|
|
try Test.addSourceFromReader("ab\\ ", "ab\\ ", 0, &.{});
|
|
try Test.addSourceFromReader("ab\\\n", "ab", 0, &.{2});
|
|
try Test.addSourceFromReader("ab\\\r\n", "ab", 0, &.{2});
|
|
try Test.addSourceFromReader("ab\\\r", "ab", 0, &.{2});
|
|
|
|
// carriage return normalization
|
|
try Test.addSourceFromReader("ab\r", "ab\n", 0, &.{});
|
|
try Test.addSourceFromReader("ab\r\r", "ab\n\n", 0, &.{});
|
|
try Test.addSourceFromReader("ab\r\r\n", "ab\n\n", 0, &.{});
|
|
try Test.addSourceFromReader("ab\r\r\n\r", "ab\n\n\n", 0, &.{});
|
|
try Test.addSourceFromReader("\r\\", "\n\\", 0, &.{});
|
|
try Test.addSourceFromReader("\\\r\\", "\\", 0, &.{0});
|
|
|
|
try std.testing.checkAllAllocationFailures(std.testing.allocator, Test.withAllocationFailures, .{});
|
|
}
|
|
|
|
test "addSourceFromReader - exhaustive check for carriage return elimination" {
|
|
const alphabet = [_]u8{ '\r', '\n', ' ', '\\', 'a' };
|
|
const alen = alphabet.len;
|
|
var buf: [alphabet.len]u8 = [1]u8{alphabet[0]} ** alen;
|
|
|
|
var comp = Compilation.init(std.testing.allocator);
|
|
defer comp.deinit();
|
|
|
|
var source_count: u32 = 0;
|
|
|
|
while (true) {
|
|
const source = try comp.addSourceFromBuffer(&buf, &buf);
|
|
source_count += 1;
|
|
try std.testing.expect(std.mem.indexOfScalar(u8, source.buf, '\r') == null);
|
|
|
|
if (std.mem.allEqual(u8, &buf, alphabet[alen - 1])) break;
|
|
|
|
var idx = std.mem.indexOfScalar(u8, &alphabet, buf[buf.len - 1]).?;
|
|
buf[buf.len - 1] = alphabet[(idx + 1) % alen];
|
|
var j = buf.len - 1;
|
|
while (j > 0) : (j -= 1) {
|
|
idx = std.mem.indexOfScalar(u8, &alphabet, buf[j - 1]).?;
|
|
if (buf[j] == alphabet[0]) buf[j - 1] = alphabet[(idx + 1) % alen] else break;
|
|
}
|
|
}
|
|
try std.testing.expect(source_count == std.math.powi(usize, alen, alen) catch unreachable);
|
|
}
|
|
|
|
test "ignore BOM at beginning of file" {
|
|
const BOM = "\xEF\xBB\xBF";
|
|
|
|
const Test = struct {
|
|
fn run(buf: []const u8) !void {
|
|
var comp = Compilation.init(std.testing.allocator);
|
|
defer comp.deinit();
|
|
|
|
var buf_reader = std.io.fixedBufferStream(buf);
|
|
const source = try comp.addSourceFromReader(buf_reader.reader(), "file.c", .user);
|
|
const expected_output = if (mem.startsWith(u8, buf, BOM)) buf[BOM.len..] else buf;
|
|
try std.testing.expectEqualStrings(expected_output, source.buf);
|
|
}
|
|
};
|
|
|
|
try Test.run(BOM);
|
|
try Test.run(BOM ++ "x");
|
|
try Test.run("x" ++ BOM);
|
|
try Test.run(BOM ++ " ");
|
|
try Test.run(BOM ++ "\n");
|
|
try Test.run(BOM ++ "\\");
|
|
|
|
try Test.run(BOM[0..1] ++ "x");
|
|
try Test.run(BOM[0..2] ++ "x");
|
|
try Test.run(BOM[1..] ++ "x");
|
|
try Test.run(BOM[2..] ++ "x");
|
|
}
|