const std = @import("std"); const mem = std.mem; const assert = std.debug.assert; const CallingConvention = std.builtin.CallingConvention; const aro = @import("aro"); const CToken = aro.Tokenizer.Token; const Tree = aro.Tree; const Node = Tree.Node; const TokenIndex = Tree.TokenIndex; const QualType = aro.QualType; const ast = @import("ast.zig"); const ZigNode = ast.Node; const ZigTag = ZigNode.Tag; const builtins = @import("builtins.zig"); const helpers = @import("helpers.zig"); const MacroTranslator = @import("MacroTranslator.zig"); const PatternList = @import("PatternList.zig"); const Scope = @import("Scope.zig"); pub const Error = std.mem.Allocator.Error; pub const MacroProcessingError = Error || error{UnexpectedMacroToken}; pub const TypeError = Error || error{UnsupportedType}; pub const TransError = TypeError || error{UnsupportedTranslation}; const Translator = @This(); /// The C AST to be translated. tree: *const Tree, /// The compilation corresponding to the AST. comp: *aro.Compilation, /// The Preprocessor that produced the source for `tree`. pp: *const aro.Preprocessor, gpa: mem.Allocator, arena: mem.Allocator, alias_list: Scope.AliasList, global_scope: *Scope.Root, /// Running number used for creating new unique identifiers. mangle_count: u32 = 0, /// Table of declarations for enum, struct, union and typedef types. type_decls: std.AutoArrayHashMapUnmanaged(Node.Index, []const u8) = .empty, /// Table of record decls that have been demoted to opaques. opaque_demotes: std.AutoHashMapUnmanaged(QualType, void) = .empty, /// Table of unnamed enums and records that are child types of typedefs. unnamed_typedefs: std.AutoHashMapUnmanaged(QualType, []const u8) = .empty, /// Table of anonymous record to generated field names. anonymous_record_field_names: std.AutoHashMapUnmanaged(struct { parent: QualType, field: QualType, }, []const u8) = .empty, /// 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) = .empty, /// 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) = .empty, /// Set of identifiers known to refer to typedef declarations. /// Used when parsing macros. typedefs: std.StringArrayHashMapUnmanaged(void) = .empty, /// The lhs lval of a compound assignment expression. compound_assign_dummy: ?ZigNode = null, pub fn getMangle(t: *Translator) u32 { t.mangle_count += 1; return t.mangle_count; } /// Convert an `aro.Source.Location` to a 'file:line:column' string. pub fn locStr(t: *Translator, loc: aro.Source.Location) ![]const u8 { const expanded = loc.expand(t.comp); const filename = expanded.path; const line = expanded.line_no; const col = expanded.col; return std.fmt.allocPrint(t.arena, "{s}:{d}:{d}", .{ filename, line, col }); } fn maybeSuppressResult(t: *Translator, used: ResultUsed, result: ZigNode) TransError!ZigNode { if (used == .used) return result; return ZigTag.discard.create(t.arena, .{ .should_skip = false, .value = result }); } pub fn addTopLevelDecl(t: *Translator, name: []const u8, decl_node: ZigNode) !void { const gop = try t.global_scope.sym_table.getOrPut(t.gpa, name); if (!gop.found_existing) { gop.value_ptr.* = decl_node; try t.global_scope.nodes.append(t.gpa, decl_node); } } fn fail( t: *Translator, err: anytype, source_loc: TokenIndex, comptime format: []const u8, args: anytype, ) (@TypeOf(err) || error{OutOfMemory}) { try t.warn(&t.global_scope.base, source_loc, format, args); return err; } pub fn failDecl( t: *Translator, scope: *Scope, tok_idx: TokenIndex, name: []const u8, comptime format: []const u8, args: anytype, ) Error!void { const loc = t.tree.tokens.items(.loc)[tok_idx]; return t.failDeclExtra(scope, loc, name, format, args); } pub fn failDeclExtra( t: *Translator, scope: *Scope, loc: aro.Source.Location, name: []const u8, comptime format: []const u8, args: anytype, ) Error!void { // location // pub const name = @compileError(msg); const fail_msg = try std.fmt.allocPrint(t.arena, format, args); const fail_decl = try ZigTag.fail_decl.create(t.arena, .{ .actual = name, .mangled = fail_msg, .local = scope.id != .root, }); const str = try t.locStr(loc); const location_comment = try std.fmt.allocPrint(t.arena, "// {s}", .{str}); const loc_node = try ZigTag.warning.create(t.arena, location_comment); if (scope.id == .root) { try t.addTopLevelDecl(name, fail_decl); try scope.appendNode(loc_node); } else { try scope.appendNode(fail_decl); try scope.appendNode(loc_node); const bs = try scope.findBlockScope(t); try bs.discardVariable(name); } } fn warn(t: *Translator, scope: *Scope, tok_idx: TokenIndex, comptime format: []const u8, args: anytype) !void { const loc = t.tree.tokens.items(.loc)[tok_idx]; const str = try t.locStr(loc); const value = try std.fmt.allocPrint(t.arena, "// {s}: warning: " ++ format, .{str} ++ args); try scope.appendNode(try ZigTag.warning.create(t.arena, value)); } pub const Options = struct { gpa: mem.Allocator, comp: *aro.Compilation, pp: *const aro.Preprocessor, tree: *const aro.Tree, }; pub fn translate(options: Options) mem.Allocator.Error![]u8 { const gpa = options.gpa; var arena_allocator = std.heap.ArenaAllocator.init(gpa); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); var translator: Translator = .{ .gpa = gpa, .arena = arena, .alias_list = .empty, .global_scope = try arena.create(Scope.Root), .comp = options.comp, .pp = options.pp, .tree = options.tree, }; translator.global_scope.* = Scope.Root.init(&translator); defer { translator.type_decls.deinit(gpa); translator.alias_list.deinit(gpa); translator.global_names.deinit(gpa); translator.weak_global_names.deinit(gpa); translator.opaque_demotes.deinit(gpa); translator.unnamed_typedefs.deinit(gpa); translator.anonymous_record_field_names.deinit(gpa); translator.typedefs.deinit(gpa); translator.global_scope.deinit(); } try translator.prepopulateGlobalNameTable(); try translator.transTopLevelDecls(); // Insert empty line before macros. try translator.global_scope.nodes.append(gpa, try ZigTag.warning.create(arena, "\n")); try translator.transMacros(); for (translator.alias_list.items) |alias| { if (!translator.global_scope.sym_table.contains(alias.alias)) { const node = try ZigTag.alias.create(arena, .{ .actual = alias.alias, .mangled = alias.name }); try translator.addTopLevelDecl(alias.alias, node); } } try translator.global_scope.processContainerMemberFns(); var allocating: std.Io.Writer.Allocating = .init(gpa); defer allocating.deinit(); allocating.writer.writeAll( \\const __root = @This(); \\pub const __builtin = @import("std").zig.c_translation.builtins; \\pub const __helpers = @import("std").zig.c_translation.helpers; \\ \\ ) catch return error.OutOfMemory; var zig_ast = try ast.render(gpa, translator.global_scope.nodes.items); defer { gpa.free(zig_ast.source); zig_ast.deinit(gpa); } zig_ast.render(gpa, &allocating.writer, .{}) catch return error.OutOfMemory; return allocating.toOwnedSlice(); } fn prepopulateGlobalNameTable(t: *Translator) !void { for (t.tree.root_decls.items) |decl| { switch (decl.get(t.tree)) { .typedef => |typedef_decl| { const decl_name = t.tree.tokSlice(typedef_decl.name_tok); try t.global_names.put(t.gpa, decl_name, {}); // Check for typedefs with unnamed enum/record child types. const base = typedef_decl.qt.base(t.comp); switch (base.type) { .@"enum" => |enum_ty| { if (enum_ty.name.lookup(t.comp)[0] != '(') continue; }, .@"struct", .@"union" => |record_ty| { if (record_ty.name.lookup(t.comp)[0] != '(') continue; }, else => continue, } const gop = try t.unnamed_typedefs.getOrPut(t.gpa, base.qt); if (gop.found_existing) { // One typedef can declare multiple names. // TODO Don't put this one in `decl_table` so it's processed later. continue; } gop.value_ptr.* = decl_name; }, .struct_decl, .union_decl, .struct_forward_decl, .union_forward_decl, .enum_decl, .enum_forward_decl, => { const decl_qt = decl.qt(t.tree); const prefix, const name = switch (decl_qt.base(t.comp).type) { .@"struct" => |struct_ty| .{ "struct", struct_ty.name.lookup(t.comp) }, .@"union" => |union_ty| .{ "union", union_ty.name.lookup(t.comp) }, .@"enum" => |enum_ty| .{ "enum", enum_ty.name.lookup(t.comp) }, else => unreachable, }; const prefixed_name = try std.fmt.allocPrint(t.arena, "{s}_{s}", .{ prefix, name }); // `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 t.weak_global_names.ensureUnusedCapacity(t.gpa, 2); t.weak_global_names.putAssumeCapacity(name, {}); t.weak_global_names.putAssumeCapacity(prefixed_name, {}); }, .function, .variable => { const decl_name = t.tree.tokSlice(decl.tok(t.tree)); try t.global_names.put(t.gpa, decl_name, {}); }, .static_assert => {}, .empty_decl => {}, .global_asm => {}, else => unreachable, } } for (t.pp.defines.keys(), t.pp.defines.values()) |name, macro| { if (macro.isBuiltin()) continue; if (!t.isSelfDefinedMacro(name, macro)) { try t.global_names.put(t.gpa, name, {}); } } } /// Determines whether macro is of the form: `#define FOO FOO` (Possibly with trailing tokens) /// Macros of this form will not be translated. fn isSelfDefinedMacro(t: *Translator, name: []const u8, macro: aro.Preprocessor.Macro) bool { if (macro.is_func) return false; if (macro.tokens.len < 1) return false; const first_tok = macro.tokens[0]; const source = t.comp.getSource(macro.loc.id); const slice = source.buf[first_tok.start..first_tok.end]; return std.mem.eql(u8, name, slice); } // ======================= // Declaration translation // ======================= fn transTopLevelDecls(t: *Translator) !void { for (t.tree.root_decls.items) |decl| { try t.transDecl(&t.global_scope.base, decl); } } fn transDecl(t: *Translator, scope: *Scope, decl: Node.Index) !void { switch (decl.get(t.tree)) { .typedef => |typedef_decl| { // Implicit typedefs are translated only if referenced. if (typedef_decl.implicit) return; try t.transTypeDef(scope, decl); }, .struct_decl, .union_decl => |record_decl| { try t.transRecordDecl(scope, record_decl.container_qt); }, .enum_decl => |enum_decl| { try t.transEnumDecl(scope, enum_decl.container_qt); }, .enum_field, .record_field, .struct_forward_decl, .union_forward_decl, .enum_forward_decl, => return, .function => |function| { if (function.definition) |definition| { return t.transFnDecl(scope, definition.get(t.tree).function); } try t.transFnDecl(scope, function); }, .variable => |variable| { if (variable.definition != null) return; try t.transVarDecl(scope, variable); }, .static_assert => |static_assert| { try t.transStaticAssert(&t.global_scope.base, static_assert); }, .global_asm => |global_asm| { try t.transGlobalAsm(&t.global_scope.base, global_asm); }, .empty_decl => {}, else => unreachable, } } pub const builtin_typedef_map = std.StaticStringMap([]const u8).initComptime(.{ .{ "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(t: *Translator, scope: *Scope, typedef_node: Node.Index) Error!void { const typedef_decl = typedef_node.get(t.tree).typedef; if (t.type_decls.get(typedef_node)) |_| return; // Avoid processing this decl twice const toplevel = scope.id == .root; const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(t) else undefined; var name: []const u8 = t.tree.tokSlice(typedef_decl.name_tok); try t.typedefs.put(t.gpa, name, {}); if (builtin_typedef_map.get(name)) |builtin| { return t.type_decls.putNoClobber(t.gpa, typedef_node, builtin); } if (!toplevel) name = try bs.makeMangledName(name); try t.type_decls.putNoClobber(t.gpa, typedef_node, name); const typedef_loc = typedef_decl.name_tok; const init_node = t.transType(scope, typedef_decl.qt, typedef_loc) catch |err| switch (err) { error.UnsupportedType => { return t.failDecl(scope, typedef_loc, name, "unable to resolve typedef child type", .{}); }, error.OutOfMemory => |e| return e, }; const payload = try t.arena.create(ast.Payload.SimpleVarDecl); payload.* = .{ .base = .{ .tag = if (toplevel) .pub_var_simple else .var_simple }, .data = .{ .name = name, .init = init_node, }, }; const node = ZigNode.initPayload(&payload.base); if (toplevel) { try t.addTopLevelDecl(name, node); } else { try scope.appendNode(node); try bs.discardVariable(name); } } fn mangleWeakGlobalName(t: *Translator, want_name: []const u8) Error![]const u8 { var cur_name = want_name; if (!t.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(t.arena, "{s}_{d}", .{ want_name, t.getMangle() }); } while (t.global_names.contains(cur_name)) { cur_name = try std.fmt.allocPrint(t.arena, "{s}_{d}", .{ want_name, t.getMangle() }); } return cur_name; } fn transRecordDecl(t: *Translator, scope: *Scope, record_qt: QualType) Error!void { const base = record_qt.base(t.comp); const record_ty = switch (base.type) { .@"struct", .@"union" => |record_ty| record_ty, else => unreachable, }; if (t.type_decls.get(record_ty.decl_node)) |_| return; // Avoid processing this decl twice const toplevel = scope.id == .root; const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(t) else undefined; const container_kind: ZigTag = if (base.type == .@"union") .@"union" else .@"struct"; const container_kind_name = @tagName(container_kind); var bare_name = record_ty.name.lookup(t.comp); var is_unnamed = false; var name = bare_name; if (t.unnamed_typedefs.get(base.qt)) |typedef_name| { bare_name = typedef_name; name = typedef_name; } else { if (record_ty.isAnonymous(t.comp)) { bare_name = try std.fmt.allocPrint(t.arena, "unnamed_{d}", .{t.getMangle()}); is_unnamed = true; } name = try std.fmt.allocPrint(t.arena, "{s}_{s}", .{ container_kind_name, bare_name }); if (toplevel and !is_unnamed) { name = try t.mangleWeakGlobalName(name); } } if (!toplevel) name = try bs.makeMangledName(name); try t.type_decls.putNoClobber(t.gpa, record_ty.decl_node, name); const is_pub = toplevel and !is_unnamed; const init_node = init: { if (record_ty.layout == null) { try t.opaque_demotes.put(t.gpa, base.qt, {}); break :init ZigTag.opaque_literal.init(); } var fields: std.ArrayList(ast.Payload.Container.Field) = .empty; defer fields.deinit(t.gpa); try fields.ensureUnusedCapacity(t.gpa, record_ty.fields.len); var functions: std.ArrayList(ZigNode) = .empty; defer functions.deinit(t.gpa); var unnamed_field_count: u32 = 0; // If a record doesn't have any attributes that would affect the alignment and // layout, then we can just use a simple `extern` type. If it does have attributes, // then we need to inspect the layout and assign an `align` value for each field. const has_alignment_attributes = aligned: { if (record_qt.hasAttribute(t.comp, .@"packed")) break :aligned true; if (record_qt.hasAttribute(t.comp, .aligned)) break :aligned true; for (record_ty.fields) |field| { const field_attrs = field.attributes(t.comp); for (field_attrs) |field_attr| { switch (field_attr.tag) { .@"packed", .aligned => break :aligned true, else => {}, } } } break :aligned false; }; const head_field_alignment: ?c_uint = if (has_alignment_attributes) t.headFieldAlignment(record_ty) else null; for (record_ty.fields, 0..) |field, field_index| { const field_loc = field.name_tok; // Demote record to opaque if it contains a bitfield if (field.bit_width != .null) { try t.opaque_demotes.put(t.gpa, base.qt, {}); try t.warn(scope, field_loc, "{s} demoted to opaque type - has bitfield", .{container_kind_name}); break :init ZigTag.opaque_literal.init(); } // Demote record to opaque if it contains an opaque field if (t.typeWasDemotedToOpaque(field.qt)) { try t.opaque_demotes.put(t.gpa, base.qt, {}); try t.warn(scope, field_loc, "{s} demoted to opaque type - has opaque field", .{container_kind_name}); break :init ZigTag.opaque_literal.init(); } var field_name = field.name.lookup(t.comp); if (field.name_tok == 0) { field_name = try std.fmt.allocPrint(t.arena, "unnamed_{d}", .{unnamed_field_count}); unnamed_field_count += 1; try t.anonymous_record_field_names.put(t.gpa, .{ .parent = base.qt, .field = field.qt, }, field_name); } const field_alignment = if (has_alignment_attributes) t.alignmentForField(record_ty, head_field_alignment, field_index) else null; const field_type = field_type: { // Check if this is a flexible array member. flexible: { if (field_index != record_ty.fields.len - 1 and container_kind != .@"union") break :flexible; const array_ty = field.qt.get(t.comp, .array) orelse break :flexible; if (array_ty.len != .incomplete and (array_ty.len != .fixed or array_ty.len.fixed != 0)) break :flexible; const elem_type = t.transType(scope, array_ty.elem, field_loc) catch |err| switch (err) { error.UnsupportedType => break :flexible, else => |e| return e, }; const zero_array = try ZigTag.array_type.create(t.arena, .{ .len = 0, .elem_type = elem_type }); const member_name = field_name; field_name = try std.fmt.allocPrint(t.arena, "_{s}", .{field_name}); const member = try t.createFlexibleMemberFn(member_name, field_name); try functions.append(t.gpa, member); break :field_type zero_array; } break :field_type t.transType(scope, field.qt, field_loc) catch |err| switch (err) { error.UnsupportedType => { try t.opaque_demotes.put(t.gpa, base.qt, {}); try t.warn(scope, field.name_tok, "{s} demoted to opaque type - unable to translate type of field {s}", .{ container_kind_name, field_name, }); break :init ZigTag.opaque_literal.init(); }, else => |e| return e, }; }; // 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 (container_kind == .@"struct") try t.createZeroValueNode(field.qt, field_type, .no_as) else null; fields.appendAssumeCapacity(.{ .name = field_name, .type = field_type, .alignment = field_alignment, .default_value = default_value, }); } // A record is empty if it has no fields or only flexible array fields. if (record_ty.fields.len == functions.items.len and t.comp.target.os.tag == .windows and t.comp.target.abi == .msvc) { // In MSVC empty records have the same size as their alignment. const padding_bits = record_ty.layout.?.size_bits; const alignment_bits = record_ty.layout.?.field_alignment_bits; try fields.append(t.gpa, .{ .name = "_padding", .type = try ZigTag.type.create(t.arena, try std.fmt.allocPrint(t.arena, "u{d}", .{padding_bits})), .alignment = @divExact(alignment_bits, 8), .default_value = if (container_kind == .@"struct") ZigTag.zero_literal.init() else null, }); } const container_payload = try t.arena.create(ast.Payload.Container); container_payload.* = .{ .base = .{ .tag = container_kind }, .data = .{ .layout = .@"extern", .fields = try t.arena.dupe(ast.Payload.Container.Field, fields.items), .decls = try t.arena.dupe(ZigNode, functions.items), }, }; break :init ZigNode.initPayload(&container_payload.base); }; const payload = try t.arena.create(ast.Payload.SimpleVarDecl); payload.* = .{ .base = .{ .tag = if (is_pub) .pub_var_simple else .var_simple }, .data = .{ .name = name, .init = init_node, }, }; const node = ZigNode.initPayload(&payload.base); if (toplevel) { try t.addTopLevelDecl(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 !t.global_names.contains(bare_name) and t.weak_global_names.contains(bare_name)) try t.alias_list.append(t.gpa, .{ .alias = bare_name, .name = name }); try t.global_scope.container_member_fns_map.put(t.gpa, record_qt, .{ .container_decl_ptr = &payload.data.init, }); } else { try scope.appendNode(node); try bs.discardVariable(name); } } fn transFnDecl(t: *Translator, scope: *Scope, function: Node.Function) Error!void { const func_ty = function.qt.get(t.comp, .func).?; const is_pub = scope.id == .root; const fn_name = t.tree.tokSlice(function.name_tok); if (scope.getAlias(fn_name) != null or t.global_scope.containsNow(fn_name)) return; // Avoid processing this decl twice const fn_decl_loc = function.name_tok; const has_body = function.body != null and func_ty.kind != .variadic; if (function.body != null and func_ty.kind == .variadic) { try t.warn(scope, function.name_tok, "TODO unable to translate variadic function, demoted to extern", .{}); } const is_always_inline = has_body and function.qt.getAttribute(t.comp, .always_inline) != null; const proto_ctx: FnProtoContext = .{ .fn_name = fn_name, .is_always_inline = is_always_inline, .is_extern = !has_body, .is_export = !function.static and has_body and !is_always_inline and !function.@"inline", .is_pub = is_pub, .has_body = has_body, .cc = if (function.qt.getAttribute(t.comp, .calling_convention)) |some| switch (some.cc) { .c => .c, .stdcall => .x86_stdcall, .thiscall => .x86_thiscall, .fastcall => .x86_fastcall, .regcall => .x86_regcall, .riscv_vector => .riscv_vector, .aarch64_sve_pcs => .aarch64_sve_pcs, .aarch64_vector_pcs => .aarch64_vfabi, .arm_aapcs => .arm_aapcs, .arm_aapcs_vfp => .arm_aapcs_vfp, .vectorcall => switch (t.comp.target.cpu.arch) { .x86 => .x86_vectorcall, .aarch64, .aarch64_be => .aarch64_vfabi, else => .c, }, .x86_64_sysv => .x86_64_sysv, .x86_64_win => .x86_64_win, } else .c, }; const proto_node = t.transFnType(&t.global_scope.base, function.qt, func_ty, fn_decl_loc, proto_ctx) catch |err| switch (err) { error.UnsupportedType => { return t.failDecl(scope, fn_decl_loc, fn_name, "unable to resolve prototype of function", .{}); }, error.OutOfMemory => |e| return e, }; const proto_payload = proto_node.castTag(.func).?; if (!has_body) { if (scope.id != .root) { const bs: *Scope.Block = try scope.findBlockScope(t); const mangled_name = try bs.createMangledName(fn_name, false, Scope.Block.extern_local_prefix); const wrapped = try ZigTag.wrapped_local.create(t.arena, .{ .name = mangled_name, .init = proto_node }); try scope.appendNode(wrapped); try bs.discardVariable(mangled_name); return; } try t.global_scope.addMemberFunction(func_ty, proto_payload); return t.addTopLevelDecl(fn_name, proto_node); } // actual function definition with body const body_stmt = function.body.?.get(t.tree).compound_stmt; var block_scope = try Scope.Block.init(t, &t.global_scope.base, false); block_scope.return_type = func_ty.return_type; defer block_scope.deinit(); var param_id: c_uint = 0; for (proto_payload.data.params, func_ty.params) |*param, param_info| { const param_name = param.name orelse { proto_payload.data.is_extern = true; proto_payload.data.is_export = false; proto_payload.data.is_inline = false; try t.warn(&t.global_scope.base, fn_decl_loc, "function {s} parameter has no name, demoted to extern", .{fn_name}); return t.addTopLevelDecl(fn_name, proto_node); }; const is_const = param_info.qt.@"const"; const mangled_param_name = try block_scope.makeMangledName(param_name); param.name = mangled_param_name; if (!is_const) { const bare_arg_name = try std.fmt.allocPrint(t.arena, "arg_{s}", .{mangled_param_name}); const arg_name = try block_scope.makeMangledName(bare_arg_name); param.name = arg_name; const redecl_node = try ZigTag.arg_redecl.create(t.arena, .{ .actual = mangled_param_name, .mangled = arg_name }); try block_scope.statements.append(t.gpa, redecl_node); } try block_scope.discardVariable(mangled_param_name); param_id += 1; } t.transCompoundStmtInline(body_stmt, &block_scope) catch |err| switch (err) { error.OutOfMemory => |e| return e, error.UnsupportedTranslation, error.UnsupportedType, => { proto_payload.data.is_extern = true; proto_payload.data.is_export = false; proto_payload.data.is_inline = false; try t.warn(&t.global_scope.base, fn_decl_loc, "unable to translate function, demoted to extern", .{}); return t.addTopLevelDecl(fn_name, proto_node); }, }; try t.global_scope.addMemberFunction(func_ty, proto_payload); proto_payload.data.body = try block_scope.complete(); return t.addTopLevelDecl(fn_name, proto_node); } fn transVarDecl(t: *Translator, scope: *Scope, variable: Node.Variable) Error!void { const base_name = t.tree.tokSlice(variable.name_tok); const toplevel = scope.id == .root; const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(t) else undefined; const name, const use_base_name = blk: { if (toplevel) break :blk .{ base_name, false }; // Local extern and static variables are wrapped in a struct. const prefix: ?[]const u8 = switch (variable.storage_class) { .@"extern" => Scope.Block.extern_local_prefix, .static => Scope.Block.static_local_prefix, else => null, }; break :blk .{ try bs.createMangledName(base_name, false, prefix), prefix != null }; }; if (t.typeWasDemotedToOpaque(variable.qt)) { if (variable.storage_class != .@"extern" and scope.id == .root) { return t.failDecl(scope, variable.name_tok, name, "non-extern variable has opaque type", .{}); } else { return t.failDecl(scope, variable.name_tok, name, "local variable has opaque type", .{}); } } const type_node = (if (variable.initializer) |init| t.transTypeInit(scope, variable.qt, init, variable.name_tok) else t.transType(scope, variable.qt, variable.name_tok)) catch |err| switch (err) { error.UnsupportedType => { return t.failDecl(scope, variable.name_tok, name, "unable to translate variable declaration type", .{}); }, else => |e| return e, }; const array_ty = variable.qt.get(t.comp, .array); var is_const = variable.qt.@"const" or (array_ty != null and array_ty.?.elem.@"const"); var is_extern = variable.storage_class == .@"extern"; const init_node = init: { if (variable.initializer) |init| { const maybe_literal = init.get(t.tree); const init_node = (if (maybe_literal == .string_literal_expr) t.transStringLiteralInitializer(init, maybe_literal.string_literal_expr, type_node) else t.transExprCoercing(scope, init, .used)) catch |err| switch (err) { error.UnsupportedTranslation, error.UnsupportedType => { return t.failDecl(scope, variable.name_tok, name, "unable to resolve var init expr", .{}); }, else => |e| return e, }; if (!variable.qt.is(t.comp, .bool) and init_node.isBoolRes()) { break :init try ZigTag.int_from_bool.create(t.arena, init_node); } else { break :init init_node; } } if (variable.storage_class == .@"extern") { if (array_ty != null and array_ty.?.len == .incomplete) { // 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. // Since this is really a pointer to the underlying data, we tweak a few properties. is_extern = false; is_const = true; const name_str = try std.fmt.allocPrint(t.arena, "\"{s}\"", .{base_name}); break :init try ZigTag.builtin_extern.create(t.arena, .{ .type = type_node, .name = try ZigTag.string_literal.create(t.arena, name_str), }); } break :init null; } if (toplevel or variable.storage_class == .static or variable.thread_local) { // The C language specification states that variables with static or threadlocal // storage without an initializer are initialized to a zero value. break :init try t.createZeroValueNode(variable.qt, type_node, .no_as); } break :init ZigTag.undefined_literal.init(); }; const linksection_string = blk: { if (variable.qt.getAttribute(t.comp, .section)) |section| { break :blk t.comp.interner.get(section.name.ref()).bytes; } break :blk null; }; // TODO actually set with @export/@extern const linkage = variable.qt.linkage(t.comp); if (linkage != .strong) { try t.warn(scope, variable.name_tok, "TODO {s} linkage ignored", .{@tagName(linkage)}); } const alignment: ?c_uint = variable.qt.requestedAlignment(t.comp) orelse null; var node = try ZigTag.var_decl.create(t.arena, .{ .is_pub = toplevel, .is_const = is_const, .is_extern = is_extern, .is_export = toplevel and variable.storage_class == .auto and linkage == .strong, .is_threadlocal = variable.thread_local, .linksection_string = linksection_string, .alignment = alignment, .name = if (use_base_name) base_name else name, .type = type_node, .init = init_node, }); if (toplevel) { try t.addTopLevelDecl(name, node); } else { if (use_base_name) { node = try ZigTag.wrapped_local.create(t.arena, .{ .name = name, .init = node }); } try scope.appendNode(node); try bs.discardVariable(name); if (variable.qt.getAttribute(t.comp, .cleanup)) |cleanup_attr| { const cleanup_fn_name = t.tree.tokSlice(cleanup_attr.function.tok); const mangled_fn_name = scope.getAlias(cleanup_fn_name) orelse cleanup_fn_name; const fn_id = try ZigTag.identifier.create(t.arena, mangled_fn_name); const varname = try ZigTag.identifier.create(t.arena, name); const args = try t.arena.alloc(ZigNode, 1); args[0] = try ZigTag.address_of.create(t.arena, varname); const cleanup_call = try ZigTag.call.create(t.arena, .{ .lhs = fn_id, .args = args }); const discard = try ZigTag.discard.create(t.arena, .{ .should_skip = false, .value = cleanup_call }); const deferred_cleanup = try ZigTag.@"defer".create(t.arena, discard); try bs.statements.append(t.gpa, deferred_cleanup); } } } fn transEnumDecl(t: *Translator, scope: *Scope, enum_qt: QualType) Error!void { const base = enum_qt.base(t.comp); const enum_ty = base.type.@"enum"; if (t.type_decls.get(enum_ty.decl_node)) |_| return; // Avoid processing this decl twice const toplevel = scope.id == .root; const bs: *Scope.Block = if (!toplevel) try scope.findBlockScope(t) else undefined; var bare_name = enum_ty.name.lookup(t.comp); var is_unnamed = false; var name = bare_name; if (t.unnamed_typedefs.get(base.qt)) |typedef_name| { bare_name = typedef_name; name = typedef_name; } else { if (enum_ty.isAnonymous(t.comp)) { bare_name = try std.fmt.allocPrint(t.arena, "unnamed_{d}", .{t.getMangle()}); is_unnamed = true; } name = try std.fmt.allocPrint(t.arena, "enum_{s}", .{bare_name}); } if (!toplevel) name = try bs.makeMangledName(name); try t.type_decls.putNoClobber(t.gpa, enum_ty.decl_node, name); const enum_type_node = if (!base.qt.hasIncompleteSize(t.comp)) blk: { const enum_decl = enum_ty.decl_node.get(t.tree).enum_decl; for (enum_ty.fields, enum_decl.fields) |field, field_node| { var enum_val_name = field.name.lookup(t.comp); if (!toplevel) { enum_val_name = try bs.makeMangledName(enum_val_name); } const enum_const_type_node: ?ZigNode = t.transType(scope, field.qt, field.name_tok) catch |err| switch (err) { error.UnsupportedType => null, else => |e| return e, }; const val = t.tree.value_map.get(field_node).?; const enum_const_def = try ZigTag.enum_constant.create(t.arena, .{ .name = enum_val_name, .is_public = toplevel, .type = enum_const_type_node, .value = try t.createIntNode(val), }); if (toplevel) try t.addTopLevelDecl(enum_val_name, enum_const_def) else { try scope.appendNode(enum_const_def); try bs.discardVariable(enum_val_name); } } break :blk t.transType(scope, enum_ty.tag.?, enum_decl.name_or_kind_tok) catch |err| switch (err) { error.UnsupportedType => { return t.failDecl(scope, enum_decl.name_or_kind_tok, name, "unable to translate enum integer type", .{}); }, else => |e| return e, }; } else blk: { try t.opaque_demotes.put(t.gpa, base.qt, {}); break :blk ZigTag.opaque_literal.init(); }; const is_pub = toplevel and !is_unnamed; const payload = try t.arena.create(ast.Payload.SimpleVarDecl); payload.* = .{ .base = .{ .tag = if (is_pub) .pub_var_simple else .var_simple }, .data = .{ .init = enum_type_node, .name = name, }, }; const node = ZigNode.initPayload(&payload.base); if (toplevel) { try t.addTopLevelDecl(name, node); if (!is_unnamed) try t.alias_list.append(t.gpa, .{ .alias = bare_name, .name = name }); } else { try scope.appendNode(node); try bs.discardVariable(name); } } fn transStaticAssert(t: *Translator, scope: *Scope, static_assert: Node.StaticAssert) Error!void { const condition = t.transExpr(scope, static_assert.cond, .used) catch |err| switch (err) { error.UnsupportedTranslation, error.UnsupportedType => { return try t.warn(&t.global_scope.base, static_assert.cond.tok(t.tree), "unable to translate _Static_assert condition", .{}); }, error.OutOfMemory => |e| return e, }; // generate @compileError message that matches C compiler output const diagnostic = if (static_assert.message) |message| str: { // Aro guarantees this to be a string literal. const str_val = t.tree.value_map.get(message).?; const str_qt = message.qt(t.tree); const bytes = t.comp.interner.get(str_val.ref()).bytes; var allocating: std.Io.Writer.Allocating = .init(t.gpa); defer allocating.deinit(); allocating.writer.writeAll("\"static assertion failed \\") catch return error.OutOfMemory; aro.Value.printString(bytes, str_qt, t.comp, &allocating.writer) catch return error.OutOfMemory; allocating.writer.end -= 1; // printString adds a terminating " so we need to remove it allocating.writer.writeAll("\\\"\"") catch return error.OutOfMemory; break :str try ZigTag.string_literal.create(t.arena, try t.arena.dupe(u8, allocating.written())); } else try ZigTag.string_literal.create(t.arena, "\"static assertion failed\""); const assert_node = try ZigTag.static_assert.create(t.arena, .{ .lhs = condition, .rhs = diagnostic }); try scope.appendNode(assert_node); } fn transGlobalAsm(t: *Translator, scope: *Scope, global_asm: Node.GlobalAsm) Error!void { const asm_string = t.tree.value_map.get(global_asm.asm_str).?; const bytes = t.comp.interner.get(asm_string.ref()).bytes; var allocating: std.Io.Writer.Allocating = try .initCapacity(t.gpa, bytes.len); defer allocating.deinit(); aro.Value.printString(bytes, global_asm.asm_str.qt(t.tree), t.comp, &allocating.writer) catch return error.OutOfMemory; const str_node = try ZigTag.string_literal.create(t.arena, try t.arena.dupe(u8, allocating.written())); const asm_node = try ZigTag.asm_simple.create(t.arena, str_node); const block = try ZigTag.block_single.create(t.arena, asm_node); const comptime_node = try ZigTag.@"comptime".create(t.arena, block); try scope.appendNode(comptime_node); } // ================ // Type translation // ================ fn getTypeStr(t: *Translator, qt: QualType) ![]const u8 { var allocating: std.Io.Writer.Allocating = .init(t.gpa); defer allocating.deinit(); qt.print(t.comp, &allocating.writer) catch return error.OutOfMemory; return t.arena.dupe(u8, allocating.written()); } fn transType(t: *Translator, scope: *Scope, qt: QualType, source_loc: TokenIndex) TypeError!ZigNode { loop: switch (qt.type(t.comp)) { .atomic => { const type_name = try t.getTypeStr(qt); return t.fail(error.UnsupportedType, source_loc, "TODO support atomic type: '{s}'", .{type_name}); }, .void => return ZigTag.type.create(t.arena, "anyopaque"), .bool => return ZigTag.type.create(t.arena, "bool"), .int => |int_ty| switch (int_ty) { //.char => return ZigTag.type.create(t.arena, "c_char"), // TODO: this is the preferred translation .char => return ZigTag.type.create(t.arena, "u8"), .schar => return ZigTag.type.create(t.arena, "i8"), .uchar => return ZigTag.type.create(t.arena, "u8"), .short => return ZigTag.type.create(t.arena, "c_short"), .ushort => return ZigTag.type.create(t.arena, "c_ushort"), .int => return ZigTag.type.create(t.arena, "c_int"), .uint => return ZigTag.type.create(t.arena, "c_uint"), .long => return ZigTag.type.create(t.arena, "c_long"), .ulong => return ZigTag.type.create(t.arena, "c_ulong"), .long_long => return ZigTag.type.create(t.arena, "c_longlong"), .ulong_long => return ZigTag.type.create(t.arena, "c_ulonglong"), .int128 => return ZigTag.type.create(t.arena, "i128"), .uint128 => return ZigTag.type.create(t.arena, "u128"), }, .float => |float_ty| switch (float_ty) { .fp16, .float16 => return ZigTag.type.create(t.arena, "f16"), .float => return ZigTag.type.create(t.arena, "f32"), .double => return ZigTag.type.create(t.arena, "f64"), .long_double => return ZigTag.type.create(t.arena, "c_longdouble"), .float128 => return ZigTag.type.create(t.arena, "f128"), .bf16, .float32, .float64, .float32x, .float64x, .float128x, .dfloat32, .dfloat64, .dfloat128, .dfloat64x, => return t.fail(error.UnsupportedType, source_loc, "TODO support float type: '{s}'", .{try t.getTypeStr(qt)}), }, .pointer => |pointer_ty| { const child_qt = pointer_ty.child; const is_fn_proto = child_qt.is(t.comp, .func); const is_const = is_fn_proto or child_qt.@"const"; const is_volatile = child_qt.@"volatile"; const elem_type = try t.transType(scope, child_qt, source_loc); const ptr_info: @FieldType(ast.Payload.Pointer, "data") = .{ .is_const = is_const, .is_volatile = is_volatile, .elem_type = elem_type, .is_allowzero = false, }; if (is_fn_proto or t.typeIsOpaque(child_qt) or t.typeWasDemotedToOpaque(child_qt)) { const ptr = try ZigTag.single_pointer.create(t.arena, ptr_info); return ZigTag.optional_type.create(t.arena, ptr); } return ZigTag.c_pointer.create(t.arena, ptr_info); }, .array => |array_ty| { const elem_qt = array_ty.elem; switch (array_ty.len) { .incomplete, .unspecified_variable => { const elem_type = try t.transType(scope, elem_qt, source_loc); return ZigTag.c_pointer.create(t.arena, .{ .is_const = elem_qt.@"const", .is_volatile = elem_qt.@"volatile", .is_allowzero = false, .elem_type = elem_type, }); }, .fixed, .static => |len| { const elem_type = try t.transType(scope, elem_qt, source_loc); return ZigTag.array_type.create(t.arena, .{ .len = len, .elem_type = elem_type }); }, .variable => return t.fail(error.UnsupportedType, source_loc, "VLA unsupported '{s}'", .{try t.getTypeStr(qt)}), } }, .func => |func_ty| return t.transFnType(scope, qt, func_ty, source_loc, .{}), .@"struct", .@"union" => |record_ty| { var trans_scope = scope; if (!record_ty.isAnonymous(t.comp)) { if (t.weak_global_names.contains(record_ty.name.lookup(t.comp))) trans_scope = &t.global_scope.base; } try t.transRecordDecl(trans_scope, qt); const name = t.type_decls.get(record_ty.decl_node).?; return ZigTag.identifier.create(t.arena, name); }, .@"enum" => |enum_ty| { var trans_scope = scope; const is_anonymous = enum_ty.isAnonymous(t.comp); if (!is_anonymous) { if (t.weak_global_names.contains(enum_ty.name.lookup(t.comp))) trans_scope = &t.global_scope.base; } try t.transEnumDecl(trans_scope, qt); const name = t.type_decls.get(enum_ty.decl_node).?; return ZigTag.identifier.create(t.arena, name); }, .typedef => |typedef_ty| { var trans_scope = scope; const typedef_name = typedef_ty.name.lookup(t.comp); if (builtin_typedef_map.get(typedef_name)) |builtin| return ZigTag.type.create(t.arena, builtin); if (t.global_names.contains(typedef_name)) trans_scope = &t.global_scope.base; try t.transTypeDef(trans_scope, typedef_ty.decl_node); const name = t.type_decls.get(typedef_ty.decl_node).?; return ZigTag.identifier.create(t.arena, name); }, .attributed => |attributed_ty| continue :loop attributed_ty.base.type(t.comp), .typeof => |typeof_ty| continue :loop typeof_ty.base.type(t.comp), .vector => |vector_ty| { const len = try t.createNumberNode(vector_ty.len, .int); const elem_type = try t.transType(scope, vector_ty.elem, source_loc); return ZigTag.vector.create(t.arena, .{ .lhs = len, .rhs = elem_type }); }, else => return t.fail(error.UnsupportedType, source_loc, "unsupported type: '{s}'", .{try t.getTypeStr(qt)}), } } /// Look ahead through the fields of the record to determine what the alignment of the record /// would be without any align/packed/etc. attributes. This helps us determine whether or not /// the fields with 0 offset need an `align` qualifier. Strictly speaking, we could just /// pedantically assign those fields the same alignment as the parent's pointer alignment, /// but this helps the generated code to be a little less verbose. fn headFieldAlignment(t: *Translator, record_decl: aro.Type.Record) ?c_uint { const bits_per_byte = 8; const parent_ptr_alignment_bits = record_decl.layout.?.pointer_alignment_bits; const parent_ptr_alignment = parent_ptr_alignment_bits / bits_per_byte; var max_field_alignment_bits: u64 = 0; for (record_decl.fields) |field| max_field_alignment_bits = @max(max_field_alignment_bits, bits_per_byte * field.qt.alignof(t.comp)); if (max_field_alignment_bits != parent_ptr_alignment_bits) { return parent_ptr_alignment; } else { return null; } } /// This function inspects the generated layout of a record to determine the alignment for a /// particular field. This approach is necessary because unlike Zig, a C compiler is not /// required to fulfill the requested alignment, which means we'd risk generating different code /// if we only look at the user-requested alignment. /// /// Returns a ?c_uint to match Clang's behavior of using c_uint. The return type can be changed /// after the Clang frontend for translate-c is removed. A null value indicates that a field is /// 'naturally aligned'. fn alignmentForField( t: *Translator, record_decl: aro.Type.Record, head_field_alignment: ?c_uint, field_index: usize, ) ?c_uint { const fields = record_decl.fields; assert(fields.len != 0); const field = fields[field_index]; const bits_per_byte = 8; const parent_ptr_alignment_bits = record_decl.layout.?.pointer_alignment_bits; const parent_ptr_alignment = parent_ptr_alignment_bits / bits_per_byte; // bitfields aren't supported yet. Until support is added, records with bitfields // should be demoted to opaque, and this function shouldn't be called for them. if (field.bit_width != .null) { @panic("TODO: add bitfield support for records"); } const field_offset_bits: u64 = field.layout.offset_bits; const field_size_bits: u64 = field.layout.size_bits; // Fields with zero width always have an alignment of 1 if (field_size_bits == 0) { return 1; } // Fields with 0 offset inherit the parent's pointer alignment. if (field_offset_bits == 0) { return head_field_alignment; } // Records have a natural alignment when used as a field, and their size is // a multiple of this alignment value. For all other types, the natural alignment // is their size. const field_natural_alignment_bits: u64 = bits_per_byte * field.qt.alignof(t.comp); const rem_bits = field_offset_bits % field_natural_alignment_bits; // If there's a remainder, then the alignment is smaller than the field's // natural alignment if (rem_bits > 0) { const rem_alignment = rem_bits / bits_per_byte; if (rem_alignment > 0 and std.math.isPowerOfTwo(rem_alignment)) { const actual_alignment = @min(rem_alignment, parent_ptr_alignment); return @as(c_uint, @truncate(actual_alignment)); } else { return 1; } } // A field may have an offset which positions it to be naturally aligned, but the // parent's pointer alignment determines if this is actually true, so we take the minimum // value. // For example, a float field (4 bytes wide) with a 4 byte offset is positioned to have natural // alignment, but if the parent pointer alignment is 2, then the actual alignment of the // float is 2. const field_natural_alignment: u64 = field_natural_alignment_bits / bits_per_byte; const offset_alignment = field_offset_bits / bits_per_byte; const possible_alignment = @min(parent_ptr_alignment, offset_alignment); if (possible_alignment == field_natural_alignment) { return null; } else if (possible_alignment < field_natural_alignment) { if (std.math.isPowerOfTwo(possible_alignment)) { return possible_alignment; } else { return 1; } } else { // possible_alignment > field_natural_alignment // Here, the field is positioned be at a higher alignment than it's natural alignment. This means we // need to determine whether it's a specified alignment. We can determine that from the padding preceding // the field. const padding_from_prev_field: u64 = blk: { if (field_offset_bits != 0) { const previous_field = fields[field_index - 1]; break :blk (field_offset_bits - previous_field.layout.offset_bits) - previous_field.layout.size_bits; } else { break :blk 0; } }; if (padding_from_prev_field < field_natural_alignment_bits) { return null; } else { return possible_alignment; } } } const FnProtoContext = struct { is_pub: bool = false, is_export: bool = false, is_extern: bool = false, is_always_inline: bool = false, fn_name: ?[]const u8 = null, has_body: bool = false, cc: ast.Payload.Func.CallingConvention = .c, }; fn transFnType( t: *Translator, scope: *Scope, func_qt: QualType, func_ty: aro.Type.Func, source_loc: TokenIndex, ctx: FnProtoContext, ) !ZigNode { const param_count: usize = func_ty.params.len; const fn_params = try t.arena.alloc(ast.Payload.Param, param_count); for (func_ty.params, fn_params) |param_info, *param_node| { const param_qt = param_info.qt; const is_noalias = param_qt.restrict; const param_name: ?[]const u8 = if (param_info.name == .empty) null else param_info.name.lookup(t.comp); const type_node = try t.transType(scope, param_qt, param_info.name_tok); param_node.* = .{ .is_noalias = is_noalias, .name = param_name, .type = type_node, }; } const linksection_string = blk: { if (func_qt.getAttribute(t.comp, .section)) |section| { break :blk t.comp.interner.get(section.name.ref()).bytes; } break :blk null; }; const alignment: ?c_uint = func_qt.requestedAlignment(t.comp) orelse null; const explicit_callconv = if ((ctx.is_always_inline or ctx.is_export or ctx.is_extern) and ctx.cc == .c) null else ctx.cc; const return_type_node = blk: { if (func_qt.getAttribute(t.comp, .noreturn) != null) { break :blk ZigTag.noreturn_type.init(); } else { const return_qt = func_ty.return_type; if (return_qt.is(t.comp, .void)) { // convert primitive anyopaque to actual void (only for return type) break :blk ZigTag.void_type.init(); } else { break :blk t.transType(scope, return_qt, source_loc) catch |err| switch (err) { error.UnsupportedType => { try t.warn(scope, source_loc, "unsupported function proto return type", .{}); return err; }, error.OutOfMemory => |e| return e, }; } } }; // TODO actually set with @export/@extern const linkage = func_qt.linkage(t.comp); if (linkage != .strong) { try t.warn(scope, source_loc, "TODO {s} linkage ignored", .{@tagName(linkage)}); } const payload = try t.arena.create(ast.Payload.Func); payload.* = .{ .base = .{ .tag = .func }, .data = .{ .is_pub = ctx.is_pub, .is_extern = ctx.is_extern, .is_export = ctx.is_export and linkage == .strong, .is_inline = ctx.is_always_inline, .is_var_args = switch (func_ty.kind) { .normal => false, .variadic => true, .old_style => !ctx.is_export and !ctx.is_always_inline and !ctx.has_body, }, .name = ctx.fn_name, .linksection_string = linksection_string, .explicit_callconv = explicit_callconv, .params = fn_params, .return_type = return_type_node, .body = null, .alignment = alignment, }, }; return ZigNode.initPayload(&payload.base); } /// Produces a Zig AST node by translating a Type, respecting the width, but modifying the signed-ness. /// Asserts the type is an integer. fn transTypeIntWidthOf(t: *Translator, qt: QualType, is_signed: bool) TypeError!ZigNode { return ZigTag.type.create(t.arena, loop: switch (qt.base(t.comp).type) { .int => |int_ty| switch (int_ty) { .char, .schar, .uchar => if (is_signed) "i8" else "u8", .short, .ushort => if (is_signed) "c_short" else "c_ushort", .int, .uint => if (is_signed) "c_int" else "c_uint", .long, .ulong => if (is_signed) "c_long" else "c_ulong", .long_long, .ulong_long => if (is_signed) "c_longlong" else "c_ulonglong", .int128, .uint128 => if (is_signed) "i128" else "u128", }, .bit_int => |bit_int_ty| try std.fmt.allocPrint(t.arena, "{s}{d}", .{ if (is_signed) "i" else "u", bit_int_ty.bits, }), .@"enum" => |enum_ty| blk: { const tag_ty = enum_ty.tag orelse break :blk if (is_signed) "c_int" else "c_uint"; continue :loop tag_ty.base(t.comp).type; }, else => unreachable, // only call this function when it has already been determined the type is int }); } fn transTypeInit( t: *Translator, scope: *Scope, qt: QualType, init: Node.Index, source_loc: TokenIndex, ) TypeError!ZigNode { switch (init.get(t.tree)) { .string_literal_expr => |literal| { const elem_ty = try t.transType(scope, qt.childType(t.comp), source_loc); const string_lit_size = literal.qt.arrayLen(t.comp).?; const array_size = qt.arrayLen(t.comp).?; if (array_size == string_lit_size) { return ZigTag.null_sentinel_array_type.create(t.arena, .{ .len = array_size - 1, .elem_type = elem_ty }); } else { return ZigTag.array_type.create(t.arena, .{ .len = array_size, .elem_type = elem_ty }); } }, else => {}, } return t.transType(scope, qt, source_loc); } // ============ // Type helpers // ============ fn typeIsOpaque(t: *Translator, qt: QualType) bool { return switch (qt.base(t.comp).type) { .void => true, .@"struct", .@"union" => |record_ty| { if (record_ty.layout == null) return true; for (record_ty.fields) |field| { if (field.bit_width != .null) return true; } return false; }, else => false, }; } fn typeWasDemotedToOpaque(t: *Translator, qt: QualType) bool { return t.opaque_demotes.contains(qt); } fn typeHasWrappingOverflow(t: *Translator, qt: QualType) bool { if (t.signedness(qt) == .unsigned) { // unsigned integer overflow wraps around. return true; } else { // float, signed integer, and pointer overflow is undefined behavior. return false; } } /// Signedness of type when translated to Zig. /// Different from `QualType.signedness()` for `char` and enums. /// Returns null for non-int types. fn signedness(t: *Translator, qt: QualType) ?std.builtin.Signedness { return loop: switch (qt.base(t.comp).type) { .bool => .unsigned, .bit_int => |bit_int| bit_int.signedness, .int => |int_ty| switch (int_ty) { .char => .unsigned, // Always translated as u8 .schar, .short, .int, .long, .long_long, .int128 => .signed, .uchar, .ushort, .uint, .ulong, .ulong_long, .uint128 => .unsigned, }, .@"enum" => |enum_ty| { const tag_qt = enum_ty.tag orelse return .signed; continue :loop tag_qt.base(t.comp).type; }, else => return null, }; } // ===================== // Statement translation // ===================== fn transStmt(t: *Translator, scope: *Scope, stmt: Node.Index) TransError!ZigNode { switch (stmt.get(t.tree)) { .compound_stmt => |compound| { return t.transCompoundStmt(scope, compound); }, .static_assert => |static_assert| { try t.transStaticAssert(scope, static_assert); return ZigTag.declaration.init(); }, .return_stmt => |return_stmt| return t.transReturnStmt(scope, return_stmt), .null_stmt => return ZigTag.empty_block.init(), .if_stmt => |if_stmt| return t.transIfStmt(scope, if_stmt), .while_stmt => |while_stmt| return t.transWhileStmt(scope, while_stmt), .do_while_stmt => |do_while_stmt| return t.transDoWhileStmt(scope, do_while_stmt), .for_stmt => |for_stmt| return t.transForStmt(scope, for_stmt), .continue_stmt => return ZigTag.@"continue".init(), .break_stmt => return ZigTag.@"break".init(), .typedef => |typedef_decl| { assert(!typedef_decl.implicit); try t.transTypeDef(scope, stmt); return ZigTag.declaration.init(); }, .struct_decl, .union_decl => |record_decl| { try t.transRecordDecl(scope, record_decl.container_qt); return ZigTag.declaration.init(); }, .enum_decl => |enum_decl| { try t.transEnumDecl(scope, enum_decl.container_qt); return ZigTag.declaration.init(); }, .function => |function| { try t.transFnDecl(scope, function); return ZigTag.declaration.init(); }, .variable => |variable| { try t.transVarDecl(scope, variable); return ZigTag.declaration.init(); }, .switch_stmt => |switch_stmt| return t.transSwitch(scope, switch_stmt), .case_stmt, .default_stmt => { return t.fail(error.UnsupportedTranslation, stmt.tok(t.tree), "TODO complex switch", .{}); }, .goto_stmt, .computed_goto_stmt, .labeled_stmt => { return t.fail(error.UnsupportedTranslation, stmt.tok(t.tree), "TODO goto", .{}); }, .asm_stmt => { return t.fail(error.UnsupportedTranslation, stmt.tok(t.tree), "TODO asm stmt", .{}); }, else => return t.transExprCoercing(scope, stmt, .unused), } } fn transCompoundStmtInline(t: *Translator, compound: Node.CompoundStmt, block: *Scope.Block) TransError!void { for (compound.body) |stmt| { const result = try t.transStmt(&block.base, stmt); switch (result.tag()) { .declaration, .empty_block => {}, else => try block.statements.append(t.gpa, result), } } } fn transCompoundStmt(t: *Translator, scope: *Scope, compound: Node.CompoundStmt) TransError!ZigNode { var block_scope = try Scope.Block.init(t, scope, false); defer block_scope.deinit(); try t.transCompoundStmtInline(compound, &block_scope); return try block_scope.complete(); } fn transReturnStmt(t: *Translator, scope: *Scope, return_stmt: Node.ReturnStmt) TransError!ZigNode { switch (return_stmt.operand) { .none => return ZigTag.return_void.init(), .expr => |operand| { var rhs = try t.transExprCoercing(scope, operand, .used); const return_qt = scope.findBlockReturnType(); if (rhs.isBoolRes() and !return_qt.is(t.comp, .bool)) { rhs = try ZigTag.int_from_bool.create(t.arena, rhs); } return ZigTag.@"return".create(t.arena, rhs); }, .implicit => |zero| { if (zero) return ZigTag.@"return".create(t.arena, ZigTag.zero_literal.init()); const return_qt = scope.findBlockReturnType(); if (return_qt.is(t.comp, .void)) return ZigTag.empty_block.init(); return ZigTag.@"return".create(t.arena, ZigTag.undefined_literal.init()); }, } } /// 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(t: *Translator, scope: *Scope, stmt: Node.Index) TransError!ZigNode { switch (stmt.get(t.tree)) { .break_stmt, .continue_stmt, .compound_stmt, .decl_ref_expr, .enumeration_ref, .do_while_stmt, .for_stmt, .if_stmt, .return_stmt, .null_stmt, .while_stmt, => return t.transStmt(scope, stmt), else => return t.blockify(scope, stmt), } } /// Translate statement and place it in its own block. fn blockify(t: *Translator, scope: *Scope, stmt: Node.Index) TransError!ZigNode { var block_scope = try Scope.Block.init(t, scope, false); defer block_scope.deinit(); const result = try t.transStmt(&block_scope.base, stmt); try block_scope.statements.append(t.gpa, result); return block_scope.complete(); } fn transIfStmt(t: *Translator, scope: *Scope, if_stmt: Node.IfStmt) TransError!ZigNode { var cond_scope: Scope.Condition = .{ .base = .{ .parent = scope, .id = .condition, }, }; defer cond_scope.deinit(); const cond = try t.transBoolExpr(&cond_scope.base, if_stmt.cond); // block needed to keep else statement from attaching to inner while const must_blockify = (if_stmt.else_body != null) and switch (if_stmt.then_body.get(t.tree)) { .while_stmt, .do_while_stmt, .for_stmt => true, else => false, }; const then_node = if (must_blockify) try t.blockify(scope, if_stmt.then_body) else try t.maybeBlockify(scope, if_stmt.then_body); const else_node = if (if_stmt.else_body) |stmt| try t.maybeBlockify(scope, stmt) else null; return ZigTag.@"if".create(t.arena, .{ .cond = cond, .then = then_node, .@"else" = else_node }); } fn transWhileStmt(t: *Translator, scope: *Scope, while_stmt: Node.WhileStmt) TransError!ZigNode { var cond_scope: Scope.Condition = .{ .base = .{ .parent = scope, .id = .condition, }, }; defer cond_scope.deinit(); const cond = try t.transBoolExpr(&cond_scope.base, while_stmt.cond); var loop_scope: Scope = .{ .parent = scope, .id = .loop, }; const body = try t.maybeBlockify(&loop_scope, while_stmt.body); return ZigTag.@"while".create(t.arena, .{ .cond = cond, .body = body, .cont_expr = null }); } fn transDoWhileStmt(t: *Translator, scope: *Scope, do_stmt: Node.DoWhileStmt) TransError!ZigNode { 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 t.transBoolExpr(&cond_scope.base, do_stmt.cond); const if_not_break = switch (cond.tag()) { .true_literal => { const body_node = try t.maybeBlockify(scope, do_stmt.body); return ZigTag.while_true.create(t.arena, body_node); }, else => try ZigTag.if_not_break.create(t.arena, cond), }; var body_node = try t.transStmt(&loop_scope, do_stmt.body); 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 (do_stmt.body.get(t.tree) == .compound_stmt) { // 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 t.arena.alloc(ZigNode, 2); statements[0] = body_node; statements[1] = if_not_break; body_node = try ZigTag.block.create(t.arena, .{ .label = null, .stmts = statements }); } return ZigTag.while_true.create(t.arena, body_node); } fn transForStmt(t: *Translator, scope: *Scope, for_stmt: Node.ForStmt) TransError!ZigNode { var loop_scope: Scope = .{ .parent = scope, .id = .loop, }; var block_scope: ?Scope.Block = null; defer if (block_scope) |*bs| bs.deinit(); switch (for_stmt.init) { .decls => |decls| { block_scope = try Scope.Block.init(t, scope, false); loop_scope.parent = &block_scope.?.base; for (decls) |decl| { try t.transDecl(&block_scope.?.base, decl); } }, .expr => |maybe_init| if (maybe_init) |init| { block_scope = try Scope.Block.init(t, scope, false); loop_scope.parent = &block_scope.?.base; const init_node = try t.transStmt(&block_scope.?.base, init); try loop_scope.appendNode(init_node); }, } var cond_scope: Scope.Condition = .{ .base = .{ .parent = &loop_scope, .id = .condition, }, }; defer cond_scope.deinit(); const cond = if (for_stmt.cond) |cond| try t.transBoolExpr(&cond_scope.base, cond) else ZigTag.true_literal.init(); const cont_expr = if (for_stmt.incr) |incr| try t.transExpr(&cond_scope.base, incr, .unused) else null; const body = try t.maybeBlockify(&loop_scope, for_stmt.body); const while_node = try ZigTag.@"while".create(t.arena, .{ .cond = cond, .body = body, .cont_expr = cont_expr }); if (block_scope) |*bs| { try bs.statements.append(t.gpa, while_node); return try bs.complete(); } else { return while_node; } } fn transSwitch(t: *Translator, scope: *Scope, switch_stmt: Node.SwitchStmt) TransError!ZigNode { var loop_scope: Scope = .{ .parent = scope, .id = .loop, }; var block_scope = try Scope.Block.init(t, &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 t.transExpr(&cond_scope.base, switch_stmt.cond, .used); var cases: std.ArrayList(ZigNode) = .empty; defer cases.deinit(t.gpa); var has_default = false; const body_node = switch_stmt.body.get(t.tree); if (body_node != .compound_stmt) { return t.fail(error.UnsupportedTranslation, switch_stmt.switch_tok, "TODO complex switch", .{}); } const body = body_node.compound_stmt.body; // Iterate over switch body and collect all cases. // Fallthrough is handled by duplicating statements. for (body, 0..) |stmt, i| { switch (stmt.get(t.tree)) { .case_stmt => { var items: std.ArrayList(ZigNode) = .empty; defer items.deinit(t.gpa); const sub = try t.transCaseStmt(base_scope, stmt, &items); const res = try t.transSwitchProngStmt(base_scope, sub, body[i..]); if (items.items.len == 0) { has_default = true; const switch_else = try ZigTag.switch_else.create(t.arena, res); try cases.append(t.gpa, switch_else); } else { const switch_prong = try ZigTag.switch_prong.create(t.arena, .{ .cases = try t.arena.dupe(ZigNode, items.items), .cond = res, }); try cases.append(t.gpa, switch_prong); } }, .default_stmt => |default_stmt| { has_default = true; var sub = default_stmt.body; while (true) switch (sub.get(t.tree)) { .case_stmt => |sub_case| sub = sub_case.body, .default_stmt => |sub_default| sub = sub_default.body, else => break, }; const res = try t.transSwitchProngStmt(base_scope, sub, body[i..]); const switch_else = try ZigTag.switch_else.create(t.arena, res); try cases.append(t.gpa, switch_else); }, else => {}, // collected in transSwitchProngStmt } } if (!has_default) { const else_prong = try ZigTag.switch_else.create(t.arena, ZigTag.empty_block.init()); try cases.append(t.gpa, else_prong); } const switch_node = try ZigTag.@"switch".create(t.arena, .{ .cond = switch_expr, .cases = try t.arena.dupe(ZigNode, cases.items), }); try block_scope.statements.append(t.gpa, switch_node); try block_scope.statements.append(t.gpa, ZigTag.@"break".init()); const while_body = try block_scope.complete(); return ZigTag.while_true.create(t.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( t: *Translator, scope: *Scope, stmt: Node.Index, items: *std.ArrayList(ZigNode), ) TransError!Node.Index { var sub = stmt; var seen_default = false; while (true) { switch (sub.get(t.tree)) { .default_stmt => |default_stmt| { seen_default = true; items.items.len = 0; sub = default_stmt.body; }, .case_stmt => |case_stmt| { if (seen_default) { items.items.len = 0; sub = case_stmt.body; continue; } const expr = if (case_stmt.end) |end| blk: { const start_node = try t.transExpr(scope, case_stmt.start, .used); const end_node = try t.transExpr(scope, end, .used); break :blk try ZigTag.ellipsis3.create(t.arena, .{ .lhs = start_node, .rhs = end_node }); } else try t.transExpr(scope, case_stmt.start, .used); try items.append(t.gpa, expr); sub = case_stmt.body; }, 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( t: *Translator, scope: *Scope, stmt: Node.Index, body: []const Node.Index, ) TransError!ZigNode { switch (stmt.get(t.tree)) { .break_stmt => return ZigTag.@"break".init(), .return_stmt => return t.transStmt(scope, stmt), .case_stmt, .default_stmt => unreachable, else => { var block_scope = try Scope.Block.init(t, scope, false); defer block_scope.deinit(); // we do not need to translate `stmt` since it is the first stmt of `body` try t.transSwitchProngStmtInline(&block_scope, body); return try block_scope.complete(); }, } } /// Collects all statements seen by this case into a block. fn transSwitchProngStmtInline( t: *Translator, block: *Scope.Block, body: []const Node.Index, ) TransError!void { for (body) |stmt| { switch (stmt.get(t.tree)) { .return_stmt => { const result = try t.transStmt(&block.base, stmt); try block.statements.append(t.gpa, result); return; }, .break_stmt => { try block.statements.append(t.gpa, ZigTag.@"break".init()); return; }, .case_stmt => |case_stmt| { var sub = case_stmt.body; while (true) switch (sub.get(t.tree)) { .case_stmt => |sub_case| sub = sub_case.body, .default_stmt => |sub_default| sub = sub_default.body, else => break, }; const result = try t.transStmt(&block.base, sub); assert(result.tag() != .declaration); try block.statements.append(t.gpa, result); if (result.isNoreturn(true)) return; }, .default_stmt => |default_stmt| { var sub = default_stmt.body; while (true) switch (sub.get(t.tree)) { .case_stmt => |sub_case| sub = sub_case.body, .default_stmt => |sub_default| sub = sub_default.body, else => break, }; const result = try t.transStmt(&block.base, sub); assert(result.tag() != .declaration); try block.statements.append(t.gpa, result); if (result.isNoreturn(true)) return; }, .compound_stmt => |compound_stmt| { const result = try t.transCompoundStmt(&block.base, compound_stmt); try block.statements.append(t.gpa, result); if (result.isNoreturn(true)) return; }, else => { const result = try t.transStmt(&block.base, stmt); switch (result.tag()) { .declaration, .empty_block => {}, else => try block.statements.append(t.gpa, result), } }, } } } // ====================== // Expression translation // ====================== const ResultUsed = enum { used, unused }; fn transExpr(t: *Translator, scope: *Scope, expr: Node.Index, used: ResultUsed) TransError!ZigNode { const qt = expr.qt(t.tree); return t.maybeSuppressResult(used, switch (expr.get(t.tree)) { .paren_expr => |paren_expr| { return t.transExpr(scope, paren_expr.operand, used); }, .cast => |cast| return t.transCastExpr(scope, cast, cast.qt, used, .with_as), .decl_ref_expr => |decl_ref| try t.transDeclRefExpr(scope, decl_ref), .enumeration_ref => |enum_ref| try t.transDeclRefExpr(scope, enum_ref), .addr_of_expr => |addr_of_expr| try ZigTag.address_of.create(t.arena, try t.transExpr(scope, addr_of_expr.operand, .used)), .deref_expr => |deref_expr| res: { if (t.typeWasDemotedToOpaque(qt)) return t.fail(error.UnsupportedTranslation, deref_expr.op_tok, "cannot dereference opaque type", .{}); // Dereferencing a function pointer is a no-op. if (qt.is(t.comp, .func)) return t.transExpr(scope, deref_expr.operand, used); break :res try ZigTag.deref.create(t.arena, try t.transExpr(scope, deref_expr.operand, .used)); }, .bool_not_expr => |bool_not_expr| try ZigTag.not.create(t.arena, try t.transBoolExpr(scope, bool_not_expr.operand)), .bit_not_expr => |bit_not_expr| try ZigTag.bit_not.create(t.arena, try t.transExpr(scope, bit_not_expr.operand, .used)), .plus_expr => |plus_expr| return t.transExpr(scope, plus_expr.operand, used), .negate_expr => |negate_expr| res: { const operand_qt = negate_expr.operand.qt(t.tree); if (!t.typeHasWrappingOverflow(operand_qt)) { const sub_expr_node = try t.transExpr(scope, negate_expr.operand, .used); const to_negate = if (sub_expr_node.isBoolRes()) blk: { const ty_node = try ZigTag.type.create(t.arena, "c_int"); const int_node = try ZigTag.int_from_bool.create(t.arena, sub_expr_node); break :blk try ZigTag.as.create(t.arena, .{ .lhs = ty_node, .rhs = int_node }); } else sub_expr_node; break :res try ZigTag.negate.create(t.arena, to_negate); } else if (t.signedness(operand_qt) == .unsigned) { // use -% x for unsigned integers break :res try ZigTag.negate_wrap.create(t.arena, try t.transExpr(scope, negate_expr.operand, .used)); } else return t.fail(error.UnsupportedTranslation, negate_expr.op_tok, "C negation with non float non integer", .{}); }, .div_expr => |div_expr| res: { if (qt.isInt(t.comp) and t.signedness(qt) == .signed) { // signed integer division uses @divTrunc const lhs = try t.transExpr(scope, div_expr.lhs, .used); const rhs = try t.transExpr(scope, div_expr.rhs, .used); break :res try ZigTag.div_trunc.create(t.arena, .{ .lhs = lhs, .rhs = rhs }); } // unsigned/float division uses the operator break :res try t.transBinExpr(scope, div_expr, .div); }, .mod_expr => |mod_expr| res: { if (qt.isInt(t.comp) and t.signedness(qt) == .signed) { // signed integer remainder uses __helpers.signedRemainder const lhs = try t.transExpr(scope, mod_expr.lhs, .used); const rhs = try t.transExpr(scope, mod_expr.rhs, .used); break :res try t.createHelperCallNode(.signedRemainder, &.{ lhs, rhs }); } // unsigned/float division uses the operator break :res try t.transBinExpr(scope, mod_expr, .mod); }, .add_expr => |add_expr| res: { // `ptr + idx` and `idx + ptr` -> ptr + @as(usize, @bitCast(@as(isize, @intCast(idx)))) const lhs_qt = add_expr.lhs.qt(t.tree); const rhs_qt = add_expr.rhs.qt(t.tree); if (qt.isPointer(t.comp) and (t.signedness(lhs_qt) == .signed or t.signedness(rhs_qt) == .signed)) { break :res try t.transPointerArithmeticSignedOp(scope, add_expr, .add); } if (t.signedness(qt) == .unsigned) { break :res try t.transBinExpr(scope, add_expr, .add_wrap); } else { break :res try t.transBinExpr(scope, add_expr, .add); } }, .sub_expr => |sub_expr| res: { // `ptr - idx` -> ptr - @as(usize, @bitCast(@as(isize, @intCast(idx)))) const lhs_qt = sub_expr.lhs.qt(t.tree); const rhs_qt = sub_expr.rhs.qt(t.tree); if (qt.isPointer(t.comp) and (t.signedness(lhs_qt) == .signed or t.signedness(rhs_qt) == .signed)) { break :res try t.transPointerArithmeticSignedOp(scope, sub_expr, .sub); } if (sub_expr.lhs.qt(t.tree).isPointer(t.comp) and sub_expr.rhs.qt(t.tree).isPointer(t.comp)) { break :res try t.transPtrDiffExpr(scope, sub_expr); } else if (t.signedness(qt) == .unsigned) { break :res try t.transBinExpr(scope, sub_expr, .sub_wrap); } else { break :res try t.transBinExpr(scope, sub_expr, .sub); } }, .mul_expr => |mul_expr| if (t.signedness(qt) == .unsigned) try t.transBinExpr(scope, mul_expr, .mul_wrap) else try t.transBinExpr(scope, mul_expr, .mul), .less_than_expr => |lt| try t.transBinExpr(scope, lt, .less_than), .greater_than_expr => |gt| try t.transBinExpr(scope, gt, .greater_than), .less_than_equal_expr => |lte| try t.transBinExpr(scope, lte, .less_than_equal), .greater_than_equal_expr => |gte| try t.transBinExpr(scope, gte, .greater_than_equal), .equal_expr => |equal_expr| try t.transBinExpr(scope, equal_expr, .equal), .not_equal_expr => |not_equal_expr| try t.transBinExpr(scope, not_equal_expr, .not_equal), .bool_and_expr => |bool_and_expr| try t.transBoolBinExpr(scope, bool_and_expr, .@"and"), .bool_or_expr => |bool_or_expr| try t.transBoolBinExpr(scope, bool_or_expr, .@"or"), .bit_and_expr => |bit_and_expr| try t.transBinExpr(scope, bit_and_expr, .bit_and), .bit_or_expr => |bit_or_expr| try t.transBinExpr(scope, bit_or_expr, .bit_or), .bit_xor_expr => |bit_xor_expr| try t.transBinExpr(scope, bit_xor_expr, .bit_xor), .shl_expr => |shl_expr| try t.transShiftExpr(scope, shl_expr, .shl), .shr_expr => |shr_expr| try t.transShiftExpr(scope, shr_expr, .shr), .member_access_expr => |member_access| try t.transMemberAccess(scope, .normal, member_access, null), .member_access_ptr_expr => |member_access| try t.transMemberAccess(scope, .ptr, member_access, null), .array_access_expr => |array_access| try t.transArrayAccess(scope, array_access, null), .builtin_ref => unreachable, .builtin_call_expr => |call| return t.transBuiltinCall(scope, call, used), .call_expr => |call| return t.transCall(scope, call, used), .builtin_types_compatible_p => |compatible| blk: { const lhs = try t.transType(scope, compatible.lhs, compatible.builtin_tok); const rhs = try t.transType(scope, compatible.rhs, compatible.builtin_tok); break :blk try ZigTag.equal.create(t.arena, .{ .lhs = lhs, .rhs = rhs, }); }, .builtin_choose_expr => |choose| return t.transCondExpr(scope, choose, used), .cond_expr => |cond_expr| return t.transCondExpr(scope, cond_expr, used), .binary_cond_expr => |conditional| return t.transBinaryCondExpr(scope, conditional, used), .cond_dummy_expr => unreachable, .assign_expr => |assign| return t.transAssignExpr(scope, assign, used), .add_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), .sub_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), .mul_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), .div_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), .mod_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), .shl_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), .shr_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), .bit_and_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), .bit_xor_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), .bit_or_assign_expr => |assign| return t.transCompoundAssign(scope, assign, used), .compound_assign_dummy_expr => { assert(used == .used); return t.compound_assign_dummy.?; }, .comma_expr => |comma_expr| return t.transCommaExpr(scope, comma_expr, used), .pre_inc_expr => |un| return t.transIncDecExpr(scope, un, .pre, .inc, used), .pre_dec_expr => |un| return t.transIncDecExpr(scope, un, .pre, .dec, used), .post_inc_expr => |un| return t.transIncDecExpr(scope, un, .post, .inc, used), .post_dec_expr => |un| return t.transIncDecExpr(scope, un, .post, .dec, used), .int_literal => return t.transIntLiteral(scope, expr, used, .with_as), .char_literal => return t.transCharLiteral(scope, expr, used, .with_as), .float_literal => return t.transFloatLiteral(scope, expr, used, .with_as), .string_literal_expr => |literal| try t.transStringLiteral(scope, expr, literal), .bool_literal => res: { const val = t.tree.value_map.get(expr).?; break :res if (val.toBool(t.comp)) ZigTag.true_literal.init() else ZigTag.false_literal.init(); }, .nullptr_literal => ZigTag.null_literal.init(), .imaginary_literal => |literal| { return t.fail(error.UnsupportedTranslation, literal.op_tok, "TODO complex numbers", .{}); }, .compound_literal_expr => |literal| return t.transCompoundLiteral(scope, literal, used), .default_init_expr => |default_init| return t.transDefaultInit(scope, default_init, used, .with_as), .array_init_expr => |array_init| return t.transArrayInit(scope, array_init, used), .union_init_expr => |union_init| return t.transUnionInit(scope, union_init, used), .struct_init_expr => |struct_init| return t.transStructInit(scope, struct_init, used), .array_filler_expr => unreachable, .sizeof_expr => |sizeof| try t.transTypeInfo(scope, .sizeof, sizeof), .alignof_expr => |alignof| try t.transTypeInfo(scope, .alignof, alignof), .imag_expr, .real_expr => |un| { return t.fail(error.UnsupportedTranslation, un.op_tok, "TODO complex numbers", .{}); }, .addr_of_label => |addr_of_label| { return t.fail(error.UnsupportedTranslation, addr_of_label.label_tok, "TODO computed goto", .{}); }, .generic_expr => |generic| return t.transExpr(scope, generic.chosen, used), .generic_association_expr => |generic| return t.transExpr(scope, generic.expr, used), .generic_default_expr => |generic| return t.transExpr(scope, generic.expr, used), .stmt_expr => |stmt_expr| return t.transStmtExpr(scope, stmt_expr, used), .builtin_convertvector => |convertvector| try t.transConvertvectorExpr(scope, convertvector), .builtin_shufflevector => |shufflevector| try t.transShufflevectorExpr(scope, shufflevector), .compound_stmt, .static_assert, .return_stmt, .null_stmt, .if_stmt, .while_stmt, .do_while_stmt, .for_stmt, .continue_stmt, .break_stmt, .labeled_stmt, .switch_stmt, .case_stmt, .default_stmt, .goto_stmt, .computed_goto_stmt, .asm_stmt, .global_asm, .typedef, .struct_decl, .union_decl, .enum_decl, .function, .param, .variable, .enum_field, .record_field, .struct_forward_decl, .union_forward_decl, .enum_forward_decl, .empty_decl, => unreachable, // not an expression }); } /// 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(t: *Translator, scope: *Scope, expr: Node.Index, used: ResultUsed) TransError!ZigNode { switch (expr.get(t.tree)) { .int_literal => return t.transIntLiteral(scope, expr, used, .no_as), .char_literal => return t.transCharLiteral(scope, expr, used, .no_as), .float_literal => return t.transFloatLiteral(scope, expr, used, .no_as), .cast => |cast| switch (cast.kind) { .no_op => { const operand = cast.operand.get(t.tree); if (operand == .cast) { return t.transCastExpr(scope, operand.cast, cast.qt, used, .no_as); } return t.transExprCoercing(scope, cast.operand, used); }, .lval_to_rval => return t.transExprCoercing(scope, cast.operand, used), else => return t.transCastExpr(scope, cast, cast.qt, used, .no_as), }, .default_init_expr => |default_init| return try t.transDefaultInit(scope, default_init, used, .no_as), .compound_literal_expr => |literal| { if (!literal.thread_local and literal.storage_class != .static) { return t.transExprCoercing(scope, literal.initializer, used); } }, else => {}, } return t.transExpr(scope, expr, used); } fn transBoolExpr(t: *Translator, scope: *Scope, expr: Node.Index) TransError!ZigNode { switch (expr.get(t.tree)) { .int_literal => { const int_val = t.tree.value_map.get(expr).?; return if (int_val.isZero(t.comp)) ZigTag.false_literal.init() else ZigTag.true_literal.init(); }, .cast => |cast| switch (cast.kind) { .bool_to_int => return t.transExpr(scope, cast.operand, .used), .array_to_pointer => { const operand = cast.operand.get(t.tree); if (operand == .string_literal_expr) { // @intFromPtr("foo") != 0, always true const str = try t.transStringLiteral(scope, cast.operand, operand.string_literal_expr); const int_from_ptr = try ZigTag.int_from_ptr.create(t.arena, str); return ZigTag.not_equal.create(t.arena, .{ .lhs = int_from_ptr, .rhs = ZigTag.zero_literal.init() }); } }, else => {}, }, else => {}, } const maybe_bool_res = try t.transExpr(scope, expr, .used); if (maybe_bool_res.isBoolRes()) { return maybe_bool_res; } return t.finishBoolExpr(expr.qt(t.tree), maybe_bool_res); } fn finishBoolExpr(t: *Translator, qt: QualType, node: ZigNode) TransError!ZigNode { const sk = qt.scalarKind(t.comp); if (sk == .bool) return node; if (sk == .nullptr_t) { // node == null, always true return ZigTag.equal.create(t.arena, .{ .lhs = node, .rhs = ZigTag.null_literal.init() }); } if (sk.isPointer()) { // node != null return ZigTag.not_equal.create(t.arena, .{ .lhs = node, .rhs = ZigTag.null_literal.init() }); } if (sk != .none) { // node != 0 return ZigTag.not_equal.create(t.arena, .{ .lhs = node, .rhs = ZigTag.zero_literal.init() }); } unreachable; // Unexpected bool expression type } fn transCastExpr( t: *Translator, scope: *Scope, cast: Node.Cast, dest_qt: QualType, used: ResultUsed, suppress_as: SuppressCast, ) TransError!ZigNode { const operand = switch (cast.kind) { .no_op => { const operand = cast.operand.get(t.tree); if (operand == .cast) { return t.transCastExpr(scope, operand.cast, cast.qt, used, suppress_as); } return t.transExpr(scope, cast.operand, used); }, .lval_to_rval, .function_to_pointer => { return t.transExpr(scope, cast.operand, used); }, .int_cast => int_cast: { const src_qt = cast.operand.qt(t.tree); if (cast.implicit) { if (t.tree.value_map.get(cast.operand)) |val| { const max_int = try aro.Value.maxInt(dest_qt, t.comp); const min_int = try aro.Value.minInt(dest_qt, t.comp); if (val.compare(.lte, max_int, t.comp) and val.compare(.gte, min_int, t.comp)) { break :int_cast try t.transExprCoercing(scope, cast.operand, .used); } } } const operand = try t.transExpr(scope, cast.operand, .used); break :int_cast try t.transIntCast(operand, src_qt, dest_qt); }, .to_void => { assert(used == .unused); return try t.transExpr(scope, cast.operand, .unused); }, .null_to_pointer => ZigTag.null_literal.init(), .array_to_pointer => array_to_pointer: { const child_qt = dest_qt.childType(t.comp); loop: switch (cast.operand.get(t.tree)) { .string_literal_expr => |literal| { const sub_expr_node = try t.transExpr(scope, cast.operand, .used); const ref = if (literal.kind == .utf8 or literal.kind == .ascii) sub_expr_node else try ZigTag.address_of.create(t.arena, sub_expr_node); const casted = if (child_qt.@"const") ref else try ZigTag.const_cast.create(t.arena, sub_expr_node); return t.maybeSuppressResult(used, casted); }, .paren_expr => |paren_expr| { continue :loop paren_expr.operand.get(t.tree); }, .generic_expr => |generic| { continue :loop generic.chosen.get(t.tree); }, .generic_association_expr => |generic| { continue :loop generic.expr.get(t.tree); }, .generic_default_expr => |generic| { continue :loop generic.expr.get(t.tree); }, else => {}, } if (cast.operand.qt(t.tree).arrayLen(t.comp) == null) { return try t.transExpr(scope, cast.operand, used); } const sub_expr_node = try t.transExpr(scope, cast.operand, .used); const ref = try ZigTag.address_of.create(t.arena, sub_expr_node); const align_cast = try ZigTag.align_cast.create(t.arena, ref); break :array_to_pointer try ZigTag.ptr_cast.create(t.arena, align_cast); }, .int_to_pointer => int_to_pointer: { var sub_expr_node = try t.transExpr(scope, cast.operand, .used); const operand_qt = cast.operand.qt(t.tree); if (t.signedness(operand_qt) == .signed or operand_qt.bitSizeof(t.comp) > t.comp.target.ptrBitWidth()) { sub_expr_node = try ZigTag.as.create(t.arena, .{ .lhs = try ZigTag.type.create(t.arena, "usize"), .rhs = try ZigTag.int_cast.create(t.arena, sub_expr_node), }); } break :int_to_pointer try ZigTag.ptr_from_int.create(t.arena, sub_expr_node); }, .int_to_bool => { const sub_expr_node = try t.transExpr(scope, cast.operand, .used); if (sub_expr_node.isBoolRes()) return sub_expr_node; if (cast.operand.qt(t.tree).is(t.comp, .bool)) return sub_expr_node; const cmp_node = try ZigTag.not_equal.create(t.arena, .{ .lhs = sub_expr_node, .rhs = ZigTag.zero_literal.init() }); return t.maybeSuppressResult(used, cmp_node); }, .float_to_bool => { const sub_expr_node = try t.transExpr(scope, cast.operand, .used); const cmp_node = try ZigTag.not_equal.create(t.arena, .{ .lhs = sub_expr_node, .rhs = ZigTag.zero_literal.init() }); return t.maybeSuppressResult(used, cmp_node); }, .pointer_to_bool => { const sub_expr_node = try t.transExpr(scope, cast.operand, .used); // Special case function pointers as @intFromPtr(expr) != 0 if (cast.operand.qt(t.tree).get(t.comp, .pointer)) |ptr_ty| if (ptr_ty.child.is(t.comp, .func)) { const ptr_node = if (sub_expr_node.tag() == .identifier) try ZigTag.address_of.create(t.arena, sub_expr_node) else sub_expr_node; const int_from_ptr = try ZigTag.int_from_ptr.create(t.arena, ptr_node); const cmp_node = try ZigTag.not_equal.create(t.arena, .{ .lhs = int_from_ptr, .rhs = ZigTag.zero_literal.init() }); return t.maybeSuppressResult(used, cmp_node); }; const cmp_node = try ZigTag.not_equal.create(t.arena, .{ .lhs = sub_expr_node, .rhs = ZigTag.null_literal.init() }); return t.maybeSuppressResult(used, cmp_node); }, .bool_to_int => bool_to_int: { const sub_expr_node = try t.transExpr(scope, cast.operand, .used); break :bool_to_int try ZigTag.int_from_bool.create(t.arena, sub_expr_node); }, .bool_to_float => bool_to_float: { const sub_expr_node = try t.transExpr(scope, cast.operand, .used); const int_from_bool = try ZigTag.int_from_bool.create(t.arena, sub_expr_node); break :bool_to_float try ZigTag.float_from_int.create(t.arena, int_from_bool); }, .bool_to_pointer => bool_to_pointer: { const sub_expr_node = try t.transExpr(scope, cast.operand, .used); const int_from_bool = try ZigTag.int_from_bool.create(t.arena, sub_expr_node); break :bool_to_pointer try ZigTag.ptr_from_int.create(t.arena, int_from_bool); }, .float_cast => float_cast: { const sub_expr_node = try t.transExpr(scope, cast.operand, .used); break :float_cast try ZigTag.float_cast.create(t.arena, sub_expr_node); }, .int_to_float => int_to_float: { const sub_expr_node = try t.transExpr(scope, cast.operand, used); const int_node = if (sub_expr_node.isBoolRes()) try ZigTag.int_from_bool.create(t.arena, sub_expr_node) else sub_expr_node; break :int_to_float try ZigTag.float_from_int.create(t.arena, int_node); }, .float_to_int => float_to_int: { const sub_expr_node = try t.transExpr(scope, cast.operand, .used); break :float_to_int try ZigTag.int_from_float.create(t.arena, sub_expr_node); }, .pointer_to_int => pointer_to_int: { const sub_expr_node = try t.transPointerCastExpr(scope, cast.operand); const ptr_node = try ZigTag.int_from_ptr.create(t.arena, sub_expr_node); break :pointer_to_int try ZigTag.int_cast.create(t.arena, ptr_node); }, .bitcast => bitcast: { const sub_expr_node = try t.transPointerCastExpr(scope, cast.operand); const operand_qt = cast.operand.qt(t.tree); if (dest_qt.isPointer(t.comp) and operand_qt.isPointer(t.comp)) { var casted = try ZigTag.align_cast.create(t.arena, sub_expr_node); casted = try ZigTag.ptr_cast.create(t.arena, casted); const src_elem = operand_qt.childType(t.comp); const dest_elem = dest_qt.childType(t.comp); if ((src_elem.@"const" or src_elem.is(t.comp, .func)) and !dest_elem.@"const") { casted = try ZigTag.const_cast.create(t.arena, casted); } if (src_elem.@"volatile" and !dest_elem.@"volatile") { casted = try ZigTag.volatile_cast.create(t.arena, casted); } break :bitcast casted; } break :bitcast try ZigTag.bit_cast.create(t.arena, sub_expr_node); }, .union_cast => union_cast: { const union_type = try t.transType(scope, dest_qt, cast.l_paren); const operand_qt = cast.operand.qt(t.tree); const union_base = dest_qt.base(t.comp); const field = for (union_base.type.@"union".fields) |field| { if (field.qt.eql(operand_qt, t.comp)) break field; } else unreachable; const field_name = if (field.name_tok == 0) t.anonymous_record_field_names.get(.{ .parent = union_base.qt, .field = field.qt, }).? else field.name.lookup(t.comp); const field_init = try t.arena.create(ast.Payload.ContainerInit.Initializer); field_init.* = .{ .name = field_name, .value = try t.transExpr(scope, cast.operand, .used), }; break :union_cast try ZigTag.container_init.create(t.arena, .{ .lhs = union_type, .inits = field_init[0..1], }); }, else => return t.fail(error.UnsupportedTranslation, cast.l_paren, "TODO translate {s} cast", .{@tagName(cast.kind)}), }; if (suppress_as == .no_as) return t.maybeSuppressResult(used, operand); if (used == .unused) return t.maybeSuppressResult(used, operand); const as = try ZigTag.as.create(t.arena, .{ .lhs = try t.transType(scope, dest_qt, cast.l_paren), .rhs = operand, }); return as; } fn transIntCast(t: *Translator, operand: ZigNode, src_qt: QualType, dest_qt: QualType) !ZigNode { const src_dest_order = src_qt.intRankOrder(dest_qt, t.comp); const different_sign = t.signedness(src_qt) != t.signedness(dest_qt); const needs_bitcast = different_sign and !(t.signedness(src_qt) == .unsigned and src_dest_order == .lt); var casted = operand; if (casted.isBoolRes()) { casted = try ZigTag.int_from_bool.create(t.arena, casted); } else if (src_dest_order == .gt) { // No C type is smaller than the 1 bit from @intFromBool casted = try ZigTag.truncate.create(t.arena, casted); } if (needs_bitcast) { if (src_dest_order != .eq) { casted = try ZigTag.as.create(t.arena, .{ .lhs = try t.transTypeIntWidthOf(dest_qt, t.signedness(src_qt) == .signed), .rhs = casted, }); } return ZigTag.bit_cast.create(t.arena, casted); } return casted; } /// Same as `transExpr` but adds a `&` if the expression is an identifier referencing a function type. fn transPointerCastExpr(t: *Translator, scope: *Scope, expr: Node.Index) TransError!ZigNode { const sub_expr_node = try t.transExpr(scope, expr, .used); switch (expr.get(t.tree)) { .cast => |cast| if (cast.kind == .function_to_pointer and sub_expr_node.tag() == .identifier) { return ZigTag.address_of.create(t.arena, sub_expr_node); }, else => {}, } return sub_expr_node; } fn transDeclRefExpr(t: *Translator, scope: *Scope, decl_ref: Node.DeclRef) TransError!ZigNode { const name = t.tree.tokSlice(decl_ref.name_tok); const maybe_alias = scope.getAlias(name); const mangled_name = maybe_alias orelse name; switch (decl_ref.decl.get(t.tree)) { .function => |function| if (function.definition == null and function.body == null) { // Try translating the decl again in case of out of scope declaration. try t.transFnDecl(scope, function); }, else => {}, } const decl = decl_ref.decl.get(t.tree); const ref_expr = blk: { const identifier = try ZigTag.identifier.create(t.arena, mangled_name); if (decl_ref.qt.is(t.comp, .func) and maybe_alias != null) { break :blk try ZigTag.field_access.create(t.arena, .{ .lhs = identifier, .field_name = name, }); } if (decl == .variable and maybe_alias != null) { switch (decl.variable.storage_class) { .@"extern", .static => { break :blk try ZigTag.field_access.create(t.arena, .{ .lhs = identifier, .field_name = name, }); }, else => {}, } } break :blk identifier; }; scope.skipVariableDiscard(mangled_name); return ref_expr; } fn transBinExpr(t: *Translator, scope: *Scope, bin: Node.Binary, op_id: ZigTag) TransError!ZigNode { const lhs_uncasted = try t.transExpr(scope, bin.lhs, .used); const rhs_uncasted = try t.transExpr(scope, bin.rhs, .used); const lhs = if (lhs_uncasted.isBoolRes()) try ZigTag.int_from_bool.create(t.arena, lhs_uncasted) else lhs_uncasted; const rhs = if (rhs_uncasted.isBoolRes()) try ZigTag.int_from_bool.create(t.arena, rhs_uncasted) else rhs_uncasted; return t.createBinOpNode(op_id, lhs, rhs); } fn transBoolBinExpr(t: *Translator, scope: *Scope, bin: Node.Binary, op: ZigTag) !ZigNode { std.debug.assert(op == .@"and" or op == .@"or"); const lhs = try t.transBoolExpr(scope, bin.lhs); const rhs = try t.transBoolExpr(scope, bin.rhs); return t.createBinOpNode(op, lhs, rhs); } fn transShiftExpr(t: *Translator, scope: *Scope, bin: Node.Binary, op_id: ZigTag) !ZigNode { std.debug.assert(op_id == .shl or op_id == .shr); // lhs >> @intCast(rh) const lhs = try t.transExpr(scope, bin.lhs, .used); const rhs = try t.transExprCoercing(scope, bin.rhs, .used); const rhs_casted = try ZigTag.int_cast.create(t.arena, rhs); return t.createBinOpNode(op_id, lhs, rhs_casted); } fn transCondExpr( t: *Translator, scope: *Scope, conditional: Node.Conditional, used: ResultUsed, ) TransError!ZigNode { var cond_scope: Scope.Condition = .{ .base = .{ .parent = scope, .id = .condition, }, }; defer cond_scope.deinit(); const res_is_bool = conditional.qt.is(t.comp, .bool); const cond = try t.transBoolExpr(&cond_scope.base, conditional.cond); var then_body = try t.transExpr(scope, conditional.then_expr, used); if (!res_is_bool and then_body.isBoolRes()) { then_body = try ZigTag.int_from_bool.create(t.arena, then_body); } var else_body = try t.transExpr(scope, conditional.else_expr, used); if (!res_is_bool and else_body.isBoolRes()) { else_body = try ZigTag.int_from_bool.create(t.arena, else_body); } // The `ResultUsed` is forwarded to both branches so no need to suppress the result here. return ZigTag.@"if".create(t.arena, .{ .cond = cond, .then = then_body, .@"else" = else_body }); } fn transBinaryCondExpr( t: *Translator, scope: *Scope, conditional: Node.Conditional, used: ResultUsed, ) TransError!ZigNode { // GNU extension of the ternary operator where the middle expression is // omitted, the condition itself is returned if it evaluates to true. if (used == .unused) { // Result unused so this can be translated as // if (condition) else_expr; var cond_scope: Scope.Condition = .{ .base = .{ .parent = scope, .id = .condition, }, }; defer cond_scope.deinit(); return ZigTag.@"if".create(t.arena, .{ .cond = try t.transBoolExpr(&cond_scope.base, conditional.cond), .then = try t.transExpr(scope, conditional.else_expr, .unused), .@"else" = null, }); } const res_is_bool = conditional.qt.is(t.comp, .bool); // c: (condition)?:(else_expr) // zig: (blk: { // const _cond_temp = (condition); // break :blk if (_cond_temp) _cond_temp else (else_expr); // }) var block_scope = try Scope.Block.init(t, scope, true); defer block_scope.deinit(); const cond_temp = try block_scope.reserveMangledName("cond_temp"); const init_node = try t.transExpr(&block_scope.base, conditional.cond, .used); const temp_decl = try ZigTag.var_simple.create(t.arena, .{ .name = cond_temp, .init = init_node }); try block_scope.statements.append(t.gpa, temp_decl); var cond_scope: Scope.Condition = .{ .base = .{ .parent = &block_scope.base, .id = .condition, }, }; defer cond_scope.deinit(); const cond_ident = try ZigTag.identifier.create(t.arena, cond_temp); const cond_node = try t.finishBoolExpr(conditional.cond.qt(t.tree), cond_ident); var then_body = cond_ident; if (!res_is_bool and init_node.isBoolRes()) { then_body = try ZigTag.int_from_bool.create(t.arena, then_body); } var else_body = try t.transExpr(&block_scope.base, conditional.else_expr, .used); if (!res_is_bool and else_body.isBoolRes()) { else_body = try ZigTag.int_from_bool.create(t.arena, else_body); } const if_node = try ZigTag.@"if".create(t.arena, .{ .cond = cond_node, .then = then_body, .@"else" = else_body, }); const break_node = try ZigTag.break_val.create(t.arena, .{ .label = block_scope.label, .val = if_node, }); try block_scope.statements.append(t.gpa, break_node); return block_scope.complete(); } fn transCommaExpr(t: *Translator, scope: *Scope, bin: Node.Binary, used: ResultUsed) TransError!ZigNode { if (used == .unused) { const lhs = try t.transExprCoercing(scope, bin.lhs, .unused); try scope.appendNode(lhs); const rhs = try t.transExprCoercing(scope, bin.rhs, .unused); return rhs; } var block_scope = try Scope.Block.init(t, scope, true); defer block_scope.deinit(); const lhs = try t.transExprCoercing(&block_scope.base, bin.lhs, .unused); try block_scope.statements.append(t.gpa, lhs); const rhs = try t.transExprCoercing(&block_scope.base, bin.rhs, .used); const break_node = try ZigTag.break_val.create(t.arena, .{ .label = block_scope.label, .val = rhs, }); try block_scope.statements.append(t.gpa, break_node); return try block_scope.complete(); } fn transAssignExpr(t: *Translator, scope: *Scope, bin: Node.Binary, used: ResultUsed) !ZigNode { if (used == .unused) { const lhs = try t.transExpr(scope, bin.lhs, .used); var rhs = try t.transExprCoercing(scope, bin.rhs, .used); const lhs_qt = bin.lhs.qt(t.tree); if (rhs.isBoolRes() and !lhs_qt.is(t.comp, .bool)) { rhs = try ZigTag.int_from_bool.create(t.arena, rhs); } return t.createBinOpNode(.assign, lhs, rhs); } var block_scope = try Scope.Block.init(t, scope, true); defer block_scope.deinit(); const tmp = try block_scope.reserveMangledName("tmp"); var rhs = try t.transExpr(&block_scope.base, bin.rhs, .used); const lhs_qt = bin.lhs.qt(t.tree); if (rhs.isBoolRes() and !lhs_qt.is(t.comp, .bool)) { rhs = try ZigTag.int_from_bool.create(t.arena, rhs); } const tmp_decl = try ZigTag.var_simple.create(t.arena, .{ .name = tmp, .init = rhs }); try block_scope.statements.append(t.gpa, tmp_decl); const lhs = try t.transExprCoercing(&block_scope.base, bin.lhs, .used); const tmp_ident = try ZigTag.identifier.create(t.arena, tmp); const assign = try t.createBinOpNode(.assign, lhs, tmp_ident); try block_scope.statements.append(t.gpa, assign); const break_node = try ZigTag.break_val.create(t.arena, .{ .label = block_scope.label, .val = tmp_ident, }); try block_scope.statements.append(t.gpa, break_node); return try block_scope.complete(); } fn transCompoundAssign( t: *Translator, scope: *Scope, assign: Node.Binary, used: ResultUsed, ) !ZigNode { // If the result is unused we can try using the equivalent Zig operator // without a block if (used == .unused) { if (try t.transCompoundAssignSimple(scope, null, assign)) |some| { return some; } } // Otherwise we need to wrap the the compound assignment in a block. var block_scope = try Scope.Block.init(t, scope, used == .used); defer block_scope.deinit(); const ref = try block_scope.reserveMangledName("ref"); const lhs_expr = try t.transExpr(&block_scope.base, assign.lhs, .used); const addr_of = try ZigTag.address_of.create(t.arena, lhs_expr); const ref_decl = try ZigTag.var_simple.create(t.arena, .{ .name = ref, .init = addr_of }); try block_scope.statements.append(t.gpa, ref_decl); const lhs_node = try ZigTag.identifier.create(t.arena, ref); const ref_node = try ZigTag.deref.create(t.arena, lhs_node); // Use the equivalent Zig operator if possible. if (try t.transCompoundAssignSimple(scope, ref_node, assign)) |some| { try block_scope.statements.append(t.gpa, some); } else { const old_dummy = t.compound_assign_dummy; defer t.compound_assign_dummy = old_dummy; t.compound_assign_dummy = ref_node; // Otherwise do the operation and assignment separately. const rhs_node = try t.transExprCoercing(&block_scope.base, assign.rhs, .used); const assign_node = try t.createBinOpNode(.assign, ref_node, rhs_node); try block_scope.statements.append(t.gpa, assign_node); } if (used == .used) { const break_node = try ZigTag.break_val.create(t.arena, .{ .label = block_scope.label, .val = ref_node, }); try block_scope.statements.append(t.gpa, break_node); } return block_scope.complete(); } /// Translates compound assignment using the equivalent Zig operator if possible. fn transCompoundAssignSimple(t: *Translator, scope: *Scope, lhs_dummy_opt: ?ZigNode, assign: Node.Binary) TransError!?ZigNode { const assign_rhs = assign.rhs.get(t.tree); if (assign_rhs == .cast) return null; const is_signed = t.signedness(assign.qt) == .signed; switch (assign_rhs) { .div_expr, .mod_expr => if (is_signed) return null, else => {}, } const lhs_ptr = assign.qt.isPointer(t.comp); const bin, const op: ZigTag, const cast: enum { none, shift, usize } = switch (assign_rhs) { .add_expr => |bin| .{ bin, if (t.typeHasWrappingOverflow(bin.qt)) .add_wrap_assign else .add_assign, if (lhs_ptr and t.signedness(bin.rhs.qt(t.tree)) == .signed) .usize else .none, }, .sub_expr => |bin| .{ bin, if (t.typeHasWrappingOverflow(bin.qt)) .sub_wrap_assign else .sub_assign, if (lhs_ptr and t.signedness(bin.rhs.qt(t.tree)) == .signed) .usize else .none, }, .mul_expr => |bin| .{ bin, if (t.typeHasWrappingOverflow(bin.qt)) .mul_wrap_assign else .mul_assign, .none, }, .mod_expr => |bin| .{ bin, .mod_assign, .none }, .div_expr => |bin| .{ bin, .div_assign, .none }, .shl_expr => |bin| .{ bin, .shl_assign, .shift }, .shr_expr => |bin| .{ bin, .shr_assign, .shift }, .bit_and_expr => |bin| .{ bin, .bit_and_assign, .none }, .bit_xor_expr => |bin| .{ bin, .bit_xor_assign, .none }, .bit_or_expr => |bin| .{ bin, .bit_or_assign, .none }, else => unreachable, }; const lhs_node = blk: { const old_dummy = t.compound_assign_dummy; defer t.compound_assign_dummy = old_dummy; t.compound_assign_dummy = lhs_dummy_opt orelse try t.transExpr(scope, assign.lhs, .used); break :blk try t.transExpr(scope, bin.lhs, .used); }; const rhs_node = try t.transExprCoercing(scope, bin.rhs, .used); const casted_rhs = switch (cast) { .none => rhs_node, .shift => try ZigTag.int_cast.create(t.arena, rhs_node), .usize => try t.usizeCastForWrappingPtrArithmetic(rhs_node), }; return try t.createBinOpNode(op, lhs_node, casted_rhs); } fn transIncDecExpr( t: *Translator, scope: *Scope, un: Node.Unary, position: enum { pre, post }, kind: enum { inc, dec }, used: ResultUsed, ) !ZigNode { const is_wrapping = t.typeHasWrappingOverflow(un.qt); const op: ZigTag = switch (kind) { .inc => if (is_wrapping) .add_wrap_assign else .add_assign, .dec => if (is_wrapping) .sub_wrap_assign else .sub_assign, }; const one_literal = ZigTag.one_literal.init(); if (used == .unused) { const operand = try t.transExpr(scope, un.operand, .used); return try t.createBinOpNode(op, operand, one_literal); } var block_scope = try Scope.Block.init(t, scope, true); defer block_scope.deinit(); const ref = try block_scope.reserveMangledName("ref"); const operand = try t.transExprCoercing(&block_scope.base, un.operand, .used); const operand_ref = try ZigTag.address_of.create(t.arena, operand); const ref_decl = try ZigTag.var_simple.create(t.arena, .{ .name = ref, .init = operand_ref }); try block_scope.statements.append(t.gpa, ref_decl); const ref_ident = try ZigTag.identifier.create(t.arena, ref); const ref_deref = try ZigTag.deref.create(t.arena, ref_ident); const effect = try t.createBinOpNode(op, ref_deref, one_literal); switch (position) { .pre => { try block_scope.statements.append(t.gpa, effect); const break_node = try ZigTag.break_val.create(t.arena, .{ .label = block_scope.label, .val = ref_deref, }); try block_scope.statements.append(t.gpa, break_node); }, .post => { const tmp = try block_scope.reserveMangledName("tmp"); const tmp_decl = try ZigTag.var_simple.create(t.arena, .{ .name = tmp, .init = ref_deref }); try block_scope.statements.append(t.gpa, tmp_decl); try block_scope.statements.append(t.gpa, effect); const tmp_ident = try ZigTag.identifier.create(t.arena, tmp); const break_node = try ZigTag.break_val.create(t.arena, .{ .label = block_scope.label, .val = tmp_ident, }); try block_scope.statements.append(t.gpa, break_node); }, } return try block_scope.complete(); } fn transPtrDiffExpr(t: *Translator, scope: *Scope, bin: Node.Binary) TransError!ZigNode { const lhs_uncasted = try t.transExpr(scope, bin.lhs, .used); const rhs_uncasted = try t.transExpr(scope, bin.rhs, .used); const lhs = try ZigTag.int_from_ptr.create(t.arena, lhs_uncasted); const rhs = try ZigTag.int_from_ptr.create(t.arena, rhs_uncasted); const sub_res = try t.createBinOpNode(.sub_wrap, lhs, rhs); // @divExact(@as(, @bitCast(@intFromPtr(lhs)) -% @intFromPtr(rhs)), @sizeOf()) const ptrdiff_type = try t.transTypeIntWidthOf(bin.qt, true); const bitcast = try ZigTag.as.create(t.arena, .{ .lhs = ptrdiff_type, .rhs = try ZigTag.bit_cast.create(t.arena, sub_res), }); // 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 Type and arbitrarily choose left. const lhs_ty = try t.transType(scope, bin.lhs.qt(t.tree), bin.lhs.tok(t.tree)); const c_pointer = t.getContainer(lhs_ty).?; if (c_pointer.castTag(.c_pointer)) |c_pointer_payload| { const sizeof = try ZigTag.sizeof.create(t.arena, c_pointer_payload.data.elem_type); return ZigTag.div_exact.create(t.arena, .{ .lhs = bitcast, .rhs = sizeof, }); } else { // This is an opaque/incomplete type. This subtraction exhibits Undefined Behavior by the C99 spec. // However, allowing subtraction on `void *` and function pointers is a commonly used extension. // So, just return the value in byte units, mirroring the behavior of this language extension as implemented by GCC and Clang. return bitcast; } } /// 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 makes 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 transPointerArithmeticSignedOp(t: *Translator, scope: *Scope, bin: Node.Binary, op_id: ZigTag) TransError!ZigNode { std.debug.assert(op_id == .add or op_id == .sub); const lhs_qt = bin.lhs.qt(t.tree); const swap_operands = op_id == .add and t.signedness(lhs_qt) == .signed; const swizzled_lhs = if (swap_operands) bin.rhs else bin.lhs; const swizzled_rhs = if (swap_operands) bin.lhs else bin.rhs; const lhs_node = try t.transExpr(scope, swizzled_lhs, .used); const rhs_node = try t.transExpr(scope, swizzled_rhs, .used); const bitcast_node = try t.usizeCastForWrappingPtrArithmetic(rhs_node); return t.createBinOpNode(op_id, lhs_node, bitcast_node); } fn transMemberAccess( t: *Translator, scope: *Scope, kind: enum { normal, ptr }, member_access: Node.MemberAccess, opt_base: ?ZigNode, ) TransError!ZigNode { const base_info = switch (kind) { .normal => member_access.base.qt(t.tree), .ptr => member_access.base.qt(t.tree).childType(t.comp), }; if (t.typeWasDemotedToOpaque(base_info)) { return t.fail(error.UnsupportedTranslation, member_access.access_tok, "member access of demoted record", .{}); } const record = base_info.getRecord(t.comp).?; const field = record.fields[member_access.member_index]; const field_name = if (field.name_tok == 0) t.anonymous_record_field_names.get(.{ .parent = base_info.base(t.comp).qt, .field = field.qt, }).? else field.name.lookup(t.comp); const base_node = opt_base orelse try t.transExpr(scope, member_access.base, .used); const lhs = switch (kind) { .normal => base_node, .ptr => try ZigTag.deref.create(t.arena, base_node), }; const field_access = try ZigTag.field_access.create(t.arena, .{ .lhs = lhs, .field_name = field_name, }); // Flexible array members are translated as member functions. if (member_access.member_index == record.fields.len - 1 or base_info.base(t.comp).type == .@"union") { if (field.qt.get(t.comp, .array)) |array_ty| { if (array_ty.len == .incomplete or (array_ty.len == .fixed and array_ty.len.fixed == 0)) { return ZigTag.call.create(t.arena, .{ .lhs = field_access, .args = &.{} }); } } } return field_access; } fn transArrayAccess(t: *Translator, scope: *Scope, array_access: Node.ArrayAccess, opt_base: ?ZigNode) TransError!ZigNode { // Unwrap the base statement if it's an array decayed to a bare pointer type // so that we index the array itself const base = base: { const base = array_access.base.get(t.tree); if (base != .cast) break :base array_access.base; if (base.cast.kind != .array_to_pointer) break :base array_access.base; break :base base.cast.operand; }; const base_node = opt_base orelse try t.transExpr(scope, base, .used); const index = index: { const index = try t.transExpr(scope, array_access.index, .used); const index_qt = array_access.index.qt(t.tree); const maybe_bigger_than_usize = switch (index_qt.base(t.comp).type) { .bool => { break :index try ZigTag.int_from_bool.create(t.arena, index); }, .int => |int| switch (int) { .long_long, .ulong_long, .int128, .uint128 => true, else => false, }, .bit_int => |bit_int| bit_int.bits > t.comp.target.ptrBitWidth(), else => unreachable, }; const is_nonnegative_int_literal = if (t.tree.value_map.get(array_access.index)) |val| val.compare(.gte, .zero, t.comp) else false; const is_signed = t.signedness(index_qt) == .signed; if (is_signed and !is_nonnegative_int_literal) { // First cast to `isize` to get proper sign extension and // then @bitCast to `usize` to satisfy the compiler. const index_isize = try ZigTag.as.create(t.arena, .{ .lhs = try ZigTag.type.create(t.arena, "isize"), .rhs = try ZigTag.int_cast.create(t.arena, index), }); break :index try ZigTag.bit_cast.create(t.arena, index_isize); } if (maybe_bigger_than_usize) { break :index try ZigTag.int_cast.create(t.arena, index); } break :index index; }; return ZigTag.array_access.create(t.arena, .{ .lhs = base_node, .rhs = index, }); } fn transOffsetof(t: *Translator, scope: *Scope, arg: Node.Index) TransError!ZigNode { // Translate __builtin_offsetof(T, designator) as // @intFromPtr(&(@as(*allowzero T, @ptrFromInt(0)).designator)) const member = try t.transMemberDesignator(scope, arg); const address = try ZigTag.address_of.create(t.arena, member); return ZigTag.int_from_ptr.create(t.arena, address); } fn transMemberDesignator(t: *Translator, scope: *Scope, arg: Node.Index) TransError!ZigNode { switch (arg.get(t.tree)) { .default_init_expr => |default| { const elem_node = try t.transType(scope, default.qt, default.last_tok); const ptr_ty = try ZigTag.single_pointer.create(t.arena, .{ .elem_type = elem_node, .is_allowzero = true, .is_const = false, .is_volatile = false, }); const zero = try ZigTag.ptr_from_int.create(t.arena, ZigTag.zero_literal.init()); return ZigTag.as.create(t.arena, .{ .lhs = ptr_ty, .rhs = zero }); }, .array_access_expr => |access| { const base = try t.transMemberDesignator(scope, access.base); return t.transArrayAccess(scope, access, base); }, .member_access_expr => |access| { const base = try t.transMemberDesignator(scope, access.base); return t.transMemberAccess(scope, .normal, access, base); }, .cast => |cast| { assert(cast.kind == .array_to_pointer); return t.transMemberDesignator(scope, cast.operand); }, else => unreachable, } } fn transBuiltinCall( t: *Translator, scope: *Scope, call: Node.BuiltinCall, used: ResultUsed, ) TransError!ZigNode { const builtin_name = t.tree.tokSlice(call.builtin_tok); if (std.mem.eql(u8, builtin_name, "__builtin_offsetof")) { const res = try t.transOffsetof(scope, call.args[0]); return t.maybeSuppressResult(used, res); } const builtin = builtins.map.get(builtin_name) orelse return t.fail(error.UnsupportedTranslation, call.builtin_tok, "TODO implement function '{s}' in std.zig.c_builtins", .{builtin_name}); if (builtin.tag) |tag| switch (tag) { .byte_swap, .ceil, .cos, .sin, .exp, .exp2, .exp10, .abs, .log, .log2, .log10, .round, .sqrt, .trunc, .floor => { assert(call.args.len == 1); const arg = try t.transExprCoercing(scope, call.args[0], .used); const arg_ty = try t.transType(scope, call.args[0].qt(t.tree), call.args[0].tok(t.tree)); const coerced = try ZigTag.as.create(t.arena, .{ .lhs = arg_ty, .rhs = arg }); const ptr = try t.arena.create(ast.Payload.UnOp); ptr.* = .{ .base = .{ .tag = tag }, .data = coerced }; return t.maybeSuppressResult(used, ZigNode.initPayload(&ptr.base)); }, .@"unreachable" => return ZigTag.@"unreachable".init(), else => unreachable, }; const arg_nodes = try t.arena.alloc(ZigNode, call.args.len); for (call.args, arg_nodes) |c_arg, *zig_arg| { zig_arg.* = try t.transExprCoercing(scope, c_arg, .used); } const builtin_identifier = try ZigTag.identifier.create(t.arena, "__builtin"); const field_access = try ZigTag.field_access.create(t.arena, .{ .lhs = builtin_identifier, .field_name = builtin.name, }); const res = try ZigTag.call.create(t.arena, .{ .lhs = field_access, .args = arg_nodes, }); if (call.qt.is(t.comp, .void)) return res; return t.maybeSuppressResult(used, res); } fn transCall( t: *Translator, scope: *Scope, call: Node.Call, used: ResultUsed, ) TransError!ZigNode { const raw_fn_expr = try t.transExpr(scope, call.callee, .used); const fn_expr = blk: { loop: switch (call.callee.get(t.tree)) { .paren_expr => |paren_expr| { continue :loop paren_expr.operand.get(t.tree); }, .decl_ref_expr => |decl_ref| { if (decl_ref.qt.is(t.comp, .func)) break :blk raw_fn_expr; }, .cast => |cast| { if (cast.kind == .function_to_pointer) { continue :loop cast.operand.get(t.tree); } }, .deref_expr, .addr_of_expr => |un| { continue :loop un.operand.get(t.tree); }, .generic_expr => |generic| { continue :loop generic.chosen.get(t.tree); }, .generic_association_expr => |generic| { continue :loop generic.expr.get(t.tree); }, .generic_default_expr => |generic| { continue :loop generic.expr.get(t.tree); }, else => {}, } break :blk try ZigTag.unwrap.create(t.arena, raw_fn_expr); }; const callee_qt = call.callee.qt(t.tree); const maybe_ptr_ty = callee_qt.get(t.comp, .pointer); const func_qt = if (maybe_ptr_ty) |ptr| ptr.child else callee_qt; const func_ty = func_qt.get(t.comp, .func).?; const arg_nodes = try t.arena.alloc(ZigNode, call.args.len); for (call.args, arg_nodes, 0..) |c_arg, *zig_arg, i| { if (i < func_ty.params.len) { zig_arg.* = try t.transExprCoercing(scope, c_arg, .used); if (zig_arg.isBoolRes() and !func_ty.params[i].qt.is(t.comp, .bool)) { // 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). zig_arg.* = try ZigTag.int_from_bool.create(t.arena, zig_arg.*); } } else { zig_arg.* = try t.transExpr(scope, c_arg, .used); if (zig_arg.isBoolRes()) { // Same as above but now we don't have a result type. const u1_node = try ZigTag.int_from_bool.create(t.arena, zig_arg.*); const c_int_node = try ZigTag.type.create(t.arena, "c_int"); zig_arg.* = try ZigTag.as.create(t.arena, .{ .lhs = c_int_node, .rhs = u1_node }); } } } const res = try ZigTag.call.create(t.arena, .{ .lhs = fn_expr, .args = arg_nodes, }); if (call.qt.is(t.comp, .void)) return res; return t.maybeSuppressResult(used, res); } const SuppressCast = enum { with_as, no_as }; fn transIntLiteral( t: *Translator, scope: *Scope, literal_index: Node.Index, used: ResultUsed, suppress_as: SuppressCast, ) TransError!ZigNode { const val = t.tree.value_map.get(literal_index).?; const int_lit_node = try t.createIntNode(val); if (suppress_as == .no_as) { return t.maybeSuppressResult(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))))); // @as(T, x) const ty_node = try t.transType(scope, literal_index.qt(t.tree), literal_index.tok(t.tree)); const as = try ZigTag.as.create(t.arena, .{ .lhs = ty_node, .rhs = int_lit_node }); return t.maybeSuppressResult(used, as); } fn transCharLiteral( t: *Translator, scope: *Scope, literal_index: Node.Index, used: ResultUsed, suppress_as: SuppressCast, ) TransError!ZigNode { const val = t.tree.value_map.get(literal_index).?; const char_literal = literal_index.get(t.tree).char_literal; const narrow = char_literal.kind == .ascii or char_literal.kind == .utf8; // C has a somewhat obscure feature called multi-character character constant // e.g. 'abcd' const int_value = val.toInt(u32, t.comp).?; const int_lit_node = if (char_literal.kind == .ascii and int_value > 255) try t.createNumberNode(int_value, .int) else try t.createCharLiteralNode(narrow, int_value); if (suppress_as == .no_as) { return t.maybeSuppressResult(used, int_lit_node); } // See comment in `transIntLiteral` for why this code is here. // @as(T, x) const as_node = try ZigTag.as.create(t.arena, .{ .lhs = try t.transType(scope, char_literal.qt, char_literal.literal_tok), .rhs = int_lit_node, }); return t.maybeSuppressResult(used, as_node); } fn transFloatLiteral( t: *Translator, scope: *Scope, literal_index: Node.Index, used: ResultUsed, suppress_as: SuppressCast, ) TransError!ZigNode { const val = t.tree.value_map.get(literal_index).?; const float_literal = literal_index.get(t.tree).float_literal; var allocating: std.Io.Writer.Allocating = .init(t.gpa); defer allocating.deinit(); _ = val.print(float_literal.qt, t.comp, &allocating.writer) catch return error.OutOfMemory; const float_lit_node = try ZigTag.float_literal.create(t.arena, try t.arena.dupe(u8, allocating.written())); if (suppress_as == .no_as) { return t.maybeSuppressResult(used, float_lit_node); } const as_node = try ZigTag.as.create(t.arena, .{ .lhs = try t.transType(scope, float_literal.qt, float_literal.literal_tok), .rhs = float_lit_node, }); return t.maybeSuppressResult(used, as_node); } fn transStringLiteral( t: *Translator, scope: *Scope, expr: Node.Index, literal: Node.CharLiteral, ) TransError!ZigNode { switch (literal.kind) { .ascii, .utf8 => return t.transNarrowStringLiteral(expr, literal), .utf16, .utf32, .wide => { const name = try std.fmt.allocPrint(t.arena, "{s}_string_{d}", .{ @tagName(literal.kind), t.getMangle() }); const array_type = try t.transTypeInit(scope, literal.qt, expr, literal.literal_tok); const lit_array = try t.transStringLiteralInitializer(expr, literal, array_type); const decl = try ZigTag.var_simple.create(t.arena, .{ .name = name, .init = lit_array }); try scope.appendNode(decl); return ZigTag.identifier.create(t.arena, name); }, } } fn transNarrowStringLiteral( t: *Translator, expr: Node.Index, literal: Node.CharLiteral, ) TransError!ZigNode { const val = t.tree.value_map.get(expr).?; const bytes = t.comp.interner.get(val.ref()).bytes; var allocating: std.Io.Writer.Allocating = try .initCapacity(t.gpa, bytes.len); defer allocating.deinit(); aro.Value.printString(bytes, literal.qt, t.comp, &allocating.writer) catch return error.OutOfMemory; return ZigTag.string_literal.create(t.arena, try t.arena.dupe(u8, allocating.written())); } /// 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( t: *Translator, expr: Node.Index, literal: Node.CharLiteral, array_type: ZigNode, ) TransError!ZigNode { assert(array_type.tag() == .array_type or array_type.tag() == .null_sentinel_array_type); const is_narrow = literal.kind == .ascii or literal.kind == .utf8; // The length of the string literal excluding the sentinel. const str_length = literal.qt.arrayLen(t.comp).? - 1; const payload = (array_type.castTag(.array_type) orelse array_type.castTag(.null_sentinel_array_type).?).data; const array_size = payload.len; const elem_type = payload.elem_type; if (array_size == 0) return ZigTag.empty_array.create(t.arena, array_type); const num_inits = @min(str_length, array_size); if (num_inits == 0) { return ZigTag.array_filler.create(t.arena, .{ .type = elem_type, .filler = ZigTag.zero_literal.init(), .count = array_size, }); } const init_node = if (is_narrow) blk: { // "string literal".* or string literal"[0..num_inits].* var str = try t.transNarrowStringLiteral(expr, literal); if (str_length != array_size) str = try ZigTag.string_slice.create(t.arena, .{ .string = str, .end = num_inits }); break :blk try ZigTag.deref.create(t.arena, str); } else blk: { const size = literal.qt.childType(t.comp).sizeof(t.comp); const val = t.tree.value_map.get(expr).?; const bytes = t.comp.interner.get(val.ref()).bytes; const init_list = try t.arena.alloc(ZigNode, @intCast(num_inits)); for (init_list, 0..) |*item, i| { const codepoint = switch (size) { 2 => @as(*const u16, @ptrCast(@alignCast(bytes.ptr + i * 2))).*, 4 => @as(*const u32, @ptrCast(@alignCast(bytes.ptr + i * 4))).*, else => unreachable, }; item.* = try t.createCharLiteralNode(false, codepoint); } const init_args: ast.Payload.Array.ArrayTypeInfo = .{ .len = num_inits, .elem_type = elem_type }; const init_array_type = if (array_type.tag() == .array_type) try ZigTag.array_type.create(t.arena, init_args) else try ZigTag.null_sentinel_array_type.create(t.arena, init_args); break :blk try ZigTag.array_init.create(t.arena, .{ .cond = init_array_type, .cases = init_list, }); }; if (num_inits == array_size) return init_node; assert(array_size > str_length); // If array_size <= str_length, `num_inits == array_size` and we've already returned. const filler_node = try ZigTag.array_filler.create(t.arena, .{ .type = elem_type, .filler = ZigTag.zero_literal.init(), .count = array_size - str_length, }); return ZigTag.array_cat.create(t.arena, .{ .lhs = init_node, .rhs = filler_node }); } fn transCompoundLiteral( t: *Translator, scope: *Scope, literal: Node.CompoundLiteral, used: ResultUsed, ) TransError!ZigNode { if (used == .unused) { return t.transExpr(scope, literal.initializer, .unused); } // TODO taking a reference to a compound literal should result in a mutable // pointer (unless the literal is const). const initializer = try t.transExprCoercing(scope, literal.initializer, .used); const ty = try t.transType(scope, literal.qt, literal.l_paren_tok); if (!literal.thread_local and literal.storage_class != .static) { // In the simple case a compound literal can be translated // simply as `@as(type, initializer)`. return ZigTag.as.create(t.arena, .{ .lhs = ty, .rhs = initializer }); } // Otherwise static or thread local compound literals are translated as // a reference to a variable wrapped in a struct. var block_scope = try Scope.Block.init(t, scope, true); defer block_scope.deinit(); const tmp = try block_scope.reserveMangledName("tmp"); const wrapped_name = "compound_literal"; // const tmp = struct { var compound_literal = initializer }; const temp_decl = try ZigTag.var_decl.create(t.arena, .{ .is_pub = false, .is_const = literal.qt.@"const", .is_extern = false, .is_export = false, .is_threadlocal = literal.thread_local, .linksection_string = null, .alignment = null, .name = wrapped_name, .type = ty, .init = initializer, }); const wrapped = try ZigTag.wrapped_local.create(t.arena, .{ .name = tmp, .init = temp_decl }); try block_scope.statements.append(t.gpa, wrapped); // break :blk tmp.compound_literal const static_tmp_ident = try ZigTag.identifier.create(t.arena, tmp); const field_access = try ZigTag.field_access.create(t.arena, .{ .lhs = static_tmp_ident, .field_name = wrapped_name, }); const break_node = try ZigTag.break_val.create(t.arena, .{ .label = block_scope.label, .val = field_access, }); try block_scope.statements.append(t.gpa, break_node); return block_scope.complete(); } fn transDefaultInit( t: *Translator, scope: *Scope, default_init: Node.DefaultInit, used: ResultUsed, suppress_as: SuppressCast, ) TransError!ZigNode { assert(used == .used); const type_node = try t.transType(scope, default_init.qt, default_init.last_tok); return try t.createZeroValueNode(default_init.qt, type_node, suppress_as); } fn transArrayInit( t: *Translator, scope: *Scope, array_init: Node.ContainerInit, used: ResultUsed, ) TransError!ZigNode { assert(used == .used); const array_item_qt = array_init.container_qt.childType(t.comp); const array_item_type = try t.transType(scope, array_item_qt, array_init.l_brace_tok); var maybe_lhs: ?ZigNode = null; var val_list: std.ArrayList(ZigNode) = .empty; defer val_list.deinit(t.gpa); var i: usize = 0; while (i < array_init.items.len) { const rhs = switch (array_init.items[i].get(t.tree)) { .array_filler_expr => |array_filler| blk: { const node = try ZigTag.array_filler.create(t.arena, .{ .type = array_item_type, .filler = try t.createZeroValueNode(array_item_qt, array_item_type, .no_as), .count = @intCast(array_filler.count), }); i += 1; break :blk node; }, else => blk: { defer val_list.clearRetainingCapacity(); while (i < array_init.items.len) : (i += 1) { if (array_init.items[i].get(t.tree) == .array_filler_expr) break; const expr = try t.transExprCoercing(scope, array_init.items[i], .used); try val_list.append(t.gpa, expr); } const array_type = try ZigTag.array_type.create(t.arena, .{ .elem_type = array_item_type, .len = val_list.items.len, }); const array_init_node = try ZigTag.array_init.create(t.arena, .{ .cond = array_type, .cases = try t.arena.dupe(ZigNode, val_list.items), }); break :blk array_init_node; }, }; maybe_lhs = if (maybe_lhs) |lhs| blk: { const cat = try ZigTag.array_cat.create(t.arena, .{ .lhs = lhs, .rhs = rhs, }); break :blk cat; } else rhs; } return maybe_lhs orelse try ZigTag.container_init_dot.create(t.arena, &.{}); } fn transUnionInit( t: *Translator, scope: *Scope, union_init: Node.UnionInit, used: ResultUsed, ) TransError!ZigNode { assert(used == .used); const init_expr = union_init.initializer orelse return ZigTag.undefined_literal.init(); if (init_expr.get(t.tree) == .default_init_expr) { return try t.transExpr(scope, init_expr, used); } const union_type = try t.transType(scope, union_init.union_qt, union_init.l_brace_tok); const union_base = union_init.union_qt.base(t.comp); const field = union_base.type.@"union".fields[union_init.field_index]; const field_name = if (field.name_tok == 0) t.anonymous_record_field_names.get(.{ .parent = union_base.qt, .field = field.qt, }).? else field.name.lookup(t.comp); const field_init = try t.arena.create(ast.Payload.ContainerInit.Initializer); field_init.* = .{ .name = field_name, .value = try t.transExprCoercing(scope, init_expr, .used), }; const container_init = try ZigTag.container_init.create(t.arena, .{ .lhs = union_type, .inits = field_init[0..1], }); return container_init; } fn transStructInit( t: *Translator, scope: *Scope, struct_init: Node.ContainerInit, used: ResultUsed, ) TransError!ZigNode { assert(used == .used); const struct_type = try t.transType(scope, struct_init.container_qt, struct_init.l_brace_tok); const field_inits = try t.arena.alloc(ast.Payload.ContainerInit.Initializer, struct_init.items.len); const struct_base = struct_init.container_qt.base(t.comp); for ( field_inits, struct_init.items, struct_base.type.@"struct".fields, ) |*init, field_expr, field| { const field_name = if (field.name_tok == 0) t.anonymous_record_field_names.get(.{ .parent = struct_base.qt, .field = field.qt, }).? else field.name.lookup(t.comp); init.* = .{ .name = field_name, .value = try t.transExprCoercing(scope, field_expr, .used), }; } const container_init = try ZigTag.container_init.create(t.arena, .{ .lhs = struct_type, .inits = field_inits, }); return container_init; } fn transTypeInfo( t: *Translator, scope: *Scope, op: ZigTag, typeinfo: Node.TypeInfo, ) TransError!ZigNode { const operand = operand: { if (typeinfo.expr) |expr| { const operand = try t.transExpr(scope, expr, .used); if (operand.tag() == .string_literal) { const deref = try ZigTag.deref.create(t.arena, operand); break :operand try ZigTag.typeof.create(t.arena, deref); } break :operand try ZigTag.typeof.create(t.arena, operand); } break :operand try t.transType(scope, typeinfo.operand_qt, typeinfo.op_tok); }; const payload = try t.arena.create(ast.Payload.UnOp); payload.* = .{ .base = .{ .tag = op }, .data = operand, }; return ZigNode.initPayload(&payload.base); } fn transStmtExpr( t: *Translator, scope: *Scope, stmt_expr: Node.Unary, used: ResultUsed, ) TransError!ZigNode { const compound_stmt = stmt_expr.operand.get(t.tree).compound_stmt; if (used == .unused) { return t.transCompoundStmt(scope, compound_stmt); } var block_scope = try Scope.Block.init(t, scope, true); defer block_scope.deinit(); for (compound_stmt.body[0 .. compound_stmt.body.len - 1]) |stmt| { const result = try t.transStmt(&block_scope.base, stmt); switch (result.tag()) { .declaration, .empty_block => {}, else => try block_scope.statements.append(t.gpa, result), } } const last_result = try t.transExpr(&block_scope.base, compound_stmt.body[compound_stmt.body.len - 1], .used); switch (last_result.tag()) { .declaration, .empty_block => {}, else => { const break_node = try ZigTag.break_val.create(t.arena, .{ .label = block_scope.label, .val = last_result, }); try block_scope.statements.append(t.gpa, break_node); }, } return block_scope.complete(); } fn transConvertvectorExpr( t: *Translator, scope: *Scope, convertvector: Node.Convertvector, ) TransError!ZigNode { var block_scope = try Scope.Block.init(t, scope, true); defer block_scope.deinit(); const src_expr_node = try t.transExpr(&block_scope.base, convertvector.operand, .used); const tmp = try block_scope.reserveMangledName("tmp"); const tmp_decl = try ZigTag.var_simple.create(t.arena, .{ .name = tmp, .init = src_expr_node }); try block_scope.statements.append(t.gpa, tmp_decl); const tmp_ident = try ZigTag.identifier.create(t.arena, tmp); const dest_type_node = try t.transType(&block_scope.base, convertvector.dest_qt, convertvector.builtin_tok); const dest_vec_ty = convertvector.dest_qt.get(t.comp, .vector).?; const src_vec_ty = convertvector.operand.qt(t.tree).get(t.comp, .vector).?; const src_elem_sk = src_vec_ty.elem.scalarKind(t.comp); const dest_elem_sk = convertvector.dest_qt.childType(t.comp).scalarKind(t.comp); const items = try t.arena.alloc(ZigNode, dest_vec_ty.len); for (items, 0..dest_vec_ty.len) |*item, i| { const value = try ZigTag.array_access.create(t.arena, .{ .lhs = tmp_ident, .rhs = try t.createNumberNode(i, .int), }); if (src_elem_sk == .float and dest_elem_sk == .float) { item.* = try ZigTag.float_cast.create(t.arena, value); } else if (src_elem_sk == .float) { item.* = try ZigTag.int_from_float.create(t.arena, value); } else if (dest_elem_sk == .float) { item.* = try ZigTag.float_from_int.create(t.arena, value); } else { item.* = try t.transIntCast(value, src_vec_ty.elem, dest_vec_ty.elem); } } const vec_init = try ZigTag.array_init.create(t.arena, .{ .cond = dest_type_node, .cases = items, }); const break_node = try ZigTag.break_val.create(t.arena, .{ .label = block_scope.label, .val = vec_init, }); try block_scope.statements.append(t.gpa, break_node); return block_scope.complete(); } fn transShufflevectorExpr( t: *Translator, scope: *Scope, shufflevector: Node.Shufflevector, ) TransError!ZigNode { if (shufflevector.indexes.len == 0) { return t.fail(error.UnsupportedTranslation, shufflevector.builtin_tok, "@shuffle needs at least 1 index", .{}); } const a = try t.transExpr(scope, shufflevector.lhs, .used); const b = try t.transExpr(scope, shufflevector.rhs, .used); // First two arguments to __builtin_shufflevector must be the same type const vector_child_type = try t.vectorTypeInfo(a, "child"); const vector_len = try t.vectorTypeInfo(a, "len"); const shuffle_mask = blk: { const mask_len = shufflevector.indexes.len; const mask_type = try ZigTag.vector.create(t.arena, .{ .lhs = try t.createNumberNode(mask_len, .int), .rhs = try ZigTag.type.create(t.arena, "i32"), }); const init_list = try t.arena.alloc(ZigNode, mask_len); for (init_list, shufflevector.indexes) |*init, index| { const index_expr = try t.transExprCoercing(scope, index, .used); const converted_index = try t.createHelperCallNode(.shuffleVectorIndex, &.{ index_expr, vector_len }); init.* = converted_index; } break :blk try ZigTag.array_init.create(t.arena, .{ .cond = mask_type, .cases = init_list, }); }; return ZigTag.shuffle.create(t.arena, .{ .element_type = vector_child_type, .a = a, .b = b, .mask_vector = shuffle_mask, }); } // ===================== // Node creation helpers // ===================== fn createZeroValueNode( t: *Translator, qt: QualType, type_node: ZigNode, suppress_as: SuppressCast, ) !ZigNode { switch (qt.base(t.comp).type) { .bool => return ZigTag.false_literal.init(), .int, .bit_int, .float => { const zero_literal = ZigTag.zero_literal.init(); return switch (suppress_as) { .with_as => try t.createBinOpNode(.as, type_node, zero_literal), .no_as => zero_literal, }; }, .pointer => { const null_literal = ZigTag.null_literal.init(); return switch (suppress_as) { .with_as => try t.createBinOpNode(.as, type_node, null_literal), .no_as => null_literal, }; }, else => {}, } return try ZigTag.std_mem_zeroes.create(t.arena, type_node); } fn createIntNode(t: *Translator, int: aro.Value) !ZigNode { var space: aro.Interner.Tag.Int.BigIntSpace = undefined; var big = t.comp.interner.get(int.ref()).toBigInt(&space); const is_negative = !big.positive; big.positive = true; const str = big.toStringAlloc(t.arena, 10, .lower) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, }; const res = try ZigTag.integer_literal.create(t.arena, str); if (is_negative) return ZigTag.negate.create(t.arena, res); return res; } fn createNumberNode(t: *Translator, num: anytype, num_kind: enum { int, float }) !ZigNode { const fmt_s = switch (@typeInfo(@TypeOf(num))) { .int, .comptime_int => "{d}", else => "{s}", }; const str = try std.fmt.allocPrint(t.arena, fmt_s, .{num}); if (num_kind == .float) return ZigTag.float_literal.create(t.arena, str) else return ZigTag.integer_literal.create(t.arena, str); } fn createCharLiteralNode(t: *Translator, narrow: bool, val: u32) TransError!ZigNode { return ZigTag.char_literal.create(t.arena, if (narrow) try std.fmt.allocPrint(t.arena, "'{f}'", .{std.zig.fmtChar(@as(u8, @intCast(val)))}) else try std.fmt.allocPrint(t.arena, "'\\u{{{x}}}'", .{val})); } fn createBinOpNode( t: *Translator, op: ZigTag, lhs: ZigNode, rhs: ZigNode, ) !ZigNode { const payload = try t.arena.create(ast.Payload.BinOp); payload.* = .{ .base = .{ .tag = op }, .data = .{ .lhs = lhs, .rhs = rhs, }, }; return ZigNode.initPayload(&payload.base); } pub fn createHelperCallNode(t: *Translator, name: std.meta.DeclEnum(std.zig.c_translation.helpers), args_opt: ?[]const ZigNode) !ZigNode { if (args_opt) |args| { return ZigTag.helper_call.create(t.arena, .{ .name = @tagName(name), .args = try t.arena.dupe(ZigNode, args), }); } else { return ZigTag.helper_ref.create(t.arena, @tagName(name)); } } /// 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 -> @as(usize, @bitCast(@as(isize, @intCast(node)))) fn usizeCastForWrappingPtrArithmetic(t: *Translator, node: ZigNode) TransError!ZigNode { const intcast_node = try ZigTag.as.create(t.arena, .{ .lhs = try ZigTag.type.create(t.arena, "isize"), .rhs = try ZigTag.int_cast.create(t.arena, node), }); return ZigTag.as.create(t.arena, .{ .lhs = try ZigTag.type.create(t.arena, "usize"), .rhs = try ZigTag.bit_cast.create(t.arena, intcast_node), }); } /// @typeInfo(@TypeOf(vec_node)).vector. fn vectorTypeInfo(t: *Translator, vec_node: ZigNode, field: []const u8) TransError!ZigNode { const typeof_call = try ZigTag.typeof.create(t.arena, vec_node); const typeinfo_call = try ZigTag.typeinfo.create(t.arena, typeof_call); const vector_type_info = try ZigTag.field_access.create(t.arena, .{ .lhs = typeinfo_call, .field_name = "vector" }); return ZigTag.field_access.create(t.arena, .{ .lhs = vector_type_info, .field_name = field }); } /// Build a getter function for a flexible array field in a C record /// 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 createFlexibleMemberFn( t: *Translator, member_name: []const u8, field_name: []const u8, ) Error!ZigNode { const self_param_name = "self"; const self_param = try ZigTag.identifier.create(t.arena, self_param_name); const self_type = try ZigTag.typeof.create(t.arena, self_param); const fn_params = try t.arena.alloc(ast.Payload.Param, 1); fn_params[0] = .{ .name = self_param_name, .type = ZigTag.@"anytype".init(), .is_noalias = false, }; // @typeInfo(@TypeOf(self.*.)).pointer.child const dereffed = try ZigTag.deref.create(t.arena, self_param); const field_access = try ZigTag.field_access.create(t.arena, .{ .lhs = dereffed, .field_name = field_name }); const type_of = try ZigTag.typeof.create(t.arena, field_access); const type_info = try ZigTag.typeinfo.create(t.arena, type_of); const array_info = try ZigTag.field_access.create(t.arena, .{ .lhs = type_info, .field_name = "array" }); const child_info = try ZigTag.field_access.create(t.arena, .{ .lhs = array_info, .field_name = "child" }); const return_type = try t.createHelperCallNode(.FlexibleArrayType, &.{ self_type, child_info }); // return @ptrCast(&self.*.); const address_of = try ZigTag.address_of.create(t.arena, field_access); const aligned = try ZigTag.align_cast.create(t.arena, address_of); const casted = try ZigTag.ptr_cast.create(t.arena, aligned); const return_stmt = try ZigTag.@"return".create(t.arena, casted); const body = try ZigTag.block_single.create(t.arena, return_stmt); return ZigTag.func.create(t.arena, .{ .is_pub = true, .is_extern = false, .is_export = false, .is_inline = false, .is_var_args = false, .name = member_name, .linksection_string = null, .explicit_callconv = null, .params = fn_params, .return_type = return_type, .body = body, .alignment = null, }); } // ================= // Macro translation // ================= fn transMacros(t: *Translator) !void { var tok_list: std.ArrayList(CToken) = .empty; defer tok_list.deinit(t.gpa); var pattern_list = try PatternList.init(t.gpa); defer pattern_list.deinit(t.gpa); for (t.pp.defines.keys(), t.pp.defines.values()) |name, macro| { if (macro.isBuiltin()) continue; if (t.global_scope.containsNow(name)) { continue; } tok_list.items.len = 0; try tok_list.ensureUnusedCapacity(t.gpa, macro.tokens.len); for (macro.tokens) |tok| { switch (tok.id) { .invalid => continue, .whitespace => continue, .comment => continue, .macro_ws => continue, else => {}, } tok_list.appendAssumeCapacity(tok); } if (macro.is_func) { const ms: PatternList.MacroSlicer = .{ .tokens = tok_list.items, .source = t.comp.getSource(macro.loc.id).buf, .params = @intCast(macro.params.len), }; if (try pattern_list.match(ms)) |impl| { const decl = try ZigTag.pub_var_simple.create(t.arena, .{ .name = name, .init = try t.createHelperCallNode(impl, null), }); try t.addTopLevelDecl(name, decl); continue; } } if (t.checkTranslatableMacro(tok_list.items, macro.params)) |err| { switch (err) { .undefined_identifier => |ident| try t.failDeclExtra(&t.global_scope.base, macro.loc, name, "unable to translate macro: undefined identifier `{s}`", .{ident}), .invalid_arg_usage => |ident| try t.failDeclExtra(&t.global_scope.base, macro.loc, name, "unable to translate macro: untranslatable usage of arg `{s}`", .{ident}), } continue; } var macro_translator: MacroTranslator = .{ .t = t, .tokens = tok_list.items, .source = t.comp.getSource(macro.loc.id).buf, .name = name, .macro = macro, }; const res = if (macro.is_func) macro_translator.transFnMacro() else macro_translator.transMacro(); res catch |err| switch (err) { error.ParseError => continue, error.OutOfMemory => |e| return e, }; } } const MacroTranslateError = union(enum) { undefined_identifier: []const u8, invalid_arg_usage: []const u8, }; fn checkTranslatableMacro(t: *Translator, tokens: []const CToken, params: []const []const u8) ?MacroTranslateError { var last_is_type_kw = false; var i: usize = 0; while (i < tokens.len) : (i += 1) { const token = tokens[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; }, .macro_param, .macro_param_no_expand => { if (last_is_type_kw) { return .{ .invalid_arg_usage = params[token.end] }; } }, .identifier, .extended_identifier => { const identifier = t.pp.tokSlice(token); if (!t.global_scope.contains(identifier) and !builtins.map.has(identifier)) { return .{ .undefined_identifier = identifier }; } }, else => {}, } last_is_type_kw = false; } return null; } fn getContainer(t: *Translator, node: ZigNode) ?ZigNode { 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 (t.global_scope.sym_table.get(ident.data)) |value| { if (value.castTag(.var_decl)) |var_decl| return t.getContainer(var_decl.data.init.?); if (value.castTag(.var_simple) orelse value.castTag(.pub_var_simple)) |var_decl| return t.getContainer(var_decl.data.init); } }, .field_access => { const field_access = node.castTag(.field_access).?; if (t.getContainerTypeOf(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 t.getContainer(field.type); } } } } }, else => {}, } return null; } fn getContainerTypeOf(t: *Translator, ref: ZigNode) ?ZigNode { if (ref.castTag(.identifier)) |ident| { if (t.global_scope.sym_table.get(ident.data)) |value| { if (value.castTag(.var_decl)) |var_decl| { return t.getContainer(var_decl.data.type); } } } else if (ref.castTag(.field_access)) |field_access| { if (t.getContainerTypeOf(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 t.getContainer(field.type); } } } else return ty_node; } } return null; } pub fn getFnProto(t: *Translator, ref: ZigNode) ?*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 (t.getContainerTypeOf(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; }