zig/src/translate_c.zig
Andrew Kelley 9c5fe5b5a4 LLVM: C calling convention lowering fixes
For parameters and return types of functions with the C calling
convention, the LLVM backend now has a special lowering for the function
type that makes the function adhere to the C ABI. The AIR instruction
lowerings for call, ret, and ret_load are adjusted to bitcast the real
type to the ABI type if necessary.

More work on this will need to be done, however, this improvement is
enough that stage3 now passes all the same behavior tests that stage2
passes - notably, translate-c no longer has a segfault due to C ABI
issues with Zig's Clang C API wrapper.
2022-04-21 20:27:06 -07:00

6589 lines
257 KiB
Zig

//! This is the userland implementation of translate-c which is used by both stage1
//! and stage2.
const std = @import("std");
const testing = std.testing;
const assert = std.debug.assert;
const clang = @import("clang.zig");
const ctok = std.c.tokenizer;
const CToken = std.c.Token;
const mem = std.mem;
const math = std.math;
const meta = std.meta;
const ast = @import("translate_c/ast.zig");
const Node = ast.Node;
const Tag = Node.Tag;
const CallingConvention = std.builtin.CallingConvention;
pub const ClangErrMsg = clang.Stage2ErrorMsg;
pub const Error = std.mem.Allocator.Error;
const MacroProcessingError = Error || error{UnexpectedMacroToken};
const TypeError = Error || error{UnsupportedType};
const TransError = TypeError || error{UnsupportedTranslation};
const SymbolTable = std.StringArrayHashMap(Node);
const AliasList = std.ArrayList(struct {
alias: []const u8,
name: []const u8,
});
// Maps macro parameter names to token position, for determining if different
// identifiers refer to the same positional argument in different macros.
const ArgsPositionMap = std.StringArrayHashMapUnmanaged(usize);
const Scope = struct {
id: Id,
parent: ?*Scope,
const Id = enum {
block,
root,
condition,
loop,
do_loop,
};
/// Used for the scope of condition expressions, for example `if (cond)`.
/// The block is lazily initialised because it is only needed for rare
/// cases of comma operators being used.
const Condition = struct {
base: Scope,
block: ?Block = null,
fn getBlockScope(self: *Condition, c: *Context) !*Block {
if (self.block) |*b| return b;
self.block = try Block.init(c, &self.base, true);
return &self.block.?;
}
fn deinit(self: *Condition) void {
if (self.block) |*b| b.deinit();
}
};
/// Represents an in-progress Node.Block. This struct is stack-allocated.
/// When it is deinitialized, it produces an Node.Block which is allocated
/// into the main arena.
const Block = struct {
base: Scope,
statements: std.ArrayList(Node),
variables: AliasList,
mangle_count: u32 = 0,
label: ?[]const u8 = null,
/// By default all variables are discarded, since we do not know in advance if they
/// will be used. This maps the variable's name to the Discard payload, so that if
/// the variable is subsequently referenced we can indicate that the discard should
/// be skipped during the intermediate AST -> Zig AST render step.
variable_discards: std.StringArrayHashMap(*ast.Payload.Discard),
/// When the block corresponds to a function, keep track of the return type
/// so that the return expression can be cast, if necessary
return_type: ?clang.QualType = null,
/// C static local variables are wrapped in a block-local struct. The struct
/// is named after the (mangled) variable name, the Zig variable within the
/// struct itself is given this name.
const StaticInnerName = "static";
fn init(c: *Context, parent: *Scope, labeled: bool) !Block {
var blk = Block{
.base = .{
.id = .block,
.parent = parent,
},
.statements = std.ArrayList(Node).init(c.gpa),
.variables = AliasList.init(c.gpa),
.variable_discards = std.StringArrayHashMap(*ast.Payload.Discard).init(c.gpa),
};
if (labeled) {
blk.label = try blk.makeMangledName(c, "blk");
}
return blk;
}
fn deinit(self: *Block) void {
self.statements.deinit();
self.variables.deinit();
self.variable_discards.deinit();
self.* = undefined;
}
fn complete(self: *Block, c: *Context) !Node {
if (self.base.parent.?.id == .do_loop) {
// We reserve 1 extra statement if the parent is a do_loop. This is in case of
// do while, we want to put `if (cond) break;` at the end.
const alloc_len = self.statements.items.len + @boolToInt(self.base.parent.?.id == .do_loop);
var stmts = try c.arena.alloc(Node, alloc_len);
stmts.len = self.statements.items.len;
mem.copy(Node, stmts, self.statements.items);
return Tag.block.create(c.arena, .{
.label = self.label,
.stmts = stmts,
});
}
if (self.statements.items.len == 0) return Tag.empty_block.init();
return Tag.block.create(c.arena, .{
.label = self.label,
.stmts = try c.arena.dupe(Node, self.statements.items),
});
}
/// Given the desired name, return a name that does not shadow anything from outer scopes.
/// Inserts the returned name into the scope.
fn makeMangledName(scope: *Block, c: *Context, name: []const u8) ![]const u8 {
const name_copy = try c.arena.dupe(u8, name);
var proposed_name = name_copy;
while (scope.contains(proposed_name)) {
scope.mangle_count += 1;
proposed_name = try std.fmt.allocPrint(c.arena, "{s}_{d}", .{ name, scope.mangle_count });
}
try scope.variables.append(.{ .name = name_copy, .alias = proposed_name });
return proposed_name;
}
fn getAlias(scope: *Block, name: []const u8) []const u8 {
for (scope.variables.items) |p| {
if (mem.eql(u8, p.name, name))
return p.alias;
}
return scope.base.parent.?.getAlias(name);
}
fn localContains(scope: *Block, name: []const u8) bool {
for (scope.variables.items) |p| {
if (mem.eql(u8, p.alias, name))
return true;
}
return false;
}
fn contains(scope: *Block, name: []const u8) bool {
if (scope.localContains(name))
return true;
return scope.base.parent.?.contains(name);
}
fn discardVariable(scope: *Block, c: *Context, name: []const u8) Error!void {
const name_node = try Tag.identifier.create(c.arena, name);
const discard = try Tag.discard.create(c.arena, .{ .should_skip = false, .value = name_node });
try scope.statements.append(discard);
try scope.variable_discards.putNoClobber(name, discard.castTag(.discard).?);
}
};
const Root = struct {
base: Scope,
sym_table: SymbolTable,
macro_table: SymbolTable,
context: *Context,
nodes: std.ArrayList(Node),
fn init(c: *Context) Root {
return .{
.base = .{
.id = .root,
.parent = null,
},
.sym_table = SymbolTable.init(c.gpa),
.macro_table = SymbolTable.init(c.gpa),
.context = c,
.nodes = std.ArrayList(Node).init(c.gpa),
};
}
fn deinit(scope: *Root) void {
scope.sym_table.deinit();
scope.macro_table.deinit();
scope.nodes.deinit();
}
/// Check if the global scope contains this name, without looking into the "future", e.g.
/// ignore the preprocessed decl and macro names.
fn containsNow(scope: *Root, name: []const u8) bool {
return scope.sym_table.contains(name) or scope.macro_table.contains(name);
}
/// Check if the global scope contains the name, includes all decls that haven't been translated yet.
fn contains(scope: *Root, name: []const u8) bool {
return scope.containsNow(name) or scope.context.global_names.contains(name);
}
};
fn findBlockScope(inner: *Scope, c: *Context) !*Scope.Block {
var scope = inner;
while (true) {
switch (scope.id) {
.root => unreachable,
.block => return @fieldParentPtr(Block, "base", scope),
.condition => return @fieldParentPtr(Condition, "base", scope).getBlockScope(c),
else => scope = scope.parent.?,
}
}
}
fn findBlockReturnType(inner: *Scope, c: *Context) clang.QualType {
_ = c;
var scope = inner;
while (true) {
switch (scope.id) {
.root => unreachable,
.block => {
const block = @fieldParentPtr(Block, "base", scope);
if (block.return_type) |qt| return qt;
scope = scope.parent.?;
},
else => scope = scope.parent.?,
}
}
}
fn getAlias(scope: *Scope, name: []const u8) []const u8 {
return switch (scope.id) {
.root => return name,
.block => @fieldParentPtr(Block, "base", scope).getAlias(name),
.loop, .do_loop, .condition => scope.parent.?.getAlias(name),
};
}
fn contains(scope: *Scope, name: []const u8) bool {
return switch (scope.id) {
.root => @fieldParentPtr(Root, "base", scope).contains(name),
.block => @fieldParentPtr(Block, "base", scope).contains(name),
.loop, .do_loop, .condition => scope.parent.?.contains(name),
};
}
fn getBreakableScope(inner: *Scope) *Scope {
var scope = inner;
while (true) {
switch (scope.id) {
.root => unreachable,
.loop, .do_loop => return scope,
else => scope = scope.parent.?,
}
}
}
/// Appends a node to the first block scope if inside a function, or to the root tree if not.
fn appendNode(inner: *Scope, node: Node) !void {
var scope = inner;
while (true) {
switch (scope.id) {
.root => {
const root = @fieldParentPtr(Root, "base", scope);
return root.nodes.append(node);
},
.block => {
const block = @fieldParentPtr(Block, "base", scope);
return block.statements.append(node);
},
else => scope = scope.parent.?,
}
}
}
fn skipVariableDiscard(inner: *Scope, name: []const u8) void {
var scope = inner;
while (true) {
switch (scope.id) {
.root => return,
.block => {
const block = @fieldParentPtr(Block, "base", scope);
if (block.variable_discards.get(name)) |discard| {
discard.data.should_skip = true;
return;
}
},
else => {},
}
scope = scope.parent.?;
}
}
};
pub const Context = struct {
gpa: mem.Allocator,
arena: mem.Allocator,
source_manager: *clang.SourceManager,
decl_table: std.AutoArrayHashMapUnmanaged(usize, []const u8) = .{},
alias_list: AliasList,
global_scope: *Scope.Root,
clang_context: *clang.ASTContext,
mangle_count: u32 = 0,
/// Table of record decls that have been demoted to opaques.
opaque_demotes: std.AutoHashMapUnmanaged(usize, void) = .{},
/// Table of unnamed enums and records that are child types of typedefs.
unnamed_typedefs: std.AutoHashMapUnmanaged(usize, []const u8) = .{},
/// Needed to decide if we are parsing a typename
typedefs: std.StringArrayHashMapUnmanaged(void) = .{},
/// This one is different than the root scope's name table. This contains
/// a list of names that we found by visiting all the top level decls without
/// translating them. The other maps are updated as we translate; this one is updated
/// up front in a pre-processing step.
global_names: std.StringArrayHashMapUnmanaged(void) = .{},
pattern_list: PatternList,
/// This is used to emit different code depending on whether
/// the output zig source code is intended to be compiled with stage1 or stage2.
/// Ideally we will have stage1 and stage2 support the exact same Zig language,
/// but for now they diverge because I would rather focus on finishing and shipping
/// stage2 than implementing the features in stage1.
/// The list of differences are currently:
/// * function pointers in stage1 are e.g. `fn()void`
/// but in stage2 they are `*const fn()void`.
zig_is_stage1: bool,
fn getMangle(c: *Context) u32 {
c.mangle_count += 1;
return c.mangle_count;
}
/// Convert a null-terminated C string to a slice allocated in the arena
fn str(c: *Context, s: [*:0]const u8) ![]u8 {
return c.arena.dupe(u8, mem.sliceTo(s, 0));
}
/// Convert a clang source location to a file:line:column string
fn locStr(c: *Context, loc: clang.SourceLocation) ![]u8 {
const spelling_loc = c.source_manager.getSpellingLoc(loc);
const filename_c = c.source_manager.getFilename(spelling_loc);
const filename = if (filename_c) |s| try c.str(s) else @as([]const u8, "(no file)");
const line = c.source_manager.getSpellingLineNumber(spelling_loc);
const column = c.source_manager.getSpellingColumnNumber(spelling_loc);
return std.fmt.allocPrint(c.arena, "{s}:{d}:{d}", .{ filename, line, column });
}
};
pub fn translate(
gpa: mem.Allocator,
args_begin: [*]?[*]const u8,
args_end: [*]?[*]const u8,
errors: *[]ClangErrMsg,
resources_path: [*:0]const u8,
zig_is_stage1: bool,
) !std.zig.Ast {
// TODO stage2 bug
var tmp = errors;
const ast_unit = clang.LoadFromCommandLine(
args_begin,
args_end,
&tmp.ptr,
&tmp.len,
resources_path,
) orelse {
if (errors.len == 0) return error.ASTUnitFailure;
return error.SemanticAnalyzeFail;
};
defer ast_unit.delete();
// For memory that has the same lifetime as the Ast that we return
// from this function.
var arena_allocator = std.heap.ArenaAllocator.init(gpa);
errdefer arena_allocator.deinit();
const arena = arena_allocator.allocator();
var context = Context{
.gpa = gpa,
.arena = arena,
.source_manager = ast_unit.getSourceManager(),
.alias_list = AliasList.init(gpa),
.global_scope = try arena.create(Scope.Root),
.clang_context = ast_unit.getASTContext(),
.pattern_list = try PatternList.init(gpa),
.zig_is_stage1 = zig_is_stage1,
};
context.global_scope.* = Scope.Root.init(&context);
defer {
context.decl_table.deinit(gpa);
context.alias_list.deinit();
context.global_names.deinit(gpa);
context.opaque_demotes.deinit(gpa);
context.unnamed_typedefs.deinit(gpa);
context.typedefs.deinit(gpa);
context.global_scope.deinit();
context.pattern_list.deinit(gpa);
}
inline for (@typeInfo(std.zig.c_builtins).Struct.decls) |decl| {
if (decl.is_pub) {
const builtin = try Tag.pub_var_simple.create(arena, .{
.name = decl.name,
.init = try Tag.import_c_builtin.create(arena, decl.name),
});
try addTopLevelDecl(&context, decl.name, builtin);
}
}
try prepopulateGlobalNameTable(ast_unit, &context);
if (!ast_unit.visitLocalTopLevelDecls(&context, declVisitorC)) {
return error.OutOfMemory;
}
try transPreprocessorEntities(&context, ast_unit);
try addMacros(&context);
for (context.alias_list.items) |alias| {
if (!context.global_scope.sym_table.contains(alias.alias)) {
const node = try Tag.alias.create(arena, .{ .actual = alias.alias, .mangled = alias.name });
try addTopLevelDecl(&context, alias.alias, node);
}
}
return ast.render(gpa, context.global_scope.nodes.items);
}
fn prepopulateGlobalNameTable(ast_unit: *clang.ASTUnit, c: *Context) !void {
if (!ast_unit.visitLocalTopLevelDecls(c, declVisitorNamesOnlyC)) {
return error.OutOfMemory;
}
// TODO if we see #undef, delete it from the table
var it = ast_unit.getLocalPreprocessingEntities_begin();
const it_end = ast_unit.getLocalPreprocessingEntities_end();
while (it.I != it_end.I) : (it.I += 1) {
const entity = it.deref();
switch (entity.getKind()) {
.MacroDefinitionKind => {
const macro = @ptrCast(*clang.MacroDefinitionRecord, entity);
const raw_name = macro.getName_getNameStart();
const name = try c.str(raw_name);
try c.global_names.put(c.gpa, name, {});
},
else => {},
}
}
}
fn declVisitorNamesOnlyC(context: ?*anyopaque, decl: *const clang.Decl) callconv(.C) bool {
const c = @ptrCast(*Context, @alignCast(@alignOf(Context), context));
declVisitorNamesOnly(c, decl) catch return false;
return true;
}
fn declVisitorC(context: ?*anyopaque, decl: *const clang.Decl) callconv(.C) bool {
const c = @ptrCast(*Context, @alignCast(@alignOf(Context), context));
declVisitor(c, decl) catch return false;
return true;
}
fn declVisitorNamesOnly(c: *Context, decl: *const clang.Decl) Error!void {
if (decl.castToNamedDecl()) |named_decl| {
const decl_name = try c.str(named_decl.getName_bytes_begin());
try c.global_names.put(c.gpa, decl_name, {});
// Check for typedefs with unnamed enum/record child types.
if (decl.getKind() == .Typedef) {
const typedef_decl = @ptrCast(*const clang.TypedefNameDecl, decl);
var child_ty = typedef_decl.getUnderlyingType().getTypePtr();
const addr: usize = while (true) switch (child_ty.getTypeClass()) {
.Enum => {
const enum_ty = @ptrCast(*const clang.EnumType, child_ty);
const enum_decl = enum_ty.getDecl();
// check if this decl is unnamed
if (@ptrCast(*const clang.NamedDecl, enum_decl).getName_bytes_begin()[0] != 0) return;
break @ptrToInt(enum_decl.getCanonicalDecl());
},
.Record => {
const record_ty = @ptrCast(*const clang.RecordType, child_ty);
const record_decl = record_ty.getDecl();
// check if this decl is unnamed
if (@ptrCast(*const clang.NamedDecl, record_decl).getName_bytes_begin()[0] != 0) return;
break @ptrToInt(record_decl.getCanonicalDecl());
},
.Elaborated => {
const elaborated_ty = @ptrCast(*const clang.ElaboratedType, child_ty);
child_ty = elaborated_ty.getNamedType().getTypePtr();
},
.Decayed => {
const decayed_ty = @ptrCast(*const clang.DecayedType, child_ty);
child_ty = decayed_ty.getDecayedType().getTypePtr();
},
.Attributed => {
const attributed_ty = @ptrCast(*const clang.AttributedType, child_ty);
child_ty = attributed_ty.getEquivalentType().getTypePtr();
},
.MacroQualified => {
const macroqualified_ty = @ptrCast(*const clang.MacroQualifiedType, child_ty);
child_ty = macroqualified_ty.getModifiedType().getTypePtr();
},
else => return,
} else unreachable;
const result = try c.unnamed_typedefs.getOrPut(c.gpa, addr);
if (result.found_existing) {
// One typedef can declare multiple names.
// Don't put this one in `decl_table` so it's processed later.
return;
}
result.value_ptr.* = decl_name;
// Put this typedef in the decl_table to avoid redefinitions.
try c.decl_table.putNoClobber(c.gpa, @ptrToInt(typedef_decl.getCanonicalDecl()), decl_name);
try c.typedefs.put(c.gpa, decl_name, {});
}
}
}
fn declVisitor(c: *Context, decl: *const clang.Decl) Error!void {
switch (decl.getKind()) {
.Function => {
return visitFnDecl(c, @ptrCast(*const clang.FunctionDecl, decl));
},
.Typedef => {
try transTypeDef(c, &c.global_scope.base, @ptrCast(*const clang.TypedefNameDecl, decl));
},
.Enum => {
try transEnumDecl(c, &c.global_scope.base, @ptrCast(*const clang.EnumDecl, decl));
},
.Record => {
try transRecordDecl(c, &c.global_scope.base, @ptrCast(*const clang.RecordDecl, decl));
},
.Var => {
return visitVarDecl(c, @ptrCast(*const clang.VarDecl, decl), null);
},
.Empty => {
// Do nothing
},
.FileScopeAsm => {
try transFileScopeAsm(c, &c.global_scope.base, @ptrCast(*const clang.FileScopeAsmDecl, decl));
},
else => {
const decl_name = try c.str(decl.getDeclKindName());
try warn(c, &c.global_scope.base, decl.getLocation(), "ignoring {s} declaration", .{decl_name});
},
}
}
fn transFileScopeAsm(c: *Context, scope: *Scope, file_scope_asm: *const clang.FileScopeAsmDecl) Error!void {
const asm_string = file_scope_asm.getAsmString();
var len: usize = undefined;
const bytes_ptr = asm_string.getString_bytes_begin_size(&len);
const str = try std.fmt.allocPrint(c.arena, "\"{}\"", .{std.zig.fmtEscapes(bytes_ptr[0..len])});
const str_node = try Tag.string_literal.create(c.arena, str);
const asm_node = try Tag.asm_simple.create(c.arena, str_node);
const block = try Tag.block_single.create(c.arena, asm_node);
const comptime_node = try Tag.@"comptime".create(c.arena, block);
try scope.appendNode(comptime_node);
}
fn visitFnDecl(c: *Context, fn_decl: *const clang.FunctionDecl) Error!void {
const fn_name = try c.str(@ptrCast(*const clang.NamedDecl, fn_decl).getName_bytes_begin());
if (c.global_scope.sym_table.contains(fn_name))
return; // Avoid processing this decl twice
// Skip this declaration if a proper definition exists
if (!fn_decl.isThisDeclarationADefinition()) {
if (fn_decl.getDefinition()) |def|
return visitFnDecl(c, def);
}
const fn_decl_loc = fn_decl.getLocation();
const has_body = fn_decl.hasBody();
const storage_class = fn_decl.getStorageClass();
const is_always_inline = has_body and fn_decl.hasAlwaysInlineAttr();
var decl_ctx = FnDeclContext{
.fn_name = fn_name,
.has_body = has_body,
.storage_class = storage_class,
.is_always_inline = is_always_inline,
.is_export = switch (storage_class) {
.None => has_body and !is_always_inline and !fn_decl.isInlineSpecified(),
.Extern, .Static => false,
.PrivateExtern => return failDecl(c, fn_decl_loc, fn_name, "unsupported storage class: private extern", .{}),
.Auto => unreachable, // Not legal on functions
.Register => unreachable, // Not legal on functions
},
};
var fn_qt = fn_decl.getType();
const fn_type = while (true) {
const fn_type = fn_qt.getTypePtr();
switch (fn_type.getTypeClass()) {
.Attributed => {
const attr_type = @ptrCast(*const clang.AttributedType, fn_type);
fn_qt = attr_type.getEquivalentType();
},
.Paren => {
const paren_type = @ptrCast(*const clang.ParenType, fn_type);
fn_qt = paren_type.getInnerType();
},
else => break fn_type,
}
} else unreachable;
const fn_ty = @ptrCast(*const clang.FunctionType, fn_type);
const return_qt = fn_ty.getReturnType();
const proto_node = switch (fn_type.getTypeClass()) {
.FunctionProto => blk: {
const fn_proto_type = @ptrCast(*const clang.FunctionProtoType, fn_type);
if (has_body and fn_proto_type.isVariadic()) {
decl_ctx.has_body = false;
decl_ctx.storage_class = .Extern;
decl_ctx.is_export = false;
decl_ctx.is_always_inline = false;
try warn(c, &c.global_scope.base, fn_decl_loc, "TODO unable to translate variadic function, demoted to extern", .{});
}
break :blk transFnProto(c, fn_decl, fn_proto_type, fn_decl_loc, decl_ctx, true) catch |err| switch (err) {
error.UnsupportedType => {
return failDecl(c, fn_decl_loc, fn_name, "unable to resolve prototype of function", .{});
},
error.OutOfMemory => |e| return e,
};
},
.FunctionNoProto => blk: {
const fn_no_proto_type = @ptrCast(*const clang.FunctionType, fn_type);
break :blk transFnNoProto(c, fn_no_proto_type, fn_decl_loc, decl_ctx, true) catch |err| switch (err) {
error.UnsupportedType => {
return failDecl(c, fn_decl_loc, fn_name, "unable to resolve prototype of function", .{});
},
error.OutOfMemory => |e| return e,
};
},
else => return failDecl(c, fn_decl_loc, fn_name, "unable to resolve function type {}", .{fn_type.getTypeClass()}),
};
if (!decl_ctx.has_body) {
return addTopLevelDecl(c, fn_name, Node.initPayload(&proto_node.base));
}
// actual function definition with body
const body_stmt = fn_decl.getBody();
var block_scope = try Scope.Block.init(c, &c.global_scope.base, false);
block_scope.return_type = return_qt;
defer block_scope.deinit();
var scope = &block_scope.base;
var param_id: c_uint = 0;
for (proto_node.data.params) |*param| {
const param_name = param.name orelse {
proto_node.data.is_extern = true;
proto_node.data.is_export = false;
proto_node.data.is_inline = false;
try warn(c, &c.global_scope.base, fn_decl_loc, "function {s} parameter has no name, demoted to extern", .{fn_name});
return addTopLevelDecl(c, fn_name, Node.initPayload(&proto_node.base));
};
const c_param = fn_decl.getParamDecl(param_id);
const qual_type = c_param.getOriginalType();
const is_const = qual_type.isConstQualified();
const mangled_param_name = try block_scope.makeMangledName(c, param_name);
param.name = mangled_param_name;
if (!is_const) {
const bare_arg_name = try std.fmt.allocPrint(c.arena, "arg_{s}", .{mangled_param_name});
const arg_name = try block_scope.makeMangledName(c, bare_arg_name);
param.name = arg_name;
const redecl_node = try Tag.arg_redecl.create(c.arena, .{ .actual = mangled_param_name, .mangled = arg_name });
try block_scope.statements.append(redecl_node);
}
try block_scope.discardVariable(c, mangled_param_name);
param_id += 1;
}
const casted_body = @ptrCast(*const clang.CompoundStmt, body_stmt);
transCompoundStmtInline(c, casted_body, &block_scope) catch |err| switch (err) {
error.OutOfMemory => |e| return e,
error.UnsupportedTranslation,
error.UnsupportedType,
=> {
proto_node.data.is_extern = true;
proto_node.data.is_export = false;
proto_node.data.is_inline = false;
try warn(c, &c.global_scope.base, fn_decl_loc, "unable to translate function, demoted to extern", .{});
return addTopLevelDecl(c, fn_name, Node.initPayload(&proto_node.base));
},
};
// add return statement if the function didn't have one
blk: {
const maybe_body = try block_scope.complete(c);
if (fn_ty.getNoReturnAttr() or isAnyopaque(return_qt) or maybe_body.isNoreturn(false)) {
proto_node.data.body = maybe_body;
break :blk;
}
const rhs = transZeroInitExpr(c, scope, fn_decl_loc, return_qt.getTypePtr()) catch |err| switch (err) {
error.OutOfMemory => |e| return e,
error.UnsupportedTranslation,
error.UnsupportedType,
=> {
proto_node.data.is_extern = true;
proto_node.data.is_export = false;
proto_node.data.is_inline = false;
try warn(c, &c.global_scope.base, fn_decl_loc, "unable to create a return value for function, demoted to extern", .{});
return addTopLevelDecl(c, fn_name, Node.initPayload(&proto_node.base));
},
};
const ret = try Tag.@"return".create(c.arena, rhs);
try block_scope.statements.append(ret);
proto_node.data.body = try block_scope.complete(c);
}
return addTopLevelDecl(c, fn_name, Node.initPayload(&proto_node.base));
}
fn transQualTypeMaybeInitialized(c: *Context, scope: *Scope, qt: clang.QualType, decl_init: ?*const clang.Expr, loc: clang.SourceLocation) TransError!Node {
return if (decl_init) |init_expr|
transQualTypeInitialized(c, scope, qt, init_expr, loc)
else
transQualType(c, scope, qt, loc);
}
/// This is used in global scope to convert a string literal `S` to [*c]u8:
/// &(struct {
/// var static = S.*;
/// }).static;
fn stringLiteralToCharStar(c: *Context, str: Node) Error!Node {
const var_name = Scope.Block.StaticInnerName;
const variables = try c.arena.alloc(Node, 1);
variables[0] = try Tag.mut_str.create(c.arena, .{ .name = var_name, .init = str });
const anon_struct = try Tag.@"struct".create(c.arena, .{
.layout = .none,
.fields = &.{},
.functions = &.{},
.variables = variables,
});
const member_access = try Tag.field_access.create(c.arena, .{
.lhs = anon_struct,
.field_name = var_name,
});
return Tag.address_of.create(c.arena, member_access);
}
/// if mangled_name is not null, this var decl was declared in a block scope.
fn visitVarDecl(c: *Context, var_decl: *const clang.VarDecl, mangled_name: ?[]const u8) Error!void {
const var_name = mangled_name orelse try c.str(@ptrCast(*const clang.NamedDecl, var_decl).getName_bytes_begin());
if (c.global_scope.sym_table.contains(var_name))
return; // Avoid processing this decl twice
const is_pub = mangled_name == null;
const is_threadlocal = var_decl.getTLSKind() != .None;
const scope = &c.global_scope.base;
const var_decl_loc = var_decl.getLocation();
const qual_type = var_decl.getTypeSourceInfo_getType();
const storage_class = var_decl.getStorageClass();
const is_const = qual_type.isConstQualified();
const has_init = var_decl.hasInit();
const decl_init = var_decl.getInit();
// In C extern variables with initializers behave like Zig exports.
// extern int foo = 2;
// does the same as:
// extern int foo;
// int foo = 2;
var is_extern = storage_class == .Extern and !has_init;
var is_export = !is_extern and storage_class != .Static;
const type_node = transQualTypeMaybeInitialized(c, scope, qual_type, decl_init, var_decl_loc) catch |err| switch (err) {
error.UnsupportedTranslation, error.UnsupportedType => {
return failDecl(c, var_decl_loc, var_name, "unable to resolve variable type", .{});
},
error.OutOfMemory => |e| return e,
};
var init_node: ?Node = null;
// If the initialization expression is not present, initialize with undefined.
// If it is an integer literal, we can skip the @as since it will be redundant
// with the variable type.
if (has_init) trans_init: {
if (decl_init) |expr| {
const node_or_error = if (expr.getStmtClass() == .StringLiteralClass)
transStringLiteralInitializer(c, scope, @ptrCast(*const clang.StringLiteral, expr), type_node)
else
transExprCoercing(c, scope, expr, .used);
init_node = node_or_error catch |err| switch (err) {
error.UnsupportedTranslation,
error.UnsupportedType,
=> {
is_extern = true;
is_export = false;
try warn(c, scope, var_decl_loc, "unable to translate variable initializer, demoted to extern", .{});
break :trans_init;
},
error.OutOfMemory => |e| return e,
};
if (!qualTypeIsBoolean(qual_type) and isBoolRes(init_node.?)) {
init_node = try Tag.bool_to_int.create(c.arena, init_node.?);
} else if (init_node.?.tag() == .string_literal and qualTypeIsCharStar(qual_type)) {
init_node = try stringLiteralToCharStar(c, init_node.?);
}
} else {
init_node = Tag.undefined_literal.init();
}
} else if (storage_class != .Extern) {
// The C language specification states that variables with static or threadlocal
// storage without an initializer are initialized to a zero value.
// std.mem.zeroes(T)
init_node = try Tag.std_mem_zeroes.create(c.arena, type_node);
}
const linksection_string = blk: {
var str_len: usize = undefined;
if (var_decl.getSectionAttribute(&str_len)) |str_ptr| {
break :blk str_ptr[0..str_len];
}
break :blk null;
};
const node = try Tag.var_decl.create(c.arena, .{
.is_pub = is_pub,
.is_const = is_const,
.is_extern = is_extern,
.is_export = is_export,
.is_threadlocal = is_threadlocal,
.linksection_string = linksection_string,
.alignment = zigAlignment(var_decl.getAlignedAttribute(c.clang_context)),
.name = var_name,
.type = type_node,
.init = init_node,
});
return addTopLevelDecl(c, var_name, node);
}
const builtin_typedef_map = std.ComptimeStringMap([]const u8, .{
.{ "uint8_t", "u8" },
.{ "int8_t", "i8" },
.{ "uint16_t", "u16" },
.{ "int16_t", "i16" },
.{ "uint32_t", "u32" },
.{ "int32_t", "i32" },
.{ "uint64_t", "u64" },
.{ "int64_t", "i64" },
.{ "intptr_t", "isize" },
.{ "uintptr_t", "usize" },
.{ "ssize_t", "isize" },
.{ "size_t", "usize" },
});
fn transTypeDef(c: *Context, scope: *Scope, typedef_decl: *const clang.TypedefNameDecl) Error!void {
if (c.decl_table.get(@ptrToInt(typedef_decl.getCanonicalDecl()))) |_|
return; // Avoid processing this decl twice
const toplevel = scope.id == .root;
const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(c) else undefined;
var name: []const u8 = try c.str(@ptrCast(*const clang.NamedDecl, typedef_decl).getName_bytes_begin());
try c.typedefs.put(c.gpa, name, {});
if (builtin_typedef_map.get(name)) |builtin| {
return c.decl_table.putNoClobber(c.gpa, @ptrToInt(typedef_decl.getCanonicalDecl()), builtin);
}
if (!toplevel) name = try bs.makeMangledName(c, name);
try c.decl_table.putNoClobber(c.gpa, @ptrToInt(typedef_decl.getCanonicalDecl()), name);
const child_qt = typedef_decl.getUnderlyingType();
const typedef_loc = typedef_decl.getLocation();
const init_node = transQualType(c, scope, child_qt, typedef_loc) catch |err| switch (err) {
error.UnsupportedType => {
return failDecl(c, typedef_loc, name, "unable to resolve typedef child type", .{});
},
error.OutOfMemory => |e| return e,
};
const payload = try c.arena.create(ast.Payload.SimpleVarDecl);
payload.* = .{
.base = .{ .tag = ([2]Tag{ .var_simple, .pub_var_simple })[@boolToInt(toplevel)] },
.data = .{
.name = name,
.init = init_node,
},
};
const node = Node.initPayload(&payload.base);
if (toplevel) {
try addTopLevelDecl(c, name, node);
} else {
try scope.appendNode(node);
if (node.tag() != .pub_var_simple) {
try bs.discardVariable(c, name);
}
}
}
/// Build a getter function for a flexible array member at the end of a C struct
/// e.g. `T items[]` or `T items[0]`. The generated function returns a [*c] pointer
/// to the flexible array with the correct const and volatile qualifiers
fn buildFlexibleArrayFn(
c: *Context,
scope: *Scope,
layout: *const clang.ASTRecordLayout,
field_name: []const u8,
field_decl: *const clang.FieldDecl,
) TypeError!Node {
const field_qt = field_decl.getType();
const u8_type = try Tag.type.create(c.arena, "u8");
const self_param_name = "self";
const self_param = try Tag.identifier.create(c.arena, self_param_name);
const self_type = try Tag.typeof.create(c.arena, self_param);
const fn_params = try c.arena.alloc(ast.Payload.Param, 1);
fn_params[0] = .{
.name = self_param_name,
.type = Tag.@"anytype".init(),
.is_noalias = false,
};
const array_type = @ptrCast(*const clang.ArrayType, field_qt.getTypePtr());
const element_qt = array_type.getElementType();
const element_type = try transQualType(c, scope, element_qt, field_decl.getLocation());
var block_scope = try Scope.Block.init(c, scope, false);
defer block_scope.deinit();
const intermediate_type_name = try block_scope.makeMangledName(c, "Intermediate");
const intermediate_type = try Tag.helpers_flexible_array_type.create(c.arena, .{ .lhs = self_type, .rhs = u8_type });
const intermediate_type_decl = try Tag.var_simple.create(c.arena, .{
.name = intermediate_type_name,
.init = intermediate_type,
});
try block_scope.statements.append(intermediate_type_decl);
const intermediate_type_ident = try Tag.identifier.create(c.arena, intermediate_type_name);
const return_type_name = try block_scope.makeMangledName(c, "ReturnType");
const return_type = try Tag.helpers_flexible_array_type.create(c.arena, .{ .lhs = self_type, .rhs = element_type });
const return_type_decl = try Tag.var_simple.create(c.arena, .{
.name = return_type_name,
.init = return_type,
});
try block_scope.statements.append(return_type_decl);
const return_type_ident = try Tag.identifier.create(c.arena, return_type_name);
const field_index = field_decl.getFieldIndex();
const bit_offset = layout.getFieldOffset(field_index); // this is a target-specific constant based on the struct layout
const byte_offset = bit_offset / 8;
const casted_self = try Tag.ptr_cast.create(c.arena, .{
.lhs = intermediate_type_ident,
.rhs = self_param,
});
const field_offset = try transCreateNodeNumber(c, byte_offset, .int);
const field_ptr = try Tag.add.create(c.arena, .{ .lhs = casted_self, .rhs = field_offset });
const alignment = try Tag.alignof.create(c.arena, element_type);
const ptr_val = try Tag.align_cast.create(c.arena, .{ .lhs = alignment, .rhs = field_ptr });
const ptr_cast = try Tag.ptr_cast.create(c.arena, .{ .lhs = return_type_ident, .rhs = ptr_val });
const return_stmt = try Tag.@"return".create(c.arena, ptr_cast);
try block_scope.statements.append(return_stmt);
const payload = try c.arena.create(ast.Payload.Func);
payload.* = .{
.base = .{ .tag = .func },
.data = .{
.is_pub = true,
.is_extern = false,
.is_export = false,
.is_inline = false,
.is_var_args = false,
.name = field_name,
.linksection_string = null,
.explicit_callconv = null,
.params = fn_params,
.return_type = return_type,
.body = try block_scope.complete(c),
.alignment = null,
},
};
return Node.initPayload(&payload.base);
}
fn isFlexibleArrayFieldDecl(c: *Context, field_decl: *const clang.FieldDecl) bool {
return qualTypeCanon(field_decl.getType()).isIncompleteOrZeroLengthArrayType(c.clang_context);
}
/// clang's RecordDecl::hasFlexibleArrayMember is not suitable for determining
/// this because it returns false for a record that ends with a zero-length
/// array, but we consider those to be flexible arrays
fn hasFlexibleArrayField(c: *Context, record_def: *const clang.RecordDecl) bool {
var it = record_def.field_begin();
const end_it = record_def.field_end();
while (it.neq(end_it)) : (it = it.next()) {
const field_decl = it.deref();
if (isFlexibleArrayFieldDecl(c, field_decl)) return true;
}
return false;
}
fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordDecl) Error!void {
if (c.decl_table.get(@ptrToInt(record_decl.getCanonicalDecl()))) |_|
return; // Avoid processing this decl twice
const record_loc = record_decl.getLocation();
const toplevel = scope.id == .root;
const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(c) else undefined;
var is_union = false;
var container_kind_name: []const u8 = undefined;
var bare_name: []const u8 = try c.str(@ptrCast(*const clang.NamedDecl, record_decl).getName_bytes_begin());
if (record_decl.isUnion()) {
container_kind_name = "union";
is_union = true;
} else if (record_decl.isStruct()) {
container_kind_name = "struct";
} else {
try c.decl_table.putNoClobber(c.gpa, @ptrToInt(record_decl.getCanonicalDecl()), bare_name);
return failDecl(c, record_loc, bare_name, "record {s} is not a struct or union", .{bare_name});
}
var is_unnamed = false;
var name = bare_name;
if (c.unnamed_typedefs.get(@ptrToInt(record_decl.getCanonicalDecl()))) |typedef_name| {
bare_name = typedef_name;
name = typedef_name;
} else {
// Record declarations such as `struct {...} x` have no name but they're not
// anonymous hence here isAnonymousStructOrUnion is not needed
if (bare_name.len == 0) {
bare_name = try std.fmt.allocPrint(c.arena, "unnamed_{d}", .{c.getMangle()});
is_unnamed = true;
}
name = try std.fmt.allocPrint(c.arena, "{s}_{s}", .{ container_kind_name, bare_name });
}
if (!toplevel) name = try bs.makeMangledName(c, name);
try c.decl_table.putNoClobber(c.gpa, @ptrToInt(record_decl.getCanonicalDecl()), name);
const is_pub = toplevel and !is_unnamed;
const init_node = blk: {
const record_def = record_decl.getDefinition() orelse {
try c.opaque_demotes.put(c.gpa, @ptrToInt(record_decl.getCanonicalDecl()), {});
break :blk Tag.opaque_literal.init();
};
const is_packed = record_decl.getPackedAttribute();
var fields = std.ArrayList(ast.Payload.Record.Field).init(c.gpa);
defer fields.deinit();
var functions = std.ArrayList(Node).init(c.gpa);
defer functions.deinit();
const has_flexible_array = hasFlexibleArrayField(c, record_def);
var unnamed_field_count: u32 = 0;
var it = record_def.field_begin();
const end_it = record_def.field_end();
const layout = record_def.getASTRecordLayout(c.clang_context);
const record_alignment = layout.getAlignment();
while (it.neq(end_it)) : (it = it.next()) {
const field_decl = it.deref();
const field_loc = field_decl.getLocation();
const field_qt = field_decl.getType();
if (field_decl.isBitField()) {
try c.opaque_demotes.put(c.gpa, @ptrToInt(record_decl.getCanonicalDecl()), {});
try warn(c, scope, field_loc, "{s} demoted to opaque type - has bitfield", .{container_kind_name});
break :blk Tag.opaque_literal.init();
}
var is_anon = false;
var field_name = try c.str(@ptrCast(*const clang.NamedDecl, field_decl).getName_bytes_begin());
if (field_decl.isAnonymousStructOrUnion() or field_name.len == 0) {
// Context.getMangle() is not used here because doing so causes unpredictable field names for anonymous fields.
field_name = try std.fmt.allocPrint(c.arena, "unnamed_{d}", .{unnamed_field_count});
unnamed_field_count += 1;
is_anon = true;
}
if (isFlexibleArrayFieldDecl(c, field_decl)) {
const flexible_array_fn = buildFlexibleArrayFn(c, scope, layout, field_name, field_decl) catch |err| switch (err) {
error.UnsupportedType => {
try c.opaque_demotes.put(c.gpa, @ptrToInt(record_decl.getCanonicalDecl()), {});
try warn(c, scope, record_loc, "{s} demoted to opaque type - unable to translate type of flexible array field {s}", .{ container_kind_name, field_name });
break :blk Tag.opaque_literal.init();
},
else => |e| return e,
};
try functions.append(flexible_array_fn);
continue;
}
const field_type = transQualType(c, scope, field_qt, field_loc) catch |err| switch (err) {
error.UnsupportedType => {
try c.opaque_demotes.put(c.gpa, @ptrToInt(record_decl.getCanonicalDecl()), {});
try warn(c, scope, record_loc, "{s} demoted to opaque type - unable to translate type of field {s}", .{ container_kind_name, field_name });
break :blk Tag.opaque_literal.init();
},
else => |e| return e,
};
const alignment = if (has_flexible_array and field_decl.getFieldIndex() == 0)
@intCast(c_uint, record_alignment)
else
zigAlignment(field_decl.getAlignedAttribute(c.clang_context));
if (is_anon) {
try c.decl_table.putNoClobber(c.gpa, @ptrToInt(field_decl.getCanonicalDecl()), field_name);
}
try fields.append(.{
.name = field_name,
.type = field_type,
.alignment = alignment,
});
}
const record_payload = try c.arena.create(ast.Payload.Record);
record_payload.* = .{
.base = .{ .tag = ([2]Tag{ .@"struct", .@"union" })[@boolToInt(is_union)] },
.data = .{
.layout = if (is_packed) .@"packed" else .@"extern",
.fields = try c.arena.dupe(ast.Payload.Record.Field, fields.items),
.functions = try c.arena.dupe(Node, functions.items),
.variables = &.{},
},
};
break :blk Node.initPayload(&record_payload.base);
};
const payload = try c.arena.create(ast.Payload.SimpleVarDecl);
payload.* = .{
.base = .{ .tag = ([2]Tag{ .var_simple, .pub_var_simple })[@boolToInt(is_pub)] },
.data = .{
.name = name,
.init = init_node,
},
};
const node = Node.initPayload(&payload.base);
if (toplevel) {
try addTopLevelDecl(c, name, node);
if (!is_unnamed)
try c.alias_list.append(.{ .alias = bare_name, .name = name });
} else {
try scope.appendNode(node);
if (node.tag() != .pub_var_simple) {
try bs.discardVariable(c, name);
}
}
}
fn transEnumDecl(c: *Context, scope: *Scope, enum_decl: *const clang.EnumDecl) Error!void {
if (c.decl_table.get(@ptrToInt(enum_decl.getCanonicalDecl()))) |_|
return; // Avoid processing this decl twice
const enum_loc = enum_decl.getLocation();
const toplevel = scope.id == .root;
const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(c) else undefined;
var is_unnamed = false;
var bare_name: []const u8 = try c.str(@ptrCast(*const clang.NamedDecl, enum_decl).getName_bytes_begin());
var name = bare_name;
if (c.unnamed_typedefs.get(@ptrToInt(enum_decl.getCanonicalDecl()))) |typedef_name| {
bare_name = typedef_name;
name = typedef_name;
} else {
if (bare_name.len == 0) {
bare_name = try std.fmt.allocPrint(c.arena, "unnamed_{d}", .{c.getMangle()});
is_unnamed = true;
}
name = try std.fmt.allocPrint(c.arena, "enum_{s}", .{bare_name});
}
if (!toplevel) name = try bs.makeMangledName(c, name);
try c.decl_table.putNoClobber(c.gpa, @ptrToInt(enum_decl.getCanonicalDecl()), name);
const enum_type_node = if (enum_decl.getDefinition()) |enum_def| blk: {
var it = enum_def.enumerator_begin();
const end_it = enum_def.enumerator_end();
while (it.neq(end_it)) : (it = it.next()) {
const enum_const = it.deref();
var enum_val_name: []const u8 = try c.str(@ptrCast(*const clang.NamedDecl, enum_const).getName_bytes_begin());
if (!toplevel) {
enum_val_name = try bs.makeMangledName(c, enum_val_name);
}
const enum_const_qt = @ptrCast(*const clang.ValueDecl, enum_const).getType();
const enum_const_loc = @ptrCast(*const clang.Decl, enum_const).getLocation();
const enum_const_type_node: ?Node = transQualType(c, scope, enum_const_qt, enum_const_loc) catch |err| switch (err) {
error.UnsupportedType => null,
else => |e| return e,
};
const enum_const_def = try Tag.enum_constant.create(c.arena, .{
.name = enum_val_name,
.is_public = toplevel,
.type = enum_const_type_node,
.value = try transCreateNodeAPInt(c, enum_const.getInitVal()),
});
if (toplevel)
try addTopLevelDecl(c, enum_val_name, enum_const_def)
else {
try scope.appendNode(enum_const_def);
try bs.discardVariable(c, enum_val_name);
}
}
const int_type = enum_decl.getIntegerType();
// The underlying type may be null in case of forward-declared enum
// types, while that's not ISO-C compliant many compilers allow this and
// default to the usual integer type used for all the enums.
// default to c_int since msvc and gcc default to different types
break :blk if (int_type.ptr != null)
transQualType(c, scope, int_type, enum_loc) catch |err| switch (err) {
error.UnsupportedType => {
return failDecl(c, enum_loc, name, "unable to translate enum integer type", .{});
},
else => |e| return e,
}
else
try Tag.type.create(c.arena, "c_int");
} else blk: {
try c.opaque_demotes.put(c.gpa, @ptrToInt(enum_decl.getCanonicalDecl()), {});
break :blk Tag.opaque_literal.init();
};
const is_pub = toplevel and !is_unnamed;
const payload = try c.arena.create(ast.Payload.SimpleVarDecl);
payload.* = .{
.base = .{ .tag = ([2]Tag{ .var_simple, .pub_var_simple })[@boolToInt(is_pub)] },
.data = .{
.init = enum_type_node,
.name = name,
},
};
const node = Node.initPayload(&payload.base);
if (toplevel) {
try addTopLevelDecl(c, name, node);
if (!is_unnamed)
try c.alias_list.append(.{ .alias = bare_name, .name = name });
} else {
try scope.appendNode(node);
if (node.tag() != .pub_var_simple) {
try bs.discardVariable(c, name);
}
}
}
const ResultUsed = enum {
used,
unused,
};
fn transStmt(
c: *Context,
scope: *Scope,
stmt: *const clang.Stmt,
result_used: ResultUsed,
) TransError!Node {
const sc = stmt.getStmtClass();
switch (sc) {
.BinaryOperatorClass => return transBinaryOperator(c, scope, @ptrCast(*const clang.BinaryOperator, stmt), result_used),
.CompoundStmtClass => return transCompoundStmt(c, scope, @ptrCast(*const clang.CompoundStmt, stmt)),
.CStyleCastExprClass => return transCStyleCastExprClass(c, scope, @ptrCast(*const clang.CStyleCastExpr, stmt), result_used),
.DeclStmtClass => return transDeclStmt(c, scope, @ptrCast(*const clang.DeclStmt, stmt)),
.DeclRefExprClass => return transDeclRefExpr(c, scope, @ptrCast(*const clang.DeclRefExpr, stmt)),
.ImplicitCastExprClass => return transImplicitCastExpr(c, scope, @ptrCast(*const clang.ImplicitCastExpr, stmt), result_used),
.IntegerLiteralClass => return transIntegerLiteral(c, scope, @ptrCast(*const clang.IntegerLiteral, stmt), result_used, .with_as),
.ReturnStmtClass => return transReturnStmt(c, scope, @ptrCast(*const clang.ReturnStmt, stmt)),
.StringLiteralClass => return transStringLiteral(c, scope, @ptrCast(*const clang.StringLiteral, stmt), result_used),
.ParenExprClass => {
const expr = try transExpr(c, scope, @ptrCast(*const clang.ParenExpr, stmt).getSubExpr(), .used);
return maybeSuppressResult(c, scope, result_used, expr);
},
.InitListExprClass => return transInitListExpr(c, scope, @ptrCast(*const clang.InitListExpr, stmt), result_used),
.ImplicitValueInitExprClass => return transImplicitValueInitExpr(c, scope, @ptrCast(*const clang.Expr, stmt), result_used),
.IfStmtClass => return transIfStmt(c, scope, @ptrCast(*const clang.IfStmt, stmt)),
.WhileStmtClass => return transWhileLoop(c, scope, @ptrCast(*const clang.WhileStmt, stmt)),
.DoStmtClass => return transDoWhileLoop(c, scope, @ptrCast(*const clang.DoStmt, stmt)),
.NullStmtClass => {
return Tag.empty_block.init();
},
.ContinueStmtClass => return Tag.@"continue".init(),
.BreakStmtClass => return Tag.@"break".init(),
.ForStmtClass => return transForLoop(c, scope, @ptrCast(*const clang.ForStmt, stmt)),
.FloatingLiteralClass => return transFloatingLiteral(c, scope, @ptrCast(*const clang.FloatingLiteral, stmt), result_used),
.ConditionalOperatorClass => {
return transConditionalOperator(c, scope, @ptrCast(*const clang.ConditionalOperator, stmt), result_used);
},
.BinaryConditionalOperatorClass => {
return transBinaryConditionalOperator(c, scope, @ptrCast(*const clang.BinaryConditionalOperator, stmt), result_used);
},
.SwitchStmtClass => return transSwitch(c, scope, @ptrCast(*const clang.SwitchStmt, stmt)),
.CaseStmtClass, .DefaultStmtClass => {
return fail(c, error.UnsupportedTranslation, stmt.getBeginLoc(), "TODO complex switch", .{});
},
.ConstantExprClass => return transConstantExpr(c, scope, @ptrCast(*const clang.Expr, stmt), result_used),
.PredefinedExprClass => return transPredefinedExpr(c, scope, @ptrCast(*const clang.PredefinedExpr, stmt), result_used),
.CharacterLiteralClass => return transCharLiteral(c, scope, @ptrCast(*const clang.CharacterLiteral, stmt), result_used, .with_as),
.StmtExprClass => return transStmtExpr(c, scope, @ptrCast(*const clang.StmtExpr, stmt), result_used),
.MemberExprClass => return transMemberExpr(c, scope, @ptrCast(*const clang.MemberExpr, stmt), result_used),
.ArraySubscriptExprClass => return transArrayAccess(c, scope, @ptrCast(*const clang.ArraySubscriptExpr, stmt), result_used),
.CallExprClass => return transCallExpr(c, scope, @ptrCast(*const clang.CallExpr, stmt), result_used),
.UnaryExprOrTypeTraitExprClass => return transUnaryExprOrTypeTraitExpr(c, scope, @ptrCast(*const clang.UnaryExprOrTypeTraitExpr, stmt), result_used),
.UnaryOperatorClass => return transUnaryOperator(c, scope, @ptrCast(*const clang.UnaryOperator, stmt), result_used),
.CompoundAssignOperatorClass => return transCompoundAssignOperator(c, scope, @ptrCast(*const clang.CompoundAssignOperator, stmt), result_used),
.OpaqueValueExprClass => {
const source_expr = @ptrCast(*const clang.OpaqueValueExpr, stmt).getSourceExpr().?;
const expr = try transExpr(c, scope, source_expr, .used);
return maybeSuppressResult(c, scope, result_used, expr);
},
.OffsetOfExprClass => return transOffsetOfExpr(c, scope, @ptrCast(*const clang.OffsetOfExpr, stmt), result_used),
.CompoundLiteralExprClass => {
const compound_literal = @ptrCast(*const clang.CompoundLiteralExpr, stmt);
return transExpr(c, scope, compound_literal.getInitializer(), result_used);
},
.GenericSelectionExprClass => {
const gen_sel = @ptrCast(*const clang.GenericSelectionExpr, stmt);
return transExpr(c, scope, gen_sel.getResultExpr(), result_used);
},
.ConvertVectorExprClass => {
const conv_vec = @ptrCast(*const clang.ConvertVectorExpr, stmt);
const conv_vec_node = try transConvertVectorExpr(c, scope, stmt.getBeginLoc(), conv_vec);
return maybeSuppressResult(c, scope, result_used, conv_vec_node);
},
.ShuffleVectorExprClass => {
const shuffle_vec_expr = @ptrCast(*const clang.ShuffleVectorExpr, stmt);
const shuffle_vec_node = try transShuffleVectorExpr(c, scope, shuffle_vec_expr);
return maybeSuppressResult(c, scope, result_used, shuffle_vec_node);
},
.ChooseExprClass => {
const choose_expr = @ptrCast(*const clang.ChooseExpr, stmt);
return transExpr(c, scope, choose_expr.getChosenSubExpr(), result_used);
},
// When adding new cases here, see comment for maybeBlockify()
.GCCAsmStmtClass,
.GotoStmtClass,
.IndirectGotoStmtClass,
.AttributedStmtClass,
.AddrLabelExprClass,
.AtomicExprClass,
.BlockExprClass,
.UserDefinedLiteralClass,
.BuiltinBitCastExprClass,
.DesignatedInitExprClass,
.LabelStmtClass,
=> return fail(c, error.UnsupportedTranslation, stmt.getBeginLoc(), "TODO implement translation of stmt class {s}", .{@tagName(sc)}),
else => return fail(c, error.UnsupportedTranslation, stmt.getBeginLoc(), "unsupported stmt class {s}", .{@tagName(sc)}),
}
}
/// See https://clang.llvm.org/docs/LanguageExtensions.html#langext-builtin-convertvector
fn transConvertVectorExpr(
c: *Context,
scope: *Scope,
source_loc: clang.SourceLocation,
expr: *const clang.ConvertVectorExpr,
) TransError!Node {
_ = source_loc;
const base_stmt = @ptrCast(*const clang.Stmt, expr);
var block_scope = try Scope.Block.init(c, scope, true);
defer block_scope.deinit();
const src_expr = expr.getSrcExpr();
const src_type = qualTypeCanon(src_expr.getType());
const src_vector_ty = @ptrCast(*const clang.VectorType, src_type);
const src_element_qt = src_vector_ty.getElementType();
const src_expr_node = try transExpr(c, &block_scope.base, src_expr, .used);
const dst_qt = expr.getTypeSourceInfo_getType();
const dst_type_node = try transQualType(c, &block_scope.base, dst_qt, base_stmt.getBeginLoc());
const dst_vector_ty = @ptrCast(*const clang.VectorType, qualTypeCanon(dst_qt));
const num_elements = dst_vector_ty.getNumElements();
const dst_element_qt = dst_vector_ty.getElementType();
// workaround for https://github.com/ziglang/zig/issues/8322
// we store the casted results into temp variables and use those
// to initialize the vector. Eventually we can just directly
// construct the init_list from casted source members
var i: usize = 0;
while (i < num_elements) : (i += 1) {
const mangled_name = try block_scope.makeMangledName(c, "tmp");
const value = try Tag.array_access.create(c.arena, .{
.lhs = src_expr_node,
.rhs = try transCreateNodeNumber(c, i, .int),
});
const tmp_decl_node = try Tag.var_simple.create(c.arena, .{
.name = mangled_name,
.init = try transCCast(c, &block_scope.base, base_stmt.getBeginLoc(), dst_element_qt, src_element_qt, value),
});
try block_scope.statements.append(tmp_decl_node);
}
const init_list = try c.arena.alloc(Node, num_elements);
for (init_list) |*init, init_index| {
const tmp_decl = block_scope.statements.items[init_index];
const name = tmp_decl.castTag(.var_simple).?.data.name;
init.* = try Tag.identifier.create(c.arena, name);
}
const vec_init = try Tag.array_init.create(c.arena, .{
.cond = dst_type_node,
.cases = init_list,
});
const break_node = try Tag.break_val.create(c.arena, .{
.label = block_scope.label,
.val = vec_init,
});
try block_scope.statements.append(break_node);
return block_scope.complete(c);
}
fn makeShuffleMask(c: *Context, scope: *Scope, expr: *const clang.ShuffleVectorExpr, vector_len: Node) TransError!Node {
const num_subexprs = expr.getNumSubExprs();
assert(num_subexprs >= 3); // two source vectors + at least 1 index expression
const mask_len = num_subexprs - 2;
const mask_type = try Tag.vector.create(c.arena, .{
.lhs = try transCreateNodeNumber(c, mask_len, .int),
.rhs = try Tag.type.create(c.arena, "i32"),
});
const init_list = try c.arena.alloc(Node, mask_len);
for (init_list) |*init, i| {
const index_expr = try transExprCoercing(c, scope, expr.getExpr(@intCast(c_uint, i + 2)), .used);
const converted_index = try Tag.helpers_shuffle_vector_index.create(c.arena, .{ .lhs = index_expr, .rhs = vector_len });
init.* = converted_index;
}
return Tag.array_init.create(c.arena, .{
.cond = mask_type,
.cases = init_list,
});
}
/// @typeInfo(@TypeOf(vec_node)).Vector.<field>
fn vectorTypeInfo(arena: mem.Allocator, vec_node: Node, field: []const u8) TransError!Node {
const typeof_call = try Tag.typeof.create(arena, vec_node);
const typeinfo_call = try Tag.typeinfo.create(arena, typeof_call);
const vector_type_info = try Tag.field_access.create(arena, .{ .lhs = typeinfo_call, .field_name = "Vector" });
return Tag.field_access.create(arena, .{ .lhs = vector_type_info, .field_name = field });
}
fn transShuffleVectorExpr(
c: *Context,
scope: *Scope,
expr: *const clang.ShuffleVectorExpr,
) TransError!Node {
const base_expr = @ptrCast(*const clang.Expr, expr);
const num_subexprs = expr.getNumSubExprs();
if (num_subexprs < 3) return fail(c, error.UnsupportedTranslation, base_expr.getBeginLoc(), "ShuffleVector needs at least 1 index", .{});
const a = try transExpr(c, scope, expr.getExpr(0), .used);
const b = try transExpr(c, scope, expr.getExpr(1), .used);
// clang requires first two arguments to __builtin_shufflevector to be same type
const vector_child_type = try vectorTypeInfo(c.arena, a, "child");
const vector_len = try vectorTypeInfo(c.arena, a, "len");
const shuffle_mask = try makeShuffleMask(c, scope, expr, vector_len);
return Tag.shuffle.create(c.arena, .{
.element_type = vector_child_type,
.a = a,
.b = b,
.mask_vector = shuffle_mask,
});
}
/// Translate a "simple" offsetof expression containing exactly one component,
/// when that component is of kind .Field - e.g. offsetof(mytype, myfield)
fn transSimpleOffsetOfExpr(
c: *Context,
scope: *Scope,
expr: *const clang.OffsetOfExpr,
) TransError!Node {
_ = scope;
assert(expr.getNumComponents() == 1);
const component = expr.getComponent(0);
if (component.getKind() == .Field) {
const field_decl = component.getField();
if (field_decl.getParent()) |record_decl| {
if (c.decl_table.get(@ptrToInt(record_decl.getCanonicalDecl()))) |type_name| {
const type_node = try Tag.type.create(c.arena, type_name);
var raw_field_name = try c.str(@ptrCast(*const clang.NamedDecl, field_decl).getName_bytes_begin());
const quoted_field_name = try std.fmt.allocPrint(c.arena, "\"{s}\"", .{raw_field_name});
const field_name_node = try Tag.string_literal.create(c.arena, quoted_field_name);
return Tag.offset_of.create(c.arena, .{
.lhs = type_node,
.rhs = field_name_node,
});
}
}
}
return fail(c, error.UnsupportedTranslation, expr.getBeginLoc(), "failed to translate simple OffsetOfExpr", .{});
}
fn transOffsetOfExpr(
c: *Context,
scope: *Scope,
expr: *const clang.OffsetOfExpr,
result_used: ResultUsed,
) TransError!Node {
if (expr.getNumComponents() == 1) {
const offsetof_expr = try transSimpleOffsetOfExpr(c, scope, expr);
return maybeSuppressResult(c, scope, result_used, offsetof_expr);
}
// TODO implement OffsetOfExpr with more than 1 component
// OffsetOfExpr API:
// call expr.getComponent(idx) while idx < expr.getNumComponents()
// component.getKind() will be either .Array or .Field (other kinds are C++-only)
// if .Field, use component.getField() to retrieve *clang.FieldDecl
// if .Array, use component.getArrayExprIndex() to get a c_uint which
// can be passed to expr.getIndexExpr(expr_index) to get the *clang.Expr for the array index
return fail(c, error.UnsupportedTranslation, expr.getBeginLoc(), "TODO: implement complex OffsetOfExpr translation", .{});
}
/// Cast a signed integer node to a usize, for use in pointer arithmetic. Negative numbers
/// will become very large positive numbers but that is ok since we only use this in
/// pointer arithmetic expressions, where wraparound will ensure we get the correct value.
/// node -> @bitCast(usize, @intCast(isize, node))
fn usizeCastForWrappingPtrArithmetic(gpa: mem.Allocator, node: Node) TransError!Node {
const intcast_node = try Tag.int_cast.create(gpa, .{
.lhs = try Tag.type.create(gpa, "isize"),
.rhs = node,
});
return Tag.bit_cast.create(gpa, .{
.lhs = try Tag.type.create(gpa, "usize"),
.rhs = intcast_node,
});
}
/// Translate an arithmetic expression with a pointer operand and a signed-integer operand.
/// Zig requires a usize argument for pointer arithmetic, so we intCast to isize and then
/// bitcast to usize; pointer wraparound make the math work.
/// Zig pointer addition is not commutative (unlike C); the pointer operand needs to be on the left.
/// The + operator in C is not a sequence point so it should be safe to switch the order if necessary.
fn transCreatePointerArithmeticSignedOp(
c: *Context,
scope: *Scope,
stmt: *const clang.BinaryOperator,
result_used: ResultUsed,
) TransError!Node {
const is_add = stmt.getOpcode() == .Add;
const lhs = stmt.getLHS();
const rhs = stmt.getRHS();
const swap_operands = is_add and cIsSignedInteger(getExprQualType(c, lhs));
const swizzled_lhs = if (swap_operands) rhs else lhs;
const swizzled_rhs = if (swap_operands) lhs else rhs;
const lhs_node = try transExpr(c, scope, swizzled_lhs, .used);
const rhs_node = try transExpr(c, scope, swizzled_rhs, .used);
const bitcast_node = try usizeCastForWrappingPtrArithmetic(c.arena, rhs_node);
return transCreateNodeInfixOp(
c,
scope,
if (is_add) .add else .sub,
lhs_node,
bitcast_node,
result_used,
);
}
fn transBinaryOperator(
c: *Context,
scope: *Scope,
stmt: *const clang.BinaryOperator,
result_used: ResultUsed,
) TransError!Node {
const op = stmt.getOpcode();
const qt = stmt.getType();
const isPointerDiffExpr = cIsPointerDiffExpr(c, stmt);
switch (op) {
.Assign => return try transCreateNodeAssign(c, scope, result_used, stmt.getLHS(), stmt.getRHS()),
.Comma => {
var block_scope = try Scope.Block.init(c, scope, true);
defer block_scope.deinit();
const lhs = try transExpr(c, &block_scope.base, stmt.getLHS(), .unused);
try block_scope.statements.append(lhs);
const rhs = try transExpr(c, &block_scope.base, stmt.getRHS(), .used);
const break_node = try Tag.break_val.create(c.arena, .{
.label = block_scope.label,
.val = rhs,
});
try block_scope.statements.append(break_node);
const block_node = try block_scope.complete(c);
return maybeSuppressResult(c, scope, result_used, block_node);
},
.Div => {
if (cIsSignedInteger(qt)) {
// signed integer division uses @divTrunc
const lhs = try transExpr(c, scope, stmt.getLHS(), .used);
const rhs = try transExpr(c, scope, stmt.getRHS(), .used);
const div_trunc = try Tag.div_trunc.create(c.arena, .{ .lhs = lhs, .rhs = rhs });
return maybeSuppressResult(c, scope, result_used, div_trunc);
}
},
.Rem => {
if (cIsSignedInteger(qt)) {
// signed integer remainder uses std.zig.c_translation.signedRemainder
const lhs = try transExpr(c, scope, stmt.getLHS(), .used);
const rhs = try transExpr(c, scope, stmt.getRHS(), .used);
const rem = try Tag.signed_remainder.create(c.arena, .{ .lhs = lhs, .rhs = rhs });
return maybeSuppressResult(c, scope, result_used, rem);
}
},
.Shl => {
return transCreateNodeShiftOp(c, scope, stmt, .shl, result_used);
},
.Shr => {
return transCreateNodeShiftOp(c, scope, stmt, .shr, result_used);
},
.LAnd => {
return transCreateNodeBoolInfixOp(c, scope, stmt, .@"and", result_used);
},
.LOr => {
return transCreateNodeBoolInfixOp(c, scope, stmt, .@"or", result_used);
},
.Add, .Sub => {
// `ptr + idx` and `idx + ptr` -> ptr + @bitCast(usize, @intCast(isize, idx))
// `ptr - idx` -> ptr - @bitCast(usize, @intCast(isize, idx))
if (qualTypeIsPtr(qt) and (cIsSignedInteger(getExprQualType(c, stmt.getLHS())) or
cIsSignedInteger(getExprQualType(c, stmt.getRHS())))) return transCreatePointerArithmeticSignedOp(c, scope, stmt, result_used);
},
else => {},
}
var op_id: Tag = undefined;
switch (op) {
.Add => {
if (cIsUnsignedInteger(qt)) {
op_id = .add_wrap;
} else {
op_id = .add;
}
},
.Sub => {
if (cIsUnsignedInteger(qt) or isPointerDiffExpr) {
op_id = .sub_wrap;
} else {
op_id = .sub;
}
},
.Mul => {
if (cIsUnsignedInteger(qt)) {
op_id = .mul_wrap;
} else {
op_id = .mul;
}
},
.Div => {
// unsigned/float division uses the operator
op_id = .div;
},
.Rem => {
// unsigned/float division uses the operator
op_id = .mod;
},
.LT => {
op_id = .less_than;
},
.GT => {
op_id = .greater_than;
},
.LE => {
op_id = .less_than_equal;
},
.GE => {
op_id = .greater_than_equal;
},
.EQ => {
op_id = .equal;
},
.NE => {
op_id = .not_equal;
},
.And => {
op_id = .bit_and;
},
.Xor => {
op_id = .bit_xor;
},
.Or => {
op_id = .bit_or;
},
else => unreachable,
}
const lhs_uncasted = try transExpr(c, scope, stmt.getLHS(), .used);
const rhs_uncasted = try transExpr(c, scope, stmt.getRHS(), .used);
const lhs = if (isBoolRes(lhs_uncasted))
try Tag.bool_to_int.create(c.arena, lhs_uncasted)
else if (isPointerDiffExpr)
try Tag.ptr_to_int.create(c.arena, lhs_uncasted)
else
lhs_uncasted;
const rhs = if (isBoolRes(rhs_uncasted))
try Tag.bool_to_int.create(c.arena, rhs_uncasted)
else if (isPointerDiffExpr)
try Tag.ptr_to_int.create(c.arena, rhs_uncasted)
else
rhs_uncasted;
const infixOpNode = try transCreateNodeInfixOp(c, scope, op_id, lhs, rhs, result_used);
if (isPointerDiffExpr) {
// @divExact(@bitCast(<platform-ptrdiff_t>, @ptrToInt(lhs) -% @ptrToInt(rhs)), @sizeOf(<lhs target type>))
const ptrdiff_type = try transQualTypeIntWidthOf(c, qt, true);
// C standard requires that pointer subtraction operands are of the same type,
// otherwise it is undefined behavior. So we can assume the left and right
// sides are the same QualType and arbitrarily choose left.
const lhs_expr = stmt.getLHS();
const lhs_qt = getExprQualType(c, lhs_expr);
const lhs_qt_translated = try transQualType(c, scope, lhs_qt, lhs_expr.getBeginLoc());
const elem_type = lhs_qt_translated.castTag(.c_pointer).?.data.elem_type;
const sizeof = try Tag.sizeof.create(c.arena, elem_type);
const bitcast = try Tag.bit_cast.create(c.arena, .{ .lhs = ptrdiff_type, .rhs = infixOpNode });
return Tag.div_exact.create(c.arena, .{
.lhs = bitcast,
.rhs = sizeof,
});
}
return infixOpNode;
}
fn transCompoundStmtInline(
c: *Context,
stmt: *const clang.CompoundStmt,
block: *Scope.Block,
) TransError!void {
var it = stmt.body_begin();
const end_it = stmt.body_end();
while (it != end_it) : (it += 1) {
const result = try transStmt(c, &block.base, it[0], .unused);
switch (result.tag()) {
.declaration, .empty_block => {},
else => try block.statements.append(result),
}
}
}
fn transCompoundStmt(c: *Context, scope: *Scope, stmt: *const clang.CompoundStmt) TransError!Node {
var block_scope = try Scope.Block.init(c, scope, false);
defer block_scope.deinit();
try transCompoundStmtInline(c, stmt, &block_scope);
return try block_scope.complete(c);
}
fn transCStyleCastExprClass(
c: *Context,
scope: *Scope,
stmt: *const clang.CStyleCastExpr,
result_used: ResultUsed,
) TransError!Node {
const cast_expr = @ptrCast(*const clang.CastExpr, stmt);
const sub_expr = stmt.getSubExpr();
const dst_type = stmt.getType();
const src_type = sub_expr.getType();
const sub_expr_node = try transExpr(c, scope, sub_expr, .used);
const loc = stmt.getBeginLoc();
const cast_node = if (cast_expr.getCastKind() == .ToUnion) blk: {
const field_decl = cast_expr.getTargetFieldForToUnionCast(dst_type, src_type).?; // C syntax error if target field is null
const field_name = try c.str(@ptrCast(*const clang.NamedDecl, field_decl).getName_bytes_begin());
const union_ty = try transQualType(c, scope, dst_type, loc);
const inits = [1]ast.Payload.ContainerInit.Initializer{.{ .name = field_name, .value = sub_expr_node }};
break :blk try Tag.container_init.create(c.arena, .{
.lhs = union_ty,
.inits = try c.arena.dupe(ast.Payload.ContainerInit.Initializer, &inits),
});
} else (try transCCast(
c,
scope,
loc,
dst_type,
src_type,
sub_expr_node,
));
return maybeSuppressResult(c, scope, result_used, cast_node);
}
/// Clang reports the alignment in bits, we use bytes
/// Clang uses 0 for "no alignment specified", we use null
fn zigAlignment(bit_alignment: c_uint) ?c_uint {
if (bit_alignment == 0) return null;
return bit_alignment / 8;
}
fn transDeclStmtOne(
c: *Context,
scope: *Scope,
decl: *const clang.Decl,
block_scope: *Scope.Block,
) TransError!void {
switch (decl.getKind()) {
.Var => {
const var_decl = @ptrCast(*const clang.VarDecl, decl);
const decl_init = var_decl.getInit();
const qual_type = var_decl.getTypeSourceInfo_getType();
const name = try c.str(@ptrCast(*const clang.NamedDecl, var_decl).getName_bytes_begin());
const mangled_name = try block_scope.makeMangledName(c, name);
if (var_decl.getStorageClass() == .Extern) {
// This is actually a global variable, put it in the global scope and reference it.
// `_ = mangled_name;`
return visitVarDecl(c, var_decl, mangled_name);
}
const is_static_local = var_decl.isStaticLocal();
const is_const = qual_type.isConstQualified();
const loc = decl.getLocation();
const type_node = try transQualTypeMaybeInitialized(c, scope, qual_type, decl_init, loc);
var init_node = if (decl_init) |expr|
if (expr.getStmtClass() == .StringLiteralClass)
try transStringLiteralInitializer(c, scope, @ptrCast(*const clang.StringLiteral, expr), type_node)
else
try transExprCoercing(c, scope, expr, .used)
else if (is_static_local)
try Tag.std_mem_zeroes.create(c.arena, type_node)
else
Tag.undefined_literal.init();
if (!qualTypeIsBoolean(qual_type) and isBoolRes(init_node)) {
init_node = try Tag.bool_to_int.create(c.arena, init_node);
} else if (init_node.tag() == .string_literal and qualTypeIsCharStar(qual_type)) {
const dst_type_node = try transQualType(c, scope, qual_type, loc);
init_node = try removeCVQualifiers(c, dst_type_node, init_node);
}
const var_name: []const u8 = if (is_static_local) Scope.Block.StaticInnerName else mangled_name;
var node = try Tag.var_decl.create(c.arena, .{
.is_pub = false,
.is_const = is_const,
.is_extern = false,
.is_export = false,
.is_threadlocal = var_decl.getTLSKind() != .None,
.linksection_string = null,
.alignment = zigAlignment(var_decl.getAlignedAttribute(c.clang_context)),
.name = var_name,
.type = type_node,
.init = init_node,
});
if (is_static_local) {
node = try Tag.static_local_var.create(c.arena, .{ .name = mangled_name, .init = node });
}
try block_scope.statements.append(node);
try block_scope.discardVariable(c, mangled_name);
const cleanup_attr = var_decl.getCleanupAttribute();
if (cleanup_attr) |fn_decl| {
const cleanup_fn_name = try c.str(@ptrCast(*const clang.NamedDecl, fn_decl).getName_bytes_begin());
const fn_id = try Tag.identifier.create(c.arena, cleanup_fn_name);
const varname = try Tag.identifier.create(c.arena, mangled_name);
const args = try c.arena.alloc(Node, 1);
args[0] = try Tag.address_of.create(c.arena, varname);
const cleanup_call = try Tag.call.create(c.arena, .{ .lhs = fn_id, .args = args });
const discard = try Tag.discard.create(c.arena, .{ .should_skip = false, .value = cleanup_call });
const deferred_cleanup = try Tag.@"defer".create(c.arena, discard);
try block_scope.statements.append(deferred_cleanup);
}
},
.Typedef => {
try transTypeDef(c, scope, @ptrCast(*const clang.TypedefNameDecl, decl));
},
.Record => {
try transRecordDecl(c, scope, @ptrCast(*const clang.RecordDecl, decl));
},
.Enum => {
try transEnumDecl(c, scope, @ptrCast(*const clang.EnumDecl, decl));
},
.Function => {
try visitFnDecl(c, @ptrCast(*const clang.FunctionDecl, decl));
},
else => {
const decl_name = try c.str(decl.getDeclKindName());
try warn(c, &c.global_scope.base, decl.getLocation(), "ignoring {s} declaration", .{decl_name});
},
}
}
fn transDeclStmt(c: *Context, scope: *Scope, stmt: *const clang.DeclStmt) TransError!Node {
const block_scope = try scope.findBlockScope(c);
var it = stmt.decl_begin();
const end_it = stmt.decl_end();
while (it != end_it) : (it += 1) {
try transDeclStmtOne(c, scope, it[0], block_scope);
}
return Tag.declaration.init();
}
fn transDeclRefExpr(
c: *Context,
scope: *Scope,
expr: *const clang.DeclRefExpr,
) TransError!Node {
const value_decl = expr.getDecl();
const name = try c.str(@ptrCast(*const clang.NamedDecl, value_decl).getName_bytes_begin());
const mangled_name = scope.getAlias(name);
var ref_expr = try Tag.identifier.create(c.arena, mangled_name);
if (@ptrCast(*const clang.Decl, value_decl).getKind() == .Var) {
const var_decl = @ptrCast(*const clang.VarDecl, value_decl);
if (var_decl.isStaticLocal()) {
ref_expr = try Tag.field_access.create(c.arena, .{
.lhs = ref_expr,
.field_name = Scope.Block.StaticInnerName,
});
}
}
scope.skipVariableDiscard(mangled_name);
return ref_expr;
}
fn transImplicitCastExpr(
c: *Context,
scope: *Scope,
expr: *const clang.ImplicitCastExpr,
result_used: ResultUsed,
) TransError!Node {
const sub_expr = expr.getSubExpr();
const dest_type = getExprQualType(c, @ptrCast(*const clang.Expr, expr));
const src_type = getExprQualType(c, sub_expr);
switch (expr.getCastKind()) {
.BitCast, .FloatingCast, .FloatingToIntegral, .IntegralToFloating, .IntegralCast, .PointerToIntegral, .IntegralToPointer => {
const sub_expr_node = try transExpr(c, scope, sub_expr, .used);
const casted = try transCCast(c, scope, expr.getBeginLoc(), dest_type, src_type, sub_expr_node);
return maybeSuppressResult(c, scope, result_used, casted);
},
.LValueToRValue, .NoOp, .FunctionToPointerDecay => {
const sub_expr_node = try transExpr(c, scope, sub_expr, .used);
return maybeSuppressResult(c, scope, result_used, sub_expr_node);
},
.ArrayToPointerDecay => {
const sub_expr_node = try transExpr(c, scope, sub_expr, .used);
if (exprIsNarrowStringLiteral(sub_expr) or exprIsFlexibleArrayRef(c, sub_expr)) {
return maybeSuppressResult(c, scope, result_used, sub_expr_node);
}
const addr = try Tag.address_of.create(c.arena, sub_expr_node);
const casted = try transCPtrCast(c, scope, expr.getBeginLoc(), dest_type, src_type, addr);
return maybeSuppressResult(c, scope, result_used, casted);
},
.NullToPointer => {
return Tag.null_literal.init();
},
.PointerToBoolean => {
// @ptrToInt(val) != 0
const ptr_to_int = try Tag.ptr_to_int.create(c.arena, try transExpr(c, scope, sub_expr, .used));
const ne = try Tag.not_equal.create(c.arena, .{ .lhs = ptr_to_int, .rhs = Tag.zero_literal.init() });
return maybeSuppressResult(c, scope, result_used, ne);
},
.IntegralToBoolean, .FloatingToBoolean => {
const sub_expr_node = try transExpr(c, scope, sub_expr, .used);
// The expression is already a boolean one, return it as-is
if (isBoolRes(sub_expr_node))
return maybeSuppressResult(c, scope, result_used, sub_expr_node);
// val != 0
const ne = try Tag.not_equal.create(c.arena, .{ .lhs = sub_expr_node, .rhs = Tag.zero_literal.init() });
return maybeSuppressResult(c, scope, result_used, ne);
},
.BuiltinFnToFnPtr => {
return transBuiltinFnExpr(c, scope, sub_expr, result_used);
},
.ToVoid => {
// Should only appear in the rhs and lhs of a ConditionalOperator
return transExpr(c, scope, sub_expr, .unused);
},
else => |kind| return fail(
c,
error.UnsupportedTranslation,
@ptrCast(*const clang.Stmt, expr).getBeginLoc(),
"unsupported CastKind {s}",
.{@tagName(kind)},
),
}
}
fn isBuiltinDefined(name: []const u8) bool {
inline for (@typeInfo(std.zig.c_builtins).Struct.decls) |decl| {
if (!decl.is_pub) continue;
if (std.mem.eql(u8, name, decl.name)) return true;
}
return false;
}
fn transBuiltinFnExpr(c: *Context, scope: *Scope, expr: *const clang.Expr, used: ResultUsed) TransError!Node {
const node = try transExpr(c, scope, expr, used);
if (node.castTag(.identifier)) |ident| {
const name = ident.data;
if (!isBuiltinDefined(name)) return fail(c, error.UnsupportedTranslation, expr.getBeginLoc(), "TODO implement function '{s}' in std.zig.c_builtins", .{name});
}
return node;
}
fn transBoolExpr(
c: *Context,
scope: *Scope,
expr: *const clang.Expr,
used: ResultUsed,
) TransError!Node {
if (@ptrCast(*const clang.Stmt, expr).getStmtClass() == .IntegerLiteralClass) {
var signum: c_int = undefined;
if (!(@ptrCast(*const clang.IntegerLiteral, expr).getSignum(&signum, c.clang_context))) {
return fail(c, error.UnsupportedTranslation, expr.getBeginLoc(), "invalid integer literal", .{});
}
const is_zero = signum == 0;
return Node{ .tag_if_small_enough = @enumToInt(([2]Tag{ .true_literal, .false_literal })[@boolToInt(is_zero)]) };
}
var res = try transExpr(c, scope, expr, used);
if (isBoolRes(res)) {
return maybeSuppressResult(c, scope, used, res);
}
const ty = getExprQualType(c, expr).getTypePtr();
const node = try finishBoolExpr(c, scope, expr.getBeginLoc(), ty, res, used);
return maybeSuppressResult(c, scope, used, node);
}
fn exprIsBooleanType(expr: *const clang.Expr) bool {
return qualTypeIsBoolean(expr.getType());
}
fn exprIsNarrowStringLiteral(expr: *const clang.Expr) bool {
switch (expr.getStmtClass()) {
.StringLiteralClass => {
const string_lit = @ptrCast(*const clang.StringLiteral, expr);
return string_lit.getCharByteWidth() == 1;
},
.PredefinedExprClass => return true,
.UnaryOperatorClass => {
const op_expr = @ptrCast(*const clang.UnaryOperator, expr).getSubExpr();
return exprIsNarrowStringLiteral(op_expr);
},
.ParenExprClass => {
const op_expr = @ptrCast(*const clang.ParenExpr, expr).getSubExpr();
return exprIsNarrowStringLiteral(op_expr);
},
.GenericSelectionExprClass => {
const gen_sel = @ptrCast(*const clang.GenericSelectionExpr, expr);
return exprIsNarrowStringLiteral(gen_sel.getResultExpr());
},
else => return false,
}
}
fn exprIsFlexibleArrayRef(c: *Context, expr: *const clang.Expr) bool {
if (expr.getStmtClass() == .MemberExprClass) {
const member_expr = @ptrCast(*const clang.MemberExpr, expr);
const member_decl = member_expr.getMemberDecl();
const decl_kind = @ptrCast(*const clang.Decl, member_decl).getKind();
if (decl_kind == .Field) {
const field_decl = @ptrCast(*const clang.FieldDecl, member_decl);
return isFlexibleArrayFieldDecl(c, field_decl);
}
}
return false;
}
fn isBoolRes(res: Node) bool {
switch (res.tag()) {
.@"or",
.@"and",
.equal,
.not_equal,
.less_than,
.less_than_equal,
.greater_than,
.greater_than_equal,
.not,
.false_literal,
.true_literal,
=> return true,
else => return false,
}
}
fn finishBoolExpr(
c: *Context,
scope: *Scope,
loc: clang.SourceLocation,
ty: *const clang.Type,
node: Node,
used: ResultUsed,
) TransError!Node {
switch (ty.getTypeClass()) {
.Builtin => {
const builtin_ty = @ptrCast(*const clang.BuiltinType, ty);
switch (builtin_ty.getKind()) {
.Bool => return node,
.Char_U,
.UChar,
.Char_S,
.SChar,
.UShort,
.UInt,
.ULong,
.ULongLong,
.Short,
.Int,
.Long,
.LongLong,
.UInt128,
.Int128,
.Float,
.Double,
.Float128,
.LongDouble,
.WChar_U,
.Char8,
.Char16,
.Char32,
.WChar_S,
.Float16,
=> {
// node != 0
return Tag.not_equal.create(c.arena, .{ .lhs = node, .rhs = Tag.zero_literal.init() });
},
.NullPtr => {
// node == null
return Tag.equal.create(c.arena, .{ .lhs = node, .rhs = Tag.null_literal.init() });
},
else => {},
}
},
.Pointer => {
// node != null
return Tag.not_equal.create(c.arena, .{ .lhs = node, .rhs = Tag.null_literal.init() });
},
.Typedef => {
const typedef_ty = @ptrCast(*const clang.TypedefType, ty);
const typedef_decl = typedef_ty.getDecl();
const underlying_type = typedef_decl.getUnderlyingType();
return finishBoolExpr(c, scope, loc, underlying_type.getTypePtr(), node, used);
},
.Enum => {
// node != 0
return Tag.not_equal.create(c.arena, .{ .lhs = node, .rhs = Tag.zero_literal.init() });
},
.Elaborated => {
const elaborated_ty = @ptrCast(*const clang.ElaboratedType, ty);
const named_type = elaborated_ty.getNamedType();
return finishBoolExpr(c, scope, loc, named_type.getTypePtr(), node, used);
},
else => {},
}
return fail(c, error.UnsupportedType, loc, "unsupported bool expression type", .{});
}
const SuppressCast = enum {
with_as,
no_as,
};
fn transIntegerLiteral(
c: *Context,
scope: *Scope,
expr: *const clang.IntegerLiteral,
result_used: ResultUsed,
suppress_as: SuppressCast,
) TransError!Node {
var eval_result: clang.ExprEvalResult = undefined;
if (!expr.EvaluateAsInt(&eval_result, c.clang_context)) {
const loc = expr.getBeginLoc();
return fail(c, error.UnsupportedTranslation, loc, "invalid integer literal", .{});
}
if (suppress_as == .no_as) {
const int_lit_node = try transCreateNodeAPInt(c, eval_result.Val.getInt());
return maybeSuppressResult(c, scope, result_used, int_lit_node);
}
// Integer literals in C have types, and this can matter for several reasons.
// For example, this is valid C:
// unsigned char y = 256;
// How this gets evaluated is the 256 is an integer, which gets truncated to signed char, then bit-casted
// to unsigned char, resulting in 0. In order for this to work, we have to emit this zig code:
// var y = @bitCast(u8, @truncate(i8, @as(c_int, 256)));
// Ideally in translate-c we could flatten this out to simply:
// var y: u8 = 0;
// But the first step is to be correct, and the next step is to make the output more elegant.
// @as(T, x)
const expr_base = @ptrCast(*const clang.Expr, expr);
const ty_node = try transQualType(c, scope, expr_base.getType(), expr_base.getBeginLoc());
const rhs = try transCreateNodeAPInt(c, eval_result.Val.getInt());
const as = try Tag.as.create(c.arena, .{ .lhs = ty_node, .rhs = rhs });
return maybeSuppressResult(c, scope, result_used, as);
}
fn transReturnStmt(
c: *Context,
scope: *Scope,
expr: *const clang.ReturnStmt,
) TransError!Node {
const val_expr = expr.getRetValue() orelse
return Tag.return_void.init();
var rhs = try transExprCoercing(c, scope, val_expr, .used);
const return_qt = scope.findBlockReturnType(c);
if (isBoolRes(rhs) and !qualTypeIsBoolean(return_qt)) {
rhs = try Tag.bool_to_int.create(c.arena, rhs);
}
return Tag.@"return".create(c.arena, rhs);
}
fn transNarrowStringLiteral(
c: *Context,
scope: *Scope,
stmt: *const clang.StringLiteral,
result_used: ResultUsed,
) TransError!Node {
var len: usize = undefined;
const bytes_ptr = stmt.getString_bytes_begin_size(&len);
const str = try std.fmt.allocPrint(c.arena, "\"{}\"", .{std.zig.fmtEscapes(bytes_ptr[0..len])});
const node = try Tag.string_literal.create(c.arena, str);
return maybeSuppressResult(c, scope, result_used, node);
}
fn transStringLiteral(
c: *Context,
scope: *Scope,
stmt: *const clang.StringLiteral,
result_used: ResultUsed,
) TransError!Node {
const kind = stmt.getKind();
switch (kind) {
.Ascii, .UTF8 => return transNarrowStringLiteral(c, scope, stmt, result_used),
.UTF16, .UTF32, .Wide => {
const str_type = @tagName(stmt.getKind());
const name = try std.fmt.allocPrint(c.arena, "zig.{s}_string_{d}", .{ str_type, c.getMangle() });
const expr_base = @ptrCast(*const clang.Expr, stmt);
const array_type = try transQualTypeInitialized(c, scope, expr_base.getType(), expr_base, expr_base.getBeginLoc());
const lit_array = try transStringLiteralInitializer(c, scope, stmt, array_type);
const decl = try Tag.var_simple.create(c.arena, .{ .name = name, .init = lit_array });
try scope.appendNode(decl);
const node = try Tag.identifier.create(c.arena, name);
return maybeSuppressResult(c, scope, result_used, node);
},
}
}
fn getArrayPayload(array_type: Node) ast.Payload.Array.ArrayTypeInfo {
return (array_type.castTag(.array_type) orelse array_type.castTag(.null_sentinel_array_type).?).data;
}
/// Translate a string literal that is initializing an array. In general narrow string
/// literals become `"<string>".*` or `"<string>"[0..<size>].*` if they need truncation.
/// Wide string literals become an array of integers. zero-fillers pad out the array to
/// the appropriate length, if necessary.
fn transStringLiteralInitializer(
c: *Context,
scope: *Scope,
stmt: *const clang.StringLiteral,
array_type: Node,
) TransError!Node {
assert(array_type.tag() == .array_type or array_type.tag() == .null_sentinel_array_type);
const is_narrow = stmt.getKind() == .Ascii or stmt.getKind() == .UTF8;
const str_length = stmt.getLength();
const payload = getArrayPayload(array_type);
const array_size = payload.len;
const elem_type = payload.elem_type;
if (array_size == 0) return Tag.empty_array.create(c.arena, elem_type);
const num_inits = math.min(str_length, array_size);
const init_node = if (num_inits > 0) blk: {
if (is_narrow) {
// "string literal".* or string literal"[0..num_inits].*
var str = try transNarrowStringLiteral(c, scope, stmt, .used);
if (str_length != array_size) str = try Tag.string_slice.create(c.arena, .{ .string = str, .end = num_inits });
break :blk try Tag.deref.create(c.arena, str);
} else {
const init_list = try c.arena.alloc(Node, num_inits);
var i: c_uint = 0;
while (i < num_inits) : (i += 1) {
init_list[i] = try transCreateCharLitNode(c, false, stmt.getCodeUnit(i));
}
const init_args = .{ .len = num_inits, .elem_type = elem_type };
const init_array_type = try if (array_type.tag() == .array_type) Tag.array_type.create(c.arena, init_args) else Tag.null_sentinel_array_type.create(c.arena, init_args);
break :blk try Tag.array_init.create(c.arena, .{
.cond = init_array_type,
.cases = init_list,
});
}
} else null;
if (num_inits == array_size) return init_node.?; // init_node is only null if num_inits == 0; but if num_inits == array_size == 0 we've already returned
assert(array_size > str_length); // If array_size <= str_length, `num_inits == array_size` and we've already returned.
const filler_node = try Tag.array_filler.create(c.arena, .{
.type = elem_type,
.filler = Tag.zero_literal.init(),
.count = array_size - str_length,
});
if (init_node) |some| {
return Tag.array_cat.create(c.arena, .{ .lhs = some, .rhs = filler_node });
} else {
return filler_node;
}
}
/// determine whether `stmt` is a "pointer subtraction expression" - a subtraction where
/// both operands resolve to addresses. The C standard requires that both operands
/// point to elements of the same array object, but we do not verify that here.
fn cIsPointerDiffExpr(c: *Context, stmt: *const clang.BinaryOperator) bool {
_ = c;
const lhs = @ptrCast(*const clang.Stmt, stmt.getLHS());
const rhs = @ptrCast(*const clang.Stmt, stmt.getRHS());
return stmt.getOpcode() == .Sub and
qualTypeIsPtr(@ptrCast(*const clang.Expr, lhs).getType()) and
qualTypeIsPtr(@ptrCast(*const clang.Expr, rhs).getType());
}
fn cIsEnum(qt: clang.QualType) bool {
return qt.getCanonicalType().getTypeClass() == .Enum;
}
fn cIsVector(qt: clang.QualType) bool {
return qt.getCanonicalType().getTypeClass() == .Vector;
}
/// Get the underlying int type of an enum. The C compiler chooses a signed int
/// type that is large enough to hold all of the enum's values. It is not required
/// to be the smallest possible type that can hold all the values.
fn cIntTypeForEnum(enum_qt: clang.QualType) clang.QualType {
assert(cIsEnum(enum_qt));
const ty = enum_qt.getCanonicalType().getTypePtr();
const enum_ty = @ptrCast(*const clang.EnumType, ty);
const enum_decl = enum_ty.getDecl();
return enum_decl.getIntegerType();
}
// when modifying this function, make sure to also update std.zig.c_translation.cast
fn transCCast(
c: *Context,
scope: *Scope,
loc: clang.SourceLocation,
dst_type: clang.QualType,
src_type: clang.QualType,
expr: Node,
) !Node {
if (qualTypeCanon(dst_type).isVoidType()) return expr;
if (dst_type.eq(src_type)) return expr;
if (qualTypeIsPtr(dst_type) and qualTypeIsPtr(src_type))
return transCPtrCast(c, scope, loc, dst_type, src_type, expr);
if (cIsEnum(dst_type)) return transCCast(c, scope, loc, cIntTypeForEnum(dst_type), src_type, expr);
if (cIsEnum(src_type)) return transCCast(c, scope, loc, dst_type, cIntTypeForEnum(src_type), expr);
const dst_node = try transQualType(c, scope, dst_type, loc);
if (cIsInteger(dst_type) and cIsInteger(src_type)) {
// 1. If src_type is an enum, determine the underlying signed int type
// 2. Extend or truncate without changing signed-ness.
// 3. Bit-cast to correct signed-ness
const src_type_is_signed = cIsSignedInteger(src_type);
var src_int_expr = expr;
if (isBoolRes(src_int_expr)) {
src_int_expr = try Tag.bool_to_int.create(c.arena, src_int_expr);
}
switch (cIntTypeCmp(dst_type, src_type)) {
.lt => {
// @truncate(SameSignSmallerInt, src_int_expr)
const ty_node = try transQualTypeIntWidthOf(c, dst_type, src_type_is_signed);
src_int_expr = try Tag.truncate.create(c.arena, .{ .lhs = ty_node, .rhs = src_int_expr });
},
.gt => {
// @as(SameSignBiggerInt, src_int_expr)
const ty_node = try transQualTypeIntWidthOf(c, dst_type, src_type_is_signed);
src_int_expr = try Tag.as.create(c.arena, .{ .lhs = ty_node, .rhs = src_int_expr });
},
.eq => {
// src_int_expr = src_int_expr
},
}
// @bitCast(dest_type, intermediate_value)
return Tag.bit_cast.create(c.arena, .{ .lhs = dst_node, .rhs = src_int_expr });
}
if (cIsVector(src_type) or cIsVector(dst_type)) {
// C cast where at least 1 operand is a vector requires them to be same size
// @bitCast(dest_type, val)
return Tag.bit_cast.create(c.arena, .{ .lhs = dst_node, .rhs = expr });
}
if (cIsInteger(dst_type) and qualTypeIsPtr(src_type)) {
// @intCast(dest_type, @ptrToInt(val))
const ptr_to_int = try Tag.ptr_to_int.create(c.arena, expr);
return Tag.int_cast.create(c.arena, .{ .lhs = dst_node, .rhs = ptr_to_int });
}
if (cIsInteger(src_type) and qualTypeIsPtr(dst_type)) {
// @intToPtr(dest_type, val)
return Tag.int_to_ptr.create(c.arena, .{ .lhs = dst_node, .rhs = expr });
}
if (cIsFloating(src_type) and cIsFloating(dst_type)) {
// @floatCast(dest_type, val)
return Tag.float_cast.create(c.arena, .{ .lhs = dst_node, .rhs = expr });
}
if (cIsFloating(src_type) and !cIsFloating(dst_type)) {
// @floatToInt(dest_type, val)
return Tag.float_to_int.create(c.arena, .{ .lhs = dst_node, .rhs = expr });
}
if (!cIsFloating(src_type) and cIsFloating(dst_type)) {
var rhs = expr;
if (qualTypeIsBoolean(src_type)) rhs = try Tag.bool_to_int.create(c.arena, expr);
// @intToFloat(dest_type, val)
return Tag.int_to_float.create(c.arena, .{ .lhs = dst_node, .rhs = rhs });
}
if (qualTypeIsBoolean(src_type) and !qualTypeIsBoolean(dst_type)) {
// @boolToInt returns either a comptime_int or a u1
// TODO: if dst_type is 1 bit & signed (bitfield) we need @bitCast
// instead of @as
const bool_to_int = try Tag.bool_to_int.create(c.arena, expr);
return Tag.as.create(c.arena, .{ .lhs = dst_node, .rhs = bool_to_int });
}
// @as(dest_type, val)
return Tag.as.create(c.arena, .{ .lhs = dst_node, .rhs = expr });
}
fn transExpr(c: *Context, scope: *Scope, expr: *const clang.Expr, used: ResultUsed) TransError!Node {
return transStmt(c, scope, @ptrCast(*const clang.Stmt, expr), used);
}
/// Same as `transExpr` but with the knowledge that the operand will be type coerced, and therefore
/// an `@as` would be redundant. This is used to prevent redundant `@as` in integer literals.
fn transExprCoercing(c: *Context, scope: *Scope, expr: *const clang.Expr, used: ResultUsed) TransError!Node {
switch (@ptrCast(*const clang.Stmt, expr).getStmtClass()) {
.IntegerLiteralClass => {
return transIntegerLiteral(c, scope, @ptrCast(*const clang.IntegerLiteral, expr), .used, .no_as);
},
.CharacterLiteralClass => {
return transCharLiteral(c, scope, @ptrCast(*const clang.CharacterLiteral, expr), .used, .no_as);
},
.UnaryOperatorClass => {
const un_expr = @ptrCast(*const clang.UnaryOperator, expr);
if (un_expr.getOpcode() == .Extension) {
return transExprCoercing(c, scope, un_expr.getSubExpr(), used);
}
},
.ImplicitCastExprClass => {
const cast_expr = @ptrCast(*const clang.ImplicitCastExpr, expr);
const sub_expr = cast_expr.getSubExpr();
switch (@ptrCast(*const clang.Stmt, sub_expr).getStmtClass()) {
.IntegerLiteralClass, .CharacterLiteralClass => switch (cast_expr.getCastKind()) {
.IntegralToFloating => return transExprCoercing(c, scope, sub_expr, used),
.IntegralCast => {
const dest_type = getExprQualType(c, expr);
if (literalFitsInType(c, sub_expr, dest_type))
return transExprCoercing(c, scope, sub_expr, used);
},
else => {},
},
else => {},
}
},
else => {},
}
return transExpr(c, scope, expr, .used);
}
fn literalFitsInType(c: *Context, expr: *const clang.Expr, qt: clang.QualType) bool {
var width = qualTypeIntBitWidth(c, qt) catch 8;
if (width == 0) width = 8; // Byte is the smallest type.
const is_signed = cIsSignedInteger(qt);
const width_max_int = (@as(u64, 1) << math.lossyCast(u6, width - @boolToInt(is_signed))) - 1;
switch (@ptrCast(*const clang.Stmt, expr).getStmtClass()) {
.CharacterLiteralClass => {
const char_lit = @ptrCast(*const clang.CharacterLiteral, expr);
const val = char_lit.getValue();
// If the val is less than the max int then it fits.
return val <= width_max_int;
},
.IntegerLiteralClass => {
const int_lit = @ptrCast(*const clang.IntegerLiteral, expr);
var eval_result: clang.ExprEvalResult = undefined;
if (!int_lit.EvaluateAsInt(&eval_result, c.clang_context)) {
return false;
}
const int = eval_result.Val.getInt();
return int.lessThanEqual(width_max_int);
},
else => unreachable,
}
}
fn transInitListExprRecord(
c: *Context,
scope: *Scope,
loc: clang.SourceLocation,
expr: *const clang.InitListExpr,
ty: *const clang.Type,
) TransError!Node {
var is_union_type = false;
// Unions and Structs are both represented as RecordDecl
const record_ty = ty.getAsRecordType() orelse
blk: {
is_union_type = true;
break :blk ty.getAsUnionType();
} orelse unreachable;
const record_decl = record_ty.getDecl();
const record_def = record_decl.getDefinition() orelse
unreachable;
const ty_node = try transType(c, scope, ty, loc);
const init_count = expr.getNumInits();
var field_inits = std.ArrayList(ast.Payload.ContainerInit.Initializer).init(c.gpa);
defer field_inits.deinit();
var init_i: c_uint = 0;
var it = record_def.field_begin();
const end_it = record_def.field_end();
while (it.neq(end_it)) : (it = it.next()) {
const field_decl = it.deref();
// The initializer for a union type has a single entry only
if (is_union_type and field_decl != expr.getInitializedFieldInUnion()) {
continue;
}
assert(init_i < init_count);
const elem_expr = expr.getInit(init_i);
init_i += 1;
// Generate the field assignment expression:
// .field_name = expr
var raw_name = try c.str(@ptrCast(*const clang.NamedDecl, field_decl).getName_bytes_begin());
if (field_decl.isAnonymousStructOrUnion()) {
const name = c.decl_table.get(@ptrToInt(field_decl.getCanonicalDecl())).?;
raw_name = try c.arena.dupe(u8, name);
}
var init_expr = try transExpr(c, scope, elem_expr, .used);
const field_qt = field_decl.getType();
if (init_expr.tag() == .string_literal and qualTypeIsCharStar(field_qt)) {
if (scope.id == .root) {
init_expr = try stringLiteralToCharStar(c, init_expr);
} else {
const dst_type_node = try transQualType(c, scope, field_qt, loc);
init_expr = try removeCVQualifiers(c, dst_type_node, init_expr);
}
}
try field_inits.append(.{
.name = raw_name,
.value = init_expr,
});
}
if (ty_node.castTag(.identifier)) |ident_node| {
scope.skipVariableDiscard(ident_node.data);
}
return Tag.container_init.create(c.arena, .{
.lhs = ty_node,
.inits = try c.arena.dupe(ast.Payload.ContainerInit.Initializer, field_inits.items),
});
}
fn transInitListExprArray(
c: *Context,
scope: *Scope,
loc: clang.SourceLocation,
expr: *const clang.InitListExpr,
ty: *const clang.Type,
) TransError!Node {
const arr_type = ty.getAsArrayTypeUnsafe();
const child_qt = arr_type.getElementType();
const child_type = try transQualType(c, scope, child_qt, loc);
const init_count = expr.getNumInits();
assert(@ptrCast(*const clang.Type, arr_type).isConstantArrayType());
const const_arr_ty = @ptrCast(*const clang.ConstantArrayType, arr_type);
const size_ap_int = const_arr_ty.getSize();
const all_count = size_ap_int.getLimitedValue(usize);
const leftover_count = all_count - init_count;
if (all_count == 0) {
return Tag.empty_array.create(c.arena, child_type);
}
const init_node = if (init_count != 0) blk: {
const init_list = try c.arena.alloc(Node, init_count);
for (init_list) |*init, i| {
const elem_expr = expr.getInit(@intCast(c_uint, i));
init.* = try transExprCoercing(c, scope, elem_expr, .used);
}
const init_node = try Tag.array_init.create(c.arena, .{
.cond = try Tag.array_type.create(c.arena, .{ .len = init_count, .elem_type = child_type }),
.cases = init_list,
});
if (leftover_count == 0) {
return init_node;
}
break :blk init_node;
} else null;
const filler_val_expr = expr.getArrayFiller();
const filler_node = try Tag.array_filler.create(c.arena, .{
.type = child_type,
.filler = try transExprCoercing(c, scope, filler_val_expr, .used),
.count = leftover_count,
});
if (init_node) |some| {
return Tag.array_cat.create(c.arena, .{ .lhs = some, .rhs = filler_node });
} else {
return filler_node;
}
}
fn transInitListExprVector(
c: *Context,
scope: *Scope,
loc: clang.SourceLocation,
expr: *const clang.InitListExpr,
ty: *const clang.Type,
) TransError!Node {
_ = ty;
const qt = getExprQualType(c, @ptrCast(*const clang.Expr, expr));
const vector_type = try transQualType(c, scope, qt, loc);
const init_count = expr.getNumInits();
if (init_count == 0) {
return Tag.container_init.create(c.arena, .{
.lhs = vector_type,
.inits = try c.arena.alloc(ast.Payload.ContainerInit.Initializer, 0),
});
}
var block_scope = try Scope.Block.init(c, scope, true);
defer block_scope.deinit();
// workaround for https://github.com/ziglang/zig/issues/8322
// we store the initializers in temp variables and use those
// to initialize the vector. Eventually we can just directly
// construct the init_list from casted source members
var i: usize = 0;
while (i < init_count) : (i += 1) {
const mangled_name = try block_scope.makeMangledName(c, "tmp");
const init_expr = expr.getInit(@intCast(c_uint, i));
const tmp_decl_node = try Tag.var_simple.create(c.arena, .{
.name = mangled_name,
.init = try transExpr(c, &block_scope.base, init_expr, .used),
});
try block_scope.statements.append(tmp_decl_node);
}
const init_list = try c.arena.alloc(Node, init_count);
for (init_list) |*init, init_index| {
const tmp_decl = block_scope.statements.items[init_index];
const name = tmp_decl.castTag(.var_simple).?.data.name;
init.* = try Tag.identifier.create(c.arena, name);
}
const array_init = try Tag.array_init.create(c.arena, .{
.cond = vector_type,
.cases = init_list,
});
const break_node = try Tag.break_val.create(c.arena, .{
.label = block_scope.label,
.val = array_init,
});
try block_scope.statements.append(break_node);
return block_scope.complete(c);
}
fn transInitListExpr(
c: *Context,
scope: *Scope,
expr: *const clang.InitListExpr,
used: ResultUsed,
) TransError!Node {
const qt = getExprQualType(c, @ptrCast(*const clang.Expr, expr));
var qual_type = qt.getTypePtr();
const source_loc = @ptrCast(*const clang.Expr, expr).getBeginLoc();
if (qualTypeWasDemotedToOpaque(c, qt)) {
return fail(c, error.UnsupportedTranslation, source_loc, "cannot initialize opaque type", .{});
}
if (qual_type.isRecordType()) {
return maybeSuppressResult(c, scope, used, try transInitListExprRecord(
c,
scope,
source_loc,
expr,
qual_type,
));
} else if (qual_type.isArrayType()) {
return maybeSuppressResult(c, scope, used, try transInitListExprArray(
c,
scope,
source_loc,
expr,
qual_type,
));
} else if (qual_type.isVectorType()) {
return maybeSuppressResult(c, scope, used, try transInitListExprVector(
c,
scope,
source_loc,
expr,
qual_type,
));
} else {
const type_name = c.str(qual_type.getTypeClassName());
return fail(c, error.UnsupportedType, source_loc, "unsupported initlist type: '{s}'", .{type_name});
}
}
fn transZeroInitExpr(
c: *Context,
scope: *Scope,
source_loc: clang.SourceLocation,
ty: *const clang.Type,
) TransError!Node {
switch (ty.getTypeClass()) {
.Builtin => {
const builtin_ty = @ptrCast(*const clang.BuiltinType, ty);
switch (builtin_ty.getKind()) {
.Bool => return Tag.false_literal.init(),
.Char_U,
.UChar,
.Char_S,
.Char8,
.SChar,
.UShort,
.UInt,
.ULong,
.ULongLong,
.Short,
.Int,
.Long,
.LongLong,
.UInt128,
.Int128,
.Float,
.Double,
.Float128,
.Float16,
.LongDouble,
=> return Tag.zero_literal.init(),
else => return fail(c, error.UnsupportedType, source_loc, "unsupported builtin type", .{}),
}
},
.Pointer => return Tag.null_literal.init(),
.Typedef => {
const typedef_ty = @ptrCast(*const clang.TypedefType, ty);
const typedef_decl = typedef_ty.getDecl();
return transZeroInitExpr(
c,
scope,
source_loc,
typedef_decl.getUnderlyingType().getTypePtr(),
);
},
else => return Tag.std_mem_zeroes.create(c.arena, try transType(c, scope, ty, source_loc)),
}
}
fn transImplicitValueInitExpr(
c: *Context,
scope: *Scope,
expr: *const clang.Expr,
used: ResultUsed,
) TransError!Node {
_ = used;
const source_loc = expr.getBeginLoc();
const qt = getExprQualType(c, expr);
const ty = qt.getTypePtr();
return transZeroInitExpr(c, scope, source_loc, ty);
}
/// If a statement can possibly translate to a Zig assignment (either directly because it's
/// an assignment in C or indirectly via result assignment to `_`) AND it's the sole statement
/// in the body of an if statement or loop, then we need to put the statement into its own block.
/// The `else` case here corresponds to statements that could result in an assignment. If a statement
/// class never needs a block, add its enum to the top prong.
fn maybeBlockify(c: *Context, scope: *Scope, stmt: *const clang.Stmt) TransError!Node {
switch (stmt.getStmtClass()) {
.BreakStmtClass,
.CompoundStmtClass,
.ContinueStmtClass,
.DeclRefExprClass,
.DeclStmtClass,
.DoStmtClass,
.ForStmtClass,
.IfStmtClass,
.ReturnStmtClass,
.NullStmtClass,
.WhileStmtClass,
=> return transStmt(c, scope, stmt, .unused),
else => return blockify(c, scope, stmt),
}
}
fn blockify(c: *Context, scope: *Scope, stmt: *const clang.Stmt) TransError!Node {
var block_scope = try Scope.Block.init(c, scope, false);
defer block_scope.deinit();
const result = try transStmt(c, &block_scope.base, stmt, .unused);
try block_scope.statements.append(result);
return block_scope.complete(c);
}
fn transIfStmt(
c: *Context,
scope: *Scope,
stmt: *const clang.IfStmt,
) TransError!Node {
// if (c) t
// if (c) t else e
var cond_scope = Scope.Condition{
.base = .{
.parent = scope,
.id = .condition,
},
};
defer cond_scope.deinit();
const cond_expr = @ptrCast(*const clang.Expr, stmt.getCond());
const cond = try transBoolExpr(c, &cond_scope.base, cond_expr, .used);
const then_stmt = stmt.getThen();
const else_stmt = stmt.getElse();
const then_class = then_stmt.getStmtClass();
// block needed to keep else statement from attaching to inner while
const must_blockify = (else_stmt != null) and switch (then_class) {
.DoStmtClass, .ForStmtClass, .WhileStmtClass => true,
else => false,
};
const then_body = if (must_blockify)
try blockify(c, scope, then_stmt)
else
try maybeBlockify(c, scope, then_stmt);
const else_body = if (else_stmt) |expr|
try maybeBlockify(c, scope, expr)
else
null;
return Tag.@"if".create(c.arena, .{ .cond = cond, .then = then_body, .@"else" = else_body });
}
fn transWhileLoop(
c: *Context,
scope: *Scope,
stmt: *const clang.WhileStmt,
) TransError!Node {
var cond_scope = Scope.Condition{
.base = .{
.parent = scope,
.id = .condition,
},
};
defer cond_scope.deinit();
const cond_expr = @ptrCast(*const clang.Expr, stmt.getCond());
const cond = try transBoolExpr(c, &cond_scope.base, cond_expr, .used);
var loop_scope = Scope{
.parent = scope,
.id = .loop,
};
const body = try maybeBlockify(c, &loop_scope, stmt.getBody());
return Tag.@"while".create(c.arena, .{ .cond = cond, .body = body, .cont_expr = null });
}
fn transDoWhileLoop(
c: *Context,
scope: *Scope,
stmt: *const clang.DoStmt,
) TransError!Node {
var loop_scope = Scope{
.parent = scope,
.id = .do_loop,
};
// if (!cond) break;
var cond_scope = Scope.Condition{
.base = .{
.parent = scope,
.id = .condition,
},
};
defer cond_scope.deinit();
const cond = try transBoolExpr(c, &cond_scope.base, @ptrCast(*const clang.Expr, stmt.getCond()), .used);
const if_not_break = switch (cond.tag()) {
.false_literal => return transStmt(c, scope, stmt.getBody(), .unused),
.true_literal => {
const body_node = try maybeBlockify(c, scope, stmt.getBody());
return Tag.while_true.create(c.arena, body_node);
},
else => try Tag.if_not_break.create(c.arena, cond),
};
const body_node = if (stmt.getBody().getStmtClass() == .CompoundStmtClass) blk: {
// there's already a block in C, so we'll append our condition to it.
// c: do {
// c: a;
// c: b;
// c: } while(c);
// zig: while (true) {
// zig: a;
// zig: b;
// zig: if (!cond) break;
// zig: }
const node = try transStmt(c, &loop_scope, stmt.getBody(), .unused);
const block = node.castTag(.block).?;
block.data.stmts.len += 1; // This is safe since we reserve one extra space in Scope.Block.complete.
block.data.stmts[block.data.stmts.len - 1] = if_not_break;
break :blk node;
} else blk: {
// the C statement is without a block, so we need to create a block to contain it.
// c: do
// c: a;
// c: while(c);
// zig: while (true) {
// zig: a;
// zig: if (!cond) break;
// zig: }
const statements = try c.arena.alloc(Node, 2);
statements[0] = try transStmt(c, &loop_scope, stmt.getBody(), .unused);
statements[1] = if_not_break;
break :blk try Tag.block.create(c.arena, .{ .label = null, .stmts = statements });
};
return Tag.while_true.create(c.arena, body_node);
}
fn transForLoop(
c: *Context,
scope: *Scope,
stmt: *const clang.ForStmt,
) TransError!Node {
var loop_scope = Scope{
.parent = scope,
.id = .loop,
};
var block_scope: ?Scope.Block = null;
defer if (block_scope) |*bs| bs.deinit();
if (stmt.getInit()) |init| {
block_scope = try Scope.Block.init(c, scope, false);
loop_scope.parent = &block_scope.?.base;
const init_node = try transStmt(c, &block_scope.?.base, init, .unused);
if (init_node.tag() != .declaration) try block_scope.?.statements.append(init_node);
}
var cond_scope = Scope.Condition{
.base = .{
.parent = &loop_scope,
.id = .condition,
},
};
defer cond_scope.deinit();
const cond = if (stmt.getCond()) |cond|
try transBoolExpr(c, &cond_scope.base, cond, .used)
else
Tag.true_literal.init();
const cont_expr = if (stmt.getInc()) |incr|
try transExpr(c, &cond_scope.base, incr, .unused)
else
null;
const body = try maybeBlockify(c, &loop_scope, stmt.getBody());
const while_node = try Tag.@"while".create(c.arena, .{ .cond = cond, .body = body, .cont_expr = cont_expr });
if (block_scope) |*bs| {
try bs.statements.append(while_node);
return try bs.complete(c);
} else {
return while_node;
}
}
fn transSwitch(
c: *Context,
scope: *Scope,
stmt: *const clang.SwitchStmt,
) TransError!Node {
var loop_scope = Scope{
.parent = scope,
.id = .loop,
};
var block_scope = try Scope.Block.init(c, &loop_scope, false);
defer block_scope.deinit();
const base_scope = &block_scope.base;
var cond_scope = Scope.Condition{
.base = .{
.parent = base_scope,
.id = .condition,
},
};
defer cond_scope.deinit();
const switch_expr = try transExpr(c, &cond_scope.base, stmt.getCond(), .used);
var cases = std.ArrayList(Node).init(c.gpa);
defer cases.deinit();
var has_default = false;
const body = stmt.getBody();
assert(body.getStmtClass() == .CompoundStmtClass);
const compound_stmt = @ptrCast(*const clang.CompoundStmt, body);
var it = compound_stmt.body_begin();
const end_it = compound_stmt.body_end();
// Iterate over switch body and collect all cases.
// Fallthrough is handled by duplicating statements.
while (it != end_it) : (it += 1) {
switch (it[0].getStmtClass()) {
.CaseStmtClass => {
var items = std.ArrayList(Node).init(c.gpa);
defer items.deinit();
const sub = try transCaseStmt(c, base_scope, it[0], &items);
const res = try transSwitchProngStmt(c, base_scope, sub, it, end_it);
if (items.items.len == 0) {
has_default = true;
const switch_else = try Tag.switch_else.create(c.arena, res);
try cases.append(switch_else);
} else {
const switch_prong = try Tag.switch_prong.create(c.arena, .{
.cases = try c.arena.dupe(Node, items.items),
.cond = res,
});
try cases.append(switch_prong);
}
},
.DefaultStmtClass => {
has_default = true;
const default_stmt = @ptrCast(*const clang.DefaultStmt, it[0]);
var sub = default_stmt.getSubStmt();
while (true) switch (sub.getStmtClass()) {
.CaseStmtClass => sub = @ptrCast(*const clang.CaseStmt, sub).getSubStmt(),
.DefaultStmtClass => sub = @ptrCast(*const clang.DefaultStmt, sub).getSubStmt(),
else => break,
};
const res = try transSwitchProngStmt(c, base_scope, sub, it, end_it);
const switch_else = try Tag.switch_else.create(c.arena, res);
try cases.append(switch_else);
},
else => {}, // collected in transSwitchProngStmt
}
}
if (!has_default) {
const else_prong = try Tag.switch_else.create(c.arena, Tag.empty_block.init());
try cases.append(else_prong);
}
const switch_node = try Tag.@"switch".create(c.arena, .{
.cond = switch_expr,
.cases = try c.arena.dupe(Node, cases.items),
});
try block_scope.statements.append(switch_node);
try block_scope.statements.append(Tag.@"break".init());
const while_body = try block_scope.complete(c);
return Tag.while_true.create(c.arena, while_body);
}
/// Collects all items for this case, returns the first statement after the labels.
/// If items ends up empty, the prong should be translated as an else.
fn transCaseStmt(c: *Context, scope: *Scope, stmt: *const clang.Stmt, items: *std.ArrayList(Node)) TransError!*const clang.Stmt {
var sub = stmt;
var seen_default = false;
while (true) {
switch (sub.getStmtClass()) {
.DefaultStmtClass => {
seen_default = true;
items.items.len = 0;
const default_stmt = @ptrCast(*const clang.DefaultStmt, sub);
sub = default_stmt.getSubStmt();
},
.CaseStmtClass => {
const case_stmt = @ptrCast(*const clang.CaseStmt, sub);
if (seen_default) {
items.items.len = 0;
sub = case_stmt.getSubStmt();
continue;
}
const expr = if (case_stmt.getRHS()) |rhs| blk: {
const lhs_node = try transExprCoercing(c, scope, case_stmt.getLHS(), .used);
const rhs_node = try transExprCoercing(c, scope, rhs, .used);
break :blk try Tag.ellipsis3.create(c.arena, .{ .lhs = lhs_node, .rhs = rhs_node });
} else try transExprCoercing(c, scope, case_stmt.getLHS(), .used);
try items.append(expr);
sub = case_stmt.getSubStmt();
},
else => return sub,
}
}
}
/// Collects all statements seen by this case into a block.
/// Avoids creating a block if the first statement is a break or return.
fn transSwitchProngStmt(
c: *Context,
scope: *Scope,
stmt: *const clang.Stmt,
parent_it: clang.CompoundStmt.ConstBodyIterator,
parent_end_it: clang.CompoundStmt.ConstBodyIterator,
) TransError!Node {
switch (stmt.getStmtClass()) {
.BreakStmtClass => return Tag.@"break".init(),
.ReturnStmtClass => return transStmt(c, scope, stmt, .unused),
.CaseStmtClass, .DefaultStmtClass => unreachable,
else => {
var block_scope = try Scope.Block.init(c, scope, false);
defer block_scope.deinit();
// we do not need to translate `stmt` since it is the first stmt of `parent_it`
try transSwitchProngStmtInline(c, &block_scope, parent_it, parent_end_it);
return try block_scope.complete(c);
},
}
}
/// Collects all statements seen by this case into a block.
fn transSwitchProngStmtInline(
c: *Context,
block: *Scope.Block,
start_it: clang.CompoundStmt.ConstBodyIterator,
end_it: clang.CompoundStmt.ConstBodyIterator,
) TransError!void {
var it = start_it;
while (it != end_it) : (it += 1) {
switch (it[0].getStmtClass()) {
.ReturnStmtClass => {
const result = try transStmt(c, &block.base, it[0], .unused);
try block.statements.append(result);
return;
},
.BreakStmtClass => {
try block.statements.append(Tag.@"break".init());
return;
},
.CaseStmtClass => {
var sub = @ptrCast(*const clang.CaseStmt, it[0]).getSubStmt();
while (true) switch (sub.getStmtClass()) {
.CaseStmtClass => sub = @ptrCast(*const clang.CaseStmt, sub).getSubStmt(),
.DefaultStmtClass => sub = @ptrCast(*const clang.DefaultStmt, sub).getSubStmt(),
else => break,
};
const result = try transStmt(c, &block.base, sub, .unused);
assert(result.tag() != .declaration);
try block.statements.append(result);
if (result.isNoreturn(true)) {
return;
}
},
.DefaultStmtClass => {
var sub = @ptrCast(*const clang.DefaultStmt, it[0]).getSubStmt();
while (true) switch (sub.getStmtClass()) {
.CaseStmtClass => sub = @ptrCast(*const clang.CaseStmt, sub).getSubStmt(),
.DefaultStmtClass => sub = @ptrCast(*const clang.DefaultStmt, sub).getSubStmt(),
else => break,
};
const result = try transStmt(c, &block.base, sub, .unused);
assert(result.tag() != .declaration);
try block.statements.append(result);
if (result.isNoreturn(true)) {
return;
}
},
.CompoundStmtClass => {
const result = try transCompoundStmt(c, &block.base, @ptrCast(*const clang.CompoundStmt, it[0]));
try block.statements.append(result);
if (result.isNoreturn(true)) {
return;
}
},
else => {
const result = try transStmt(c, &block.base, it[0], .unused);
switch (result.tag()) {
.declaration, .empty_block => {},
else => try block.statements.append(result),
}
},
}
}
return;
}
fn transConstantExpr(c: *Context, scope: *Scope, expr: *const clang.Expr, used: ResultUsed) TransError!Node {
var result: clang.ExprEvalResult = undefined;
if (!expr.evaluateAsConstantExpr(&result, .Normal, c.clang_context))
return fail(c, error.UnsupportedTranslation, expr.getBeginLoc(), "invalid constant expression", .{});
switch (result.Val.getKind()) {
.Int => {
// See comment in `transIntegerLiteral` for why this code is here.
// @as(T, x)
const expr_base = @ptrCast(*const clang.Expr, expr);
const as_node = try Tag.as.create(c.arena, .{
.lhs = try transQualType(c, scope, expr_base.getType(), expr_base.getBeginLoc()),
.rhs = try transCreateNodeAPInt(c, result.Val.getInt()),
});
return maybeSuppressResult(c, scope, used, as_node);
},
else => |kind| {
return fail(c, error.UnsupportedTranslation, expr.getBeginLoc(), "unsupported constant expression kind '{s}'", .{kind});
},
}
}
fn transPredefinedExpr(c: *Context, scope: *Scope, expr: *const clang.PredefinedExpr, used: ResultUsed) TransError!Node {
return transStringLiteral(c, scope, expr.getFunctionName(), used);
}
fn transCreateCharLitNode(c: *Context, narrow: bool, val: u32) TransError!Node {
return Tag.char_literal.create(c.arena, if (narrow)
try std.fmt.allocPrint(c.arena, "'{'}'", .{std.zig.fmtEscapes(&.{@intCast(u8, val)})})
else
try std.fmt.allocPrint(c.arena, "'\\u{{{x}}}'", .{val}));
}
fn transCharLiteral(
c: *Context,
scope: *Scope,
stmt: *const clang.CharacterLiteral,
result_used: ResultUsed,
suppress_as: SuppressCast,
) TransError!Node {
const kind = stmt.getKind();
const val = stmt.getValue();
const narrow = kind == .Ascii or kind == .UTF8;
// C has a somewhat obscure feature called multi-character character constant
// e.g. 'abcd'
const int_lit_node = if (kind == .Ascii and val > 255)
try transCreateNodeNumber(c, val, .int)
else
try transCreateCharLitNode(c, narrow, val);
if (suppress_as == .no_as) {
return maybeSuppressResult(c, scope, result_used, int_lit_node);
}
// See comment in `transIntegerLiteral` for why this code is here.
// @as(T, x)
const expr_base = @ptrCast(*const clang.Expr, stmt);
const as_node = try Tag.as.create(c.arena, .{
.lhs = try transQualType(c, scope, expr_base.getType(), expr_base.getBeginLoc()),
.rhs = int_lit_node,
});
return maybeSuppressResult(c, scope, result_used, as_node);
}
fn transStmtExpr(c: *Context, scope: *Scope, stmt: *const clang.StmtExpr, used: ResultUsed) TransError!Node {
const comp = stmt.getSubStmt();
if (used == .unused) {
return transCompoundStmt(c, scope, comp);
}
var block_scope = try Scope.Block.init(c, scope, true);
defer block_scope.deinit();
var it = comp.body_begin();
const end_it = comp.body_end();
while (it != end_it - 1) : (it += 1) {
const result = try transStmt(c, &block_scope.base, it[0], .unused);
switch (result.tag()) {
.declaration, .empty_block => {},
else => try block_scope.statements.append(result),
}
}
const break_node = try Tag.break_val.create(c.arena, .{
.label = block_scope.label,
.val = try transStmt(c, &block_scope.base, it[0], .used),
});
try block_scope.statements.append(break_node);
const res = try block_scope.complete(c);
return maybeSuppressResult(c, scope, used, res);
}
fn transMemberExpr(c: *Context, scope: *Scope, stmt: *const clang.MemberExpr, result_used: ResultUsed) TransError!Node {
var container_node = try transExpr(c, scope, stmt.getBase(), .used);
if (stmt.isArrow()) {
container_node = try Tag.deref.create(c.arena, container_node);
}
const member_decl = stmt.getMemberDecl();
const name = blk: {
const decl_kind = @ptrCast(*const clang.Decl, member_decl).getKind();
// If we're referring to a anonymous struct/enum find the bogus name
// we've assigned to it during the RecordDecl translation
if (decl_kind == .Field) {
const field_decl = @ptrCast(*const clang.FieldDecl, member_decl);
if (field_decl.isAnonymousStructOrUnion()) {
const name = c.decl_table.get(@ptrToInt(field_decl.getCanonicalDecl())).?;
break :blk try c.arena.dupe(u8, name);
}
}
const decl = @ptrCast(*const clang.NamedDecl, member_decl);
break :blk try c.str(decl.getName_bytes_begin());
};
var node = try Tag.field_access.create(c.arena, .{ .lhs = container_node, .field_name = name });
if (exprIsFlexibleArrayRef(c, @ptrCast(*const clang.Expr, stmt))) {
node = try Tag.call.create(c.arena, .{ .lhs = node, .args = &.{} });
}
return maybeSuppressResult(c, scope, result_used, node);
}
/// ptr[subscr] (`subscr` is a signed integer expression, `ptr` a pointer) becomes:
/// (blk: {
/// const tmp = subscr;
/// if (tmp >= 0) break :blk ptr + @intCast(usize, tmp) else break :blk ptr - ~@bitCast(usize, @intCast(isize, tmp) +% -1);
/// }).*
/// Todo: rip this out once `[*]T + isize` becomes valid.
fn transSignedArrayAccess(
c: *Context,
scope: *Scope,
container_expr: *const clang.Expr,
subscr_expr: *const clang.Expr,
result_used: ResultUsed,
) TransError!Node {
var block_scope = try Scope.Block.init(c, scope, true);
defer block_scope.deinit();
const tmp = try block_scope.makeMangledName(c, "tmp");
const subscr_node = try transExpr(c, &block_scope.base, subscr_expr, .used);
const subscr_decl = try Tag.var_simple.create(c.arena, .{ .name = tmp, .init = subscr_node });
try block_scope.statements.append(subscr_decl);
const tmp_ref = try Tag.identifier.create(c.arena, tmp);
const container_node = try transExpr(c, &block_scope.base, container_expr, .used);
const cond_node = try Tag.greater_than_equal.create(c.arena, .{ .lhs = tmp_ref, .rhs = Tag.zero_literal.init() });
const then_value = try Tag.add.create(c.arena, .{
.lhs = container_node,
.rhs = try Tag.int_cast.create(c.arena, .{
.lhs = try Tag.type.create(c.arena, "usize"),
.rhs = tmp_ref,
}),
});
const then_body = try Tag.break_val.create(c.arena, .{
.label = block_scope.label,
.val = then_value,
});
const minuend = container_node;
const signed_size = try Tag.int_cast.create(c.arena, .{
.lhs = try Tag.type.create(c.arena, "isize"),
.rhs = tmp_ref,
});
const to_cast = try Tag.add_wrap.create(c.arena, .{
.lhs = signed_size,
.rhs = try Tag.negate.create(c.arena, Tag.one_literal.init()),
});
const bitcast_node = try Tag.bit_cast.create(c.arena, .{
.lhs = try Tag.type.create(c.arena, "usize"),
.rhs = to_cast,
});
const subtrahend = try Tag.bit_not.create(c.arena, bitcast_node);
const difference = try Tag.sub.create(c.arena, .{
.lhs = minuend,
.rhs = subtrahend,
});
const else_body = try Tag.break_val.create(c.arena, .{
.label = block_scope.label,
.val = difference,
});
const if_node = try Tag.@"if".create(c.arena, .{
.cond = cond_node,
.then = then_body,
.@"else" = else_body,
});
try block_scope.statements.append(if_node);
const block_node = try block_scope.complete(c);
const derefed = try Tag.deref.create(c.arena, block_node);
return maybeSuppressResult(c, &block_scope.base, result_used, derefed);
}
fn transArrayAccess(c: *Context, scope: *Scope, stmt: *const clang.ArraySubscriptExpr, result_used: ResultUsed) TransError!Node {
const base_stmt = stmt.getBase();
const base_qt = getExprQualType(c, base_stmt);
const is_vector = cIsVector(base_qt);
const subscr_expr = stmt.getIdx();
const subscr_qt = getExprQualType(c, subscr_expr);
const is_longlong = cIsLongLongInteger(subscr_qt);
const is_signed = cIsSignedInteger(subscr_qt);
const is_nonnegative_int_literal = cIsNonNegativeIntLiteral(c, subscr_expr);
// Unwrap the base statement if it's an array decayed to a bare pointer type
// so that we index the array itself
var unwrapped_base = base_stmt;
if (@ptrCast(*const clang.Stmt, base_stmt).getStmtClass() == .ImplicitCastExprClass) {
const implicit_cast = @ptrCast(*const clang.ImplicitCastExpr, base_stmt);
if (implicit_cast.getCastKind() == .ArrayToPointerDecay) {
unwrapped_base = implicit_cast.getSubExpr();
}
}
// Special case: actual pointer (not decayed array) and signed integer subscript
// See discussion at https://github.com/ziglang/zig/pull/8589
if (is_signed and (base_stmt == unwrapped_base) and !is_vector and !is_nonnegative_int_literal) return transSignedArrayAccess(c, scope, base_stmt, subscr_expr, result_used);
const container_node = try transExpr(c, scope, unwrapped_base, .used);
const rhs = if (is_longlong or is_signed) blk: {
// check if long long first so that signed long long doesn't just become unsigned long long
const typeid_node = if (is_longlong) try Tag.type.create(c.arena, "usize") else try transQualTypeIntWidthOf(c, subscr_qt, false);
break :blk try Tag.int_cast.create(c.arena, .{ .lhs = typeid_node, .rhs = try transExpr(c, scope, subscr_expr, .used) });
} else try transExpr(c, scope, subscr_expr, .used);
const node = try Tag.array_access.create(c.arena, .{
.lhs = container_node,
.rhs = rhs,
});
return maybeSuppressResult(c, scope, result_used, node);
}
/// Check if an expression is ultimately a reference to a function declaration
/// (which means it should not be unwrapped with `.?` in translated code)
fn cIsFunctionDeclRef(expr: *const clang.Expr) bool {
switch (expr.getStmtClass()) {
.ParenExprClass => {
const op_expr = @ptrCast(*const clang.ParenExpr, expr).getSubExpr();
return cIsFunctionDeclRef(op_expr);
},
.DeclRefExprClass => {
const decl_ref = @ptrCast(*const clang.DeclRefExpr, expr);
const value_decl = decl_ref.getDecl();
const qt = value_decl.getType();
return qualTypeChildIsFnProto(qt);
},
.ImplicitCastExprClass => {
const implicit_cast = @ptrCast(*const clang.ImplicitCastExpr, expr);
const cast_kind = implicit_cast.getCastKind();
if (cast_kind == .BuiltinFnToFnPtr) return true;
if (cast_kind == .FunctionToPointerDecay) {
return cIsFunctionDeclRef(implicit_cast.getSubExpr());
}
return false;
},
.UnaryOperatorClass => {
const un_op = @ptrCast(*const clang.UnaryOperator, expr);
const opcode = un_op.getOpcode();
return (opcode == .AddrOf or opcode == .Deref) and cIsFunctionDeclRef(un_op.getSubExpr());
},
.GenericSelectionExprClass => {
const gen_sel = @ptrCast(*const clang.GenericSelectionExpr, expr);
return cIsFunctionDeclRef(gen_sel.getResultExpr());
},
else => return false,
}
}
fn transCallExpr(c: *Context, scope: *Scope, stmt: *const clang.CallExpr, result_used: ResultUsed) TransError!Node {
const callee = stmt.getCallee();
var raw_fn_expr = try transExpr(c, scope, callee, .used);
var is_ptr = false;
const fn_ty = qualTypeGetFnProto(callee.getType(), &is_ptr);
const fn_expr = if (is_ptr and fn_ty != null and !cIsFunctionDeclRef(callee))
try Tag.unwrap.create(c.arena, raw_fn_expr)
else
raw_fn_expr;
const num_args = stmt.getNumArgs();
const args = try c.arena.alloc(Node, num_args);
const c_args = stmt.getArgs();
var i: usize = 0;
while (i < num_args) : (i += 1) {
var arg = try transExpr(c, scope, c_args[i], .used);
// In C the result type of a boolean expression is int. If this result is passed as
// an argument to a function whose parameter is also int, there is no cast. Therefore
// in Zig we'll need to cast it from bool to u1 (which will safely coerce to c_int).
if (fn_ty) |ty| {
switch (ty) {
.Proto => |fn_proto| {
const param_count = fn_proto.getNumParams();
if (i < param_count) {
const param_qt = fn_proto.getParamType(@intCast(c_uint, i));
if (isBoolRes(arg) and cIsNativeInt(param_qt)) {
arg = try Tag.bool_to_int.create(c.arena, arg);
} else if (arg.tag() == .string_literal and qualTypeIsCharStar(param_qt)) {
const loc = @ptrCast(*const clang.Stmt, stmt).getBeginLoc();
const dst_type_node = try transQualType(c, scope, param_qt, loc);
arg = try removeCVQualifiers(c, dst_type_node, arg);
}
}
},
else => {},
}
}
args[i] = arg;
}
const node = try Tag.call.create(c.arena, .{ .lhs = fn_expr, .args = args });
if (fn_ty) |ty| {
const canon = ty.getReturnType().getCanonicalType();
const ret_ty = canon.getTypePtr();
if (ret_ty.isVoidType()) {
return node;
}
}
return maybeSuppressResult(c, scope, result_used, node);
}
const ClangFunctionType = union(enum) {
Proto: *const clang.FunctionProtoType,
NoProto: *const clang.FunctionType,
fn getReturnType(self: @This()) clang.QualType {
switch (@as(meta.Tag(@This()), self)) {
.Proto => return self.Proto.getReturnType(),
.NoProto => return self.NoProto.getReturnType(),
}
}
};
fn qualTypeGetFnProto(qt: clang.QualType, is_ptr: *bool) ?ClangFunctionType {
const canon = qt.getCanonicalType();
var ty = canon.getTypePtr();
is_ptr.* = false;
if (ty.getTypeClass() == .Pointer) {
is_ptr.* = true;
const child_qt = ty.getPointeeType();
ty = child_qt.getTypePtr();
}
if (ty.getTypeClass() == .FunctionProto) {
return ClangFunctionType{ .Proto = @ptrCast(*const clang.FunctionProtoType, ty) };
}
if (ty.getTypeClass() == .FunctionNoProto) {
return ClangFunctionType{ .NoProto = @ptrCast(*const clang.FunctionType, ty) };
}
return null;
}
fn transUnaryExprOrTypeTraitExpr(
c: *Context,
scope: *Scope,
stmt: *const clang.UnaryExprOrTypeTraitExpr,
result_used: ResultUsed,
) TransError!Node {
_ = result_used;
const loc = stmt.getBeginLoc();
const type_node = try transQualType(c, scope, stmt.getTypeOfArgument(), loc);
const kind = stmt.getKind();
switch (kind) {
.SizeOf => return Tag.sizeof.create(c.arena, type_node),
.AlignOf => return Tag.alignof.create(c.arena, type_node),
.PreferredAlignOf,
.VecStep,
.OpenMPRequiredSimdAlign,
=> return fail(
c,
error.UnsupportedTranslation,
loc,
"unsupported type trait kind {}",
.{kind},
),
}
}
fn qualTypeHasWrappingOverflow(qt: clang.QualType) bool {
if (cIsUnsignedInteger(qt)) {
// unsigned integer overflow wraps around.
return true;
} else {
// float, signed integer, and pointer overflow is undefined behavior.
return false;
}
}
fn transUnaryOperator(c: *Context, scope: *Scope, stmt: *const clang.UnaryOperator, used: ResultUsed) TransError!Node {
const op_expr = stmt.getSubExpr();
switch (stmt.getOpcode()) {
.PostInc => if (qualTypeHasWrappingOverflow(stmt.getType()))
return transCreatePostCrement(c, scope, stmt, .add_wrap_assign, used)
else
return transCreatePostCrement(c, scope, stmt, .add_assign, used),
.PostDec => if (qualTypeHasWrappingOverflow(stmt.getType()))
return transCreatePostCrement(c, scope, stmt, .sub_wrap_assign, used)
else
return transCreatePostCrement(c, scope, stmt, .sub_assign, used),
.PreInc => if (qualTypeHasWrappingOverflow(stmt.getType()))
return transCreatePreCrement(c, scope, stmt, .add_wrap_assign, used)
else
return transCreatePreCrement(c, scope, stmt, .add_assign, used),
.PreDec => if (qualTypeHasWrappingOverflow(stmt.getType()))
return transCreatePreCrement(c, scope, stmt, .sub_wrap_assign, used)
else
return transCreatePreCrement(c, scope, stmt, .sub_assign, used),
.AddrOf => {
if (c.zig_is_stage1 and cIsFunctionDeclRef(op_expr)) {
return transExpr(c, scope, op_expr, used);
}
return Tag.address_of.create(c.arena, try transExpr(c, scope, op_expr, used));
},
.Deref => {
if (qualTypeWasDemotedToOpaque(c, stmt.getType()))
return fail(c, error.UnsupportedTranslation, stmt.getBeginLoc(), "cannot dereference opaque type", .{});
const node = try transExpr(c, scope, op_expr, used);
var is_ptr = false;
const fn_ty = qualTypeGetFnProto(op_expr.getType(), &is_ptr);
if (fn_ty != null and is_ptr)
return node;
return Tag.deref.create(c.arena, node);
},
.Plus => return transExpr(c, scope, op_expr, used),
.Minus => {
if (!qualTypeHasWrappingOverflow(op_expr.getType())) {
const sub_expr_node = try transExpr(c, scope, op_expr, .used);
const to_negate = if (isBoolRes(sub_expr_node)) blk: {
const ty_node = try Tag.type.create(c.arena, "c_int");
const int_node = try Tag.bool_to_int.create(c.arena, sub_expr_node);
break :blk try Tag.as.create(c.arena, .{ .lhs = ty_node, .rhs = int_node });
} else sub_expr_node;
return Tag.negate.create(c.arena, to_negate);
} else if (cIsUnsignedInteger(op_expr.getType())) {
// use -% x for unsigned integers
return Tag.negate_wrap.create(c.arena, try transExpr(c, scope, op_expr, .used));
} else return fail(c, error.UnsupportedTranslation, stmt.getBeginLoc(), "C negation with non float non integer", .{});
},
.Not => {
return Tag.bit_not.create(c.arena, try transExpr(c, scope, op_expr, .used));
},
.LNot => {
return Tag.not.create(c.arena, try transBoolExpr(c, scope, op_expr, .used));
},
.Extension => {
return transExpr(c, scope, stmt.getSubExpr(), used);
},
else => return fail(c, error.UnsupportedTranslation, stmt.getBeginLoc(), "unsupported C translation {}", .{stmt.getOpcode()}),
}
}
fn transCreatePreCrement(
c: *Context,
scope: *Scope,
stmt: *const clang.UnaryOperator,
op: Tag,
used: ResultUsed,
) TransError!Node {
const op_expr = stmt.getSubExpr();
if (used == .unused) {
// common case
// c: ++expr
// zig: expr += 1
const lhs = try transExpr(c, scope, op_expr, .used);
const rhs = Tag.one_literal.init();
return transCreateNodeInfixOp(c, scope, op, lhs, rhs, .used);
}
// worst case
// c: ++expr
// zig: (blk: {
// zig: const _ref = &expr;
// zig: _ref.* += 1;
// zig: break :blk _ref.*
// zig: })
var block_scope = try Scope.Block.init(c, scope, true);
defer block_scope.deinit();
const ref = try block_scope.makeMangledName(c, "ref");
const expr = try transExpr(c, &block_scope.base, op_expr, .used);
const addr_of = try Tag.address_of.create(c.arena, expr);
const ref_decl = try Tag.var_simple.create(c.arena, .{ .name = ref, .init = addr_of });
try block_scope.statements.append(ref_decl);
const lhs_node = try Tag.identifier.create(c.arena, ref);
const ref_node = try Tag.deref.create(c.arena, lhs_node);
const node = try transCreateNodeInfixOp(c, &block_scope.base, op, ref_node, Tag.one_literal.init(), .used);
try block_scope.statements.append(node);
const break_node = try Tag.break_val.create(c.arena, .{
.label = block_scope.label,
.val = ref_node,
});
try block_scope.statements.append(break_node);
return block_scope.complete(c);
}
fn transCreatePostCrement(
c: *Context,
scope: *Scope,
stmt: *const clang.UnaryOperator,
op: Tag,
used: ResultUsed,
) TransError!Node {
const op_expr = stmt.getSubExpr();
if (used == .unused) {
// common case
// c: expr++
// zig: expr += 1
const lhs = try transExpr(c, scope, op_expr, .used);
const rhs = Tag.one_literal.init();
return transCreateNodeInfixOp(c, scope, op, lhs, rhs, .used);
}
// worst case
// c: expr++
// zig: (blk: {
// zig: const _ref = &expr;
// zig: const _tmp = _ref.*;
// zig: _ref.* += 1;
// zig: break :blk _tmp
// zig: })
var block_scope = try Scope.Block.init(c, scope, true);
defer block_scope.deinit();
const ref = try block_scope.makeMangledName(c, "ref");
const expr = try transExpr(c, &block_scope.base, op_expr, .used);
const addr_of = try Tag.address_of.create(c.arena, expr);
const ref_decl = try Tag.var_simple.create(c.arena, .{ .name = ref, .init = addr_of });
try block_scope.statements.append(ref_decl);
const lhs_node = try Tag.identifier.create(c.arena, ref);
const ref_node = try Tag.deref.create(c.arena, lhs_node);
const tmp = try block_scope.makeMangledName(c, "tmp");
const tmp_decl = try Tag.var_simple.create(c.arena, .{ .name = tmp, .init = ref_node });
try block_scope.statements.append(tmp_decl);
const node = try transCreateNodeInfixOp(c, &block_scope.base, op, ref_node, Tag.one_literal.init(), .used);
try block_scope.statements.append(node);
const break_node = try Tag.break_val.create(c.arena, .{
.label = block_scope.label,
.val = try Tag.identifier.create(c.arena, tmp),
});
try block_scope.statements.append(break_node);
return block_scope.complete(c);
}
fn transCompoundAssignOperator(c: *Context, scope: *Scope, stmt: *const clang.CompoundAssignOperator, used: ResultUsed) TransError!Node {
switch (stmt.getOpcode()) {
.MulAssign => if (qualTypeHasWrappingOverflow(stmt.getType()))
return transCreateCompoundAssign(c, scope, stmt, .mul_wrap_assign, used)
else
return transCreateCompoundAssign(c, scope, stmt, .mul_assign, used),
.AddAssign => if (qualTypeHasWrappingOverflow(stmt.getType()))
return transCreateCompoundAssign(c, scope, stmt, .add_wrap_assign, used)
else
return transCreateCompoundAssign(c, scope, stmt, .add_assign, used),
.SubAssign => if (qualTypeHasWrappingOverflow(stmt.getType()))
return transCreateCompoundAssign(c, scope, stmt, .sub_wrap_assign, used)
else
return transCreateCompoundAssign(c, scope, stmt, .sub_assign, used),
.DivAssign => return transCreateCompoundAssign(c, scope, stmt, .div_assign, used),
.RemAssign => return transCreateCompoundAssign(c, scope, stmt, .mod_assign, used),
.ShlAssign => return transCreateCompoundAssign(c, scope, stmt, .shl_assign, used),
.ShrAssign => return transCreateCompoundAssign(c, scope, stmt, .shr_assign, used),
.AndAssign => return transCreateCompoundAssign(c, scope, stmt, .bit_and_assign, used),
.XorAssign => return transCreateCompoundAssign(c, scope, stmt, .bit_xor_assign, used),
.OrAssign => return transCreateCompoundAssign(c, scope, stmt, .bit_or_assign, used),
else => return fail(
c,
error.UnsupportedTranslation,
stmt.getBeginLoc(),
"unsupported C translation {}",
.{stmt.getOpcode()},
),
}
}
fn transCreateCompoundAssign(
c: *Context,
scope: *Scope,
stmt: *const clang.CompoundAssignOperator,
op: Tag,
used: ResultUsed,
) TransError!Node {
const is_shift = op == .shl_assign or op == .shr_assign;
const is_div = op == .div_assign;
const is_mod = op == .mod_assign;
const lhs = stmt.getLHS();
const rhs = stmt.getRHS();
const loc = stmt.getBeginLoc();
const lhs_qt = getExprQualType(c, lhs);
const rhs_qt = getExprQualType(c, rhs);
const is_signed = cIsSignedInteger(lhs_qt);
const is_ptr_op_signed = qualTypeIsPtr(lhs_qt) and cIsSignedInteger(rhs_qt);
const requires_int_cast = blk: {
const are_integers = cIsInteger(lhs_qt) and cIsInteger(rhs_qt);
const are_same_sign = cIsSignedInteger(lhs_qt) == cIsSignedInteger(rhs_qt);
break :blk are_integers and !(are_same_sign and cIntTypeCmp(lhs_qt, rhs_qt) == .eq);
};
if (used == .unused) {
// common case
// c: lhs += rhs
// zig: lhs += rhs
const lhs_node = try transExpr(c, scope, lhs, .used);
var rhs_node = try transExpr(c, scope, rhs, .used);
if (is_ptr_op_signed) rhs_node = try usizeCastForWrappingPtrArithmetic(c.arena, rhs_node);
if ((is_mod or is_div) and is_signed) {
if (requires_int_cast) rhs_node = try transCCast(c, scope, loc, lhs_qt, rhs_qt, rhs_node);
const operands = .{ .lhs = lhs_node, .rhs = rhs_node };
const builtin = if (is_mod)
try Tag.signed_remainder.create(c.arena, operands)
else
try Tag.div_trunc.create(c.arena, operands);
return transCreateNodeInfixOp(c, scope, .assign, lhs_node, builtin, .used);
}
if (is_shift) {
const cast_to_type = try qualTypeToLog2IntRef(c, scope, rhs_qt, loc);
rhs_node = try Tag.int_cast.create(c.arena, .{ .lhs = cast_to_type, .rhs = rhs_node });
} else if (requires_int_cast) {
rhs_node = try transCCast(c, scope, loc, lhs_qt, rhs_qt, rhs_node);
}
return transCreateNodeInfixOp(c, scope, op, lhs_node, rhs_node, .used);
}
// worst case
// c: lhs += rhs
// zig: (blk: {
// zig: const _ref = &lhs;
// zig: _ref.* += rhs;
// zig: break :blk _ref.*
// zig: })
var block_scope = try Scope.Block.init(c, scope, true);
defer block_scope.deinit();
const ref = try block_scope.makeMangledName(c, "ref");
const expr = try transExpr(c, &block_scope.base, lhs, .used);
const addr_of = try Tag.address_of.create(c.arena, expr);
const ref_decl = try Tag.var_simple.create(c.arena, .{ .name = ref, .init = addr_of });
try block_scope.statements.append(ref_decl);
const lhs_node = try Tag.identifier.create(c.arena, ref);
const ref_node = try Tag.deref.create(c.arena, lhs_node);
var rhs_node = try transExpr(c, &block_scope.base, rhs, .used);
if (is_ptr_op_signed) rhs_node = try usizeCastForWrappingPtrArithmetic(c.arena, rhs_node);
if ((is_mod or is_div) and is_signed) {
if (requires_int_cast) rhs_node = try transCCast(c, scope, loc, lhs_qt, rhs_qt, rhs_node);
const operands = .{ .lhs = ref_node, .rhs = rhs_node };
const builtin = if (is_mod)
try Tag.signed_remainder.create(c.arena, operands)
else
try Tag.div_trunc.create(c.arena, operands);
const assign = try transCreateNodeInfixOp(c, &block_scope.base, .assign, ref_node, builtin, .used);
try block_scope.statements.append(assign);
} else {
if (is_shift) {
const cast_to_type = try qualTypeToLog2IntRef(c, &block_scope.base, rhs_qt, loc);
rhs_node = try Tag.int_cast.create(c.arena, .{ .lhs = cast_to_type, .rhs = rhs_node });
} else if (requires_int_cast) {
rhs_node = try transCCast(c, &block_scope.base, loc, lhs_qt, rhs_qt, rhs_node);
}
const assign = try transCreateNodeInfixOp(c, &block_scope.base, op, ref_node, rhs_node, .used);
try block_scope.statements.append(assign);
}
const break_node = try Tag.break_val.create(c.arena, .{
.label = block_scope.label,
.val = ref_node,
});
try block_scope.statements.append(break_node);
return block_scope.complete(c);
}
// Casting away const or volatile requires us to use @intToPtr
fn removeCVQualifiers(c: *Context, dst_type_node: Node, expr: Node) Error!Node {
const ptr_to_int = try Tag.ptr_to_int.create(c.arena, expr);
return Tag.int_to_ptr.create(c.arena, .{ .lhs = dst_type_node, .rhs = ptr_to_int });
}
fn transCPtrCast(
c: *Context,
scope: *Scope,
loc: clang.SourceLocation,
dst_type: clang.QualType,
src_type: clang.QualType,
expr: Node,
) !Node {
const ty = dst_type.getTypePtr();
const child_type = ty.getPointeeType();
const src_ty = src_type.getTypePtr();
const src_child_type = src_ty.getPointeeType();
const dst_type_node = try transType(c, scope, ty, loc);
if (!src_ty.isArrayType() and ((src_child_type.isConstQualified() and
!child_type.isConstQualified()) or
(src_child_type.isVolatileQualified() and
!child_type.isVolatileQualified())))
{
return removeCVQualifiers(c, dst_type_node, expr);
} else {
// Implicit downcasting from higher to lower alignment values is forbidden,
// use @alignCast to side-step this problem
const rhs = if (qualTypeCanon(child_type).isVoidType())
// void has 1-byte alignment, so @alignCast is not needed
expr
else if (typeIsOpaque(c, qualTypeCanon(child_type), loc))
// For opaque types a ptrCast is enough
expr
else blk: {
const child_type_node = try transQualType(c, scope, child_type, loc);
const alignof = try Tag.std_meta_alignment.create(c.arena, child_type_node);
const align_cast = try Tag.align_cast.create(c.arena, .{ .lhs = alignof, .rhs = expr });
break :blk align_cast;
};
return Tag.ptr_cast.create(c.arena, .{ .lhs = dst_type_node, .rhs = rhs });
}
}
fn transFloatingLiteral(c: *Context, scope: *Scope, expr: *const clang.FloatingLiteral, used: ResultUsed) TransError!Node {
switch (expr.getRawSemantics()) {
.IEEEhalf, // f16
.IEEEsingle, // f32
.IEEEdouble, // f64
=> {},
else => |format| return fail(
c,
error.UnsupportedTranslation,
expr.getBeginLoc(),
"unsupported floating point constant format {}",
.{format},
),
}
// TODO use something more accurate
var dbl = expr.getValueAsApproximateDouble();
const is_negative = dbl < 0;
if (is_negative) dbl = -dbl;
const str = if (dbl == std.math.floor(dbl))
try std.fmt.allocPrint(c.arena, "{d}.0", .{dbl})
else
try std.fmt.allocPrint(c.arena, "{d}", .{dbl});
var node = try Tag.float_literal.create(c.arena, str);
if (is_negative) node = try Tag.negate.create(c.arena, node);
return maybeSuppressResult(c, scope, used, node);
}
fn transBinaryConditionalOperator(c: *Context, scope: *Scope, stmt: *const clang.BinaryConditionalOperator, used: ResultUsed) TransError!Node {
// GNU extension of the ternary operator where the middle expression is
// omitted, the condition itself is returned if it evaluates to true
const qt = @ptrCast(*const clang.Expr, stmt).getType();
const res_is_bool = qualTypeIsBoolean(qt);
const casted_stmt = @ptrCast(*const clang.AbstractConditionalOperator, stmt);
const cond_expr = casted_stmt.getCond();
const false_expr = casted_stmt.getFalseExpr();
// c: (cond_expr)?:(false_expr)
// zig: (blk: {
// const _cond_temp = (cond_expr);
// break :blk if (_cond_temp) _cond_temp else (false_expr);
// })
var block_scope = try Scope.Block.init(c, scope, true);
defer block_scope.deinit();
const mangled_name = try block_scope.makeMangledName(c, "cond_temp");
const init_node = try transExpr(c, &block_scope.base, cond_expr, .used);
const ref_decl = try Tag.var_simple.create(c.arena, .{ .name = mangled_name, .init = init_node });
try block_scope.statements.append(ref_decl);
var cond_scope = Scope.Condition{
.base = .{
.parent = &block_scope.base,
.id = .condition,
},
};
defer cond_scope.deinit();
const cond_ident = try Tag.identifier.create(c.arena, mangled_name);
const ty = getExprQualType(c, cond_expr).getTypePtr();
const cond_node = try finishBoolExpr(c, &cond_scope.base, cond_expr.getBeginLoc(), ty, cond_ident, .used);
var then_body = cond_ident;
if (!res_is_bool and isBoolRes(init_node)) {
then_body = try Tag.bool_to_int.create(c.arena, then_body);
}
var else_body = try transExpr(c, &block_scope.base, false_expr, .used);
if (!res_is_bool and isBoolRes(else_body)) {
else_body = try Tag.bool_to_int.create(c.arena, else_body);
}
const if_node = try Tag.@"if".create(c.arena, .{
.cond = cond_node,
.then = then_body,
.@"else" = else_body,
});
const break_node = try Tag.break_val.create(c.arena, .{
.label = block_scope.label,
.val = if_node,
});
try block_scope.statements.append(break_node);
const res = try block_scope.complete(c);
return maybeSuppressResult(c, scope, used, res);
}
fn transConditionalOperator(c: *Context, scope: *Scope, stmt: *const clang.ConditionalOperator, used: ResultUsed) TransError!Node {
var cond_scope = Scope.Condition{
.base = .{
.parent = scope,
.id = .condition,
},
};
defer cond_scope.deinit();
const qt = @ptrCast(*const clang.Expr, stmt).getType();
const res_is_bool = qualTypeIsBoolean(qt);
const casted_stmt = @ptrCast(*const clang.AbstractConditionalOperator, stmt);
const cond_expr = casted_stmt.getCond();
const true_expr = casted_stmt.getTrueExpr();
const false_expr = casted_stmt.getFalseExpr();
const cond = try transBoolExpr(c, &cond_scope.base, cond_expr, .used);
var then_body = try transExpr(c, scope, true_expr, used);
if (!res_is_bool and isBoolRes(then_body)) {
then_body = try Tag.bool_to_int.create(c.arena, then_body);
}
var else_body = try transExpr(c, scope, false_expr, used);
if (!res_is_bool and isBoolRes(else_body)) {
else_body = try Tag.bool_to_int.create(c.arena, else_body);
}
const if_node = try Tag.@"if".create(c.arena, .{
.cond = cond,
.then = then_body,
.@"else" = else_body,
});
// Clang inserts ImplicitCast(ToVoid)'s to both rhs and lhs so we don't need to suppress the result here.
return if_node;
}
fn maybeSuppressResult(
c: *Context,
scope: *Scope,
used: ResultUsed,
result: Node,
) TransError!Node {
_ = scope;
if (used == .used) return result;
return Tag.discard.create(c.arena, .{ .should_skip = false, .value = result });
}
fn addTopLevelDecl(c: *Context, name: []const u8, decl_node: Node) !void {
try c.global_scope.sym_table.put(name, decl_node);
try c.global_scope.nodes.append(decl_node);
}
/// Translate a qualtype for a variable with an initializer. This only matters
/// for incomplete arrays, since the initializer determines the size of the array.
fn transQualTypeInitialized(
c: *Context,
scope: *Scope,
qt: clang.QualType,
decl_init: *const clang.Expr,
source_loc: clang.SourceLocation,
) TypeError!Node {
const ty = qt.getTypePtr();
if (ty.getTypeClass() == .IncompleteArray) {
const incomplete_array_ty = @ptrCast(*const clang.IncompleteArrayType, ty);
const elem_ty = try transType(c, scope, incomplete_array_ty.getElementType().getTypePtr(), source_loc);
switch (decl_init.getStmtClass()) {
.StringLiteralClass => {
const string_lit = @ptrCast(*const clang.StringLiteral, decl_init);
const string_lit_size = string_lit.getLength();
const array_size = @intCast(usize, string_lit_size);
// incomplete array initialized with empty string, will be translated as [1]T{0}
// see https://github.com/ziglang/zig/issues/8256
if (array_size == 0) return Tag.array_type.create(c.arena, .{ .len = 1, .elem_type = elem_ty });
return Tag.null_sentinel_array_type.create(c.arena, .{ .len = array_size, .elem_type = elem_ty });
},
.InitListExprClass => {
const init_expr = @ptrCast(*const clang.InitListExpr, decl_init);
const size = init_expr.getNumInits();
return Tag.array_type.create(c.arena, .{ .len = size, .elem_type = elem_ty });
},
else => {},
}
}
return transQualType(c, scope, qt, source_loc);
}
fn transQualType(c: *Context, scope: *Scope, qt: clang.QualType, source_loc: clang.SourceLocation) TypeError!Node {
return transType(c, scope, qt.getTypePtr(), source_loc);
}
/// Produces a Zig AST node by translating a Clang QualType, respecting the width, but modifying the signed-ness.
/// Asserts the type is an integer.
fn transQualTypeIntWidthOf(c: *Context, ty: clang.QualType, is_signed: bool) TypeError!Node {
return transTypeIntWidthOf(c, qualTypeCanon(ty), is_signed);
}
/// Produces a Zig AST node by translating a Clang Type, respecting the width, but modifying the signed-ness.
/// Asserts the type is an integer.
fn transTypeIntWidthOf(c: *Context, ty: *const clang.Type, is_signed: bool) TypeError!Node {
assert(ty.getTypeClass() == .Builtin);
const builtin_ty = @ptrCast(*const clang.BuiltinType, ty);
return Tag.type.create(c.arena, switch (builtin_ty.getKind()) {
.Char_U, .Char_S, .UChar, .SChar, .Char8 => if (is_signed) "i8" else "u8",
.UShort, .Short => if (is_signed) "c_short" else "c_ushort",
.UInt, .Int => if (is_signed) "c_int" else "c_uint",
.ULong, .Long => if (is_signed) "c_long" else "c_ulong",
.ULongLong, .LongLong => if (is_signed) "c_longlong" else "c_ulonglong",
.UInt128, .Int128 => if (is_signed) "i128" else "u128",
.Char16 => if (is_signed) "i16" else "u16",
.Char32 => if (is_signed) "i32" else "u32",
else => unreachable, // only call this function when it has already been determined the type is int
});
}
fn isCBuiltinType(qt: clang.QualType, kind: clang.BuiltinTypeKind) bool {
const c_type = qualTypeCanon(qt);
if (c_type.getTypeClass() != .Builtin)
return false;
const builtin_ty = @ptrCast(*const clang.BuiltinType, c_type);
return builtin_ty.getKind() == kind;
}
fn qualTypeIsPtr(qt: clang.QualType) bool {
return qualTypeCanon(qt).getTypeClass() == .Pointer;
}
fn qualTypeIsBoolean(qt: clang.QualType) bool {
return qualTypeCanon(qt).isBooleanType();
}
fn qualTypeIntBitWidth(c: *Context, qt: clang.QualType) !u32 {
const ty = qt.getTypePtr();
switch (ty.getTypeClass()) {
.Builtin => {
const builtin_ty = @ptrCast(*const clang.BuiltinType, ty);
switch (builtin_ty.getKind()) {
.Char_U,
.UChar,
.Char_S,
.SChar,
=> return 8,
.UInt128,
.Int128,
=> return 128,
else => return 0,
}
unreachable;
},
.Typedef => {
const typedef_ty = @ptrCast(*const clang.TypedefType, ty);
const typedef_decl = typedef_ty.getDecl();
const type_name = try c.str(@ptrCast(*const clang.NamedDecl, typedef_decl).getName_bytes_begin());
if (mem.eql(u8, type_name, "uint8_t") or mem.eql(u8, type_name, "int8_t")) {
return 8;
} else if (mem.eql(u8, type_name, "uint16_t") or mem.eql(u8, type_name, "int16_t")) {
return 16;
} else if (mem.eql(u8, type_name, "uint32_t") or mem.eql(u8, type_name, "int32_t")) {
return 32;
} else if (mem.eql(u8, type_name, "uint64_t") or mem.eql(u8, type_name, "int64_t")) {
return 64;
} else {
return 0;
}
},
else => return 0,
}
}
fn qualTypeToLog2IntRef(c: *Context, scope: *Scope, qt: clang.QualType, source_loc: clang.SourceLocation) !Node {
const int_bit_width = try qualTypeIntBitWidth(c, qt);
if (int_bit_width != 0) {
// we can perform the log2 now.
const cast_bit_width = math.log2_int(u64, int_bit_width);
return Tag.log2_int_type.create(c.arena, cast_bit_width);
}
const zig_type = try transQualType(c, scope, qt, source_loc);
return Tag.std_math_Log2Int.create(c.arena, zig_type);
}
fn qualTypeChildIsFnProto(qt: clang.QualType) bool {
const ty = qualTypeCanon(qt);
switch (ty.getTypeClass()) {
.FunctionProto, .FunctionNoProto => return true,
else => return false,
}
}
fn qualTypeCanon(qt: clang.QualType) *const clang.Type {
const canon = qt.getCanonicalType();
return canon.getTypePtr();
}
fn getExprQualType(c: *Context, expr: *const clang.Expr) clang.QualType {
blk: {
// If this is a C `char *`, turn it into a `const char *`
if (expr.getStmtClass() != .ImplicitCastExprClass) break :blk;
const cast_expr = @ptrCast(*const clang.ImplicitCastExpr, expr);
if (cast_expr.getCastKind() != .ArrayToPointerDecay) break :blk;
const sub_expr = cast_expr.getSubExpr();
if (sub_expr.getStmtClass() != .StringLiteralClass) break :blk;
const array_qt = sub_expr.getType();
const array_type = @ptrCast(*const clang.ArrayType, array_qt.getTypePtr());
var pointee_qt = array_type.getElementType();
pointee_qt.addConst();
return c.clang_context.getPointerType(pointee_qt);
}
return expr.getType();
}
fn typeIsOpaque(c: *Context, ty: *const clang.Type, loc: clang.SourceLocation) bool {
switch (ty.getTypeClass()) {
.Builtin => {
const builtin_ty = @ptrCast(*const clang.BuiltinType, ty);
return builtin_ty.getKind() == .Void;
},
.Record => {
const record_ty = @ptrCast(*const clang.RecordType, ty);
const record_decl = record_ty.getDecl();
const record_def = record_decl.getDefinition() orelse
return true;
var it = record_def.field_begin();
const end_it = record_def.field_end();
while (it.neq(end_it)) : (it = it.next()) {
const field_decl = it.deref();
if (field_decl.isBitField()) {
return true;
}
}
return false;
},
.Elaborated => {
const elaborated_ty = @ptrCast(*const clang.ElaboratedType, ty);
const qt = elaborated_ty.getNamedType();
return typeIsOpaque(c, qt.getTypePtr(), loc);
},
.Typedef => {
const typedef_ty = @ptrCast(*const clang.TypedefType, ty);
const typedef_decl = typedef_ty.getDecl();
const underlying_type = typedef_decl.getUnderlyingType();
return typeIsOpaque(c, underlying_type.getTypePtr(), loc);
},
else => return false,
}
}
/// plain `char *` (not const; not explicitly signed or unsigned)
fn qualTypeIsCharStar(qt: clang.QualType) bool {
if (qualTypeIsPtr(qt)) {
const child_qt = qualTypeCanon(qt).getPointeeType();
return cIsUnqualifiedChar(child_qt) and !child_qt.isConstQualified();
}
return false;
}
/// C `char` without explicit signed or unsigned qualifier
fn cIsUnqualifiedChar(qt: clang.QualType) bool {
const c_type = qualTypeCanon(qt);
if (c_type.getTypeClass() != .Builtin) return false;
const builtin_ty = @ptrCast(*const clang.BuiltinType, c_type);
return switch (builtin_ty.getKind()) {
.Char_S, .Char_U => true,
else => false,
};
}
fn cIsInteger(qt: clang.QualType) bool {
return cIsSignedInteger(qt) or cIsUnsignedInteger(qt);
}
fn cIsUnsignedInteger(qt: clang.QualType) bool {
const c_type = qualTypeCanon(qt);
if (c_type.getTypeClass() != .Builtin) return false;
const builtin_ty = @ptrCast(*const clang.BuiltinType, c_type);
return switch (builtin_ty.getKind()) {
.Char_U,
.UChar,
.Char_S,
.UShort,
.UInt,
.ULong,
.ULongLong,
.UInt128,
.WChar_U,
=> true,
else => false,
};
}
fn cIntTypeToIndex(qt: clang.QualType) u8 {
const c_type = qualTypeCanon(qt);
assert(c_type.getTypeClass() == .Builtin);
const builtin_ty = @ptrCast(*const clang.BuiltinType, c_type);
return switch (builtin_ty.getKind()) {
.Bool, .Char_U, .Char_S, .UChar, .SChar, .Char8 => 1,
.WChar_U, .WChar_S => 2,
.UShort, .Short, .Char16 => 3,
.UInt, .Int, .Char32 => 4,
.ULong, .Long => 5,
.ULongLong, .LongLong => 6,
.UInt128, .Int128 => 7,
else => unreachable,
};
}
fn cIntTypeCmp(a: clang.QualType, b: clang.QualType) math.Order {
const a_index = cIntTypeToIndex(a);
const b_index = cIntTypeToIndex(b);
return math.order(a_index, b_index);
}
/// Checks if expr is an integer literal >= 0
fn cIsNonNegativeIntLiteral(c: *Context, expr: *const clang.Expr) bool {
if (@ptrCast(*const clang.Stmt, expr).getStmtClass() == .IntegerLiteralClass) {
var signum: c_int = undefined;
if (!(@ptrCast(*const clang.IntegerLiteral, expr).getSignum(&signum, c.clang_context))) {
return false;
}
return signum >= 0;
}
return false;
}
fn cIsSignedInteger(qt: clang.QualType) bool {
const c_type = qualTypeCanon(qt);
if (c_type.getTypeClass() != .Builtin) return false;
const builtin_ty = @ptrCast(*const clang.BuiltinType, c_type);
return switch (builtin_ty.getKind()) {
.SChar,
.Short,
.Int,
.Long,
.LongLong,
.Int128,
.WChar_S,
=> true,
else => false,
};
}
fn cIsNativeInt(qt: clang.QualType) bool {
const c_type = qualTypeCanon(qt);
if (c_type.getTypeClass() != .Builtin) return false;
const builtin_ty = @ptrCast(*const clang.BuiltinType, c_type);
return builtin_ty.getKind() == .Int;
}
fn cIsFloating(qt: clang.QualType) bool {
const c_type = qualTypeCanon(qt);
if (c_type.getTypeClass() != .Builtin) return false;
const builtin_ty = @ptrCast(*const clang.BuiltinType, c_type);
return switch (builtin_ty.getKind()) {
.Float,
.Double,
.Float128,
.LongDouble,
=> true,
else => false,
};
}
fn cIsLongLongInteger(qt: clang.QualType) bool {
const c_type = qualTypeCanon(qt);
if (c_type.getTypeClass() != .Builtin) return false;
const builtin_ty = @ptrCast(*const clang.BuiltinType, c_type);
return switch (builtin_ty.getKind()) {
.LongLong, .ULongLong, .Int128, .UInt128 => true,
else => false,
};
}
fn transCreateNodeAssign(
c: *Context,
scope: *Scope,
result_used: ResultUsed,
lhs: *const clang.Expr,
rhs: *const clang.Expr,
) !Node {
// common case
// c: lhs = rhs
// zig: lhs = rhs
if (result_used == .unused) {
const lhs_node = try transExpr(c, scope, lhs, .used);
var rhs_node = try transExprCoercing(c, scope, rhs, .used);
if (!exprIsBooleanType(lhs) and isBoolRes(rhs_node)) {
rhs_node = try Tag.bool_to_int.create(c.arena, rhs_node);
}
return transCreateNodeInfixOp(c, scope, .assign, lhs_node, rhs_node, .used);
}
// worst case
// c: lhs = rhs
// zig: (blk: {
// zig: const _tmp = rhs;
// zig: lhs = _tmp;
// zig: break :blk _tmp
// zig: })
var block_scope = try Scope.Block.init(c, scope, true);
defer block_scope.deinit();
const tmp = try block_scope.makeMangledName(c, "tmp");
const rhs_node = try transExpr(c, &block_scope.base, rhs, .used);
const tmp_decl = try Tag.var_simple.create(c.arena, .{ .name = tmp, .init = rhs_node });
try block_scope.statements.append(tmp_decl);
const lhs_node = try transExpr(c, &block_scope.base, lhs, .used);
const tmp_ident = try Tag.identifier.create(c.arena, tmp);
const assign = try transCreateNodeInfixOp(c, &block_scope.base, .assign, lhs_node, tmp_ident, .used);
try block_scope.statements.append(assign);
const break_node = try Tag.break_val.create(c.arena, .{
.label = block_scope.label,
.val = tmp_ident,
});
try block_scope.statements.append(break_node);
return block_scope.complete(c);
}
fn transCreateNodeInfixOp(
c: *Context,
scope: *Scope,
op: Tag,
lhs: Node,
rhs: Node,
used: ResultUsed,
) !Node {
const payload = try c.arena.create(ast.Payload.BinOp);
payload.* = .{
.base = .{ .tag = op },
.data = .{
.lhs = lhs,
.rhs = rhs,
},
};
return maybeSuppressResult(c, scope, used, Node.initPayload(&payload.base));
}
fn transCreateNodeBoolInfixOp(
c: *Context,
scope: *Scope,
stmt: *const clang.BinaryOperator,
op: Tag,
used: ResultUsed,
) !Node {
std.debug.assert(op == .@"and" or op == .@"or");
const lhs = try transBoolExpr(c, scope, stmt.getLHS(), .used);
const rhs = try transBoolExpr(c, scope, stmt.getRHS(), .used);
return transCreateNodeInfixOp(c, scope, op, lhs, rhs, used);
}
fn transCreateNodeAPInt(c: *Context, int: *const clang.APSInt) !Node {
const num_limbs = math.cast(usize, int.getNumWords()) catch |err| switch (err) {
error.Overflow => return error.OutOfMemory,
};
var aps_int = int;
const is_negative = int.isSigned() and int.isNegative();
if (is_negative) aps_int = aps_int.negate();
defer if (is_negative) {
aps_int.free();
};
const limbs = try c.arena.alloc(math.big.Limb, num_limbs);
defer c.arena.free(limbs);
const data = aps_int.getRawData();
switch (@sizeOf(math.big.Limb)) {
8 => {
var i: usize = 0;
while (i < num_limbs) : (i += 1) {
limbs[i] = data[i];
}
},
4 => {
var limb_i: usize = 0;
var data_i: usize = 0;
while (limb_i < num_limbs) : ({
limb_i += 2;
data_i += 1;
}) {
limbs[limb_i] = @truncate(u32, data[data_i]);
limbs[limb_i + 1] = @truncate(u32, data[data_i] >> 32);
}
},
else => @compileError("unimplemented"),
}
const big: math.big.int.Const = .{ .limbs = limbs, .positive = true };
const str = big.toStringAlloc(c.arena, 10, .lower) catch |err| switch (err) {
error.OutOfMemory => return error.OutOfMemory,
};
const res = try Tag.integer_literal.create(c.arena, str);
if (is_negative) return Tag.negate.create(c.arena, res);
return res;
}
fn transCreateNodeNumber(c: *Context, num: anytype, num_kind: enum { int, float }) !Node {
const fmt_s = if (comptime meta.trait.isNumber(@TypeOf(num))) "{d}" else "{s}";
const str = try std.fmt.allocPrint(c.arena, fmt_s, .{num});
if (num_kind == .float)
return Tag.float_literal.create(c.arena, str)
else
return Tag.integer_literal.create(c.arena, str);
}
fn transCreateNodeMacroFn(c: *Context, name: []const u8, ref: Node, proto_alias: *ast.Payload.Func) !Node {
var fn_params = std.ArrayList(ast.Payload.Param).init(c.gpa);
defer fn_params.deinit();
for (proto_alias.data.params) |param| {
const param_name = param.name orelse
try std.fmt.allocPrint(c.arena, "arg_{d}", .{c.getMangle()});
try fn_params.append(.{
.name = param_name,
.type = param.type,
.is_noalias = param.is_noalias,
});
}
const init = if (ref.castTag(.var_decl)) |v|
v.data.init.?
else if (ref.castTag(.var_simple) orelse ref.castTag(.pub_var_simple)) |v|
v.data.init
else
unreachable;
const unwrap_expr = try Tag.unwrap.create(c.arena, init);
const args = try c.arena.alloc(Node, fn_params.items.len);
for (fn_params.items) |param, i| {
args[i] = try Tag.identifier.create(c.arena, param.name.?);
}
const call_expr = try Tag.call.create(c.arena, .{
.lhs = unwrap_expr,
.args = args,
});
const return_expr = try Tag.@"return".create(c.arena, call_expr);
const block = try Tag.block_single.create(c.arena, return_expr);
return Tag.pub_inline_fn.create(c.arena, .{
.name = name,
.params = try c.arena.dupe(ast.Payload.Param, fn_params.items),
.return_type = proto_alias.data.return_type,
.body = block,
});
}
fn transCreateNodeShiftOp(
c: *Context,
scope: *Scope,
stmt: *const clang.BinaryOperator,
op: Tag,
used: ResultUsed,
) !Node {
std.debug.assert(op == .shl or op == .shr);
const lhs_expr = stmt.getLHS();
const rhs_expr = stmt.getRHS();
const rhs_location = rhs_expr.getBeginLoc();
// lhs >> @as(u5, rh)
const lhs = try transExpr(c, scope, lhs_expr, .used);
const rhs_type = try qualTypeToLog2IntRef(c, scope, stmt.getType(), rhs_location);
const rhs = try transExprCoercing(c, scope, rhs_expr, .used);
const rhs_casted = try Tag.int_cast.create(c.arena, .{ .lhs = rhs_type, .rhs = rhs });
return transCreateNodeInfixOp(c, scope, op, lhs, rhs_casted, used);
}
fn transType(c: *Context, scope: *Scope, ty: *const clang.Type, source_loc: clang.SourceLocation) TypeError!Node {
switch (ty.getTypeClass()) {
.Builtin => {
const builtin_ty = @ptrCast(*const clang.BuiltinType, ty);
return Tag.type.create(c.arena, switch (builtin_ty.getKind()) {
.Void => "anyopaque",
.Bool => "bool",
.Char_U, .UChar, .Char_S, .Char8 => "u8",
.SChar => "i8",
.UShort => "c_ushort",
.UInt => "c_uint",
.ULong => "c_ulong",
.ULongLong => "c_ulonglong",
.Short => "c_short",
.Int => "c_int",
.Long => "c_long",
.LongLong => "c_longlong",
.UInt128 => "u128",
.Int128 => "i128",
.Float => "f32",
.Double => "f64",
.Float128 => "f128",
.Float16 => "f16",
.LongDouble => "c_longdouble",
else => return fail(c, error.UnsupportedType, source_loc, "unsupported builtin type", .{}),
});
},
.FunctionProto => {
const fn_proto_ty = @ptrCast(*const clang.FunctionProtoType, ty);
const fn_proto = try transFnProto(c, null, fn_proto_ty, source_loc, null, false);
return Node.initPayload(&fn_proto.base);
},
.FunctionNoProto => {
const fn_no_proto_ty = @ptrCast(*const clang.FunctionType, ty);
const fn_proto = try transFnNoProto(c, fn_no_proto_ty, source_loc, null, false);
return Node.initPayload(&fn_proto.base);
},
.Paren => {
const paren_ty = @ptrCast(*const clang.ParenType, ty);
return transQualType(c, scope, paren_ty.getInnerType(), source_loc);
},
.Pointer => {
const child_qt = ty.getPointeeType();
const is_fn_proto = qualTypeChildIsFnProto(child_qt);
if (c.zig_is_stage1 and is_fn_proto) {
return Tag.optional_type.create(c.arena, try transQualType(c, scope, child_qt, source_loc));
}
const is_const = is_fn_proto or child_qt.isConstQualified();
const is_volatile = child_qt.isVolatileQualified();
const elem_type = try transQualType(c, scope, child_qt, source_loc);
const ptr_info = .{
.is_const = is_const,
.is_volatile = is_volatile,
.elem_type = elem_type,
};
if (is_fn_proto or
typeIsOpaque(c, child_qt.getTypePtr(), source_loc) or
qualTypeWasDemotedToOpaque(c, child_qt))
{
const ptr = try Tag.single_pointer.create(c.arena, ptr_info);
return Tag.optional_type.create(c.arena, ptr);
}
return Tag.c_pointer.create(c.arena, ptr_info);
},
.ConstantArray => {
const const_arr_ty = @ptrCast(*const clang.ConstantArrayType, ty);
const size_ap_int = const_arr_ty.getSize();
const size = size_ap_int.getLimitedValue(usize);
const elem_type = try transType(c, scope, const_arr_ty.getElementType().getTypePtr(), source_loc);
return Tag.array_type.create(c.arena, .{ .len = size, .elem_type = elem_type });
},
.IncompleteArray => {
const incomplete_array_ty = @ptrCast(*const clang.IncompleteArrayType, ty);
const child_qt = incomplete_array_ty.getElementType();
const is_const = child_qt.isConstQualified();
const is_volatile = child_qt.isVolatileQualified();
const elem_type = try transQualType(c, scope, child_qt, source_loc);
return Tag.c_pointer.create(c.arena, .{ .is_const = is_const, .is_volatile = is_volatile, .elem_type = elem_type });
},
.Typedef => {
const typedef_ty = @ptrCast(*const clang.TypedefType, ty);
const typedef_decl = typedef_ty.getDecl();
var trans_scope = scope;
if (@ptrCast(*const clang.Decl, typedef_decl).castToNamedDecl()) |named_decl| {
const decl_name = try c.str(named_decl.getName_bytes_begin());
if (c.global_names.get(decl_name)) |_| trans_scope = &c.global_scope.base;
if (builtin_typedef_map.get(decl_name)) |builtin| return Tag.type.create(c.arena, builtin);
}
try transTypeDef(c, trans_scope, typedef_decl);
const name = c.decl_table.get(@ptrToInt(typedef_decl.getCanonicalDecl())).?;
return Tag.identifier.create(c.arena, name);
},
.Record => {
const record_ty = @ptrCast(*const clang.RecordType, ty);
const record_decl = record_ty.getDecl();
var trans_scope = scope;
if (@ptrCast(*const clang.Decl, record_decl).castToNamedDecl()) |named_decl| {
const decl_name = try c.str(named_decl.getName_bytes_begin());
if (c.global_names.get(decl_name)) |_| trans_scope = &c.global_scope.base;
}
try transRecordDecl(c, trans_scope, record_decl);
const name = c.decl_table.get(@ptrToInt(record_decl.getCanonicalDecl())).?;
return Tag.identifier.create(c.arena, name);
},
.Enum => {
const enum_ty = @ptrCast(*const clang.EnumType, ty);
const enum_decl = enum_ty.getDecl();
var trans_scope = scope;
if (@ptrCast(*const clang.Decl, enum_decl).castToNamedDecl()) |named_decl| {
const decl_name = try c.str(named_decl.getName_bytes_begin());
if (c.global_names.get(decl_name)) |_| trans_scope = &c.global_scope.base;
}
try transEnumDecl(c, trans_scope, enum_decl);
const name = c.decl_table.get(@ptrToInt(enum_decl.getCanonicalDecl())).?;
return Tag.identifier.create(c.arena, name);
},
.Elaborated => {
const elaborated_ty = @ptrCast(*const clang.ElaboratedType, ty);
return transQualType(c, scope, elaborated_ty.getNamedType(), source_loc);
},
.Decayed => {
const decayed_ty = @ptrCast(*const clang.DecayedType, ty);
return transQualType(c, scope, decayed_ty.getDecayedType(), source_loc);
},
.Attributed => {
const attributed_ty = @ptrCast(*const clang.AttributedType, ty);
return transQualType(c, scope, attributed_ty.getEquivalentType(), source_loc);
},
.MacroQualified => {
const macroqualified_ty = @ptrCast(*const clang.MacroQualifiedType, ty);
return transQualType(c, scope, macroqualified_ty.getModifiedType(), source_loc);
},
.TypeOf => {
const typeof_ty = @ptrCast(*const clang.TypeOfType, ty);
return transQualType(c, scope, typeof_ty.getUnderlyingType(), source_loc);
},
.TypeOfExpr => {
const typeofexpr_ty = @ptrCast(*const clang.TypeOfExprType, ty);
const underlying_expr = transExpr(c, scope, typeofexpr_ty.getUnderlyingExpr(), .used) catch |err| switch (err) {
error.UnsupportedTranslation => {
return fail(c, error.UnsupportedType, source_loc, "unsupported underlying expression for TypeOfExpr", .{});
},
else => |e| return e,
};
return Tag.typeof.create(c.arena, underlying_expr);
},
.Vector => {
const vector_ty = @ptrCast(*const clang.VectorType, ty);
const num_elements = vector_ty.getNumElements();
const element_qt = vector_ty.getElementType();
return Tag.vector.create(c.arena, .{
.lhs = try transCreateNodeNumber(c, num_elements, .int),
.rhs = try transQualType(c, scope, element_qt, source_loc),
});
},
.ExtInt, .ExtVector => {
const type_name = c.str(ty.getTypeClassName());
return fail(c, error.UnsupportedType, source_loc, "TODO implement translation of type: '{s}'", .{type_name});
},
else => {
const type_name = c.str(ty.getTypeClassName());
return fail(c, error.UnsupportedType, source_loc, "unsupported type: '{s}'", .{type_name});
},
}
}
fn qualTypeWasDemotedToOpaque(c: *Context, qt: clang.QualType) bool {
const ty = qt.getTypePtr();
switch (qt.getTypeClass()) {
.Typedef => {
const typedef_ty = @ptrCast(*const clang.TypedefType, ty);
const typedef_decl = typedef_ty.getDecl();
const underlying_type = typedef_decl.getUnderlyingType();
return qualTypeWasDemotedToOpaque(c, underlying_type);
},
.Record => {
const record_ty = @ptrCast(*const clang.RecordType, ty);
const record_decl = record_ty.getDecl();
const canonical = @ptrToInt(record_decl.getCanonicalDecl());
return c.opaque_demotes.contains(canonical);
},
.Enum => {
const enum_ty = @ptrCast(*const clang.EnumType, ty);
const enum_decl = enum_ty.getDecl();
const canonical = @ptrToInt(enum_decl.getCanonicalDecl());
return c.opaque_demotes.contains(canonical);
},
.Elaborated => {
const elaborated_ty = @ptrCast(*const clang.ElaboratedType, ty);
return qualTypeWasDemotedToOpaque(c, elaborated_ty.getNamedType());
},
.Decayed => {
const decayed_ty = @ptrCast(*const clang.DecayedType, ty);
return qualTypeWasDemotedToOpaque(c, decayed_ty.getDecayedType());
},
.Attributed => {
const attributed_ty = @ptrCast(*const clang.AttributedType, ty);
return qualTypeWasDemotedToOpaque(c, attributed_ty.getEquivalentType());
},
.MacroQualified => {
const macroqualified_ty = @ptrCast(*const clang.MacroQualifiedType, ty);
return qualTypeWasDemotedToOpaque(c, macroqualified_ty.getModifiedType());
},
else => return false,
}
}
fn isAnyopaque(qt: clang.QualType) bool {
const ty = qt.getTypePtr();
switch (ty.getTypeClass()) {
.Builtin => {
const builtin_ty = @ptrCast(*const clang.BuiltinType, ty);
return builtin_ty.getKind() == .Void;
},
.Typedef => {
const typedef_ty = @ptrCast(*const clang.TypedefType, ty);
const typedef_decl = typedef_ty.getDecl();
return isAnyopaque(typedef_decl.getUnderlyingType());
},
else => return false,
}
}
const FnDeclContext = struct {
fn_name: []const u8,
has_body: bool,
storage_class: clang.StorageClass,
is_always_inline: bool,
is_export: bool,
};
fn transCC(
c: *Context,
fn_ty: *const clang.FunctionType,
source_loc: clang.SourceLocation,
) !CallingConvention {
const clang_cc = fn_ty.getCallConv();
switch (clang_cc) {
.C => return CallingConvention.C,
.X86StdCall => return CallingConvention.Stdcall,
.X86FastCall => return CallingConvention.Fastcall,
.X86VectorCall, .AArch64VectorCall => return CallingConvention.Vectorcall,
.X86ThisCall => return CallingConvention.Thiscall,
.AAPCS => return CallingConvention.AAPCS,
.AAPCS_VFP => return CallingConvention.AAPCSVFP,
.X86_64SysV => return CallingConvention.SysV,
else => return fail(
c,
error.UnsupportedType,
source_loc,
"unsupported calling convention: {s}",
.{@tagName(clang_cc)},
),
}
}
fn transFnProto(
c: *Context,
fn_decl: ?*const clang.FunctionDecl,
fn_proto_ty: *const clang.FunctionProtoType,
source_loc: clang.SourceLocation,
fn_decl_context: ?FnDeclContext,
is_pub: bool,
) !*ast.Payload.Func {
const fn_ty = @ptrCast(*const clang.FunctionType, fn_proto_ty);
const cc = try transCC(c, fn_ty, source_loc);
const is_var_args = fn_proto_ty.isVariadic();
return finishTransFnProto(c, fn_decl, fn_proto_ty, fn_ty, source_loc, fn_decl_context, is_var_args, cc, is_pub);
}
fn transFnNoProto(
c: *Context,
fn_ty: *const clang.FunctionType,
source_loc: clang.SourceLocation,
fn_decl_context: ?FnDeclContext,
is_pub: bool,
) !*ast.Payload.Func {
const cc = try transCC(c, fn_ty, source_loc);
const is_var_args = if (fn_decl_context) |ctx| (!ctx.is_export and ctx.storage_class != .Static and !ctx.is_always_inline) else true;
return finishTransFnProto(c, null, null, fn_ty, source_loc, fn_decl_context, is_var_args, cc, is_pub);
}
fn finishTransFnProto(
c: *Context,
fn_decl: ?*const clang.FunctionDecl,
fn_proto_ty: ?*const clang.FunctionProtoType,
fn_ty: *const clang.FunctionType,
source_loc: clang.SourceLocation,
fn_decl_context: ?FnDeclContext,
is_var_args: bool,
cc: CallingConvention,
is_pub: bool,
) !*ast.Payload.Func {
const is_export = if (fn_decl_context) |ctx| ctx.is_export else false;
const is_extern = if (fn_decl_context) |ctx| !ctx.has_body else false;
const is_inline = if (fn_decl_context) |ctx| ctx.is_always_inline else false;
const scope = &c.global_scope.base;
// TODO check for align attribute
const param_count: usize = if (fn_proto_ty != null) fn_proto_ty.?.getNumParams() else 0;
var fn_params = try std.ArrayList(ast.Payload.Param).initCapacity(c.gpa, param_count);
defer fn_params.deinit();
var i: usize = 0;
while (i < param_count) : (i += 1) {
const param_qt = fn_proto_ty.?.getParamType(@intCast(c_uint, i));
const is_noalias = param_qt.isRestrictQualified();
const param_name: ?[]const u8 =
if (fn_decl) |decl|
blk: {
const param = decl.getParamDecl(@intCast(c_uint, i));
const param_name: []const u8 = try c.str(@ptrCast(*const clang.NamedDecl, param).getName_bytes_begin());
if (param_name.len < 1)
break :blk null;
break :blk param_name;
} else null;
const type_node = try transQualType(c, scope, param_qt, source_loc);
fn_params.addOneAssumeCapacity().* = .{
.is_noalias = is_noalias,
.name = param_name,
.type = type_node,
};
}
const linksection_string = blk: {
if (fn_decl) |decl| {
var str_len: usize = undefined;
if (decl.getSectionAttribute(&str_len)) |str_ptr| {
break :blk str_ptr[0..str_len];
}
}
break :blk null;
};
const alignment = if (fn_decl) |decl| zigAlignment(decl.getAlignedAttribute(c.clang_context)) else null;
const explicit_callconv = if ((is_inline or is_export or is_extern) and cc == .C) null else cc;
const return_type_node = blk: {
if (fn_ty.getNoReturnAttr()) {
break :blk Tag.noreturn_type.init();
} else {
const return_qt = fn_ty.getReturnType();
if (isAnyopaque(return_qt)) {
// convert primitive anyopaque to actual void (only for return type)
break :blk Tag.void_type.init();
} else {
break :blk transQualType(c, scope, return_qt, source_loc) catch |err| switch (err) {
error.UnsupportedType => {
try warn(c, scope, source_loc, "unsupported function proto return type", .{});
return err;
},
error.OutOfMemory => |e| return e,
};
}
}
};
const name: ?[]const u8 = if (fn_decl_context) |ctx| ctx.fn_name else null;
const payload = try c.arena.create(ast.Payload.Func);
payload.* = .{
.base = .{ .tag = .func },
.data = .{
.is_pub = is_pub,
.is_extern = is_extern,
.is_export = is_export,
.is_inline = is_inline,
.is_var_args = is_var_args,
.name = name,
.linksection_string = linksection_string,
.explicit_callconv = explicit_callconv,
.params = try c.arena.dupe(ast.Payload.Param, fn_params.items),
.return_type = return_type_node,
.body = null,
.alignment = alignment,
},
};
return payload;
}
fn warn(c: *Context, scope: *Scope, loc: clang.SourceLocation, comptime format: []const u8, args: anytype) !void {
const args_prefix = .{c.locStr(loc)};
const value = try std.fmt.allocPrint(c.arena, "// {s}: warning: " ++ format, args_prefix ++ args);
try scope.appendNode(try Tag.warning.create(c.arena, value));
}
fn fail(
c: *Context,
err: anytype,
source_loc: clang.SourceLocation,
comptime format: []const u8,
args: anytype,
) (@TypeOf(err) || error{OutOfMemory}) {
try warn(c, &c.global_scope.base, source_loc, format, args);
return err;
}
pub fn failDecl(c: *Context, loc: clang.SourceLocation, name: []const u8, comptime format: []const u8, args: anytype) Error!void {
// location
// pub const name = @compileError(msg);
const fail_msg = try std.fmt.allocPrint(c.arena, format, args);
try addTopLevelDecl(c, name, try Tag.fail_decl.create(c.arena, .{ .actual = name, .mangled = fail_msg }));
const location_comment = try std.fmt.allocPrint(c.arena, "// {s}", .{c.locStr(loc)});
try c.global_scope.nodes.append(try Tag.warning.create(c.arena, location_comment));
}
pub fn freeErrors(errors: []ClangErrMsg) void {
errors.ptr.delete(errors.len);
}
const PatternList = struct {
patterns: []Pattern,
/// Templates must be function-like macros
/// first element is macro source, second element is the name of the function
/// in std.lib.zig.c_translation.Macros which implements it
const templates = [_][2][]const u8{
[2][]const u8{ "f_SUFFIX(X) (X ## f)", "F_SUFFIX" },
[2][]const u8{ "F_SUFFIX(X) (X ## F)", "F_SUFFIX" },
[2][]const u8{ "u_SUFFIX(X) (X ## u)", "U_SUFFIX" },
[2][]const u8{ "U_SUFFIX(X) (X ## U)", "U_SUFFIX" },
[2][]const u8{ "l_SUFFIX(X) (X ## l)", "L_SUFFIX" },
[2][]const u8{ "L_SUFFIX(X) (X ## L)", "L_SUFFIX" },
[2][]const u8{ "ul_SUFFIX(X) (X ## ul)", "UL_SUFFIX" },
[2][]const u8{ "uL_SUFFIX(X) (X ## uL)", "UL_SUFFIX" },
[2][]const u8{ "Ul_SUFFIX(X) (X ## Ul)", "UL_SUFFIX" },
[2][]const u8{ "UL_SUFFIX(X) (X ## UL)", "UL_SUFFIX" },
[2][]const u8{ "ll_SUFFIX(X) (X ## ll)", "LL_SUFFIX" },
[2][]const u8{ "LL_SUFFIX(X) (X ## LL)", "LL_SUFFIX" },
[2][]const u8{ "ull_SUFFIX(X) (X ## ull)", "ULL_SUFFIX" },
[2][]const u8{ "uLL_SUFFIX(X) (X ## uLL)", "ULL_SUFFIX" },
[2][]const u8{ "Ull_SUFFIX(X) (X ## Ull)", "ULL_SUFFIX" },
[2][]const u8{ "ULL_SUFFIX(X) (X ## ULL)", "ULL_SUFFIX" },
[2][]const u8{ "CAST_OR_CALL(X, Y) (X)(Y)", "CAST_OR_CALL" },
[2][]const u8{
\\wl_container_of(ptr, sample, member) \
\\(__typeof__(sample))((char *)(ptr) - \
\\ offsetof(__typeof__(*sample), member))
,
"WL_CONTAINER_OF",
},
[2][]const u8{ "IGNORE_ME(X) ((void)(X))", "DISCARD" },
[2][]const u8{ "IGNORE_ME(X) (void)(X)", "DISCARD" },
[2][]const u8{ "IGNORE_ME(X) ((const void)(X))", "DISCARD" },
[2][]const u8{ "IGNORE_ME(X) (const void)(X)", "DISCARD" },
[2][]const u8{ "IGNORE_ME(X) ((volatile void)(X))", "DISCARD" },
[2][]const u8{ "IGNORE_ME(X) (volatile void)(X)", "DISCARD" },
[2][]const u8{ "IGNORE_ME(X) ((const volatile void)(X))", "DISCARD" },
[2][]const u8{ "IGNORE_ME(X) (const volatile void)(X)", "DISCARD" },
[2][]const u8{ "IGNORE_ME(X) ((volatile const void)(X))", "DISCARD" },
[2][]const u8{ "IGNORE_ME(X) (volatile const void)(X)", "DISCARD" },
};
/// Assumes that `ms` represents a tokenized function-like macro.
fn buildArgsHash(allocator: mem.Allocator, ms: MacroSlicer, hash: *ArgsPositionMap) MacroProcessingError!void {
assert(ms.tokens.len > 2);
assert(ms.tokens[0].id == .Identifier);
assert(ms.tokens[1].id == .LParen);
var i: usize = 2;
while (true) : (i += 1) {
const token = ms.tokens[i];
switch (token.id) {
.RParen => break,
.Comma => continue,
.Identifier => {
const identifier = ms.slice(token);
try hash.put(allocator, identifier, i);
},
else => return error.UnexpectedMacroToken,
}
}
}
const Pattern = struct {
tokens: []const CToken,
source: []const u8,
impl: []const u8,
args_hash: ArgsPositionMap,
fn init(self: *Pattern, allocator: mem.Allocator, template: [2][]const u8) Error!void {
const source = template[0];
const impl = template[1];
var tok_list = std.ArrayList(CToken).init(allocator);
defer tok_list.deinit();
try tokenizeMacro(source, &tok_list);
const tokens = try allocator.dupe(CToken, tok_list.items);
self.* = .{
.tokens = tokens,
.source = source,
.impl = impl,
.args_hash = .{},
};
const ms = MacroSlicer{ .source = source, .tokens = tokens };
buildArgsHash(allocator, ms, &self.args_hash) catch |err| switch (err) {
error.UnexpectedMacroToken => unreachable,
else => |e| return e,
};
}
fn deinit(self: *Pattern, allocator: mem.Allocator) void {
self.args_hash.deinit(allocator);
allocator.free(self.tokens);
}
/// This function assumes that `ms` has already been validated to contain a function-like
/// macro, and that the parsed template macro in `self` also contains a function-like
/// macro. Please review this logic carefully if changing that assumption. Two
/// function-like macros are considered equivalent if and only if they contain the same
/// list of tokens, modulo parameter names.
fn isEquivalent(self: Pattern, ms: MacroSlicer, args_hash: ArgsPositionMap) bool {
if (self.tokens.len != ms.tokens.len) return false;
if (args_hash.count() != self.args_hash.count()) return false;
var i: usize = 2;
while (self.tokens[i].id != .RParen) : (i += 1) {}
const pattern_slicer = MacroSlicer{ .source = self.source, .tokens = self.tokens };
while (i < self.tokens.len) : (i += 1) {
const pattern_token = self.tokens[i];
const macro_token = ms.tokens[i];
if (meta.activeTag(pattern_token.id) != meta.activeTag(macro_token.id)) return false;
const pattern_bytes = pattern_slicer.slice(pattern_token);
const macro_bytes = ms.slice(macro_token);
switch (pattern_token.id) {
.Identifier => {
const pattern_arg_index = self.args_hash.get(pattern_bytes);
const macro_arg_index = args_hash.get(macro_bytes);
if (pattern_arg_index == null and macro_arg_index == null) {
if (!mem.eql(u8, pattern_bytes, macro_bytes)) return false;
} else if (pattern_arg_index != null and macro_arg_index != null) {
if (pattern_arg_index.? != macro_arg_index.?) return false;
} else {
return false;
}
},
.MacroString, .StringLiteral, .CharLiteral, .IntegerLiteral, .FloatLiteral => {
if (!mem.eql(u8, pattern_bytes, macro_bytes)) return false;
},
else => {
// other tags correspond to keywords and operators that do not contain a "payload"
// that can vary
},
}
}
return true;
}
};
fn init(allocator: mem.Allocator) Error!PatternList {
const patterns = try allocator.alloc(Pattern, templates.len);
for (templates) |template, i| {
try patterns[i].init(allocator, template);
}
return PatternList{ .patterns = patterns };
}
fn deinit(self: *PatternList, allocator: mem.Allocator) void {
for (self.patterns) |*pattern| pattern.deinit(allocator);
allocator.free(self.patterns);
}
fn match(self: PatternList, allocator: mem.Allocator, ms: MacroSlicer) Error!?Pattern {
var args_hash: ArgsPositionMap = .{};
defer args_hash.deinit(allocator);
buildArgsHash(allocator, ms, &args_hash) catch |err| switch (err) {
error.UnexpectedMacroToken => return null,
else => |e| return e,
};
for (self.patterns) |pattern| if (pattern.isEquivalent(ms, args_hash)) return pattern;
return null;
}
};
const MacroSlicer = struct {
source: []const u8,
tokens: []const CToken,
fn slice(self: MacroSlicer, token: CToken) []const u8 {
return self.source[token.start..token.end];
}
};
// Testing here instead of test/translate_c.zig allows us to also test that the
// mapped function exists in `std.zig.c_translation.Macros`
test "Macro matching" {
const helper = struct {
const MacroFunctions = std.zig.c_translation.Macros;
fn checkMacro(allocator: mem.Allocator, pattern_list: PatternList, source: []const u8, comptime expected_match: ?[]const u8) !void {
var tok_list = std.ArrayList(CToken).init(allocator);
defer tok_list.deinit();
try tokenizeMacro(source, &tok_list);
const macro_slicer = MacroSlicer{ .source = source, .tokens = tok_list.items };
const matched = try pattern_list.match(allocator, macro_slicer);
if (expected_match) |expected| {
try testing.expectEqualStrings(expected, matched.?.impl);
try testing.expect(@hasDecl(MacroFunctions, expected));
} else {
try testing.expectEqual(@as(@TypeOf(matched), null), matched);
}
}
};
const allocator = std.testing.allocator;
var pattern_list = try PatternList.init(allocator);
defer pattern_list.deinit(allocator);
try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## F)", "F_SUFFIX");
try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## U)", "U_SUFFIX");
try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## L)", "L_SUFFIX");
try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## LL)", "LL_SUFFIX");
try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## UL)", "UL_SUFFIX");
try helper.checkMacro(allocator, pattern_list, "BAR(Z) (Z ## ULL)", "ULL_SUFFIX");
try helper.checkMacro(allocator, pattern_list,
\\container_of(a, b, c) \
\\(__typeof__(b))((char *)(a) - \
\\ offsetof(__typeof__(*b), c))
, "WL_CONTAINER_OF");
try helper.checkMacro(allocator, pattern_list, "NO_MATCH(X, Y) (X + Y)", null);
try helper.checkMacro(allocator, pattern_list, "CAST_OR_CALL(X, Y) (X)(Y)", "CAST_OR_CALL");
try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (void)(X)", "DISCARD");
try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((void)(X))", "DISCARD");
try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (const void)(X)", "DISCARD");
try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((const void)(X))", "DISCARD");
try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (volatile void)(X)", "DISCARD");
try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((volatile void)(X))", "DISCARD");
try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (const volatile void)(X)", "DISCARD");
try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((const volatile void)(X))", "DISCARD");
try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) (volatile const void)(X)", "DISCARD");
try helper.checkMacro(allocator, pattern_list, "IGNORE_ME(X) ((volatile const void)(X))", "DISCARD");
}
const MacroCtx = struct {
source: []const u8,
list: []const CToken,
i: usize = 0,
loc: clang.SourceLocation,
name: []const u8,
fn peek(self: *MacroCtx) ?CToken.Id {
if (self.i >= self.list.len) return null;
return self.list[self.i + 1].id;
}
fn next(self: *MacroCtx) ?CToken.Id {
if (self.i >= self.list.len) return null;
self.i += 1;
return self.list[self.i].id;
}
fn skip(self: *MacroCtx, c: *Context, expected_id: std.meta.Tag(CToken.Id)) ParseError!void {
const next_id = self.next().?;
if (next_id != expected_id) {
try self.fail(
c,
"unable to translate C expr: expected '{s}' instead got '{s}'",
.{ CToken.Id.symbolName(expected_id), next_id.symbol() },
);
return error.ParseError;
}
}
fn slice(self: *MacroCtx) []const u8 {
const tok = self.list[self.i];
return self.source[tok.start..tok.end];
}
fn fail(self: *MacroCtx, c: *Context, comptime fmt: []const u8, args: anytype) !void {
return failDecl(c, self.loc, self.name, fmt, args);
}
fn makeSlicer(self: *const MacroCtx) MacroSlicer {
return MacroSlicer{ .source = self.source, .tokens = self.list };
}
fn containsUndefinedIdentifier(self: *MacroCtx, scope: *Scope, params: []const ast.Payload.Param) ?[]const u8 {
const slicer = self.makeSlicer();
var i: usize = 1; // index 0 is the macro name
while (i < self.list.len) : (i += 1) {
const token = self.list[i];
switch (token.id) {
.Period, .Arrow => i += 1, // skip next token since field identifiers can be unknown
.Identifier => {
const identifier = slicer.slice(token);
const is_param = for (params) |param| {
if (param.name != null and mem.eql(u8, identifier, param.name.?)) break true;
} else false;
if (!scope.contains(identifier) and !isBuiltinDefined(identifier) and !is_param) return identifier;
},
else => {},
}
}
return null;
}
};
fn tokenizeMacro(source: []const u8, tok_list: *std.ArrayList(CToken)) Error!void {
var tokenizer = std.c.Tokenizer{
.buffer = source,
};
while (true) {
const tok = tokenizer.next();
switch (tok.id) {
.Nl, .Eof => {
try tok_list.append(tok);
break;
},
.LineComment, .MultiLineComment => continue,
else => {},
}
try tok_list.append(tok);
}
}
fn transPreprocessorEntities(c: *Context, unit: *clang.ASTUnit) Error!void {
// TODO if we see #undef, delete it from the table
var it = unit.getLocalPreprocessingEntities_begin();
const it_end = unit.getLocalPreprocessingEntities_end();
var tok_list = std.ArrayList(CToken).init(c.gpa);
defer tok_list.deinit();
const scope = c.global_scope;
while (it.I != it_end.I) : (it.I += 1) {
const entity = it.deref();
tok_list.items.len = 0;
switch (entity.getKind()) {
.MacroDefinitionKind => {
const macro = @ptrCast(*clang.MacroDefinitionRecord, entity);
const raw_name = macro.getName_getNameStart();
const begin_loc = macro.getSourceRange_getBegin();
const end_loc = clang.Lexer.getLocForEndOfToken(macro.getSourceRange_getEnd(), c.source_manager, unit);
const name = try c.str(raw_name);
if (scope.containsNow(name)) {
continue;
}
const begin_c = c.source_manager.getCharacterData(begin_loc);
const end_c = c.source_manager.getCharacterData(end_loc);
const slice_len = @ptrToInt(end_c) - @ptrToInt(begin_c);
const slice = begin_c[0..slice_len];
try tokenizeMacro(slice, &tok_list);
var macro_ctx = MacroCtx{
.source = slice,
.list = tok_list.items,
.name = name,
.loc = begin_loc,
};
assert(mem.eql(u8, macro_ctx.slice(), name));
var macro_fn = false;
switch (macro_ctx.peek().?) {
.Identifier => {
// if it equals itself, ignore. for example, from stdio.h:
// #define stdin stdin
const tok = macro_ctx.list[1];
if (mem.eql(u8, name, slice[tok.start..tok.end])) {
continue;
}
},
.Nl, .Eof => {
// this means it is a macro without a value
// We define it as an empty string so that it can still be used with ++
const str_node = try Tag.string_literal.create(c.arena, "\"\"");
const var_decl = try Tag.pub_var_simple.create(c.arena, .{ .name = name, .init = str_node });
try c.global_scope.macro_table.put(name, var_decl);
continue;
},
.LParen => {
// if the name is immediately followed by a '(' then it is a function
macro_fn = macro_ctx.list[0].end == macro_ctx.list[1].start;
},
else => {},
}
(if (macro_fn)
transMacroFnDefine(c, &macro_ctx)
else
transMacroDefine(c, &macro_ctx)) catch |err| switch (err) {
error.ParseError => continue,
error.OutOfMemory => |e| return e,
};
},
else => {},
}
}
}
fn transMacroDefine(c: *Context, m: *MacroCtx) ParseError!void {
const scope = &c.global_scope.base;
if (m.containsUndefinedIdentifier(scope, &.{})) |ident|
return m.fail(c, "unable to translate macro: undefined identifier `{s}`", .{ident});
const init_node = try parseCExpr(c, m, scope);
const last = m.next().?;
if (last != .Eof and last != .Nl)
return m.fail(c, "unable to translate C expr: unexpected token '{s}'", .{last.symbol()});
const var_decl = try Tag.pub_var_simple.create(c.arena, .{ .name = m.name, .init = init_node });
try c.global_scope.macro_table.put(m.name, var_decl);
}
fn transMacroFnDefine(c: *Context, m: *MacroCtx) ParseError!void {
const macro_slicer = m.makeSlicer();
if (try c.pattern_list.match(c.gpa, macro_slicer)) |pattern| {
const decl = try Tag.pub_var_simple.create(c.arena, .{
.name = m.name,
.init = try Tag.helpers_macro.create(c.arena, pattern.impl),
});
try c.global_scope.macro_table.put(m.name, decl);
return;
}
var block_scope = try Scope.Block.init(c, &c.global_scope.base, false);
defer block_scope.deinit();
const scope = &block_scope.base;
try m.skip(c, .LParen);
var fn_params = std.ArrayList(ast.Payload.Param).init(c.gpa);
defer fn_params.deinit();
while (true) {
if (m.peek().? != .Identifier) break;
_ = m.next();
const mangled_name = try block_scope.makeMangledName(c, m.slice());
try fn_params.append(.{
.is_noalias = false,
.name = mangled_name,
.type = Tag.@"anytype".init(),
});
try block_scope.discardVariable(c, mangled_name);
if (m.peek().? != .Comma) break;
_ = m.next();
}
try m.skip(c, .RParen);
if (m.containsUndefinedIdentifier(scope, fn_params.items)) |ident|
return m.fail(c, "unable to translate macro: undefined identifier `{s}`", .{ident});
const expr = try parseCExpr(c, m, scope);
const last = m.next().?;
if (last != .Eof and last != .Nl)
return m.fail(c, "unable to translate C expr: unexpected token '{s}'", .{last.symbol()});
const typeof_arg = if (expr.castTag(.block)) |some| blk: {
const stmts = some.data.stmts;
const blk_last = stmts[stmts.len - 1];
const br = blk_last.castTag(.break_val).?;
break :blk br.data.val;
} else expr;
const return_type = if (typeof_arg.castTag(.helpers_cast) orelse typeof_arg.castTag(.std_mem_zeroinit)) |some|
some.data.lhs
else if (typeof_arg.castTag(.std_mem_zeroes)) |some|
some.data
else
try Tag.typeof.create(c.arena, typeof_arg);
const return_expr = try Tag.@"return".create(c.arena, expr);
try block_scope.statements.append(return_expr);
const fn_decl = try Tag.pub_inline_fn.create(c.arena, .{
.name = m.name,
.params = try c.arena.dupe(ast.Payload.Param, fn_params.items),
.return_type = return_type,
.body = try block_scope.complete(c),
});
try c.global_scope.macro_table.put(m.name, fn_decl);
}
const ParseError = Error || error{ParseError};
fn parseCExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
// TODO parseCAssignExpr here
const node = try parseCCondExpr(c, m, scope);
if (m.next().? != .Comma) {
m.i -= 1;
return node;
}
var block_scope = try Scope.Block.init(c, scope, true);
defer block_scope.deinit();
var last = node;
while (true) {
// suppress result
const ignore = try Tag.discard.create(c.arena, .{ .should_skip = false, .value = last });
try block_scope.statements.append(ignore);
last = try parseCCondExpr(c, m, &block_scope.base);
if (m.next().? != .Comma) {
m.i -= 1;
break;
}
}
const break_node = try Tag.break_val.create(c.arena, .{
.label = block_scope.label,
.val = last,
});
try block_scope.statements.append(break_node);
return try block_scope.complete(c);
}
fn parseCNumLit(c: *Context, m: *MacroCtx) ParseError!Node {
var lit_bytes = m.slice();
switch (m.list[m.i].id) {
.IntegerLiteral => |suffix| {
var radix: []const u8 = "decimal";
if (lit_bytes.len > 2 and lit_bytes[0] == '0') {
switch (lit_bytes[1]) {
'0'...'7' => {
// Octal
lit_bytes = try std.fmt.allocPrint(c.arena, "0o{s}", .{lit_bytes[1..]});
radix = "octal";
},
'X' => {
// Hexadecimal with capital X, valid in C but not in Zig
lit_bytes = try std.fmt.allocPrint(c.arena, "0x{s}", .{lit_bytes[2..]});
radix = "hexadecimal";
},
'x' => {
radix = "hexadecimal";
},
else => {},
}
}
const type_node = try Tag.type.create(c.arena, switch (suffix) {
.none => "c_int",
.u => "c_uint",
.l => "c_long",
.lu => "c_ulong",
.ll => "c_longlong",
.llu => "c_ulonglong",
.f => unreachable,
});
lit_bytes = lit_bytes[0 .. lit_bytes.len - switch (suffix) {
.none => @as(u8, 0),
.u, .l => 1,
.lu, .ll => 2,
.llu => 3,
.f => unreachable,
}];
const value = std.fmt.parseInt(i128, lit_bytes, 0) catch math.maxInt(i128);
// make the output less noisy by skipping promoteIntLiteral where
// it's guaranteed to not be required because of C standard type constraints
const guaranteed_to_fit = switch (suffix) {
.none => !meta.isError(math.cast(i16, value)),
.u => !meta.isError(math.cast(u16, value)),
.l => !meta.isError(math.cast(i32, value)),
.lu => !meta.isError(math.cast(u32, value)),
.ll => !meta.isError(math.cast(i64, value)),
.llu => !meta.isError(math.cast(u64, value)),
.f => unreachable,
};
const literal_node = try transCreateNodeNumber(c, lit_bytes, .int);
if (guaranteed_to_fit) {
return Tag.as.create(c.arena, .{ .lhs = type_node, .rhs = literal_node });
} else {
return Tag.helpers_promoteIntLiteral.create(c.arena, .{
.type = type_node,
.value = literal_node,
.radix = try Tag.enum_literal.create(c.arena, radix),
});
}
},
.FloatLiteral => |suffix| {
if (suffix != .none) lit_bytes = lit_bytes[0 .. lit_bytes.len - 1];
if (lit_bytes.len >= 2 and std.ascii.eqlIgnoreCase(lit_bytes[0..2], "0x")) {
if (mem.indexOfScalar(u8, lit_bytes, '.')) |dot_index| {
if (dot_index == 2) {
lit_bytes = try std.fmt.allocPrint(c.arena, "0x0{s}", .{lit_bytes[2..]});
} else if (dot_index + 1 == lit_bytes.len or !std.ascii.isXDigit(lit_bytes[dot_index + 1])) {
// If the literal lacks a digit after the `.`, we need to
// add one since `0x1.p10` would be invalid syntax in Zig.
lit_bytes = try std.fmt.allocPrint(c.arena, "0x{s}0{s}", .{
lit_bytes[2 .. dot_index + 1],
lit_bytes[dot_index + 1 ..],
});
}
}
if (lit_bytes[1] == 'X') {
// Hexadecimal with capital X, valid in C but not in Zig
lit_bytes = try std.fmt.allocPrint(c.arena, "0x{s}", .{lit_bytes[2..]});
}
} else if (mem.indexOfScalar(u8, lit_bytes, '.')) |dot_index| {
if (dot_index == 0) {
lit_bytes = try std.fmt.allocPrint(c.arena, "0{s}", .{lit_bytes});
} else if (dot_index + 1 == lit_bytes.len or !std.ascii.isDigit(lit_bytes[dot_index + 1])) {
// If the literal lacks a digit after the `.`, we need to
// add one since `1.` or `1.e10` would be invalid syntax in Zig.
lit_bytes = try std.fmt.allocPrint(c.arena, "{s}0{s}", .{
lit_bytes[0 .. dot_index + 1],
lit_bytes[dot_index + 1 ..],
});
}
}
if (suffix == .none)
return transCreateNodeNumber(c, lit_bytes, .float);
const type_node = try Tag.type.create(c.arena, switch (suffix) {
.f => "f32",
.l => "c_longdouble",
else => unreachable,
});
const rhs = try transCreateNodeNumber(c, lit_bytes, .float);
return Tag.as.create(c.arena, .{ .lhs = type_node, .rhs = rhs });
},
else => unreachable,
}
}
fn zigifyEscapeSequences(ctx: *Context, m: *MacroCtx) ![]const u8 {
var source = m.slice();
for (source) |c, i| {
if (c == '\"' or c == '\'') {
source = source[i..];
break;
}
}
for (source) |c| {
if (c == '\\') {
break;
}
} else return source;
var bytes = try ctx.arena.alloc(u8, source.len * 2);
var state: enum {
Start,
Escape,
Hex,
Octal,
} = .Start;
var i: usize = 0;
var count: u8 = 0;
var num: u8 = 0;
for (source) |c| {
switch (state) {
.Escape => {
switch (c) {
'n', 'r', 't', '\\', '\'', '\"' => {
bytes[i] = c;
},
'0'...'7' => {
count += 1;
num += c - '0';
state = .Octal;
bytes[i] = 'x';
},
'x' => {
state = .Hex;
bytes[i] = 'x';
},
'a' => {
bytes[i] = 'x';
i += 1;
bytes[i] = '0';
i += 1;
bytes[i] = '7';
},
'b' => {
bytes[i] = 'x';
i += 1;
bytes[i] = '0';
i += 1;
bytes[i] = '8';
},
'f' => {
bytes[i] = 'x';
i += 1;
bytes[i] = '0';
i += 1;
bytes[i] = 'C';
},
'v' => {
bytes[i] = 'x';
i += 1;
bytes[i] = '0';
i += 1;
bytes[i] = 'B';
},
'?' => {
i -= 1;
bytes[i] = '?';
},
'u', 'U' => {
try m.fail(ctx, "macro tokenizing failed: TODO unicode escape sequences", .{});
return error.ParseError;
},
else => {
try m.fail(ctx, "macro tokenizing failed: unknown escape sequence", .{});
return error.ParseError;
},
}
i += 1;
if (state == .Escape)
state = .Start;
},
.Start => {
if (c == '\\') {
state = .Escape;
}
bytes[i] = c;
i += 1;
},
.Hex => {
switch (c) {
'0'...'9' => {
num = std.math.mul(u8, num, 16) catch {
try m.fail(ctx, "macro tokenizing failed: hex literal overflowed", .{});
return error.ParseError;
};
num += c - '0';
},
'a'...'f' => {
num = std.math.mul(u8, num, 16) catch {
try m.fail(ctx, "macro tokenizing failed: hex literal overflowed", .{});
return error.ParseError;
};
num += c - 'a' + 10;
},
'A'...'F' => {
num = std.math.mul(u8, num, 16) catch {
try m.fail(ctx, "macro tokenizing failed: hex literal overflowed", .{});
return error.ParseError;
};
num += c - 'A' + 10;
},
else => {
i += std.fmt.formatIntBuf(bytes[i..], num, 16, .lower, std.fmt.FormatOptions{ .fill = '0', .width = 2 });
num = 0;
if (c == '\\')
state = .Escape
else
state = .Start;
bytes[i] = c;
i += 1;
},
}
},
.Octal => {
const accept_digit = switch (c) {
// The maximum length of a octal literal is 3 digits
'0'...'7' => count < 3,
else => false,
};
if (accept_digit) {
count += 1;
num = std.math.mul(u8, num, 8) catch {
try m.fail(ctx, "macro tokenizing failed: octal literal overflowed", .{});
return error.ParseError;
};
num += c - '0';
} else {
i += std.fmt.formatIntBuf(bytes[i..], num, 16, .lower, std.fmt.FormatOptions{ .fill = '0', .width = 2 });
num = 0;
count = 0;
if (c == '\\')
state = .Escape
else
state = .Start;
bytes[i] = c;
i += 1;
}
},
}
}
if (state == .Hex or state == .Octal)
i += std.fmt.formatIntBuf(bytes[i..], num, 16, .lower, std.fmt.FormatOptions{ .fill = '0', .width = 2 });
return bytes[0..i];
}
fn parseCPrimaryExprInner(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
const tok = m.next().?;
const slice = m.slice();
switch (tok) {
.CharLiteral => {
if (slice[0] != '\'' or slice[1] == '\\' or slice.len == 3) {
return Tag.char_literal.create(c.arena, try zigifyEscapeSequences(c, m));
} else {
const str = try std.fmt.allocPrint(c.arena, "0x{s}", .{std.fmt.fmtSliceHexLower(slice[1 .. slice.len - 1])});
return Tag.integer_literal.create(c.arena, str);
}
},
.StringLiteral => {
return Tag.string_literal.create(c.arena, try zigifyEscapeSequences(c, m));
},
.IntegerLiteral, .FloatLiteral => {
return parseCNumLit(c, m);
},
.Identifier => {
const mangled_name = scope.getAlias(slice);
if (builtin_typedef_map.get(mangled_name)) |ty| return Tag.type.create(c.arena, ty);
const identifier = try Tag.identifier.create(c.arena, mangled_name);
scope.skipVariableDiscard(identifier.castTag(.identifier).?.data);
return identifier;
},
.LParen => {
const inner_node = try parseCExpr(c, m, scope);
try m.skip(c, .RParen);
return inner_node;
},
else => {
// for handling type macros (EVIL)
// TODO maybe detect and treat type macros as typedefs in parseCSpecifierQualifierList?
m.i -= 1;
if (try parseCTypeName(c, m, scope, true)) |type_name| {
return type_name;
}
try m.fail(c, "unable to translate C expr: unexpected token '{s}'", .{tok.symbol()});
return error.ParseError;
},
}
}
fn parseCPrimaryExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
var node = try parseCPrimaryExprInner(c, m, scope);
// In C the preprocessor would handle concatting strings while expanding macros.
// This should do approximately the same by concatting any strings and identifiers
// after a primary expression.
while (true) {
switch (m.peek().?) {
.StringLiteral, .Identifier => {},
else => break,
}
node = try Tag.array_cat.create(c.arena, .{ .lhs = node, .rhs = try parseCPrimaryExprInner(c, m, scope) });
}
return node;
}
fn macroBoolToInt(c: *Context, node: Node) !Node {
if (!isBoolRes(node)) {
return node;
}
return Tag.bool_to_int.create(c.arena, node);
}
fn macroIntToBool(c: *Context, node: Node) !Node {
if (isBoolRes(node)) {
return node;
}
return Tag.not_equal.create(c.arena, .{ .lhs = node, .rhs = Tag.zero_literal.init() });
}
fn parseCCondExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
const node = try parseCOrExpr(c, m, scope);
if (m.peek().? != .QuestionMark) {
return node;
}
_ = m.next();
const then_body = try parseCOrExpr(c, m, scope);
try m.skip(c, .Colon);
const else_body = try parseCCondExpr(c, m, scope);
return Tag.@"if".create(c.arena, .{ .cond = node, .then = then_body, .@"else" = else_body });
}
fn parseCOrExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
var node = try parseCAndExpr(c, m, scope);
while (m.next().? == .PipePipe) {
const lhs = try macroIntToBool(c, node);
const rhs = try macroIntToBool(c, try parseCAndExpr(c, m, scope));
node = try Tag.@"or".create(c.arena, .{ .lhs = lhs, .rhs = rhs });
}
m.i -= 1;
return node;
}
fn parseCAndExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
var node = try parseCBitOrExpr(c, m, scope);
while (m.next().? == .AmpersandAmpersand) {
const lhs = try macroIntToBool(c, node);
const rhs = try macroIntToBool(c, try parseCBitOrExpr(c, m, scope));
node = try Tag.@"and".create(c.arena, .{ .lhs = lhs, .rhs = rhs });
}
m.i -= 1;
return node;
}
fn parseCBitOrExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
var node = try parseCBitXorExpr(c, m, scope);
while (m.next().? == .Pipe) {
const lhs = try macroBoolToInt(c, node);
const rhs = try macroBoolToInt(c, try parseCBitXorExpr(c, m, scope));
node = try Tag.bit_or.create(c.arena, .{ .lhs = lhs, .rhs = rhs });
}
m.i -= 1;
return node;
}
fn parseCBitXorExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
var node = try parseCBitAndExpr(c, m, scope);
while (m.next().? == .Caret) {
const lhs = try macroBoolToInt(c, node);
const rhs = try macroBoolToInt(c, try parseCBitAndExpr(c, m, scope));
node = try Tag.bit_xor.create(c.arena, .{ .lhs = lhs, .rhs = rhs });
}
m.i -= 1;
return node;
}
fn parseCBitAndExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
var node = try parseCEqExpr(c, m, scope);
while (m.next().? == .Ampersand) {
const lhs = try macroBoolToInt(c, node);
const rhs = try macroBoolToInt(c, try parseCEqExpr(c, m, scope));
node = try Tag.bit_and.create(c.arena, .{ .lhs = lhs, .rhs = rhs });
}
m.i -= 1;
return node;
}
fn parseCEqExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
var node = try parseCRelExpr(c, m, scope);
while (true) {
switch (m.peek().?) {
.BangEqual => {
_ = m.next();
const lhs = try macroBoolToInt(c, node);
const rhs = try macroBoolToInt(c, try parseCRelExpr(c, m, scope));
node = try Tag.not_equal.create(c.arena, .{ .lhs = lhs, .rhs = rhs });
},
.EqualEqual => {
_ = m.next();
const lhs = try macroBoolToInt(c, node);
const rhs = try macroBoolToInt(c, try parseCRelExpr(c, m, scope));
node = try Tag.equal.create(c.arena, .{ .lhs = lhs, .rhs = rhs });
},
else => return node,
}
}
}
fn parseCRelExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
var node = try parseCShiftExpr(c, m, scope);
while (true) {
switch (m.peek().?) {
.AngleBracketRight => {
_ = m.next();
const lhs = try macroBoolToInt(c, node);
const rhs = try macroBoolToInt(c, try parseCShiftExpr(c, m, scope));
node = try Tag.greater_than.create(c.arena, .{ .lhs = lhs, .rhs = rhs });
},
.AngleBracketRightEqual => {
_ = m.next();
const lhs = try macroBoolToInt(c, node);
const rhs = try macroBoolToInt(c, try parseCShiftExpr(c, m, scope));
node = try Tag.greater_than_equal.create(c.arena, .{ .lhs = lhs, .rhs = rhs });
},
.AngleBracketLeft => {
_ = m.next();
const lhs = try macroBoolToInt(c, node);
const rhs = try macroBoolToInt(c, try parseCShiftExpr(c, m, scope));
node = try Tag.less_than.create(c.arena, .{ .lhs = lhs, .rhs = rhs });
},
.AngleBracketLeftEqual => {
_ = m.next();
const lhs = try macroBoolToInt(c, node);
const rhs = try macroBoolToInt(c, try parseCShiftExpr(c, m, scope));
node = try Tag.less_than_equal.create(c.arena, .{ .lhs = lhs, .rhs = rhs });
},
else => return node,
}
}
}
fn parseCShiftExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
var node = try parseCAddSubExpr(c, m, scope);
while (true) {
switch (m.peek().?) {
.AngleBracketAngleBracketLeft => {
_ = m.next();
const lhs = try macroBoolToInt(c, node);
const rhs = try macroBoolToInt(c, try parseCAddSubExpr(c, m, scope));
node = try Tag.shl.create(c.arena, .{ .lhs = lhs, .rhs = rhs });
},
.AngleBracketAngleBracketRight => {
_ = m.next();
const lhs = try macroBoolToInt(c, node);
const rhs = try macroBoolToInt(c, try parseCAddSubExpr(c, m, scope));
node = try Tag.shr.create(c.arena, .{ .lhs = lhs, .rhs = rhs });
},
else => return node,
}
}
}
fn parseCAddSubExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
var node = try parseCMulExpr(c, m, scope);
while (true) {
switch (m.peek().?) {
.Plus => {
_ = m.next();
const lhs = try macroBoolToInt(c, node);
const rhs = try macroBoolToInt(c, try parseCMulExpr(c, m, scope));
node = try Tag.add.create(c.arena, .{ .lhs = lhs, .rhs = rhs });
},
.Minus => {
_ = m.next();
const lhs = try macroBoolToInt(c, node);
const rhs = try macroBoolToInt(c, try parseCMulExpr(c, m, scope));
node = try Tag.sub.create(c.arena, .{ .lhs = lhs, .rhs = rhs });
},
else => return node,
}
}
}
fn parseCMulExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
var node = try parseCCastExpr(c, m, scope);
while (true) {
switch (m.next().?) {
.Asterisk => {
const lhs = try macroBoolToInt(c, node);
const rhs = try macroBoolToInt(c, try parseCCastExpr(c, m, scope));
node = try Tag.mul.create(c.arena, .{ .lhs = lhs, .rhs = rhs });
},
.Slash => {
const lhs = try macroBoolToInt(c, node);
const rhs = try macroBoolToInt(c, try parseCCastExpr(c, m, scope));
node = try Tag.div.create(c.arena, .{ .lhs = lhs, .rhs = rhs });
},
.Percent => {
const lhs = try macroBoolToInt(c, node);
const rhs = try macroBoolToInt(c, try parseCCastExpr(c, m, scope));
node = try Tag.mod.create(c.arena, .{ .lhs = lhs, .rhs = rhs });
},
else => {
m.i -= 1;
return node;
},
}
}
}
fn parseCCastExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
switch (m.next().?) {
.LParen => {
if (try parseCTypeName(c, m, scope, true)) |type_name| {
try m.skip(c, .RParen);
if (m.peek().? == .LBrace) {
// initializer list
return parseCPostfixExpr(c, m, scope, type_name);
}
const node_to_cast = try parseCCastExpr(c, m, scope);
return Tag.helpers_cast.create(c.arena, .{ .lhs = type_name, .rhs = node_to_cast });
}
},
else => {},
}
m.i -= 1;
return parseCUnaryExpr(c, m, scope);
}
// allow_fail is set when unsure if we are parsing a type-name
fn parseCTypeName(c: *Context, m: *MacroCtx, scope: *Scope, allow_fail: bool) ParseError!?Node {
if (try parseCSpecifierQualifierList(c, m, scope, allow_fail)) |node| {
return try parseCAbstractDeclarator(c, m, scope, node);
} else {
return null;
}
}
fn parseCSpecifierQualifierList(c: *Context, m: *MacroCtx, scope: *Scope, allow_fail: bool) ParseError!?Node {
const tok = m.next().?;
switch (tok) {
.Identifier => {
const mangled_name = scope.getAlias(m.slice());
if (!allow_fail or c.typedefs.contains(mangled_name)) {
if (builtin_typedef_map.get(mangled_name)) |ty| return try Tag.type.create(c.arena, ty);
return try Tag.identifier.create(c.arena, mangled_name);
}
},
.Keyword_void => return try Tag.type.create(c.arena, "anyopaque"),
.Keyword_bool => return try Tag.type.create(c.arena, "bool"),
.Keyword_char,
.Keyword_int,
.Keyword_short,
.Keyword_long,
.Keyword_float,
.Keyword_double,
.Keyword_signed,
.Keyword_unsigned,
.Keyword_complex,
=> {
m.i -= 1;
return try parseCNumericType(c, m, scope);
},
.Keyword_enum, .Keyword_struct, .Keyword_union => {
// struct Foo will be declared as struct_Foo by transRecordDecl
const slice = m.slice();
try m.skip(c, .Identifier);
const name = try std.fmt.allocPrint(c.arena, "{s}_{s}", .{ slice, m.slice() });
return try Tag.identifier.create(c.arena, name);
},
else => {},
}
if (allow_fail) {
m.i -= 1;
return null;
} else {
try m.fail(c, "unable to translate C expr: unexpected token '{s}'", .{tok.symbol()});
return error.ParseError;
}
}
fn parseCNumericType(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
_ = scope;
const KwCounter = struct {
double: u8 = 0,
long: u8 = 0,
int: u8 = 0,
float: u8 = 0,
short: u8 = 0,
char: u8 = 0,
unsigned: u8 = 0,
signed: u8 = 0,
complex: u8 = 0,
fn eql(self: @This(), other: @This()) bool {
return meta.eql(self, other);
}
};
// Yes, these can be in *any* order
// This still doesn't cover cases where for example volatile is intermixed
var kw = KwCounter{};
// prevent overflow
var i: u8 = 0;
while (i < math.maxInt(u8)) : (i += 1) {
switch (m.next().?) {
.Keyword_double => kw.double += 1,
.Keyword_long => kw.long += 1,
.Keyword_int => kw.int += 1,
.Keyword_float => kw.float += 1,
.Keyword_short => kw.short += 1,
.Keyword_char => kw.char += 1,
.Keyword_unsigned => kw.unsigned += 1,
.Keyword_signed => kw.signed += 1,
.Keyword_complex => kw.complex += 1,
else => {
m.i -= 1;
break;
},
}
}
if (kw.eql(.{ .int = 1 }) or kw.eql(.{ .signed = 1 }) or kw.eql(.{ .signed = 1, .int = 1 }))
return Tag.type.create(c.arena, "c_int");
if (kw.eql(.{ .unsigned = 1 }) or kw.eql(.{ .unsigned = 1, .int = 1 }))
return Tag.type.create(c.arena, "c_uint");
if (kw.eql(.{ .long = 1 }) or kw.eql(.{ .signed = 1, .long = 1 }) or kw.eql(.{ .long = 1, .int = 1 }) or kw.eql(.{ .signed = 1, .long = 1, .int = 1 }))
return Tag.type.create(c.arena, "c_long");
if (kw.eql(.{ .unsigned = 1, .long = 1 }) or kw.eql(.{ .unsigned = 1, .long = 1, .int = 1 }))
return Tag.type.create(c.arena, "c_ulong");
if (kw.eql(.{ .long = 2 }) or kw.eql(.{ .signed = 1, .long = 2 }) or kw.eql(.{ .long = 2, .int = 1 }) or kw.eql(.{ .signed = 1, .long = 2, .int = 1 }))
return Tag.type.create(c.arena, "c_longlong");
if (kw.eql(.{ .unsigned = 1, .long = 2 }) or kw.eql(.{ .unsigned = 1, .long = 2, .int = 1 }))
return Tag.type.create(c.arena, "c_ulonglong");
if (kw.eql(.{ .signed = 1, .char = 1 }))
return Tag.type.create(c.arena, "i8");
if (kw.eql(.{ .char = 1 }) or kw.eql(.{ .unsigned = 1, .char = 1 }))
return Tag.type.create(c.arena, "u8");
if (kw.eql(.{ .short = 1 }) or kw.eql(.{ .signed = 1, .short = 1 }) or kw.eql(.{ .short = 1, .int = 1 }) or kw.eql(.{ .signed = 1, .short = 1, .int = 1 }))
return Tag.type.create(c.arena, "c_short");
if (kw.eql(.{ .unsigned = 1, .short = 1 }) or kw.eql(.{ .unsigned = 1, .short = 1, .int = 1 }))
return Tag.type.create(c.arena, "c_ushort");
if (kw.eql(.{ .float = 1 }))
return Tag.type.create(c.arena, "f32");
if (kw.eql(.{ .double = 1 }))
return Tag.type.create(c.arena, "f64");
if (kw.eql(.{ .long = 1, .double = 1 })) {
try m.fail(c, "unable to translate: TODO long double", .{});
return error.ParseError;
}
if (kw.eql(.{ .float = 1, .complex = 1 })) {
try m.fail(c, "unable to translate: TODO _Complex", .{});
return error.ParseError;
}
if (kw.eql(.{ .double = 1, .complex = 1 })) {
try m.fail(c, "unable to translate: TODO _Complex", .{});
return error.ParseError;
}
if (kw.eql(.{ .long = 1, .double = 1, .complex = 1 })) {
try m.fail(c, "unable to translate: TODO _Complex", .{});
return error.ParseError;
}
try m.fail(c, "unable to translate: invalid numeric type", .{});
return error.ParseError;
}
fn parseCAbstractDeclarator(c: *Context, m: *MacroCtx, scope: *Scope, node: Node) ParseError!Node {
_ = scope;
switch (m.next().?) {
.Asterisk => {
// last token of `node`
const prev_id = m.list[m.i - 1].id;
if (prev_id == .Keyword_void) {
const ptr = try Tag.single_pointer.create(c.arena, .{
.is_const = false,
.is_volatile = false,
.elem_type = node,
});
return Tag.optional_type.create(c.arena, ptr);
} else {
return Tag.c_pointer.create(c.arena, .{
.is_const = false,
.is_volatile = false,
.elem_type = node,
});
}
},
else => {
m.i -= 1;
return node;
},
}
}
fn parseCPostfixExpr(c: *Context, m: *MacroCtx, scope: *Scope, type_name: ?Node) ParseError!Node {
var node = type_name orelse try parseCPrimaryExpr(c, m, scope);
while (true) {
switch (m.next().?) {
.Period => {
try m.skip(c, .Identifier);
node = try Tag.field_access.create(c.arena, .{ .lhs = node, .field_name = m.slice() });
},
.Arrow => {
try m.skip(c, .Identifier);
const deref = try Tag.deref.create(c.arena, node);
node = try Tag.field_access.create(c.arena, .{ .lhs = deref, .field_name = m.slice() });
},
.LBracket => {
const index = try macroBoolToInt(c, try parseCExpr(c, m, scope));
node = try Tag.array_access.create(c.arena, .{ .lhs = node, .rhs = index });
try m.skip(c, .RBracket);
},
.LParen => {
if (m.peek().? == .RParen) {
m.i += 1;
node = try Tag.call.create(c.arena, .{ .lhs = node, .args = &[0]Node{} });
} else {
var args = std.ArrayList(Node).init(c.gpa);
defer args.deinit();
while (true) {
const arg = try parseCCondExpr(c, m, scope);
try args.append(arg);
const next_id = m.next().?;
switch (next_id) {
.Comma => {},
.RParen => break,
else => {
try m.fail(c, "unable to translate C expr: expected ',' or ')' instead got '{s}'", .{next_id.symbol()});
return error.ParseError;
},
}
}
node = try Tag.call.create(c.arena, .{ .lhs = node, .args = try c.arena.dupe(Node, args.items) });
}
},
.LBrace => {
// Check for designated field initializers
if (m.peek().? == .Period) {
var init_vals = std.ArrayList(ast.Payload.ContainerInitDot.Initializer).init(c.gpa);
defer init_vals.deinit();
while (true) {
try m.skip(c, .Period);
try m.skip(c, .Identifier);
const name = m.slice();
try m.skip(c, .Equal);
const val = try parseCCondExpr(c, m, scope);
try init_vals.append(.{ .name = name, .value = val });
const next_id = m.next().?;
switch (next_id) {
.Comma => {},
.RBrace => break,
else => {
try m.fail(c, "unable to translate C expr: expected ',' or '}}' instead got '{s}'", .{next_id.symbol()});
return error.ParseError;
},
}
}
const tuple_node = try Tag.container_init_dot.create(c.arena, try c.arena.dupe(ast.Payload.ContainerInitDot.Initializer, init_vals.items));
node = try Tag.std_mem_zeroinit.create(c.arena, .{ .lhs = node, .rhs = tuple_node });
continue;
}
var init_vals = std.ArrayList(Node).init(c.gpa);
defer init_vals.deinit();
while (true) {
const val = try parseCCondExpr(c, m, scope);
try init_vals.append(val);
const next_id = m.next().?;
switch (next_id) {
.Comma => {},
.RBrace => break,
else => {
try m.fail(c, "unable to translate C expr: expected ',' or '}}' instead got '{s}'", .{next_id.symbol()});
return error.ParseError;
},
}
}
const tuple_node = try Tag.tuple.create(c.arena, try c.arena.dupe(Node, init_vals.items));
node = try Tag.std_mem_zeroinit.create(c.arena, .{ .lhs = node, .rhs = tuple_node });
},
.PlusPlus, .MinusMinus => {
try m.fail(c, "TODO postfix inc/dec expr", .{});
return error.ParseError;
},
else => {
m.i -= 1;
return node;
},
}
}
}
fn parseCUnaryExpr(c: *Context, m: *MacroCtx, scope: *Scope) ParseError!Node {
switch (m.next().?) {
.Bang => {
const operand = try macroIntToBool(c, try parseCCastExpr(c, m, scope));
return Tag.not.create(c.arena, operand);
},
.Minus => {
const operand = try macroBoolToInt(c, try parseCCastExpr(c, m, scope));
return Tag.negate.create(c.arena, operand);
},
.Plus => return try parseCCastExpr(c, m, scope),
.Tilde => {
const operand = try macroBoolToInt(c, try parseCCastExpr(c, m, scope));
return Tag.bit_not.create(c.arena, operand);
},
.Asterisk => {
const operand = try parseCCastExpr(c, m, scope);
return Tag.deref.create(c.arena, operand);
},
.Ampersand => {
const operand = try parseCCastExpr(c, m, scope);
return Tag.address_of.create(c.arena, operand);
},
.Keyword_sizeof => {
const operand = if (m.peek().? == .LParen) blk: {
_ = m.next();
const inner = (try parseCTypeName(c, m, scope, false)).?;
try m.skip(c, .RParen);
break :blk inner;
} else try parseCUnaryExpr(c, m, scope);
return Tag.helpers_sizeof.create(c.arena, operand);
},
.Keyword_alignof => {
// TODO this won't work if using <stdalign.h>'s
// #define alignof _Alignof
try m.skip(c, .LParen);
const operand = (try parseCTypeName(c, m, scope, false)).?;
try m.skip(c, .RParen);
return Tag.alignof.create(c.arena, operand);
},
.PlusPlus, .MinusMinus => {
try m.fail(c, "TODO unary inc/dec expr", .{});
return error.ParseError;
},
else => {
m.i -= 1;
return try parseCPostfixExpr(c, m, scope, null);
},
}
}
fn getContainer(c: *Context, node: Node) ?Node {
switch (node.tag()) {
.@"union",
.@"struct",
.address_of,
.bit_not,
.not,
.optional_type,
.negate,
.negate_wrap,
.array_type,
.c_pointer,
.single_pointer,
=> return node,
.identifier => {
const ident = node.castTag(.identifier).?;
if (c.global_scope.sym_table.get(ident.data)) |value| {
if (value.castTag(.var_decl)) |var_decl|
return getContainer(c, var_decl.data.init.?);
if (value.castTag(.var_simple) orelse value.castTag(.pub_var_simple)) |var_decl|
return getContainer(c, var_decl.data.init);
}
},
.field_access => {
const field_access = node.castTag(.field_access).?;
if (getContainerTypeOf(c, field_access.data.lhs)) |ty_node| {
if (ty_node.castTag(.@"struct") orelse ty_node.castTag(.@"union")) |container| {
for (container.data.fields) |field| {
if (mem.eql(u8, field.name, field_access.data.field_name)) {
return getContainer(c, field.type);
}
}
}
}
},
else => {},
}
return null;
}
fn getContainerTypeOf(c: *Context, ref: Node) ?Node {
if (ref.castTag(.identifier)) |ident| {
if (c.global_scope.sym_table.get(ident.data)) |value| {
if (value.castTag(.var_decl)) |var_decl| {
return getContainer(c, var_decl.data.type);
}
}
} else if (ref.castTag(.field_access)) |field_access| {
if (getContainerTypeOf(c, field_access.data.lhs)) |ty_node| {
if (ty_node.castTag(.@"struct") orelse ty_node.castTag(.@"union")) |container| {
for (container.data.fields) |field| {
if (mem.eql(u8, field.name, field_access.data.field_name)) {
return getContainer(c, field.type);
}
}
} else return ty_node;
}
}
return null;
}
fn getFnProto(c: *Context, ref: Node) ?*ast.Payload.Func {
const init = if (ref.castTag(.var_decl)) |v|
v.data.init orelse return null
else if (ref.castTag(.var_simple) orelse ref.castTag(.pub_var_simple)) |v|
v.data.init
else
return null;
if (getContainerTypeOf(c, init)) |ty_node| {
if (ty_node.castTag(.optional_type)) |prefix| {
if (c.zig_is_stage1) {
if (prefix.data.castTag(.func)) |fn_proto| {
return fn_proto;
}
} else {
if (prefix.data.castTag(.single_pointer)) |sp| {
if (sp.data.elem_type.castTag(.func)) |fn_proto| {
return fn_proto;
}
}
}
}
}
return null;
}
fn addMacros(c: *Context) !void {
var it = c.global_scope.macro_table.iterator();
while (it.next()) |entry| {
if (getFnProto(c, entry.value_ptr.*)) |proto_node| {
// If a macro aliases a global variable which is a function pointer, we conclude that
// the macro is intended to represent a function that assumes the function pointer
// variable is non-null and calls it.
try addTopLevelDecl(c, entry.key_ptr.*, try transCreateNodeMacroFn(c, entry.key_ptr.*, entry.value_ptr.*, proto_node));
} else {
try addTopLevelDecl(c, entry.key_ptr.*, entry.value_ptr.*);
}
}
}