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 CallingConvention = std.builtin.CallingConvention; const ast = @import("translate_c/ast.zig"); const Node = ast.Node; const Tag = Node.Tag; const common = @import("translate_c/common.zig"); const Error = common.Error; const MacroProcessingError = common.MacroProcessingError; const TypeError = common.TypeError; const TransError = common.TransError; const SymbolTable = common.SymbolTable; const AliasList = common.AliasList; const ResultUsed = common.ResultUsed; const Scope = common.ScopeExtra(Context, clang.QualType); // 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); 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) = .{}, /// This is similar to `global_names`, but contains names which we would /// *like* to use, but do not strictly *have* to if they are unavailable. /// These are relevant to types, which ideally we would name like /// 'struct_foo' with an alias 'foo', but if either of those names is taken, /// may be mangled. /// This is distinct from `global_names` so we can detect at a type /// declaration whether or not the name is available. weak_global_names: std.StringArrayHashMapUnmanaged(void) = .{}, pattern_list: PatternList, 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: *std.zig.ErrorBundle, resources_path: [*:0]const u8, ) !std.zig.Ast { var clang_errors: []clang.ErrorMsg = &.{}; const ast_unit = clang.LoadFromCommandLine( args_begin, args_end, &clang_errors.ptr, &clang_errors.len, resources_path, ) orelse { defer clang.ErrorMsg.delete(clang_errors.ptr, clang_errors.len); var bundle: std.zig.ErrorBundle.Wip = undefined; try bundle.init(gpa); defer bundle.deinit(); for (clang_errors) |c_error| { const line = line: { const source = c_error.source orelse break :line 0; var start = c_error.offset; while (start > 0) : (start -= 1) { if (source[start - 1] == '\n') break; } var end = c_error.offset; while (true) : (end += 1) { if (source[end] == 0) break; if (source[end] == '\n') break; } break :line try bundle.addString(source[start..end]); }; try bundle.addRootErrorMessage(.{ .msg = try bundle.addString(c_error.msg_ptr[0..c_error.msg_len]), .src_loc = if (c_error.filename_ptr) |filename_ptr| try bundle.addSourceLocation(.{ .src_path = try bundle.addString(filename_ptr[0..c_error.filename_len]), .span_start = c_error.offset, .span_main = c_error.offset, .span_end = c_error.offset + 1, .line = c_error.line, .column = c_error.column, .source_line = line, }) else .none, }); } errors.* = try bundle.toOwnedBundle(""); 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), }; 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| { 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| { 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); } /// Determines whether macro is of the form: `#define FOO FOO` (Possibly with trailing tokens) /// Macros of this form will not be translated. fn isSelfDefinedMacro(unit: *const clang.ASTUnit, c: *const Context, macro: *const clang.MacroDefinitionRecord) bool { const source = getMacroText(unit, c, macro); var tokenizer = std.c.Tokenizer{ .buffer = source, }; const name_tok = tokenizer.next(); const name = source[name_tok.start..name_tok.end]; const first_tok = tokenizer.next(); // We do not just check for `.Identifier` below because keyword tokens are preferentially matched first by // the tokenizer. // In other words we would miss `#define inline inline` (`inline` is a valid c89 identifier) if (first_tok.id == .Eof) return false; return mem.eql(u8, name, source[first_tok.start..first_tok.end]); } 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 = @as(*clang.MacroDefinitionRecord, @ptrCast(entity)); const raw_name = macro.getName_getNameStart(); const name = try c.str(raw_name); if (!isSelfDefinedMacro(ast_unit, c, macro)) { try c.global_names.put(c.gpa, name, {}); } }, else => {}, } } } fn declVisitorNamesOnlyC(context: ?*anyopaque, decl: *const clang.Decl) callconv(.C) bool { const c: *Context = @ptrCast(@alignCast(context)); declVisitorNamesOnly(c, decl) catch return false; return true; } fn declVisitorC(context: ?*anyopaque, decl: *const clang.Decl) callconv(.C) bool { const c: *Context = @ptrCast(@alignCast(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()); switch (decl.getKind()) { .Record, .Enum => { // These types are prefixed with the container kind. const container_prefix = if (decl.getKind() == .Record) prefix: { const record_decl: *const clang.RecordDecl = @ptrCast(decl); if (record_decl.isUnion()) { break :prefix "union"; } else { break :prefix "struct"; } } else "enum"; const prefixed_name = try std.fmt.allocPrint(c.arena, "{s}_{s}", .{ container_prefix, decl_name }); // `decl_name` and `prefixed_name` are the preferred names for this type. // However, we can name it anything else if necessary, so these are "weak names". try c.weak_global_names.ensureUnusedCapacity(c.gpa, 2); c.weak_global_names.putAssumeCapacity(decl_name, {}); c.weak_global_names.putAssumeCapacity(prefixed_name, {}); }, else => { 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 = @as(*const clang.TypedefNameDecl, @ptrCast(decl)); var child_ty = typedef_decl.getUnderlyingType().getTypePtr(); const addr: usize = while (true) switch (child_ty.getTypeClass()) { .Enum => { const enum_ty = @as(*const clang.EnumType, @ptrCast(child_ty)); const enum_decl = enum_ty.getDecl(); // check if this decl is unnamed if (@as(*const clang.NamedDecl, @ptrCast(enum_decl)).getName_bytes_begin()[0] != 0) return; break @intFromPtr(enum_decl.getCanonicalDecl()); }, .Record => { const record_ty = @as(*const clang.RecordType, @ptrCast(child_ty)); const record_decl = record_ty.getDecl(); // check if this decl is unnamed if (@as(*const clang.NamedDecl, @ptrCast(record_decl)).getName_bytes_begin()[0] != 0) return; break @intFromPtr(record_decl.getCanonicalDecl()); }, .Elaborated => { const elaborated_ty = @as(*const clang.ElaboratedType, @ptrCast(child_ty)); child_ty = elaborated_ty.getNamedType().getTypePtr(); }, .Decayed => { const decayed_ty = @as(*const clang.DecayedType, @ptrCast(child_ty)); child_ty = decayed_ty.getDecayedType().getTypePtr(); }, .Attributed => { const attributed_ty = @as(*const clang.AttributedType, @ptrCast(child_ty)); child_ty = attributed_ty.getEquivalentType().getTypePtr(); }, .MacroQualified => { const macroqualified_ty = @as(*const clang.MacroQualifiedType, @ptrCast(child_ty)); child_ty = macroqualified_ty.getModifiedType().getTypePtr(); }, else => return, }; 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, @intFromPtr(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, @as(*const clang.FunctionDecl, @ptrCast(decl))); }, .Typedef => { try transTypeDef(c, &c.global_scope.base, @as(*const clang.TypedefNameDecl, @ptrCast(decl))); }, .Enum => { try transEnumDecl(c, &c.global_scope.base, @as(*const clang.EnumDecl, @ptrCast(decl))); }, .Record => { try transRecordDecl(c, &c.global_scope.base, @as(*const clang.RecordDecl, @ptrCast(decl))); }, .Var => { return visitVarDecl(c, @as(*const clang.VarDecl, @ptrCast(decl)), null); }, .Empty => { // Do nothing }, .FileScopeAsm => { try transFileScopeAsm(c, &c.global_scope.base, @as(*const clang.FileScopeAsmDecl, @ptrCast(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(@as(*const clang.NamedDecl, @ptrCast(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 = @as(*const clang.AttributedType, @ptrCast(fn_type)); fn_qt = attr_type.getEquivalentType(); }, .Paren => { const paren_type = @as(*const clang.ParenType, @ptrCast(fn_type)); fn_qt = paren_type.getInnerType(); }, else => break fn_type, } }; const fn_ty = @as(*const clang.FunctionType, @ptrCast(fn_type)); const return_qt = fn_ty.getReturnType(); const proto_node = switch (fn_type.getTypeClass()) { .FunctionProto => blk: { const fn_proto_type = @as(*const clang.FunctionProtoType, @ptrCast(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 = @as(*const clang.FunctionType, @ptrCast(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 = @as(*const clang.CompoundStmt, @ptrCast(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.static_inner_name; 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(@as(*const clang.NamedDecl, @ptrCast(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 has_init = var_decl.hasInit(); const decl_init = var_decl.getInit(); var is_const = qual_type.isConstQualified(); // 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; if (!is_extern and qualTypeWasDemotedToOpaque(c, qual_type)) { return failDecl(c, var_decl_loc, var_name, "non-extern variable has opaque type", .{}); } 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, @as(*const clang.StringLiteral, @ptrCast(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.int_from_bool.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); } else if (qual_type.getTypeClass() == .IncompleteArray) { // Oh no, an extern array of unknown size! These are really fun because there's no // direct equivalent in Zig. To translate correctly, we'll have to create a C-pointer // to the data initialized via @extern. const name_str = try std.fmt.allocPrint(c.arena, "\"{s}\"", .{var_name}); init_node = try Tag.builtin_extern.create(c.arena, .{ .type = type_node, .name = try Tag.string_literal.create(c.arena, name_str), }); // Since this is really a pointer to the underlying data, we tweak a few properties. is_extern = false; is_const = true; } 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 = ClangAlignment.forVar(c, var_decl).zigAlignment(), .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(@intFromPtr(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(@as(*const clang.NamedDecl, @ptrCast(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, @intFromPtr(typedef_decl.getCanonicalDecl()), builtin); } if (!toplevel) name = try bs.makeMangledName(c, name); try c.decl_table.putNoClobber(c.gpa, @intFromPtr(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 })[@intFromBool(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 field_qt_canon = qualTypeCanon(field_qt); 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 = @as(*const clang.ArrayType, @ptrCast(field_qt_canon)); 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.as.create(c.arena, .{ .lhs = intermediate_type_ident, .rhs = try Tag.ptr_cast.create(c.arena, 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 ptr_cast = try Tag.as.create(c.arena, .{ .lhs = return_type_ident, .rhs = try Tag.ptr_cast.create( c.arena, try Tag.align_cast.create( c.arena, field_ptr, ), ), }); 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); } /// Return true if `field_decl` is the flexible array field for its parent record fn isFlexibleArrayFieldDecl(c: *Context, field_decl: *const clang.FieldDecl) bool { const record_decl = field_decl.getParent() orelse return false; const record_flexible_field = flexibleArrayField(c, record_decl) orelse return false; return field_decl == record_flexible_field; } /// Find the flexible array field for a record if any. A flexible array field is an /// incomplete or zero-length array that occurs as the last field of a record. /// 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 flexibleArrayField(c: *Context, record_def: *const clang.RecordDecl) ?*const clang.FieldDecl { var it = record_def.field_begin(); const end_it = record_def.field_end(); var flexible_field: ?*const clang.FieldDecl = null; while (it.neq(end_it)) : (it = it.next()) { const field_decl = it.deref(); const ty = qualTypeCanon(field_decl.getType()); const incomplete_or_zero_size = ty.isIncompleteOrZeroLengthArrayType(c.clang_context); if (incomplete_or_zero_size) { flexible_field = field_decl; } else { flexible_field = null; } } return flexible_field; } fn mangleWeakGlobalName(c: *Context, want_name: []const u8) ![]const u8 { var cur_name = want_name; if (!c.weak_global_names.contains(want_name)) { // This type wasn't noticed by the name detection pass, so nothing has been treating this as // a weak global name. We must mangle it to avoid conflicts with locals. cur_name = try std.fmt.allocPrint(c.arena, "{s}_{d}", .{ want_name, c.getMangle() }); } while (c.global_names.contains(cur_name)) { cur_name = try std.fmt.allocPrint(c.arena, "{s}_{d}", .{ want_name, c.getMangle() }); } return cur_name; } fn transRecordDecl(c: *Context, scope: *Scope, record_decl: *const clang.RecordDecl) Error!void { if (c.decl_table.get(@intFromPtr(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(@as(*const clang.NamedDecl, @ptrCast(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, @intFromPtr(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(@intFromPtr(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 and !is_unnamed) { name = try mangleWeakGlobalName(c, name); } } if (!toplevel) name = try bs.makeMangledName(c, name); try c.decl_table.putNoClobber(c.gpa, @intFromPtr(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, @intFromPtr(record_decl.getCanonicalDecl()), {}); break :blk Tag.opaque_literal.init(); }; 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 flexible_field = flexibleArrayField(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, @intFromPtr(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(@as(*const clang.NamedDecl, @ptrCast(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 (flexible_field == 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, @intFromPtr(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, @intFromPtr(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 (flexible_field != null and field_decl.getFieldIndex() == 0) @as(c_uint, @intCast(record_alignment)) else ClangAlignment.forField(c, field_decl, record_def).zigAlignment(); // C99 introduced designated initializers for structs. Omitted fields are implicitly // initialized to zero. Some C APIs are designed with this in mind. Defaulting to zero // values for translated struct fields permits Zig code to comfortably use such an API. const default_value = if (record_decl.isStruct()) try Tag.std_mem_zeroes.create(c.arena, field_type) else null; if (is_anon) { try c.decl_table.putNoClobber(c.gpa, @intFromPtr(field_decl.getCanonicalDecl()), field_name); } try fields.append(.{ .name = field_name, .type = field_type, .alignment = alignment, .default_value = default_value, }); } const record_payload = try c.arena.create(ast.Payload.Record); record_payload.* = .{ .base = .{ .tag = ([2]Tag{ .@"struct", .@"union" })[@intFromBool(is_union)] }, .data = .{ .layout = .@"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 })[@intFromBool(is_pub)] }, .data = .{ .name = name, .init = init_node, }, }; const node = Node.initPayload(&payload.base); if (toplevel) { try addTopLevelDecl(c, name, node); // Only add the alias if the name is available *and* it was caught by // name detection. Don't bother performing a weak mangle, since a // mangled name is of no real use here. if (!is_unnamed and !c.global_names.contains(bare_name) and c.weak_global_names.contains(bare_name)) 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(@intFromPtr(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(@as(*const clang.NamedDecl, @ptrCast(enum_decl)).getName_bytes_begin()); var name = bare_name; if (c.unnamed_typedefs.get(@intFromPtr(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 and !is_unnamed) { name = try mangleWeakGlobalName(c, name); } } if (!toplevel) name = try bs.makeMangledName(c, name); try c.decl_table.putNoClobber(c.gpa, @intFromPtr(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(@as(*const clang.NamedDecl, @ptrCast(enum_const)).getName_bytes_begin()); if (!toplevel) { enum_val_name = try bs.makeMangledName(c, enum_val_name); } const enum_const_qt = @as(*const clang.ValueDecl, @ptrCast(enum_const)).getType(); const enum_const_loc = @as(*const clang.Decl, @ptrCast(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, @intFromPtr(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 })[@intFromBool(is_pub)] }, .data = .{ .init = enum_type_node, .name = name, }, }; const node = Node.initPayload(&payload.base); if (toplevel) { try addTopLevelDecl(c, name, node); // Only add the alias if the name is available *and* it was caught by // name detection. Don't bother performing a weak mangle, since a // mangled name is of no real use here. if (!is_unnamed and !c.global_names.contains(bare_name) and c.weak_global_names.contains(bare_name)) 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 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, @as(*const clang.BinaryOperator, @ptrCast(stmt)), result_used), .CompoundStmtClass => return transCompoundStmt(c, scope, @as(*const clang.CompoundStmt, @ptrCast(stmt))), .CStyleCastExprClass => return transCStyleCastExprClass(c, scope, @as(*const clang.CStyleCastExpr, @ptrCast(stmt)), result_used), .DeclStmtClass => return transDeclStmt(c, scope, @as(*const clang.DeclStmt, @ptrCast(stmt))), .DeclRefExprClass => return transDeclRefExpr(c, scope, @as(*const clang.DeclRefExpr, @ptrCast(stmt))), .ImplicitCastExprClass => return transImplicitCastExpr(c, scope, @as(*const clang.ImplicitCastExpr, @ptrCast(stmt)), result_used), .IntegerLiteralClass => return transIntegerLiteral(c, scope, @as(*const clang.IntegerLiteral, @ptrCast(stmt)), result_used, .with_as), .ReturnStmtClass => return transReturnStmt(c, scope, @as(*const clang.ReturnStmt, @ptrCast(stmt))), .StringLiteralClass => return transStringLiteral(c, scope, @as(*const clang.StringLiteral, @ptrCast(stmt)), result_used), .ParenExprClass => { const expr = try transExpr(c, scope, @as(*const clang.ParenExpr, @ptrCast(stmt)).getSubExpr(), .used); return maybeSuppressResult(c, result_used, expr); }, .InitListExprClass => return transInitListExpr(c, scope, @as(*const clang.InitListExpr, @ptrCast(stmt)), result_used), .ImplicitValueInitExprClass => return transImplicitValueInitExpr(c, scope, @as(*const clang.Expr, @ptrCast(stmt))), .IfStmtClass => return transIfStmt(c, scope, @as(*const clang.IfStmt, @ptrCast(stmt))), .WhileStmtClass => return transWhileLoop(c, scope, @as(*const clang.WhileStmt, @ptrCast(stmt))), .DoStmtClass => return transDoWhileLoop(c, scope, @as(*const clang.DoStmt, @ptrCast(stmt))), .NullStmtClass => { return Tag.empty_block.init(); }, .ContinueStmtClass => return Tag.@"continue".init(), .BreakStmtClass => return Tag.@"break".init(), .ForStmtClass => return transForLoop(c, scope, @as(*const clang.ForStmt, @ptrCast(stmt))), .FloatingLiteralClass => return transFloatingLiteral(c, @as(*const clang.FloatingLiteral, @ptrCast(stmt)), result_used), .ConditionalOperatorClass => { return transConditionalOperator(c, scope, @as(*const clang.ConditionalOperator, @ptrCast(stmt)), result_used); }, .BinaryConditionalOperatorClass => { return transBinaryConditionalOperator(c, scope, @as(*const clang.BinaryConditionalOperator, @ptrCast(stmt)), result_used); }, .SwitchStmtClass => return transSwitch(c, scope, @as(*const clang.SwitchStmt, @ptrCast(stmt))), .CaseStmtClass, .DefaultStmtClass => { return fail(c, error.UnsupportedTranslation, stmt.getBeginLoc(), "TODO complex switch", .{}); }, .ConstantExprClass => return transConstantExpr(c, scope, @as(*const clang.Expr, @ptrCast(stmt)), result_used), .PredefinedExprClass => return transPredefinedExpr(c, scope, @as(*const clang.PredefinedExpr, @ptrCast(stmt)), result_used), .CharacterLiteralClass => return transCharLiteral(c, scope, @as(*const clang.CharacterLiteral, @ptrCast(stmt)), result_used, .with_as), .StmtExprClass => return transStmtExpr(c, scope, @as(*const clang.StmtExpr, @ptrCast(stmt)), result_used), .MemberExprClass => return transMemberExpr(c, scope, @as(*const clang.MemberExpr, @ptrCast(stmt)), result_used), .ArraySubscriptExprClass => return transArrayAccess(c, scope, @as(*const clang.ArraySubscriptExpr, @ptrCast(stmt)), result_used), .CallExprClass => return transCallExpr(c, scope, @as(*const clang.CallExpr, @ptrCast(stmt)), result_used), .UnaryExprOrTypeTraitExprClass => return transUnaryExprOrTypeTraitExpr(c, scope, @as(*const clang.UnaryExprOrTypeTraitExpr, @ptrCast(stmt)), result_used), .UnaryOperatorClass => return transUnaryOperator(c, scope, @as(*const clang.UnaryOperator, @ptrCast(stmt)), result_used), .CompoundAssignOperatorClass => return transCompoundAssignOperator(c, scope, @as(*const clang.CompoundAssignOperator, @ptrCast(stmt)), result_used), .OpaqueValueExprClass => { const source_expr = @as(*const clang.OpaqueValueExpr, @ptrCast(stmt)).getSourceExpr().?; const expr = try transExpr(c, scope, source_expr, .used); return maybeSuppressResult(c, result_used, expr); }, .OffsetOfExprClass => return transOffsetOfExpr(c, @as(*const clang.OffsetOfExpr, @ptrCast(stmt)), result_used), .CompoundLiteralExprClass => { const compound_literal = @as(*const clang.CompoundLiteralExpr, @ptrCast(stmt)); return transExpr(c, scope, compound_literal.getInitializer(), result_used); }, .GenericSelectionExprClass => { const gen_sel = @as(*const clang.GenericSelectionExpr, @ptrCast(stmt)); return transExpr(c, scope, gen_sel.getResultExpr(), result_used); }, .ConvertVectorExprClass => { const conv_vec = @as(*const clang.ConvertVectorExpr, @ptrCast(stmt)); const conv_vec_node = try transConvertVectorExpr(c, scope, conv_vec); return maybeSuppressResult(c, result_used, conv_vec_node); }, .ShuffleVectorExprClass => { const shuffle_vec_expr = @as(*const clang.ShuffleVectorExpr, @ptrCast(stmt)); const shuffle_vec_node = try transShuffleVectorExpr(c, scope, shuffle_vec_expr); return maybeSuppressResult(c, result_used, shuffle_vec_node); }, .ChooseExprClass => { const choose_expr = @as(*const clang.ChooseExpr, @ptrCast(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, expr: *const clang.ConvertVectorExpr, ) TransError!Node { const base_stmt = @as(*const clang.Stmt, @ptrCast(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 = @as(*const clang.VectorType, @ptrCast(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 = @as(*const clang.VectorType, @ptrCast(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, 0..) |*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, 0..) |*init, i| { const index_expr = try transExprCoercing(c, scope, expr.getExpr(@as(c_uint, @intCast(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. 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 = @as(*const clang.Expr, @ptrCast(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, expr: *const clang.OffsetOfExpr) TransError!Node { 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(@intFromPtr(record_decl.getCanonicalDecl()))) |type_name| { const type_node = try Tag.type.create(c.arena, type_name); var raw_field_name = try c.str(@as(*const clang.NamedDecl, @ptrCast(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, expr: *const clang.OffsetOfExpr, result_used: ResultUsed, ) TransError!Node { if (expr.getNumComponents() == 1) { const offsetof_expr = try transSimpleOffsetOfExpr(c, expr); return maybeSuppressResult(c, 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.as.create(gpa, .{ .lhs = try Tag.type.create(gpa, "isize"), .rhs = try Tag.int_cast.create(gpa, node), }); return Tag.as.create(gpa, .{ .lhs = try Tag.type.create(gpa, "usize"), .rhs = try Tag.bit_cast.create(gpa, 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, 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(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, 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, 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, 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.int_from_bool.create(c.arena, lhs_uncasted) else if (isPointerDiffExpr) try Tag.int_from_ptr.create(c.arena, lhs_uncasted) else lhs_uncasted; const rhs = if (isBoolRes(rhs_uncasted)) try Tag.int_from_bool.create(c.arena, rhs_uncasted) else if (isPointerDiffExpr) try Tag.int_from_ptr.create(c.arena, rhs_uncasted) else rhs_uncasted; const infixOpNode = try transCreateNodeInfixOp(c, op_id, lhs, rhs, result_used); if (isPointerDiffExpr) { // @divExact(@bitCast(, @intFromPtr(lhs) -% @intFromPtr(rhs)), @sizeOf()) 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 c_pointer = getContainer(c, lhs_qt_translated).?; const elem_type = c_pointer.castTag(.c_pointer).?.data.elem_type; const sizeof = try Tag.sizeof.create(c.arena, elem_type); const bitcast = try Tag.as.create(c.arena, .{ .lhs = ptrdiff_type, .rhs = try Tag.bit_cast.create(c.arena, 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 = @as(*const clang.CastExpr, @ptrCast(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(@as(*const clang.NamedDecl, @ptrCast(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, result_used, cast_node); } /// The alignment of a variable or field const ClangAlignment = struct { /// Clang reports the alignment in bits, we use bytes /// Clang uses 0 for "no alignment specified", we use null bit_alignment: c_uint, /// If the field or variable is marked as 'packed' /// /// According to the GCC variable attribute docs, this impacts alignment /// https://gcc.gnu.org/onlinedocs/gcc/Common-Variable-Attributes.html /// /// > The packed attribute specifies that a structure member /// > should have the smallest possible alignment /// /// Note also that specifying the 'packed' attribute on a structure /// implicitly packs all its fields (making their alignment 1). /// /// This will be null if the AST node doesn't support packing (functions) is_packed: ?bool, /// Get the alignment for a field, optionally taking into account the parent record pub fn forField(c: *const Context, field: *const clang.FieldDecl, parent: ?*const clang.RecordDecl) ClangAlignment { const parent_packed = if (parent) |record| record.getPackedAttribute() else false; // NOTE: According to GCC docs, parent attribute packed implies child attribute packed return ClangAlignment{ .bit_alignment = field.getAlignedAttribute(c.clang_context), .is_packed = field.getPackedAttribute() or parent_packed, }; } pub fn forVar(c: *const Context, var_decl: *const clang.VarDecl) ClangAlignment { return ClangAlignment{ .bit_alignment = var_decl.getAlignedAttribute(c.clang_context), .is_packed = var_decl.getPackedAttribute(), }; } pub fn forFunc(c: *const Context, fun: *const clang.FunctionDecl) ClangAlignment { return ClangAlignment{ .bit_alignment = fun.getAlignedAttribute(c.clang_context), .is_packed = null, // not supported by GCC/clang (or meaningful), }; } /// Translate the clang alignment info into a zig alignment /// /// Returns null if there is no special alignment info pub fn zigAlignment(self: ClangAlignment) ?c_uint { if (self.bit_alignment != 0) { return self.bit_alignment / 8; } else if (self.is_packed orelse false) { return 1; } else { return null; } } }; fn transDeclStmtOne( c: *Context, scope: *Scope, decl: *const clang.Decl, block_scope: *Scope.Block, ) TransError!void { switch (decl.getKind()) { .Var => { const var_decl = @as(*const clang.VarDecl, @ptrCast(decl)); const decl_init = var_decl.getInit(); const loc = decl.getLocation(); const qual_type = var_decl.getTypeSourceInfo_getType(); const name = try c.str(@as(*const clang.NamedDecl, @ptrCast(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); } else if (qualTypeWasDemotedToOpaque(c, qual_type)) { return fail(c, error.UnsupportedTranslation, loc, "local variable has opaque type", .{}); } const is_static_local = var_decl.isStaticLocal(); const is_const = qual_type.isConstQualified(); 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, @as(*const clang.StringLiteral, @ptrCast(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.int_from_bool.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.static_inner_name 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 = ClangAlignment.forVar(c, var_decl).zigAlignment(), .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(@as(*const clang.NamedDecl, @ptrCast(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, @as(*const clang.TypedefNameDecl, @ptrCast(decl))); }, .Record => { try transRecordDecl(c, scope, @as(*const clang.RecordDecl, @ptrCast(decl))); }, .Enum => { try transEnumDecl(c, scope, @as(*const clang.EnumDecl, @ptrCast(decl))); }, .Function => { try visitFnDecl(c, @as(*const clang.FunctionDecl, @ptrCast(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(@as(*const clang.NamedDecl, @ptrCast(value_decl)).getName_bytes_begin()); const mangled_name = scope.getAlias(name); var ref_expr = if (cIsFunctionDeclRef(@as(*const clang.Expr, @ptrCast(expr)))) try Tag.fn_identifier.create(c.arena, mangled_name) else try Tag.identifier.create(c.arena, mangled_name); if (@as(*const clang.Decl, @ptrCast(value_decl)).getKind() == .Var) { const var_decl = @as(*const clang.VarDecl, @ptrCast(value_decl)); if (var_decl.isStaticLocal()) { ref_expr = try Tag.field_access.create(c.arena, .{ .lhs = ref_expr, .field_name = Scope.Block.static_inner_name, }); } } 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, @as(*const clang.Expr, @ptrCast(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, result_used, casted); }, .LValueToRValue, .NoOp, .FunctionToPointerDecay => { const sub_expr_node = try transExpr(c, scope, sub_expr, .used); return maybeSuppressResult(c, 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, 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, result_used, casted); }, .NullToPointer => { return Tag.null_literal.init(); }, .PointerToBoolean => { // @intFromPtr(val) != 0 const ptr_node = try transExpr(c, scope, sub_expr, .used); const int_from_ptr = try Tag.int_from_ptr.create(c.arena, ptr_node); const ne = try Tag.not_equal.create(c.arena, .{ .lhs = int_from_ptr, .rhs = Tag.zero_literal.init() }); return maybeSuppressResult(c, 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, 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, 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, @as(*const clang.Stmt, @ptrCast(expr)).getBeginLoc(), "unsupported CastKind {s}", .{@tagName(kind)}, ), } } fn isBuiltinDefined(name: []const u8) bool { inline for (@typeInfo(std.zig.c_builtins).Struct.decls) |decl| { 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(.fn_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 (@as(*const clang.Stmt, @ptrCast(expr)).getStmtClass() == .IntegerLiteralClass) { var signum: c_int = undefined; if (!(@as(*const clang.IntegerLiteral, @ptrCast(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 = @intFromEnum(([2]Tag{ .true_literal, .false_literal })[@intFromBool(is_zero)]) }; } var res = try transExpr(c, scope, expr, used); if (isBoolRes(res)) { return maybeSuppressResult(c, used, res); } const ty = getExprQualType(c, expr).getTypePtr(); const node = try finishBoolExpr(c, scope, expr.getBeginLoc(), ty, res, used); return maybeSuppressResult(c, 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 = @as(*const clang.StringLiteral, @ptrCast(expr)); return string_lit.getCharByteWidth() == 1; }, .PredefinedExprClass => return true, .UnaryOperatorClass => { const op_expr = @as(*const clang.UnaryOperator, @ptrCast(expr)).getSubExpr(); return exprIsNarrowStringLiteral(op_expr); }, .ParenExprClass => { const op_expr = @as(*const clang.ParenExpr, @ptrCast(expr)).getSubExpr(); return exprIsNarrowStringLiteral(op_expr); }, .GenericSelectionExprClass => { const gen_sel = @as(*const clang.GenericSelectionExpr, @ptrCast(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 = @as(*const clang.MemberExpr, @ptrCast(expr)); const member_decl = member_expr.getMemberDecl(); const decl_kind = @as(*const clang.Decl, @ptrCast(member_decl)).getKind(); if (decl_kind == .Field) { const field_decl = @as(*const clang.FieldDecl, @ptrCast(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 = @as(*const clang.BuiltinType, @ptrCast(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 = @as(*const clang.TypedefType, @ptrCast(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 = @as(*const clang.ElaboratedType, @ptrCast(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, 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 = @as(u8, @bitCast(@as(i8, @truncate(@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 = @as(*const clang.Expr, @ptrCast(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, 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(); if (isBoolRes(rhs) and !qualTypeIsBoolean(return_qt)) { rhs = try Tag.int_from_bool.create(c.arena, rhs); } return Tag.@"return".create(c.arena, rhs); } fn transNarrowStringLiteral( c: *Context, 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, 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, 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 = @as(*const clang.Expr, @ptrCast(stmt)); const array_type = try transQualTypeInitialized(c, scope, expr_base.getType(), expr_base, expr_base.getBeginLoc()); const lit_array = try transStringLiteralInitializer(c, 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, 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 `"".*` or `""[0..].*` 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, 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 = @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, 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(stmt: *const clang.BinaryOperator) bool { const lhs = @as(*const clang.Stmt, @ptrCast(stmt.getLHS())); const rhs = @as(*const clang.Stmt, @ptrCast(stmt.getRHS())); return stmt.getOpcode() == .Sub and qualTypeIsPtr(@as(*const clang.Expr, @ptrCast(lhs)).getType()) and qualTypeIsPtr(@as(*const clang.Expr, @ptrCast(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 = @as(*const clang.EnumType, @ptrCast(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.int_from_bool.create(c.arena, src_int_expr); return Tag.as.create(c.arena, .{ .lhs = dst_node, .rhs = 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.as.create(c.arena, .{ .lhs = ty_node, .rhs = try Tag.truncate.create(c.arena, 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 }, } // @as(dest_type, @bitCast(intermediate_value)) return Tag.as.create(c.arena, .{ .lhs = dst_node, .rhs = try Tag.bit_cast.create(c.arena, 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 // @as(dest_type, @bitCast(val)) return Tag.as.create(c.arena, .{ .lhs = dst_node, .rhs = try Tag.bit_cast.create(c.arena, expr), }); } if (cIsInteger(dst_type) and qualTypeIsPtr(src_type)) { // @intCast(dest_type, @intFromPtr(val)) const int_from_ptr = try Tag.int_from_ptr.create(c.arena, expr); return Tag.as.create(c.arena, .{ .lhs = dst_node, .rhs = try Tag.int_cast.create(c.arena, int_from_ptr), }); } if (cIsInteger(src_type) and qualTypeIsPtr(dst_type)) { // @as(dest_type, @ptrFromInt(val)) return Tag.as.create(c.arena, .{ .lhs = dst_node, .rhs = try Tag.ptr_from_int.create(c.arena, expr), }); } if (cIsFloating(src_type) and cIsFloating(dst_type)) { // @as(dest_type, @floatCast(val)) return Tag.as.create(c.arena, .{ .lhs = dst_node, .rhs = try Tag.float_cast.create(c.arena, expr), }); } if (cIsFloating(src_type) and !cIsFloating(dst_type)) { // @as(dest_type, @intFromFloat(val)) return Tag.as.create(c.arena, .{ .lhs = dst_node, .rhs = try Tag.int_from_float.create(c.arena, expr), }); } if (!cIsFloating(src_type) and cIsFloating(dst_type)) { var rhs = expr; if (qualTypeIsBoolean(src_type)) rhs = try Tag.int_from_bool.create(c.arena, expr); // @as(dest_type, @floatFromInt(val)) return Tag.as.create(c.arena, .{ .lhs = dst_node, .rhs = try Tag.float_from_int.create(c.arena, rhs), }); } if (qualTypeIsBoolean(src_type) and !qualTypeIsBoolean(dst_type)) { // @intFromBool returns a u1 // TODO: if dst_type is 1 bit & signed (bitfield) we need @bitCast // instead of @as const int_from_bool = try Tag.int_from_bool.create(c.arena, expr); return Tag.as.create(c.arena, .{ .lhs = dst_node, .rhs = int_from_bool }); } // @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, @as(*const clang.Stmt, @ptrCast(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 (@as(*const clang.Stmt, @ptrCast(expr)).getStmtClass()) { .IntegerLiteralClass => { return transIntegerLiteral(c, scope, @as(*const clang.IntegerLiteral, @ptrCast(expr)), .used, .no_as); }, .CharacterLiteralClass => { return transCharLiteral(c, scope, @as(*const clang.CharacterLiteral, @ptrCast(expr)), .used, .no_as); }, .UnaryOperatorClass => { const un_expr = @as(*const clang.UnaryOperator, @ptrCast(expr)); if (un_expr.getOpcode() == .Extension) { return transExprCoercing(c, scope, un_expr.getSubExpr(), used); } }, .ImplicitCastExprClass => { const cast_expr = @as(*const clang.ImplicitCastExpr, @ptrCast(expr)); const sub_expr = cast_expr.getSubExpr(); switch (@as(*const clang.Stmt, @ptrCast(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 - @intFromBool(is_signed))) - 1; switch (@as(*const clang.Stmt, @ptrCast(expr)).getStmtClass()) { .CharacterLiteralClass => { const char_lit = @as(*const clang.CharacterLiteral, @ptrCast(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 = @as(*const clang.IntegerLiteral, @ptrCast(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(@as(*const clang.NamedDecl, @ptrCast(field_decl)).getName_bytes_begin()); if (field_decl.isAnonymousStructOrUnion()) { const name = c.decl_table.get(@intFromPtr(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(@as(*const clang.Type, @ptrCast(arr_type)).isConstantArrayType()); const const_arr_ty = @as(*const clang.ConstantArrayType, @ptrCast(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); } if (expr.isStringLiteralInit()) { assert(init_count == 1); const init_expr = expr.getInit(0); const string_literal = init_expr.castToStringLiteral().?; return try transStringLiteral(c, scope, string_literal, .used); } const init_node = if (init_count != 0) blk: { const init_list = try c.arena.alloc(Node, init_count); for (init_list, 0..) |*init, i| { const elem_expr = expr.getInit(@as(c_uint, @intCast(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; assert(expr.hasArrayFiller()); 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, ) TransError!Node { const qt = getExprQualType(c, @as(*const clang.Expr, @ptrCast(expr))); const vector_ty = @as(*const clang.VectorType, @ptrCast(qualTypeCanon(qt))); const init_count = expr.getNumInits(); const num_elements = vector_ty.getNumElements(); const element_qt = vector_ty.getElementType(); if (init_count == 0) { const vec_node = try Tag.vector.create(c.arena, .{ .lhs = try transCreateNodeNumber(c, num_elements, .int), .rhs = try transQualType(c, scope, element_qt, loc), }); return Tag.as.create(c.arena, .{ .lhs = vec_node, .rhs = try Tag.vector_zero_init.create(c.arena, Tag.zero_literal.init()), }); } const vector_type = try transQualType(c, scope, qt, loc); 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(@as(c_uint, @intCast(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, num_elements); for (init_list, 0..) |*init, init_index| { if (init_index < init_count) { 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); } else { init.* = Tag.undefined_literal.init(); } } 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, @as(*const clang.Expr, @ptrCast(expr))); var qual_type = qt.getTypePtr(); const source_loc = @as(*const clang.Expr, @ptrCast(expr)).getBeginLoc(); if (qualTypeWasDemotedToOpaque(c, qt)) { return fail(c, error.UnsupportedTranslation, source_loc, "cannot initialize opaque type", .{}); } if (qual_type.isRecordType()) { return maybeSuppressResult(c, used, try transInitListExprRecord( c, scope, source_loc, expr, qual_type, )); } else if (qual_type.isArrayType()) { return maybeSuppressResult(c, used, try transInitListExprArray( c, scope, source_loc, expr, qual_type, )); } else if (qual_type.isVectorType()) { return maybeSuppressResult(c, used, try transInitListExprVector(c, scope, source_loc, expr)); } else { const type_name = try 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 = @as(*const clang.BuiltinType, @ptrCast(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 = @as(*const clang.TypedefType, @ptrCast(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, ) TransError!Node { 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 = @as(*const clang.Expr, @ptrCast(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 = @as(*const clang.Expr, @ptrCast(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, @as(*const clang.Expr, @ptrCast(stmt.getCond())), .used); const if_not_break = switch (cond.tag()) { .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), }; var body_node = try transStmt(c, &loop_scope, stmt.getBody(), .unused); if (body_node.isNoreturn(true)) { // The body node ends in a noreturn statement. Simply put it in a while (true) // in case it contains breaks or continues. } else if (stmt.getBody().getStmtClass() == .CompoundStmtClass) { // 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 block = body_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; } else { // 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] = body_node; statements[1] = if_not_break; body_node = 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 = @as(*const clang.CompoundStmt, @ptrCast(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 = @as(*const clang.DefaultStmt, @ptrCast(it[0])); var sub = default_stmt.getSubStmt(); while (true) switch (sub.getStmtClass()) { .CaseStmtClass => sub = @as(*const clang.CaseStmt, @ptrCast(sub)).getSubStmt(), .DefaultStmtClass => sub = @as(*const clang.DefaultStmt, @ptrCast(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 = @as(*const clang.DefaultStmt, @ptrCast(sub)); sub = default_stmt.getSubStmt(); }, .CaseStmtClass => { const case_stmt = @as(*const clang.CaseStmt, @ptrCast(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 = @as(*const clang.CaseStmt, @ptrCast(it[0])).getSubStmt(); while (true) switch (sub.getStmtClass()) { .CaseStmtClass => sub = @as(*const clang.CaseStmt, @ptrCast(sub)).getSubStmt(), .DefaultStmtClass => sub = @as(*const clang.DefaultStmt, @ptrCast(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 = @as(*const clang.DefaultStmt, @ptrCast(it[0])).getSubStmt(); while (true) switch (sub.getStmtClass()) { .CaseStmtClass => sub = @as(*const clang.CaseStmt, @ptrCast(sub)).getSubStmt(), .DefaultStmtClass => sub = @as(*const clang.DefaultStmt, @ptrCast(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, @as(*const clang.CompoundStmt, @ptrCast(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 = @as(*const clang.Expr, @ptrCast(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, used, as_node); }, else => |kind| { return fail(c, error.UnsupportedTranslation, expr.getBeginLoc(), "unsupported constant expression kind '{}'", .{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(&.{@as(u8, @intCast(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, result_used, int_lit_node); } // See comment in `transIntegerLiteral` for why this code is here. // @as(T, x) const expr_base = @as(*const clang.Expr, @ptrCast(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, 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, 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 = @as(*const clang.Decl, @ptrCast(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 = @as(*const clang.FieldDecl, @ptrCast(member_decl)); if (field_decl.isAnonymousStructOrUnion()) { const name = c.decl_table.get(@intFromPtr(field_decl.getCanonicalDecl())).?; break :blk try c.arena.dupe(u8, name); } } const decl = @as(*const clang.NamedDecl, @ptrCast(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, @as(*const clang.Expr, @ptrCast(stmt)))) { node = try Tag.call.create(c.arena, .{ .lhs = node, .args = &.{} }); } return maybeSuppressResult(c, 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.as.create(c.arena, .{ .lhs = try Tag.type.create(c.arena, "usize"), .rhs = try Tag.int_cast.create(c.arena, 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.as.create(c.arena, .{ .lhs = try Tag.type.create(c.arena, "isize"), .rhs = try Tag.int_cast.create(c.arena, 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.as.create(c.arena, .{ .lhs = try Tag.type.create(c.arena, "usize"), .rhs = try Tag.bit_cast.create(c.arena, 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, 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 (@as(*const clang.Stmt, @ptrCast(base_stmt)).getStmtClass() == .ImplicitCastExprClass) { const implicit_cast = @as(*const clang.ImplicitCastExpr, @ptrCast(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.as.create(c.arena, .{ .lhs = typeid_node, .rhs = try Tag.int_cast.create( c.arena, 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, 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 = @as(*const clang.ParenExpr, @ptrCast(expr)).getSubExpr(); return cIsFunctionDeclRef(op_expr); }, .DeclRefExprClass => { const decl_ref = @as(*const clang.DeclRefExpr, @ptrCast(expr)); const value_decl = decl_ref.getDecl(); const qt = value_decl.getType(); return qualTypeChildIsFnProto(qt); }, .ImplicitCastExprClass => { const implicit_cast = @as(*const clang.ImplicitCastExpr, @ptrCast(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 = @as(*const clang.UnaryOperator, @ptrCast(expr)); const opcode = un_op.getOpcode(); return (opcode == .AddrOf or opcode == .Deref) and cIsFunctionDeclRef(un_op.getSubExpr()); }, .GenericSelectionExprClass => { const gen_sel = @as(*const clang.GenericSelectionExpr, @ptrCast(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(@as(c_uint, @intCast(i))); if (isBoolRes(arg) and cIsNativeInt(param_qt)) { arg = try Tag.int_from_bool.create(c.arena, arg); } else if (arg.tag() == .string_literal and qualTypeIsCharStar(param_qt)) { const loc = @as(*const clang.Stmt, @ptrCast(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, 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 = @as(*const clang.FunctionProtoType, @ptrCast(ty)) }; } if (ty.getTypeClass() == .FunctionNoProto) { return ClangFunctionType{ .NoProto = @as(*const clang.FunctionType, @ptrCast(ty)) }; } return null; } fn transUnaryExprOrTypeTraitExpr( c: *Context, scope: *Scope, stmt: *const clang.UnaryExprOrTypeTraitExpr, result_used: ResultUsed, ) TransError!Node { const loc = stmt.getBeginLoc(); const type_node = try transQualType(c, scope, stmt.getTypeOfArgument(), loc); const kind = stmt.getKind(); const node = switch (kind) { .SizeOf => try Tag.sizeof.create(c.arena, type_node), .AlignOf => try Tag.alignof.create(c.arena, type_node), .PreferredAlignOf, .VecStep, .OpenMPRequiredSimdAlign, => return fail( c, error.UnsupportedTranslation, loc, "unsupported type trait kind {}", .{kind}, ), }; return maybeSuppressResult(c, result_used, node); } 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 => { 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.int_from_bool.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, 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.reserveMangledName(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, 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, 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.reserveMangledName(c, "ref"); const tmp = try block_scope.reserveMangledName(c, "tmp"); 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_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, 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, .assign, lhs_node, builtin, .used); } if (is_shift) { rhs_node = try Tag.int_cast.create(c.arena, rhs_node); } else if (requires_int_cast) { rhs_node = try transCCast(c, scope, loc, lhs_qt, rhs_qt, rhs_node); } return transCreateNodeInfixOp(c, 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.reserveMangledName(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, .assign, ref_node, builtin, .used); try block_scope.statements.append(assign); } else { if (is_shift) { rhs_node = try Tag.int_cast.create(c.arena, 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, 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); } fn removeCVQualifiers(c: *Context, dst_type_node: Node, expr: Node) Error!Node { const const_casted = try Tag.const_cast.create(c.arena, expr); const volatile_casted = try Tag.volatile_cast.create(c.arena, const_casted); return Tag.as.create(c.arena, .{ .lhs = dst_type_node, .rhs = try Tag.ptr_cast.create(c.arena, volatile_casted), }); } 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: { break :blk try Tag.align_cast.create(c.arena, expr); }; return Tag.as.create(c.arena, .{ .lhs = dst_type_node, .rhs = try Tag.ptr_cast.create(c.arena, rhs), }); } } fn transFloatingLiteral(c: *Context, 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 == @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, 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 = @as(*const clang.Expr, @ptrCast(stmt)).getType(); const res_is_bool = qualTypeIsBoolean(qt); const casted_stmt = @as(*const clang.AbstractConditionalOperator, @ptrCast(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 cond_temp = try block_scope.reserveMangledName(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 = cond_temp, .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, cond_temp); 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.int_from_bool.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.int_from_bool.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, 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 = @as(*const clang.Expr, @ptrCast(stmt)).getType(); const res_is_bool = qualTypeIsBoolean(qt); const casted_stmt = @as(*const clang.AbstractConditionalOperator, @ptrCast(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.int_from_bool.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.int_from_bool.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, used: ResultUsed, result: Node) TransError!Node { 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 { const gop = try c.global_scope.sym_table.getOrPut(name); if (!gop.found_existing) { gop.value_ptr.* = decl_node; try c.global_scope.nodes.append(decl_node); } } fn transQualTypeInitializedStringLiteral(c: *Context, elem_ty: Node, string_lit: *const clang.StringLiteral) TypeError!Node { const string_lit_size = string_lit.getLength(); const array_size = @as(usize, @intCast(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 }); } /// 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 = @as(*const clang.IncompleteArrayType, @ptrCast(ty)); const elem_ty = try transType(c, scope, incomplete_array_ty.getElementType().getTypePtr(), source_loc); switch (decl_init.getStmtClass()) { .StringLiteralClass => { const string_lit = @as(*const clang.StringLiteral, @ptrCast(decl_init)); return transQualTypeInitializedStringLiteral(c, elem_ty, string_lit); }, .InitListExprClass => { const init_expr = @as(*const clang.InitListExpr, @ptrCast(decl_init)); const size = init_expr.getNumInits(); if (init_expr.isStringLiteralInit()) { assert(size == 1); const string_lit = init_expr.getInit(0).castToStringLiteral().?; return transQualTypeInitializedStringLiteral(c, elem_ty, string_lit); } 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 = @as(*const clang.BuiltinType, @ptrCast(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 = @as(*const clang.BuiltinType, @ptrCast(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 = @as(*const clang.BuiltinType, @ptrCast(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 = @as(*const clang.TypedefType, @ptrCast(ty)); const typedef_decl = typedef_ty.getDecl(); const type_name = try c.str(@as(*const clang.NamedDecl, @ptrCast(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 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 = @as(*const clang.ImplicitCastExpr, @ptrCast(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 = @as(*const clang.ArrayType, @ptrCast(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 = @as(*const clang.BuiltinType, @ptrCast(ty)); return builtin_ty.getKind() == .Void; }, .Record => { const record_ty = @as(*const clang.RecordType, @ptrCast(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 = @as(*const clang.ElaboratedType, @ptrCast(ty)); const qt = elaborated_ty.getNamedType(); return typeIsOpaque(c, qt.getTypePtr(), loc); }, .Typedef => { const typedef_ty = @as(*const clang.TypedefType, @ptrCast(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 = @as(*const clang.BuiltinType, @ptrCast(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 = @as(*const clang.BuiltinType, @ptrCast(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 = @as(*const clang.BuiltinType, @ptrCast(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 (@as(*const clang.Stmt, @ptrCast(expr)).getStmtClass() == .IntegerLiteralClass) { var signum: c_int = undefined; if (!(@as(*const clang.IntegerLiteral, @ptrCast(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 = @as(*const clang.BuiltinType, @ptrCast(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 = @as(*const clang.BuiltinType, @ptrCast(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 = @as(*const clang.BuiltinType, @ptrCast(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 = @as(*const clang.BuiltinType, @ptrCast(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.int_from_bool.create(c.arena, rhs_node); } return transCreateNodeInfixOp(c, .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.reserveMangledName(c, "tmp"); var rhs_node = try transExpr(c, &block_scope.base, rhs, .used); if (!exprIsBooleanType(lhs) and isBoolRes(rhs_node)) { rhs_node = try Tag.int_from_bool.create(c.arena, rhs_node); } 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, .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, 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, 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, op, lhs, rhs, used); } fn transCreateNodeAPInt(c: *Context, int: *const clang.APSInt) !Node { const num_limbs = math.cast(usize, int.getNumWords()) orelse 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] = @as(u32, @truncate(data[data_i])); limbs[limb_i + 1] = @as(u32, @truncate(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, 0..) |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(); // lhs >> @as(u5, rh) const lhs = try transExpr(c, scope, lhs_expr, .used); const rhs = try transExprCoercing(c, scope, rhs_expr, .used); const rhs_casted = try Tag.int_cast.create(c.arena, rhs); return transCreateNodeInfixOp(c, 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 = @as(*const clang.BuiltinType, @ptrCast(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 = @as(*const clang.FunctionProtoType, @ptrCast(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 = @as(*const clang.FunctionType, @ptrCast(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 = @as(*const clang.ParenType, @ptrCast(ty)); return transQualType(c, scope, paren_ty.getInnerType(), source_loc); }, .Pointer => { const child_qt = ty.getPointeeType(); const is_fn_proto = qualTypeChildIsFnProto(child_qt); 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 = @as(*const clang.ConstantArrayType, @ptrCast(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 = @as(*const clang.IncompleteArrayType, @ptrCast(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 = @as(*const clang.TypedefType, @ptrCast(ty)); const typedef_decl = typedef_ty.getDecl(); var trans_scope = scope; if (@as(*const clang.Decl, @ptrCast(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(@intFromPtr(typedef_decl.getCanonicalDecl())).?; return Tag.identifier.create(c.arena, name); }, .Record => { const record_ty = @as(*const clang.RecordType, @ptrCast(ty)); const record_decl = record_ty.getDecl(); var trans_scope = scope; if (@as(*const clang.Decl, @ptrCast(record_decl)).castToNamedDecl()) |named_decl| { const decl_name = try c.str(named_decl.getName_bytes_begin()); if (c.weak_global_names.contains(decl_name)) trans_scope = &c.global_scope.base; } try transRecordDecl(c, trans_scope, record_decl); const name = c.decl_table.get(@intFromPtr(record_decl.getCanonicalDecl())).?; return Tag.identifier.create(c.arena, name); }, .Enum => { const enum_ty = @as(*const clang.EnumType, @ptrCast(ty)); const enum_decl = enum_ty.getDecl(); var trans_scope = scope; if (@as(*const clang.Decl, @ptrCast(enum_decl)).castToNamedDecl()) |named_decl| { const decl_name = try c.str(named_decl.getName_bytes_begin()); if (c.weak_global_names.contains(decl_name)) trans_scope = &c.global_scope.base; } try transEnumDecl(c, trans_scope, enum_decl); const name = c.decl_table.get(@intFromPtr(enum_decl.getCanonicalDecl())).?; return Tag.identifier.create(c.arena, name); }, .Elaborated => { const elaborated_ty = @as(*const clang.ElaboratedType, @ptrCast(ty)); return transQualType(c, scope, elaborated_ty.getNamedType(), source_loc); }, .Decayed => { const decayed_ty = @as(*const clang.DecayedType, @ptrCast(ty)); return transQualType(c, scope, decayed_ty.getDecayedType(), source_loc); }, .Attributed => { const attributed_ty = @as(*const clang.AttributedType, @ptrCast(ty)); return transQualType(c, scope, attributed_ty.getEquivalentType(), source_loc); }, .MacroQualified => { const macroqualified_ty = @as(*const clang.MacroQualifiedType, @ptrCast(ty)); return transQualType(c, scope, macroqualified_ty.getModifiedType(), source_loc); }, .TypeOf => { const typeof_ty = @as(*const clang.TypeOfType, @ptrCast(ty)); return transQualType(c, scope, typeof_ty.getUnmodifiedType(), source_loc); }, .TypeOfExpr => { const typeofexpr_ty = @as(*const clang.TypeOfExprType, @ptrCast(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 = @as(*const clang.VectorType, @ptrCast(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), }); }, .BitInt, .ExtVector => { const type_name = try c.str(ty.getTypeClassName()); return fail(c, error.UnsupportedType, source_loc, "TODO implement translation of type: '{s}'", .{type_name}); }, else => { const type_name = try 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 = @as(*const clang.TypedefType, @ptrCast(ty)); const typedef_decl = typedef_ty.getDecl(); const underlying_type = typedef_decl.getUnderlyingType(); return qualTypeWasDemotedToOpaque(c, underlying_type); }, .Record => { const record_ty = @as(*const clang.RecordType, @ptrCast(ty)); const record_decl = record_ty.getDecl(); const canonical = @intFromPtr(record_decl.getCanonicalDecl()); if (c.opaque_demotes.contains(canonical)) return true; // check all childern for opaque types. var it = record_decl.field_begin(); const end_it = record_decl.field_end(); while (it.neq(end_it)) : (it = it.next()) { const field_decl = it.deref(); if (qualTypeWasDemotedToOpaque(c, field_decl.getType())) return true; } return false; }, .Enum => { const enum_ty = @as(*const clang.EnumType, @ptrCast(ty)); const enum_decl = enum_ty.getDecl(); const canonical = @intFromPtr(enum_decl.getCanonicalDecl()); return c.opaque_demotes.contains(canonical); }, .Elaborated => { const elaborated_ty = @as(*const clang.ElaboratedType, @ptrCast(ty)); return qualTypeWasDemotedToOpaque(c, elaborated_ty.getNamedType()); }, .Decayed => { const decayed_ty = @as(*const clang.DecayedType, @ptrCast(ty)); return qualTypeWasDemotedToOpaque(c, decayed_ty.getDecayedType()); }, .Attributed => { const attributed_ty = @as(*const clang.AttributedType, @ptrCast(ty)); return qualTypeWasDemotedToOpaque(c, attributed_ty.getEquivalentType()); }, .MacroQualified => { const macroqualified_ty = @as(*const clang.MacroQualifiedType, @ptrCast(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 = @as(*const clang.BuiltinType, @ptrCast(ty)); return builtin_ty.getKind() == .Void; }, .Typedef => { const typedef_ty = @as(*const clang.TypedefType, @ptrCast(ty)); const typedef_decl = typedef_ty.getDecl(); return isAnyopaque(typedef_decl.getUnderlyingType()); }, .Elaborated => { const elaborated_ty = @as(*const clang.ElaboratedType, @ptrCast(ty)); return isAnyopaque(elaborated_ty.getNamedType().getCanonicalType()); }, .Decayed => { const decayed_ty = @as(*const clang.DecayedType, @ptrCast(ty)); return isAnyopaque(decayed_ty.getDecayedType().getCanonicalType()); }, .Attributed => { const attributed_ty = @as(*const clang.AttributedType, @ptrCast(ty)); return isAnyopaque(attributed_ty.getEquivalentType().getCanonicalType()); }, .MacroQualified => { const macroqualified_ty = @as(*const clang.MacroQualifiedType, @ptrCast(ty)); return isAnyopaque(macroqualified_ty.getModifiedType().getCanonicalType()); }, 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 = @as(*const clang.FunctionType, @ptrCast(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(@as(c_uint, @intCast(i))); const is_noalias = param_qt.isRestrictQualified(); const param_name: ?[]const u8 = if (fn_decl) |decl| blk: { const param = decl.getParamDecl(@as(c_uint, @intCast(i))); const param_name: []const u8 = try c.str(@as(*const clang.NamedDecl, @ptrCast(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| ClangAlignment.forFunc(c, decl).zigAlignment() 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 str = try c.locStr(loc); const value = try std.fmt.allocPrint(c.arena, "// {s}: warning: " ++ format, .{str} ++ 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 str = try c.locStr(loc); const location_comment = try std.fmt.allocPrint(c.arena, "// {s}", .{str}); try c.global_scope.nodes.append(try Tag.warning.create(c.arena, location_comment)); } pub 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{ "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{ "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. pub 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; } }; pub fn init(allocator: mem.Allocator) Error!PatternList { const patterns = try allocator.alloc(Pattern, templates.len); for (templates, 0..) |template, i| { try patterns[i].init(allocator, template); } return PatternList{ .patterns = patterns }; } pub fn deinit(self: *PatternList, allocator: mem.Allocator) void { for (self.patterns) |*pattern| pattern.deinit(allocator); allocator.free(self.patterns); } pub 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, "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 }; } const MacroTranslateError = union(enum) { undefined_identifier: []const u8, invalid_arg_usage: []const u8, }; fn checkTranslatableMacro(self: *MacroCtx, scope: *Scope, params: []const ast.Payload.Param) ?MacroTranslateError { const slicer = self.makeSlicer(); var last_is_type_kw = false; 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 .Keyword_struct, .Keyword_union, .Keyword_enum => if (!last_is_type_kw) { last_is_type_kw = true; continue; }, .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 (is_param and last_is_type_kw) { return .{ .invalid_arg_usage = identifier }; } if (!scope.contains(identifier) and !isBuiltinDefined(identifier) and !is_param) { return .{ .undefined_identifier = identifier }; } }, else => {}, } last_is_type_kw = false; } 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 getMacroText(unit: *const clang.ASTUnit, c: *const Context, macro: *const clang.MacroDefinitionRecord) []const u8 { const begin_loc = macro.getSourceRange_getBegin(); const end_loc = clang.Lexer.getLocForEndOfToken(macro.getSourceRange_getEnd(), c.source_manager, unit); const begin_c = c.source_manager.getCharacterData(begin_loc); const end_c = c.source_manager.getCharacterData(end_loc); const slice_len = @intFromPtr(end_c) - @intFromPtr(begin_c); return begin_c[0..slice_len]; } 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 = @as(*clang.MacroDefinitionRecord, @ptrCast(entity)); const raw_name = macro.getName_getNameStart(); const begin_loc = macro.getSourceRange_getBegin(); const name = try c.str(raw_name); if (scope.containsNow(name)) { continue; } const source = getMacroText(unit, c, macro); try tokenizeMacro(source, &tok_list); var macro_ctx = MacroCtx{ .source = source, .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, source[tok.start..tok.end])) { assert(!c.global_names.contains(source[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, ¯o_ctx) else transMacroDefine(c, ¯o_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.checkTranslatableMacro(scope, &.{})) |err| switch (err) { .undefined_identifier => |ident| return m.fail(c, "unable to translate macro: undefined identifier `{s}`", .{ident}), .invalid_arg_usage => unreachable, // no args }; 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.checkTranslatableMacro(scope, fn_params.items)) |err| switch (err) { .undefined_identifier => |ident| return m.fail(c, "unable to translate macro: undefined identifier `{s}`", .{ident}), .invalid_arg_usage => |ident| return m.fail(c, "unable to translate macro: untranslatable usage of arg `{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 var block_scope = try Scope.Block.init(c, scope, true); defer block_scope.deinit(); const node = try parseCCondExpr(c, m, &block_scope.base); if (m.next().? != .Comma) { m.i -= 1; return node; } 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 base: []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..]}); base = "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..]}); base = "hexadecimal"; }, 'x' => { base = "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 => math.cast(i16, value) != null, .u => math.cast(u16, value) != null, .l => math.cast(i32, value) != null, .lu => math.cast(u32, value) != null, .ll => math.cast(i64, value) != null, .llu => math.cast(u64, value) != null, .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, .base = try Tag.enum_literal.create(c.arena, base), }); } }, .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.isHex(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 ..], }); } } const type_node = try Tag.type.create(c.arena, switch (suffix) { .f => "f32", .none => "f64", .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, 0..) |c, i| { if (c == '\"' or c == '\'') { source = source[i..]; break; } } for (source) |c| { if (c == '\\' or c == '\t') { 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 == '\t') { bytes[i] = '\\'; i += 1; bytes[i] = 't'; i += 1; continue; } 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]; } /// non-ASCII characters (c > 127) are also treated as non-printable by fmtSliceEscapeLower. /// If a C string literal or char literal in a macro is not valid UTF-8, we need to escape /// non-ASCII characters so that the Zig source we output will itself be UTF-8. fn escapeUnprintables(ctx: *Context, m: *MacroCtx) ![]const u8 { const zigified = try zigifyEscapeSequences(ctx, m); if (std.unicode.utf8ValidateSlice(zigified)) return zigified; const formatter = std.fmt.fmtSliceEscapeLower(zigified); const encoded_size = @as(usize, @intCast(std.fmt.count("{s}", .{formatter}))); var output = try ctx.arena.alloc(u8, encoded_size); return std.fmt.bufPrint(output, "{s}", .{formatter}) catch |err| switch (err) { error.NoSpaceLeft => unreachable, else => |e| return e, }; } 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 escapeUnprintables(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 escapeUnprintables(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 macroIntFromBool(c: *Context, node: Node) !Node { if (!isBoolRes(node)) { return node; } return Tag.int_from_bool.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 macroIntFromBool(c, node); const rhs = try macroIntFromBool(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 macroIntFromBool(c, node); const rhs = try macroIntFromBool(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 macroIntFromBool(c, node); const rhs = try macroIntFromBool(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 macroIntFromBool(c, node); const rhs = try macroIntFromBool(c, try parseCRelExpr(c, m, scope)); node = try Tag.not_equal.create(c.arena, .{ .lhs = lhs, .rhs = rhs }); }, .EqualEqual => { _ = m.next(); const lhs = try macroIntFromBool(c, node); const rhs = try macroIntFromBool(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 macroIntFromBool(c, node); const rhs = try macroIntFromBool(c, try parseCShiftExpr(c, m, scope)); node = try Tag.greater_than.create(c.arena, .{ .lhs = lhs, .rhs = rhs }); }, .AngleBracketRightEqual => { _ = m.next(); const lhs = try macroIntFromBool(c, node); const rhs = try macroIntFromBool(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 macroIntFromBool(c, node); const rhs = try macroIntFromBool(c, try parseCShiftExpr(c, m, scope)); node = try Tag.less_than.create(c.arena, .{ .lhs = lhs, .rhs = rhs }); }, .AngleBracketLeftEqual => { _ = m.next(); const lhs = try macroIntFromBool(c, node); const rhs = try macroIntFromBool(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 macroIntFromBool(c, node); const rhs = try macroIntFromBool(c, try parseCAddSubExpr(c, m, scope)); node = try Tag.shl.create(c.arena, .{ .lhs = lhs, .rhs = rhs }); }, .AngleBracketAngleBracketRight => { _ = m.next(); const lhs = try macroIntFromBool(c, node); const rhs = try macroIntFromBool(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 macroIntFromBool(c, node); const rhs = try macroIntFromBool(c, try parseCMulExpr(c, m, scope)); node = try Tag.add.create(c.arena, .{ .lhs = lhs, .rhs = rhs }); }, .Minus => { _ = m.next(); const lhs = try macroIntFromBool(c, node); const rhs = try macroIntFromBool(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 macroIntFromBool(c, node); const rhs = try macroIntFromBool(c, try parseCCastExpr(c, m, scope)); node = try Tag.mul.create(c.arena, .{ .lhs = lhs, .rhs = rhs }); }, .Slash => { const lhs = try macroIntFromBool(c, node); const rhs = try macroIntFromBool(c, try parseCCastExpr(c, m, scope)); node = try Tag.macro_arithmetic.create(c.arena, .{ .op = .div, .lhs = lhs, .rhs = rhs }); }, .Percent => { const lhs = try macroIntFromBool(c, node); const rhs = try macroIntFromBool(c, try parseCCastExpr(c, m, scope)); node = try Tag.macro_arithmetic.create(c.arena, .{ .op = .rem, .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, 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); }, .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) ParseError!Node { 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, node: Node) ParseError!Node { 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_val = try macroIntFromBool(c, try parseCExpr(c, m, scope)); const index = try Tag.as.create(c.arena, .{ .lhs = try Tag.type.create(c.arena, "usize"), .rhs = try Tag.int_cast.create(c.arena, index_val), }); 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 macroIntFromBool(c, try parseCCastExpr(c, m, scope)); return Tag.negate.create(c.arena, operand); }, .Plus => return try parseCCastExpr(c, m, scope), .Tilde => { const operand = try macroIntFromBool(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 '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 (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.*); } } }