mirror of
https://github.com/ziglang/zig.git
synced 2026-01-19 13:55:20 +00:00
1298 lines
45 KiB
Zig
1298 lines
45 KiB
Zig
const std = @import("std");
|
|
const mem = std.mem;
|
|
const ZigType = std.builtin.Type;
|
|
const CallingConvention = @import("../backend.zig").CallingConvention;
|
|
const Compilation = @import("Compilation.zig");
|
|
const Diagnostics = @import("Diagnostics.zig");
|
|
const Parser = @import("Parser.zig");
|
|
const Tree = @import("Tree.zig");
|
|
const TokenIndex = Tree.TokenIndex;
|
|
const QualType = @import("TypeStore.zig").QualType;
|
|
const Value = @import("Value.zig");
|
|
|
|
const Attribute = @This();
|
|
|
|
tag: Tag,
|
|
syntax: Syntax,
|
|
args: Arguments,
|
|
|
|
pub const Syntax = enum {
|
|
c23,
|
|
declspec,
|
|
gnu,
|
|
keyword,
|
|
};
|
|
|
|
pub const Kind = enum {
|
|
c23,
|
|
declspec,
|
|
gnu,
|
|
|
|
pub fn toSyntax(kind: Kind) Syntax {
|
|
return switch (kind) {
|
|
.c23 => .c23,
|
|
.declspec => .declspec,
|
|
.gnu => .gnu,
|
|
};
|
|
}
|
|
};
|
|
|
|
pub const Iterator = struct {
|
|
source: ?struct {
|
|
qt: QualType,
|
|
comp: *const Compilation,
|
|
},
|
|
slice: []const Attribute,
|
|
index: usize,
|
|
|
|
pub fn initSlice(slice: []const Attribute) Iterator {
|
|
return .{ .source = null, .slice = slice, .index = 0 };
|
|
}
|
|
|
|
pub fn initType(qt: QualType, comp: *const Compilation) Iterator {
|
|
return .{ .source = .{ .qt = qt, .comp = comp }, .slice = &.{}, .index = 0 };
|
|
}
|
|
|
|
/// returns the next attribute as well as its index within the slice or current type
|
|
/// The index can be used to determine when a nested type has been recursed into
|
|
pub fn next(self: *Iterator) ?struct { Attribute, usize } {
|
|
if (self.index < self.slice.len) {
|
|
defer self.index += 1;
|
|
return .{ self.slice[self.index], self.index };
|
|
}
|
|
if (self.source) |*source| {
|
|
if (source.qt.isInvalid()) {
|
|
self.source = null;
|
|
return null;
|
|
}
|
|
loop: switch (source.qt.type(source.comp)) {
|
|
.typeof => |typeof| continue :loop typeof.base.type(source.comp),
|
|
.attributed => |attributed| {
|
|
self.slice = attributed.attributes;
|
|
self.index = 1;
|
|
source.qt = attributed.base;
|
|
return .{ self.slice[0], 0 };
|
|
},
|
|
.typedef => |typedef| continue :loop typedef.base.type(source.comp),
|
|
else => self.source = null,
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
};
|
|
|
|
/// number of required arguments
|
|
pub fn requiredArgCount(attr: Tag) u32 {
|
|
switch (attr) {
|
|
inline else => |tag| {
|
|
comptime var needed = 0;
|
|
comptime {
|
|
const fields = @typeInfo(@field(attributes, @tagName(tag))).@"struct".fields;
|
|
for (fields) |arg_field| {
|
|
if (!mem.eql(u8, arg_field.name, "__name_tok") and @typeInfo(arg_field.type) != .optional) needed += 1;
|
|
}
|
|
}
|
|
return needed;
|
|
},
|
|
}
|
|
}
|
|
|
|
/// maximum number of args that can be passed
|
|
pub fn maxArgCount(attr: Tag) u32 {
|
|
switch (attr) {
|
|
inline else => |tag| {
|
|
comptime var max = 0;
|
|
comptime {
|
|
const fields = @typeInfo(@field(attributes, @tagName(tag))).@"struct".fields;
|
|
for (fields) |arg_field| {
|
|
if (!mem.eql(u8, arg_field.name, "__name_tok")) max += 1;
|
|
}
|
|
}
|
|
return max;
|
|
},
|
|
}
|
|
}
|
|
|
|
fn UnwrapOptional(comptime T: type) type {
|
|
return switch (@typeInfo(T)) {
|
|
.optional => |optional| optional.child,
|
|
else => T,
|
|
};
|
|
}
|
|
|
|
pub const Formatting = struct {
|
|
/// The quote char (single or double) to use when printing identifiers/strings corresponding
|
|
/// to the enum in the first field of the `attr`. Identifier enums use single quotes, string enums
|
|
/// use double quotes
|
|
fn quoteChar(attr: Tag) []const u8 {
|
|
switch (attr) {
|
|
.calling_convention => unreachable,
|
|
inline else => |tag| {
|
|
const fields = @typeInfo(@field(attributes, @tagName(tag))).@"struct".fields;
|
|
|
|
if (fields.len == 0) unreachable;
|
|
const Unwrapped = UnwrapOptional(fields[0].type);
|
|
if (@typeInfo(Unwrapped) != .@"enum") unreachable;
|
|
|
|
return if (Unwrapped.opts.enum_kind == .identifier) "'" else "\"";
|
|
},
|
|
}
|
|
}
|
|
|
|
/// returns a comma-separated string of quoted enum values, representing the valid
|
|
/// choices for the string or identifier enum of the first field of the `attr`.
|
|
pub fn choices(attr: Tag) []const u8 {
|
|
switch (attr) {
|
|
.calling_convention => unreachable,
|
|
inline else => |tag| {
|
|
const fields = @typeInfo(@field(attributes, @tagName(tag))).@"struct".fields;
|
|
|
|
if (fields.len == 0) unreachable;
|
|
const Unwrapped = UnwrapOptional(fields[0].type);
|
|
if (@typeInfo(Unwrapped) != .@"enum") unreachable;
|
|
|
|
const enum_fields = @typeInfo(Unwrapped).@"enum".fields;
|
|
const quote = comptime quoteChar(@enumFromInt(@intFromEnum(tag)));
|
|
comptime var values: []const u8 = quote ++ enum_fields[0].name ++ quote;
|
|
inline for (enum_fields[1..]) |enum_field| {
|
|
values = values ++ ", ";
|
|
values = values ++ quote ++ enum_field.name ++ quote;
|
|
}
|
|
return values;
|
|
},
|
|
}
|
|
}
|
|
};
|
|
|
|
/// Checks if the first argument (if it exists) is an identifier enum
|
|
pub fn wantsIdentEnum(attr: Tag) bool {
|
|
switch (attr) {
|
|
.calling_convention => return false,
|
|
inline else => |tag| {
|
|
const fields = @typeInfo(@field(attributes, @tagName(tag))).@"struct".fields;
|
|
|
|
if (fields.len == 0) return false;
|
|
const Unwrapped = UnwrapOptional(fields[0].type);
|
|
if (@typeInfo(Unwrapped) != .@"enum") return false;
|
|
|
|
return Unwrapped.opts.enum_kind == .identifier;
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn diagnoseIdent(attr: Tag, arguments: *Arguments, ident: TokenIndex, p: *Parser) !bool {
|
|
switch (attr) {
|
|
inline else => |tag| {
|
|
const fields = @typeInfo(@field(attributes, @tagName(tag))).@"struct".fields;
|
|
if (fields.len == 0) unreachable;
|
|
const Unwrapped = UnwrapOptional(fields[0].type);
|
|
if (@typeInfo(Unwrapped) != .@"enum") unreachable;
|
|
if (std.meta.stringToEnum(Unwrapped, normalize(p.tokSlice(ident)))) |enum_val| {
|
|
@field(@field(arguments, @tagName(tag)), fields[0].name) = enum_val;
|
|
return false;
|
|
}
|
|
|
|
try p.err(ident, .unknown_attr_enum, .{ @tagName(attr), Formatting.choices(attr) });
|
|
return true;
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn wantsAlignment(attr: Tag, idx: usize) bool {
|
|
switch (attr) {
|
|
inline else => |tag| {
|
|
const fields = @typeInfo(@field(attributes, @tagName(tag))).@"struct".fields;
|
|
if (fields.len == 0) return false;
|
|
|
|
return switch (idx) {
|
|
inline 0...fields.len - 1 => |i| UnwrapOptional(fields[i].type) == Alignment,
|
|
else => false,
|
|
};
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn diagnoseAlignment(attr: Tag, arguments: *Arguments, arg_idx: u32, res: Parser.Result, arg_start: TokenIndex, p: *Parser) !bool {
|
|
switch (attr) {
|
|
inline else => |tag| {
|
|
const arg_fields = @typeInfo(@field(attributes, @tagName(tag))).@"struct".fields;
|
|
if (arg_fields.len == 0) unreachable;
|
|
|
|
switch (arg_idx) {
|
|
inline 0...arg_fields.len - 1 => |arg_i| {
|
|
if (UnwrapOptional(arg_fields[arg_i].type) != Alignment) unreachable;
|
|
|
|
if (!res.val.is(.int, p.comp)) {
|
|
try p.err(arg_start, .alignas_unavailable, .{});
|
|
return true;
|
|
}
|
|
if (res.val.compare(.lt, Value.zero, p.comp)) {
|
|
try p.err(arg_start, .negative_alignment, .{res});
|
|
return true;
|
|
}
|
|
const requested = res.val.toInt(u29, p.comp) orelse {
|
|
try p.err(arg_start, .maximum_alignment, .{res});
|
|
return true;
|
|
};
|
|
if (!std.mem.isValidAlign(requested)) {
|
|
try p.err(arg_start, .non_pow2_align, .{});
|
|
return true;
|
|
}
|
|
|
|
@field(@field(arguments, @tagName(tag)), arg_fields[arg_i].name) = .{ .requested = requested };
|
|
return false;
|
|
},
|
|
else => unreachable,
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
fn diagnoseField(
|
|
comptime decl: ZigType.Declaration,
|
|
comptime field: ZigType.StructField,
|
|
comptime Wanted: type,
|
|
arguments: *Arguments,
|
|
res: Parser.Result,
|
|
arg_start: TokenIndex,
|
|
node: Tree.Node,
|
|
p: *Parser,
|
|
) !bool {
|
|
const string = "a string";
|
|
const identifier = "an identifier";
|
|
const int = "an integer constant";
|
|
const alignment = "an integer constant";
|
|
const nullptr_t = "nullptr";
|
|
const float = "a floating point number";
|
|
const complex_float = "a complex floating point number";
|
|
const expression = "an expression";
|
|
|
|
const expected: []const u8 = switch (Wanted) {
|
|
Value => string,
|
|
Identifier => identifier,
|
|
u32 => int,
|
|
Alignment => alignment,
|
|
CallingConvention => identifier,
|
|
else => switch (@typeInfo(Wanted)) {
|
|
.@"enum" => if (Wanted.opts.enum_kind == .string) string else identifier,
|
|
else => unreachable,
|
|
},
|
|
};
|
|
|
|
if (res.val.opt_ref == .none) {
|
|
if (Wanted == Identifier and node == .decl_ref_expr) {
|
|
@field(@field(arguments, decl.name), field.name) = .{ .tok = node.decl_ref_expr.name_tok };
|
|
return false;
|
|
}
|
|
|
|
try p.err(arg_start, .attribute_arg_invalid, .{ expected, expression });
|
|
return true;
|
|
}
|
|
const key = p.comp.interner.get(res.val.ref());
|
|
switch (key) {
|
|
.int => {
|
|
if (@typeInfo(Wanted) == .int) {
|
|
@field(@field(arguments, decl.name), field.name) = res.val.toInt(Wanted, p.comp) orelse {
|
|
try p.err(arg_start, .attribute_int_out_of_range, .{res});
|
|
return true;
|
|
};
|
|
|
|
return false;
|
|
}
|
|
},
|
|
.bytes => |bytes| {
|
|
if (Wanted == Value) {
|
|
validate: {
|
|
if (node != .string_literal_expr) break :validate;
|
|
switch (node.string_literal_expr.qt.childType(p.comp).get(p.comp, .int).?) {
|
|
.char, .uchar, .schar => {},
|
|
else => break :validate,
|
|
}
|
|
@field(@field(arguments, decl.name), field.name) = try p.removeNull(res.val);
|
|
return false;
|
|
}
|
|
|
|
try p.err(arg_start, .attribute_requires_string, .{decl.name});
|
|
return true;
|
|
} else if (@typeInfo(Wanted) == .@"enum" and @hasDecl(Wanted, "opts") and Wanted.opts.enum_kind == .string) {
|
|
const str = bytes[0 .. bytes.len - 1];
|
|
if (std.meta.stringToEnum(Wanted, str)) |enum_val| {
|
|
@field(@field(arguments, decl.name), field.name) = enum_val;
|
|
return false;
|
|
}
|
|
|
|
try p.err(arg_start, .unknown_attr_enum, .{ decl.name, Formatting.choices(@field(Tag, decl.name)) });
|
|
return true;
|
|
}
|
|
},
|
|
else => {},
|
|
}
|
|
|
|
try p.err(arg_start, .attribute_arg_invalid, .{ expected, switch (key) {
|
|
.int => int,
|
|
.bytes => string,
|
|
.float => float,
|
|
.complex => complex_float,
|
|
.null => nullptr_t,
|
|
else => unreachable,
|
|
} });
|
|
return true;
|
|
}
|
|
|
|
pub fn diagnose(attr: Tag, arguments: *Arguments, arg_idx: u32, res: Parser.Result, arg_start: TokenIndex, node: Tree.Node, p: *Parser) !bool {
|
|
switch (attr) {
|
|
.nonnull => return false,
|
|
inline else => |tag| {
|
|
const decl = @typeInfo(attributes).@"struct".decls[@intFromEnum(tag)];
|
|
const max_arg_count = comptime maxArgCount(tag);
|
|
if (arg_idx >= max_arg_count) {
|
|
try p.err(arg_start, .attribute_too_many_args, .{ @tagName(attr), max_arg_count });
|
|
return true;
|
|
}
|
|
|
|
const arg_fields = @typeInfo(@field(attributes, decl.name)).@"struct".fields;
|
|
switch (arg_idx) {
|
|
inline 0...arg_fields.len - 1 => |arg_i| {
|
|
return diagnoseField(decl, arg_fields[arg_i], UnwrapOptional(arg_fields[arg_i].type), arguments, res, arg_start, node, p);
|
|
},
|
|
else => unreachable,
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
const EnumTypes = enum {
|
|
string,
|
|
identifier,
|
|
};
|
|
pub const Alignment = struct {
|
|
node: Tree.Node.OptIndex = .null,
|
|
requested: u32,
|
|
};
|
|
pub const Identifier = struct {
|
|
tok: TokenIndex = 0,
|
|
};
|
|
|
|
const attributes = struct {
|
|
pub const access = struct {
|
|
access_mode: enum {
|
|
read_only,
|
|
read_write,
|
|
write_only,
|
|
none,
|
|
|
|
const opts = struct {
|
|
const enum_kind = .identifier;
|
|
};
|
|
},
|
|
ref_index: u32,
|
|
size_index: ?u32 = null,
|
|
};
|
|
pub const alias = struct {
|
|
alias: Value,
|
|
};
|
|
pub const aligned = struct {
|
|
alignment: ?Alignment = null,
|
|
__name_tok: TokenIndex,
|
|
};
|
|
pub const alloc_align = struct {
|
|
position: u32,
|
|
};
|
|
pub const alloc_size = struct {
|
|
position_1: u32,
|
|
position_2: ?u32 = null,
|
|
};
|
|
pub const allocate = struct {
|
|
segname: Value,
|
|
};
|
|
pub const allocator = struct {};
|
|
pub const always_inline = struct {};
|
|
pub const appdomain = struct {};
|
|
pub const artificial = struct {};
|
|
pub const assume_aligned = struct {
|
|
alignment: Alignment,
|
|
offset: ?u32 = null,
|
|
};
|
|
pub const cleanup = struct {
|
|
function: Identifier,
|
|
};
|
|
pub const code_seg = struct {
|
|
segname: Value,
|
|
};
|
|
pub const cold = struct {};
|
|
pub const common = struct {};
|
|
pub const @"const" = struct {};
|
|
pub const constructor = struct {
|
|
priority: ?u32 = null,
|
|
};
|
|
pub const copy = struct {
|
|
function: Identifier,
|
|
};
|
|
pub const deprecated = struct {
|
|
msg: ?Value = null,
|
|
__name_tok: TokenIndex,
|
|
};
|
|
pub const designated_init = struct {};
|
|
pub const destructor = struct {
|
|
priority: ?u32 = null,
|
|
};
|
|
pub const dllexport = struct {};
|
|
pub const dllimport = struct {};
|
|
pub const @"error" = struct {
|
|
msg: Value,
|
|
__name_tok: TokenIndex,
|
|
};
|
|
pub const externally_visible = struct {};
|
|
pub const fallthrough = struct {};
|
|
pub const flatten = struct {};
|
|
pub const format = struct {
|
|
archetype: enum {
|
|
printf,
|
|
scanf,
|
|
strftime,
|
|
strfmon,
|
|
|
|
const opts = struct {
|
|
const enum_kind = .identifier;
|
|
};
|
|
},
|
|
string_index: u32,
|
|
first_to_check: u32,
|
|
};
|
|
pub const format_arg = struct {
|
|
string_index: u32,
|
|
};
|
|
pub const gnu_inline = struct {};
|
|
pub const hot = struct {};
|
|
pub const ifunc = struct {
|
|
resolver: Value,
|
|
};
|
|
pub const interrupt = struct {};
|
|
pub const interrupt_handler = struct {};
|
|
pub const jitintrinsic = struct {};
|
|
pub const leaf = struct {};
|
|
pub const malloc = struct {};
|
|
pub const may_alias = struct {};
|
|
pub const mode = struct {
|
|
mode: enum {
|
|
// zig fmt: off
|
|
byte, word, pointer,
|
|
BI, QI, HI,
|
|
PSI, SI, PDI,
|
|
DI, TI, OI,
|
|
XI, QF, HF,
|
|
TQF, SF, DF,
|
|
XF, SD, DD,
|
|
TD, TF, QQ,
|
|
HQ, SQ, DQ,
|
|
TQ, UQQ, UHQ,
|
|
USQ, UDQ, UTQ,
|
|
HA, SA, DA,
|
|
TA, UHA, USA,
|
|
UDA, UTA, CC,
|
|
BLK, VOID, QC,
|
|
HC, SC, DC,
|
|
XC, TC, CQI,
|
|
CHI, CSI, CDI,
|
|
CTI, COI, CPSI,
|
|
BND32, BND64,
|
|
// zig fmt: on
|
|
|
|
const opts = struct {
|
|
const enum_kind = .identifier;
|
|
};
|
|
},
|
|
};
|
|
pub const naked = struct {};
|
|
pub const no_address_safety_analysis = struct {};
|
|
pub const no_icf = struct {};
|
|
pub const no_instrument_function = struct {};
|
|
pub const no_profile_instrument_function = struct {};
|
|
pub const no_reorder = struct {};
|
|
pub const no_sanitize = struct {
|
|
/// Todo: represent args as union?
|
|
alignment: Value,
|
|
object_size: ?Value = null,
|
|
};
|
|
pub const no_sanitize_address = struct {};
|
|
pub const no_sanitize_coverage = struct {};
|
|
pub const no_sanitize_thread = struct {};
|
|
pub const no_sanitize_undefined = struct {};
|
|
pub const no_split_stack = struct {};
|
|
pub const no_stack_limit = struct {};
|
|
pub const no_stack_protector = struct {};
|
|
pub const @"noalias" = struct {};
|
|
pub const noclone = struct {};
|
|
pub const nocommon = struct {};
|
|
pub const nodiscard = struct {};
|
|
pub const noinit = struct {};
|
|
pub const @"noinline" = struct {};
|
|
pub const noipa = struct {};
|
|
// TODO: arbitrary number of arguments
|
|
pub const nonnull = struct {};
|
|
pub const nonstring = struct {};
|
|
pub const noplt = struct {};
|
|
pub const @"noreturn" = struct {};
|
|
pub const nothrow = struct {};
|
|
// TODO: union args ?
|
|
// const optimize = struct {
|
|
// // optimize, // u32 | []const u8 -- optimize?
|
|
// };
|
|
// };
|
|
pub const @"packed" = struct {};
|
|
pub const patchable_function_entry = struct {};
|
|
pub const persistent = struct {};
|
|
pub const process = struct {};
|
|
pub const pure = struct {};
|
|
pub const reproducible = struct {};
|
|
pub const restrict = struct {};
|
|
pub const retain = struct {};
|
|
pub const returns_nonnull = struct {};
|
|
pub const returns_twice = struct {};
|
|
pub const safebuffers = struct {};
|
|
pub const scalar_storage_order = struct {
|
|
order: enum {
|
|
@"little-endian",
|
|
@"big-endian",
|
|
|
|
const opts = struct {
|
|
const enum_kind = .string;
|
|
};
|
|
},
|
|
};
|
|
pub const section = struct {
|
|
name: Value,
|
|
};
|
|
pub const selectany = struct {};
|
|
pub const sentinel = struct {
|
|
position: ?u32 = null,
|
|
};
|
|
pub const simd = struct {
|
|
mask: ?enum {
|
|
notinbranch,
|
|
inbranch,
|
|
|
|
const opts = struct {
|
|
const enum_kind = .string;
|
|
};
|
|
} = null,
|
|
};
|
|
pub const spectre = struct {
|
|
arg: enum {
|
|
nomitigation,
|
|
|
|
const opts = struct {
|
|
const enum_kind = .identifier;
|
|
};
|
|
},
|
|
};
|
|
pub const stack_protect = struct {};
|
|
pub const symver = struct {
|
|
version: Value, // TODO: validate format "name2@nodename"
|
|
|
|
};
|
|
pub const target = struct {
|
|
options: Value, // TODO: multiple arguments
|
|
|
|
};
|
|
pub const target_clones = struct {
|
|
options: Value, // TODO: multiple arguments
|
|
|
|
};
|
|
pub const thread = struct {};
|
|
pub const tls_model = struct {
|
|
model: enum {
|
|
@"global-dynamic",
|
|
@"local-dynamic",
|
|
@"initial-exec",
|
|
@"local-exec",
|
|
|
|
const opts = struct {
|
|
const enum_kind = .string;
|
|
};
|
|
},
|
|
};
|
|
pub const transparent_union = struct {};
|
|
pub const unavailable = struct {
|
|
msg: ?Value = null,
|
|
__name_tok: TokenIndex,
|
|
};
|
|
pub const uninitialized = struct {};
|
|
pub const unsequenced = struct {};
|
|
pub const unused = struct {};
|
|
pub const used = struct {};
|
|
pub const uuid = struct {
|
|
uuid: Value,
|
|
};
|
|
pub const vector_size = struct {
|
|
bytes: u32, // TODO: validate "The bytes argument must be a positive power-of-two multiple of the base type size"
|
|
|
|
};
|
|
pub const visibility = struct {
|
|
visibility_type: enum {
|
|
default,
|
|
hidden,
|
|
internal,
|
|
protected,
|
|
|
|
const opts = struct {
|
|
const enum_kind = .string;
|
|
};
|
|
},
|
|
};
|
|
pub const warn_if_not_aligned = struct {
|
|
alignment: Alignment,
|
|
};
|
|
pub const warn_unused_result = struct {};
|
|
pub const warning = struct {
|
|
msg: Value,
|
|
__name_tok: TokenIndex,
|
|
};
|
|
pub const weak = struct {};
|
|
pub const weakref = struct {
|
|
target: ?Value = null,
|
|
};
|
|
pub const zero_call_used_regs = struct {
|
|
choice: enum {
|
|
skip,
|
|
used,
|
|
@"used-gpr",
|
|
@"used-arg",
|
|
@"used-gpr-arg",
|
|
all,
|
|
@"all-gpr",
|
|
@"all-arg",
|
|
@"all-gpr-arg",
|
|
|
|
const opts = struct {
|
|
const enum_kind = .string;
|
|
};
|
|
},
|
|
};
|
|
pub const asm_label = struct {
|
|
name: Value,
|
|
};
|
|
pub const calling_convention = struct {
|
|
cc: CallingConvention,
|
|
};
|
|
pub const nullability = struct {
|
|
kind: enum {
|
|
nonnull,
|
|
nullable,
|
|
nullable_result,
|
|
unspecified,
|
|
|
|
const opts = struct {
|
|
const enum_kind = .identifier;
|
|
};
|
|
},
|
|
};
|
|
pub const unaligned = struct {};
|
|
pub const pcs = struct {
|
|
kind: enum {
|
|
aapcs,
|
|
@"aapcs-vfp",
|
|
|
|
const opts = struct {
|
|
const enum_kind = .string;
|
|
};
|
|
},
|
|
};
|
|
pub const riscv_vector_cc = struct {};
|
|
pub const aarch64_sve_pcs = struct {};
|
|
pub const aarch64_vector_pcs = struct {};
|
|
pub const fastcall = struct {};
|
|
pub const stdcall = struct {};
|
|
pub const vectorcall = struct {};
|
|
pub const cdecl = struct {};
|
|
pub const thiscall = struct {};
|
|
pub const sysv_abi = struct {};
|
|
pub const ms_abi = struct {};
|
|
// TODO cannot be combined with weak or selectany
|
|
pub const internal_linkage = struct {};
|
|
pub const availability = struct {};
|
|
};
|
|
|
|
pub const Tag = std.meta.DeclEnum(attributes);
|
|
|
|
pub const Arguments = blk: {
|
|
const decls = @typeInfo(attributes).@"struct".decls;
|
|
var names: [decls.len][]const u8 = undefined;
|
|
var types: [decls.len]type = undefined;
|
|
for (decls, &names, &types) |decl, *name, *T| {
|
|
name.* = decl.name;
|
|
T.* = @field(attributes, decl.name);
|
|
}
|
|
break :blk @Union(.auto, null, &names, &types, &@splat(.{}));
|
|
};
|
|
|
|
pub fn ArgumentsForTag(comptime tag: Tag) type {
|
|
const decl = @typeInfo(attributes).@"struct".decls[@intFromEnum(tag)];
|
|
return @field(attributes, decl.name);
|
|
}
|
|
|
|
pub fn initArguments(tag: Tag, name_tok: TokenIndex) Arguments {
|
|
switch (tag) {
|
|
inline else => |arg_tag| {
|
|
const union_element = @field(attributes, @tagName(arg_tag));
|
|
const init = std.mem.zeroInit(union_element, .{});
|
|
var args = @unionInit(Arguments, @tagName(arg_tag), init);
|
|
if (@hasField(@field(attributes, @tagName(arg_tag)), "__name_tok")) {
|
|
@field(args, @tagName(arg_tag)).__name_tok = name_tok;
|
|
}
|
|
return args;
|
|
},
|
|
}
|
|
}
|
|
|
|
pub fn fromString(kind: Kind, namespace: ?[]const u8, name: []const u8) ?Tag {
|
|
const Properties = struct {
|
|
tag: Tag,
|
|
gnu: bool = false,
|
|
declspec: bool = false,
|
|
c23: bool = false,
|
|
};
|
|
const attribute_names = @import("Attribute/names.zig").with(Properties);
|
|
|
|
const normalized = normalize(name);
|
|
const actual_kind: Kind = if (namespace) |ns| blk: {
|
|
const normalized_ns = normalize(ns);
|
|
if (mem.eql(u8, normalized_ns, "gnu")) {
|
|
break :blk .gnu;
|
|
}
|
|
return null;
|
|
} else kind;
|
|
|
|
const tag_and_opts = attribute_names.fromName(normalized) orelse return null;
|
|
switch (actual_kind) {
|
|
inline else => |available_kind| {
|
|
if (@field(tag_and_opts, @tagName(available_kind)))
|
|
return tag_and_opts.tag;
|
|
},
|
|
}
|
|
return null;
|
|
}
|
|
|
|
pub fn normalize(name: []const u8) []const u8 {
|
|
if (name.len >= 4 and mem.startsWith(u8, name, "__") and mem.endsWith(u8, name, "__")) {
|
|
return name[2 .. name.len - 2];
|
|
}
|
|
return name;
|
|
}
|
|
|
|
fn ignoredAttrErr(p: *Parser, tok: TokenIndex, attr: Attribute.Tag, context: []const u8) !void {
|
|
try p.err(tok, .ignored_attribute, .{ @tagName(attr), context });
|
|
}
|
|
|
|
pub fn applyParameterAttributes(p: *Parser, qt: QualType, attr_buf_start: usize, diagnostic: ?Parser.Diagnostic) !QualType {
|
|
return applyVariableOrParameterAttributes(p, qt, attr_buf_start, diagnostic, .parameter);
|
|
}
|
|
|
|
pub fn applyVariableAttributes(p: *Parser, qt: QualType, attr_buf_start: usize, diagnostic: ?Parser.Diagnostic) !QualType {
|
|
return applyVariableOrParameterAttributes(p, qt, attr_buf_start, diagnostic, .variable);
|
|
}
|
|
|
|
fn applyVariableOrParameterAttributes(p: *Parser, qt: QualType, attr_buf_start: usize, diagnostic: ?Parser.Diagnostic, context: enum { parameter, variable }) !QualType {
|
|
const gpa = p.comp.gpa;
|
|
const attrs = p.attr_buf.items(.attr)[attr_buf_start..];
|
|
const toks = p.attr_buf.items(.tok)[attr_buf_start..];
|
|
p.attr_application_buf.items.len = 0;
|
|
var base_qt = qt;
|
|
var common = false;
|
|
var nocommon = false;
|
|
for (attrs, toks) |attr, tok| switch (attr.tag) {
|
|
// zig fmt: off
|
|
.alias, .may_alias, .deprecated, .unavailable, .unused, .warn_if_not_aligned, .weak, .used,
|
|
.noinit, .retain, .persistent, .section, .mode, .asm_label, .nullability, .unaligned, .selectany, .internal_linkage,
|
|
=> try p.attr_application_buf.append(gpa, attr),
|
|
// zig fmt: on
|
|
.common => if (nocommon) {
|
|
try p.err(tok, .ignore_common, .{});
|
|
} else {
|
|
try p.attr_application_buf.append(gpa, attr);
|
|
common = true;
|
|
},
|
|
.nocommon => if (common) {
|
|
try p.err(tok, .ignore_nocommon, .{});
|
|
} else {
|
|
try p.attr_application_buf.append(gpa, attr);
|
|
nocommon = true;
|
|
},
|
|
.vector_size => try attr.applyVectorSize(p, tok, &base_qt),
|
|
.aligned => try attr.applyAligned(p, base_qt, diagnostic),
|
|
.nonnull => {
|
|
switch (context) {
|
|
.parameter => try p.err(tok, .attribute_todo, .{ "nonnull", "parameters" }),
|
|
.variable => try p.err(tok, .nonnull_not_applicable, .{}),
|
|
}
|
|
},
|
|
.nonstring => {
|
|
if (base_qt.get(p.comp, .array)) |array_ty| {
|
|
if (array_ty.elem.get(p.comp, .int)) |int_ty| switch (int_ty) {
|
|
.char, .uchar, .schar => {
|
|
try p.attr_application_buf.append(gpa, attr);
|
|
continue;
|
|
},
|
|
else => {},
|
|
};
|
|
}
|
|
try p.err(tok, .non_string_ignored, .{qt});
|
|
},
|
|
.uninitialized => if (p.func.qt == null) {
|
|
try p.err(tok, .local_variable_attribute, .{"uninitialized"});
|
|
} else {
|
|
try p.attr_application_buf.append(gpa, attr);
|
|
},
|
|
.cleanup => if (p.func.qt == null) {
|
|
try p.err(tok, .local_variable_attribute, .{"cleanup"});
|
|
} else {
|
|
try p.attr_application_buf.append(gpa, attr);
|
|
},
|
|
.calling_convention => try applyCallingConvention(attr, p, tok, base_qt),
|
|
.alloc_size,
|
|
.copy,
|
|
.tls_model,
|
|
.visibility,
|
|
=> |t| try p.err(tok, .attribute_todo, .{ @tagName(t), "variables" }),
|
|
// There is already an error in Parser for _Noreturn keyword
|
|
.noreturn => if (attr.syntax != .keyword) try ignoredAttrErr(p, tok, attr.tag, "variables"),
|
|
else => try ignoredAttrErr(p, tok, attr.tag, "variables"),
|
|
};
|
|
return applySelected(base_qt, p);
|
|
}
|
|
|
|
pub fn applyFieldAttributes(p: *Parser, field_qt: *QualType, attr_buf_start: usize) ![]const Attribute {
|
|
const attrs = p.attr_buf.items(.attr)[attr_buf_start..];
|
|
const seen = p.attr_buf.items(.seen)[attr_buf_start..];
|
|
p.attr_application_buf.items.len = 0;
|
|
for (attrs, 0..) |attr, i| switch (attr.tag) {
|
|
.@"packed" => {
|
|
try p.attr_application_buf.append(p.comp.gpa, attr);
|
|
seen[i] = true;
|
|
},
|
|
.aligned => {
|
|
try attr.applyAligned(p, field_qt.*, null);
|
|
seen[i] = true;
|
|
},
|
|
else => {},
|
|
};
|
|
return p.attr_application_buf.items;
|
|
}
|
|
|
|
pub fn applyTypeAttributes(p: *Parser, qt: QualType, attr_buf_start: usize, diagnostic: ?Parser.Diagnostic) !QualType {
|
|
const gpa = p.comp.gpa;
|
|
const attrs = p.attr_buf.items(.attr)[attr_buf_start..];
|
|
const toks = p.attr_buf.items(.tok)[attr_buf_start..];
|
|
const seens = p.attr_buf.items(.seen)[attr_buf_start..];
|
|
p.attr_application_buf.items.len = 0;
|
|
var base_qt = qt;
|
|
for (attrs, toks, seens) |attr, tok, seen| {
|
|
if (seen) continue;
|
|
|
|
switch (attr.tag) {
|
|
// zig fmt: off
|
|
.@"packed", .may_alias, .deprecated, .unavailable, .unused, .warn_if_not_aligned, .mode,
|
|
.nullability, .unaligned, .warn_unused_result,
|
|
=> try p.attr_application_buf.append(gpa, attr),
|
|
// zig fmt: on
|
|
.transparent_union => try attr.applyTransparentUnion(p, tok, base_qt),
|
|
.vector_size => try attr.applyVectorSize(p, tok, &base_qt),
|
|
.aligned => try attr.applyAligned(p, base_qt, diagnostic),
|
|
.designated_init => if (base_qt.is(p.comp, .@"struct")) {
|
|
try p.attr_application_buf.append(gpa, attr);
|
|
} else {
|
|
try p.err(tok, .designated_init_invalid, .{});
|
|
},
|
|
.calling_convention => try applyCallingConvention(attr, p, tok, base_qt),
|
|
.alloc_size,
|
|
.copy,
|
|
.scalar_storage_order,
|
|
.nonstring,
|
|
=> |t| try p.err(tok, .attribute_todo, .{ @tagName(t), "types" }),
|
|
else => try ignoredAttrErr(p, tok, attr.tag, "types"),
|
|
}
|
|
}
|
|
return applySelected(base_qt, p);
|
|
}
|
|
|
|
pub fn applyFunctionAttributes(p: *Parser, qt: QualType, attr_buf_start: usize) !QualType {
|
|
const gpa = p.comp.gpa;
|
|
const attrs = p.attr_buf.items(.attr)[attr_buf_start..];
|
|
const toks = p.attr_buf.items(.tok)[attr_buf_start..];
|
|
p.attr_application_buf.items.len = 0;
|
|
var base_qt = qt;
|
|
var hot = false;
|
|
var cold = false;
|
|
var @"noinline" = false;
|
|
var always_inline = false;
|
|
for (attrs, toks) |attr, tok| switch (attr.tag) {
|
|
// zig fmt: off
|
|
.noreturn, .unused, .used, .warning, .deprecated, .unavailable, .weak, .pure, .leaf,
|
|
.@"const", .warn_unused_result, .section, .returns_nonnull, .returns_twice, .@"error",
|
|
.externally_visible, .retain, .flatten, .gnu_inline, .alias, .asm_label, .nodiscard,
|
|
.reproducible, .unsequenced, .nothrow, .nullability, .unaligned, .internal_linkage,
|
|
=> try p.attr_application_buf.append(gpa, attr),
|
|
// zig fmt: on
|
|
.hot => if (cold) {
|
|
try p.err(tok, .ignore_hot, .{});
|
|
} else {
|
|
try p.attr_application_buf.append(gpa, attr);
|
|
hot = true;
|
|
},
|
|
.cold => if (hot) {
|
|
try p.err(tok, .ignore_cold, .{});
|
|
} else {
|
|
try p.attr_application_buf.append(gpa, attr);
|
|
cold = true;
|
|
},
|
|
.always_inline => if (@"noinline") {
|
|
try p.err(tok, .ignore_always_inline, .{});
|
|
} else {
|
|
try p.attr_application_buf.append(gpa, attr);
|
|
always_inline = true;
|
|
},
|
|
.@"noinline" => if (always_inline) {
|
|
try p.err(tok, .ignore_noinline, .{});
|
|
} else {
|
|
try p.attr_application_buf.append(gpa, attr);
|
|
@"noinline" = true;
|
|
},
|
|
.aligned => try attr.applyAligned(p, base_qt, null),
|
|
.format => try attr.applyFormat(p, base_qt),
|
|
.calling_convention => try applyCallingConvention(attr, p, tok, base_qt),
|
|
.fastcall => if (p.comp.target.cpu.arch == .x86) {
|
|
try p.attr_application_buf.append(gpa, .{
|
|
.tag = .calling_convention,
|
|
.args = .{ .calling_convention = .{ .cc = .fastcall } },
|
|
.syntax = attr.syntax,
|
|
});
|
|
} else {
|
|
try p.err(tok, .callconv_not_supported, .{"fastcall"});
|
|
},
|
|
.stdcall => if (p.comp.target.cpu.arch == .x86) {
|
|
try p.attr_application_buf.append(gpa, .{
|
|
.tag = .calling_convention,
|
|
.args = .{ .calling_convention = .{ .cc = .stdcall } },
|
|
.syntax = attr.syntax,
|
|
});
|
|
} else {
|
|
try p.err(tok, .callconv_not_supported, .{"stdcall"});
|
|
},
|
|
.thiscall => if (p.comp.target.cpu.arch == .x86) {
|
|
try p.attr_application_buf.append(gpa, .{
|
|
.tag = .calling_convention,
|
|
.args = .{ .calling_convention = .{ .cc = .thiscall } },
|
|
.syntax = attr.syntax,
|
|
});
|
|
} else {
|
|
try p.err(tok, .callconv_not_supported, .{"thiscall"});
|
|
},
|
|
.vectorcall => if (p.comp.target.cpu.arch == .x86 or p.comp.target.cpu.arch.isAARCH64()) {
|
|
try p.attr_application_buf.append(gpa, .{
|
|
.tag = .calling_convention,
|
|
.args = .{ .calling_convention = .{ .cc = .vectorcall } },
|
|
.syntax = attr.syntax,
|
|
});
|
|
} else {
|
|
try p.err(tok, .callconv_not_supported, .{"vectorcall"});
|
|
},
|
|
.cdecl => {},
|
|
.pcs => if (p.comp.target.cpu.arch.isArm()) {
|
|
try p.attr_application_buf.append(gpa, .{
|
|
.tag = .calling_convention,
|
|
.args = .{ .calling_convention = .{ .cc = switch (attr.args.pcs.kind) {
|
|
.aapcs => .arm_aapcs,
|
|
.@"aapcs-vfp" => .arm_aapcs_vfp,
|
|
} } },
|
|
.syntax = attr.syntax,
|
|
});
|
|
} else {
|
|
try p.err(tok, .callconv_not_supported, .{"pcs"});
|
|
},
|
|
.riscv_vector_cc => if (p.comp.target.cpu.arch.isRISCV()) {
|
|
try p.attr_application_buf.append(gpa, .{
|
|
.tag = .calling_convention,
|
|
.args = .{ .calling_convention = .{ .cc = .riscv_vector } },
|
|
.syntax = attr.syntax,
|
|
});
|
|
} else {
|
|
try p.err(tok, .callconv_not_supported, .{"pcs"});
|
|
},
|
|
.aarch64_sve_pcs => if (p.comp.target.cpu.arch.isAARCH64()) {
|
|
try p.attr_application_buf.append(gpa, .{
|
|
.tag = .calling_convention,
|
|
.args = .{ .calling_convention = .{ .cc = .aarch64_sve_pcs } },
|
|
.syntax = attr.syntax,
|
|
});
|
|
} else {
|
|
try p.err(tok, .callconv_not_supported, .{"pcs"});
|
|
},
|
|
.aarch64_vector_pcs => if (p.comp.target.cpu.arch.isAARCH64()) {
|
|
try p.attr_application_buf.append(gpa, .{
|
|
.tag = .calling_convention,
|
|
.args = .{ .calling_convention = .{ .cc = .aarch64_vector_pcs } },
|
|
.syntax = attr.syntax,
|
|
});
|
|
} else {
|
|
try p.err(tok, .callconv_not_supported, .{"pcs"});
|
|
},
|
|
.sysv_abi => if (p.comp.target.cpu.arch == .x86_64 and p.comp.target.os.tag == .windows) {
|
|
try p.attr_application_buf.append(gpa, .{
|
|
.tag = .calling_convention,
|
|
.args = .{ .calling_convention = .{ .cc = .x86_64_sysv } },
|
|
.syntax = attr.syntax,
|
|
});
|
|
},
|
|
.ms_abi => if (p.comp.target.cpu.arch == .x86_64 and p.comp.target.os.tag != .windows) {
|
|
try p.attr_application_buf.append(gpa, .{
|
|
.tag = .calling_convention,
|
|
.args = .{ .calling_convention = .{ .cc = .x86_64_win } },
|
|
.syntax = attr.syntax,
|
|
});
|
|
},
|
|
.malloc => {
|
|
if (base_qt.get(p.comp, .func).?.return_type.isPointer(p.comp)) {
|
|
try p.attr_application_buf.append(gpa, attr);
|
|
} else {
|
|
try ignoredAttrErr(p, tok, attr.tag, "functions that do not return pointers");
|
|
}
|
|
},
|
|
.alloc_align => {
|
|
const func_ty = base_qt.get(p.comp, .func).?;
|
|
if (func_ty.return_type.isPointer(p.comp)) {
|
|
if (attr.args.alloc_align.position == 0 or attr.args.alloc_align.position > func_ty.params.len) {
|
|
try p.err(tok, .attribute_param_out_of_bounds, .{ "alloc_align", 1 });
|
|
} else {
|
|
const arg_qt = func_ty.params[attr.args.alloc_align.position - 1].qt;
|
|
if (arg_qt.isInvalid()) continue;
|
|
const arg_sk = arg_qt.scalarKind(p.comp);
|
|
if (!arg_sk.isInt() or !arg_sk.isReal()) {
|
|
try p.err(tok, .alloc_align_required_int_param, .{});
|
|
} else {
|
|
try p.attr_application_buf.append(gpa, attr);
|
|
}
|
|
}
|
|
} else {
|
|
try p.err(tok, .alloc_align_requires_ptr_return, .{});
|
|
}
|
|
},
|
|
.access,
|
|
.alloc_size,
|
|
.artificial,
|
|
.assume_aligned,
|
|
.constructor,
|
|
.copy,
|
|
.destructor,
|
|
.format_arg,
|
|
.ifunc,
|
|
.interrupt,
|
|
.interrupt_handler,
|
|
.no_address_safety_analysis,
|
|
.no_icf,
|
|
.no_instrument_function,
|
|
.no_profile_instrument_function,
|
|
.no_reorder,
|
|
.no_sanitize,
|
|
.no_sanitize_address,
|
|
.no_sanitize_coverage,
|
|
.no_sanitize_thread,
|
|
.no_sanitize_undefined,
|
|
.no_split_stack,
|
|
.no_stack_limit,
|
|
.no_stack_protector,
|
|
.noclone,
|
|
.noipa,
|
|
.nonnull,
|
|
.noplt,
|
|
// .optimize,
|
|
.patchable_function_entry,
|
|
.sentinel,
|
|
.simd,
|
|
.stack_protect,
|
|
.symver,
|
|
.target,
|
|
.target_clones,
|
|
.visibility,
|
|
.weakref,
|
|
.zero_call_used_regs,
|
|
=> |t| try p.err(tok, .attribute_todo, .{ @tagName(t), "functions" }),
|
|
else => try ignoredAttrErr(p, tok, attr.tag, "functions"),
|
|
};
|
|
return applySelected(qt, p);
|
|
}
|
|
|
|
pub fn applyLabelAttributes(p: *Parser, attr_buf_start: usize) !QualType {
|
|
const gpa = p.comp.gpa;
|
|
const attrs = p.attr_buf.items(.attr)[attr_buf_start..];
|
|
const toks = p.attr_buf.items(.tok)[attr_buf_start..];
|
|
p.attr_application_buf.items.len = 0;
|
|
var hot = false;
|
|
var cold = false;
|
|
for (attrs, toks) |attr, tok| switch (attr.tag) {
|
|
.unused => try p.attr_application_buf.append(gpa, attr),
|
|
.hot => if (cold) {
|
|
try p.err(tok, .ignore_hot, .{});
|
|
} else {
|
|
try p.attr_application_buf.append(gpa, attr);
|
|
hot = true;
|
|
},
|
|
.cold => if (hot) {
|
|
try p.err(tok, .ignore_cold, .{});
|
|
} else {
|
|
try p.attr_application_buf.append(gpa, attr);
|
|
cold = true;
|
|
},
|
|
else => try ignoredAttrErr(p, tok, attr.tag, "labels"),
|
|
};
|
|
return applySelected(.void, p);
|
|
}
|
|
|
|
pub fn applyStatementAttributes(p: *Parser, expr_start: TokenIndex, attr_buf_start: usize) !QualType {
|
|
const attrs = p.attr_buf.items(.attr)[attr_buf_start..];
|
|
const toks = p.attr_buf.items(.tok)[attr_buf_start..];
|
|
p.attr_application_buf.items.len = 0;
|
|
for (attrs, toks) |attr, tok| switch (attr.tag) {
|
|
.fallthrough => {
|
|
for (p.tok_ids[p.tok_i..]) |tok_id| {
|
|
switch (tok_id) {
|
|
.keyword_case, .keyword_default, .eof => {
|
|
try p.attr_application_buf.append(p.comp.gpa, attr);
|
|
break;
|
|
},
|
|
.r_brace, .semicolon => {},
|
|
else => {
|
|
try p.err(expr_start, .invalid_fallthrough, .{});
|
|
break;
|
|
},
|
|
}
|
|
}
|
|
},
|
|
else => try p.err(tok, .cannot_apply_attribute_to_statement, .{@tagName(attr.tag)}),
|
|
};
|
|
return applySelected(.void, p);
|
|
}
|
|
|
|
pub fn applyEnumeratorAttributes(p: *Parser, qt: QualType, attr_buf_start: usize) !QualType {
|
|
const attrs = p.attr_buf.items(.attr)[attr_buf_start..];
|
|
const toks = p.attr_buf.items(.tok)[attr_buf_start..];
|
|
p.attr_application_buf.items.len = 0;
|
|
for (attrs, toks) |attr, tok| switch (attr.tag) {
|
|
.deprecated, .unavailable => try p.attr_application_buf.append(p.comp.gpa, attr),
|
|
else => try ignoredAttrErr(p, tok, attr.tag, "enums"),
|
|
};
|
|
return applySelected(qt, p);
|
|
}
|
|
|
|
fn applyAligned(attr: Attribute, p: *Parser, qt: QualType, diagnostic: ?Parser.Diagnostic) !void {
|
|
if (attr.args.aligned.alignment) |alignment| alignas: {
|
|
if (attr.syntax != .keyword) break :alignas;
|
|
|
|
const align_tok = attr.args.aligned.__name_tok;
|
|
if (diagnostic) |d| try p.err(align_tok, d, .{});
|
|
|
|
if (qt.isInvalid()) return;
|
|
const default_align = qt.base(p.comp).qt.alignof(p.comp);
|
|
if (qt.is(p.comp, .func)) {
|
|
try p.err(align_tok, .alignas_on_func, .{});
|
|
} else if (alignment.requested < default_align) {
|
|
try p.err(align_tok, .minimum_alignment, .{default_align});
|
|
}
|
|
}
|
|
try p.attr_application_buf.append(p.comp.gpa, attr);
|
|
}
|
|
|
|
fn applyTransparentUnion(attr: Attribute, p: *Parser, tok: TokenIndex, qt: QualType) !void {
|
|
const union_ty = qt.get(p.comp, .@"union") orelse {
|
|
return p.err(tok, .transparent_union_wrong_type, .{});
|
|
};
|
|
// TODO validate union defined at end
|
|
if (union_ty.layout == null) return;
|
|
if (union_ty.fields.len == 0) {
|
|
return p.err(tok, .transparent_union_one_field, .{});
|
|
}
|
|
const first_field_size = union_ty.fields[0].qt.bitSizeof(p.comp);
|
|
for (union_ty.fields[1..]) |field| {
|
|
const field_size = field.qt.bitSizeof(p.comp);
|
|
if (field_size == first_field_size) continue;
|
|
|
|
try p.err(field.name_tok, .transparent_union_size, .{ field.name.lookup(p.comp), field_size });
|
|
return p.err(union_ty.fields[0].name_tok, .transparent_union_size_note, .{first_field_size});
|
|
}
|
|
|
|
try p.attr_application_buf.append(p.comp.gpa, attr);
|
|
}
|
|
|
|
fn applyVectorSize(attr: Attribute, p: *Parser, tok: TokenIndex, qt: *QualType) !void {
|
|
if (qt.isInvalid()) return;
|
|
const scalar_kind = qt.scalarKind(p.comp);
|
|
if (scalar_kind != .int and scalar_kind != .float) {
|
|
if (qt.get(p.comp, .@"enum")) |enum_ty| {
|
|
if (p.comp.langopts.emulate == .clang and enum_ty.incomplete) {
|
|
return; // Clang silently ignores vector_size on incomplete enums.
|
|
}
|
|
}
|
|
try p.err(tok, .invalid_vec_elem_ty, .{qt.*});
|
|
return error.ParsingFailed;
|
|
}
|
|
if (qt.get(p.comp, .bit_int)) |bit_int| {
|
|
if (bit_int.bits < 8) {
|
|
try p.err(tok, .bit_int_vec_too_small, .{});
|
|
return error.ParsingFailed;
|
|
} else if (!std.math.isPowerOfTwo(bit_int.bits)) {
|
|
try p.err(tok, .bit_int_vec_not_pow2, .{});
|
|
return error.ParsingFailed;
|
|
}
|
|
}
|
|
|
|
const vec_bytes = attr.args.vector_size.bytes;
|
|
const elem_size = qt.sizeof(p.comp);
|
|
if (vec_bytes % elem_size != 0) {
|
|
return p.err(tok, .vec_size_not_multiple, .{});
|
|
}
|
|
|
|
qt.* = try p.comp.type_store.put(p.comp.gpa, .{ .vector = .{
|
|
.elem = qt.*,
|
|
.len = @intCast(vec_bytes / elem_size),
|
|
} });
|
|
}
|
|
|
|
fn applyFormat(attr: Attribute, p: *Parser, qt: QualType) !void {
|
|
// TODO validate
|
|
_ = qt;
|
|
try p.attr_application_buf.append(p.comp.gpa, attr);
|
|
}
|
|
|
|
fn applyCallingConvention(attr: Attribute, p: *Parser, tok: TokenIndex, qt: QualType) !void {
|
|
if (!qt.is(p.comp, .func)) {
|
|
return p.err(tok, .callconv_non_func, .{ p.tok_ids[tok].symbol(), qt });
|
|
}
|
|
switch (attr.args.calling_convention.cc) {
|
|
.c => {},
|
|
.stdcall, .thiscall, .fastcall, .regcall => switch (p.comp.target.cpu.arch) {
|
|
.x86 => try p.attr_application_buf.append(p.comp.gpa, attr),
|
|
else => try p.err(tok, .callconv_not_supported, .{p.tok_ids[tok].symbol()}),
|
|
},
|
|
.vectorcall => switch (p.comp.target.cpu.arch) {
|
|
.x86, .aarch64, .aarch64_be => try p.attr_application_buf.append(p.comp.gpa, attr),
|
|
else => try p.err(tok, .callconv_not_supported, .{p.tok_ids[tok].symbol()}),
|
|
},
|
|
.riscv_vector,
|
|
.aarch64_sve_pcs,
|
|
.aarch64_vector_pcs,
|
|
.arm_aapcs,
|
|
.arm_aapcs_vfp,
|
|
.x86_64_sysv,
|
|
.x86_64_win,
|
|
=> unreachable, // These can't come from keyword syntax
|
|
}
|
|
}
|
|
|
|
fn applySelected(qt: QualType, p: *Parser) !QualType {
|
|
if (p.attr_application_buf.items.len == 0) return qt;
|
|
if (qt.isInvalid()) return qt;
|
|
return (try p.comp.type_store.put(p.comp.gpa, .{ .attributed = .{
|
|
.base = qt,
|
|
.attributes = p.attr_application_buf.items,
|
|
} })).withQualifiers(qt);
|
|
}
|