Commit 51efd553ae

Thomas Ives <tom@trives.co.uk>
2021-11-02 23:43:51
C backend: Improve lowering of Zig types to C types
1. Changed Zig pointers to functions to be typedef'd so then we can treat them the same as other types. 2. Distinguished between const slices (zig_L prefix) and mut slices (zig_M prefix). 3. Changed lowering of Zig "const pointers" (e.g. *const u8) to to C "pointers to const" (e.g. const char *) rather than C "const pointers" (e.g. char * const) 4. Ensured that all typedefs are "linked" even if the decl doesn't require any forward declarations 5. Added test that exercises function pointer type rendering 6. Changed .slice_ptr instruction to allocate pointer local rather than a uintptr_t local
1 parent 91c3206
Changed files (3)
src
codegen
link
test
behavior
src/codegen/c.zig
@@ -179,6 +179,14 @@ pub const DeclGen = struct {
         return error.AnalysisFail;
     }
 
+    fn getTypedefName(dg: *DeclGen, t: Type) ?[]const u8 {
+        if (dg.typedefs.get(t)) |some| {
+            return some.name;
+        } else {
+            return null;
+        }
+    }
+
     fn renderDeclValue(
         dg: *DeclGen,
         writer: anytype,
@@ -200,14 +208,20 @@ pub const DeclGen = struct {
             return;
         }
 
-        // Determine if we must pointer cast.
         assert(decl.has_tv);
-        if (ty.eql(decl.ty)) {
-            try writer.writeByte('&');
-        } else {
-            try writer.writeAll("(");
-            try dg.renderType(writer, ty);
-            try writer.writeAll(")&");
+        // We shouldn't cast C function pointers as this is UB (when you call
+        // them).  The analysis until now should ensure that the C function
+        // pointers are compatible.  If they are not, then there is a bug
+        // somewhere and we should let the C compiler tell us about it.
+        if (ty.castPtrToFn() == null) {
+            // Determine if we must pointer cast.
+            if (ty.eql(decl.ty)) {
+                try writer.writeByte('&');
+            } else {
+                try writer.writeAll("(");
+                try dg.renderType(writer, ty);
+                try writer.writeAll(")&");
+            }
         }
         try dg.renderDeclName(decl, writer);
     }
@@ -514,7 +528,190 @@ pub const DeclGen = struct {
         try w.writeByte(')');
     }
 
+    fn renderPtrToFnTypedef(dg: *DeclGen, t: Type, fn_ty: Type) error{ OutOfMemory, AnalysisFail }![]const u8 {
+        var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
+        defer buffer.deinit();
+        const bw = buffer.writer();
+
+        const fn_info = fn_ty.fnInfo();
+
+        try bw.writeAll("typedef ");
+        try dg.renderType(bw, fn_info.return_type);
+        try bw.writeAll(" (*");
+
+        const name_start = buffer.items.len;
+        // TODO: typeToCIdentifier truncates to 128 bytes, we probably don't want to do this
+        try bw.print("zig_F_{s})(", .{typeToCIdentifier(t)});
+        const name_end = buffer.items.len - 2;
+
+        const param_len = fn_info.param_types.len;
+        const is_var_args = fn_info.is_var_args;
+        if (param_len == 0 and !is_var_args)
+            try bw.writeAll("void")
+        else {
+            var index: usize = 0;
+            while (index < param_len) : (index += 1) {
+                if (index > 0) {
+                    try bw.writeAll(", ");
+                }
+                try dg.renderType(bw, fn_info.param_types[index]);
+            }
+        }
+        if (is_var_args) {
+            if (param_len != 0) try bw.writeAll(", ");
+            try bw.writeAll("...");
+        }
+        try bw.writeAll(");\n");
+
+        const rendered = buffer.toOwnedSlice();
+        errdefer dg.typedefs.allocator.free(rendered);
+        const name = rendered[name_start..name_end];
+
+        try dg.typedefs.ensureUnusedCapacity(1);
+        dg.typedefs.putAssumeCapacityNoClobber(
+            try t.copy(dg.typedefs_arena),
+            .{ .name = name, .rendered = rendered },
+        );
+
+        return name;
+    }
+
+    fn renderSliceTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 {
+        var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
+        defer buffer.deinit();
+        const bw = buffer.writer();
+
+        try bw.writeAll("typedef struct { ");
+        const elem_type = t.elemType();
+        try dg.renderType(bw, elem_type);
+        if (t.isConstPtr()) {
+            try bw.writeAll(" const");
+        }
+        if (t.isVolatilePtr()) {
+            try bw.writeAll(" volatile");
+        }
+        try bw.writeAll(" *");
+        try bw.writeAll("ptr; size_t len; } ");
+        const name_index = buffer.items.len;
+        if (t.isConstPtr()) {
+            try bw.print("zig_L_{s};\n", .{typeToCIdentifier(elem_type)});
+        } else {
+            try bw.print("zig_M_{s};\n", .{typeToCIdentifier(elem_type)});
+        }
+
+        const rendered = buffer.toOwnedSlice();
+        errdefer dg.typedefs.allocator.free(rendered);
+        const name = rendered[name_index .. rendered.len - 2];
+
+        try dg.typedefs.ensureUnusedCapacity(1);
+        dg.typedefs.putAssumeCapacityNoClobber(
+            try t.copy(dg.typedefs_arena),
+            .{ .name = name, .rendered = rendered },
+        );
+
+        return name;
+    }
+
+    fn renderStructTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 {
+        const struct_obj = t.castTag(.@"struct").?.data; // Handle 0 bit types elsewhere.
+        const fqn = try struct_obj.getFullyQualifiedName(dg.typedefs.allocator);
+        defer dg.typedefs.allocator.free(fqn);
+
+        var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
+        defer buffer.deinit();
+
+        try buffer.appendSlice("typedef struct {\n");
+        {
+            var it = struct_obj.fields.iterator();
+            while (it.next()) |entry| {
+                const field_ty = entry.value_ptr.ty;
+                const name: CValue = .{ .bytes = entry.key_ptr.* };
+                try buffer.append(' ');
+                try dg.renderTypeAndName(buffer.writer(), field_ty, name, .Mut);
+                try buffer.appendSlice(";\n");
+            }
+        }
+        try buffer.appendSlice("} ");
+
+        const name_start = buffer.items.len;
+        try buffer.writer().print("zig_S_{s};\n", .{fmtIdent(fqn)});
+
+        const rendered = buffer.toOwnedSlice();
+        errdefer dg.typedefs.allocator.free(rendered);
+        const name = rendered[name_start .. rendered.len - 2];
+
+        try dg.typedefs.ensureUnusedCapacity(1);
+        dg.typedefs.putAssumeCapacityNoClobber(
+            try t.copy(dg.typedefs_arena),
+            .{ .name = name, .rendered = rendered },
+        );
+
+        return name;
+    }
+
+    fn renderErrorUnionTypedef(dg: *DeclGen, t: Type) error{ OutOfMemory, AnalysisFail }![]const u8 {
+        const child_type = t.errorUnionPayload();
+        const err_set_type = t.errorUnionSet();
+
+        var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
+        defer buffer.deinit();
+        const bw = buffer.writer();
+
+        try bw.writeAll("typedef struct { ");
+        try dg.renderType(bw, child_type);
+        try bw.writeAll(" payload; uint16_t error; } ");
+        const name_index = buffer.items.len;
+        if (err_set_type.castTag(.error_set_inferred)) |inf_err_set_payload| {
+            const func = inf_err_set_payload.data.func;
+            try bw.writeAll("zig_E_");
+            try dg.renderDeclName(func.owner_decl, bw);
+            try bw.writeAll(";\n");
+        } else {
+            try bw.print("zig_E_{s}_{s};\n", .{
+                typeToCIdentifier(err_set_type), typeToCIdentifier(child_type),
+            });
+        }
+
+        const rendered = buffer.toOwnedSlice();
+        errdefer dg.typedefs.allocator.free(rendered);
+        const name = rendered[name_index .. rendered.len - 2];
+
+        try dg.typedefs.ensureUnusedCapacity(1);
+        dg.typedefs.putAssumeCapacityNoClobber(
+            try t.copy(dg.typedefs_arena),
+            .{ .name = name, .rendered = rendered },
+        );
+
+        return name;
+    }
+
+    fn renderOptionalTypedef(dg: *DeclGen, t: Type, child_type: Type) error{ OutOfMemory, AnalysisFail }![]const u8 {
+        var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
+        defer buffer.deinit();
+        const bw = buffer.writer();
+
+        try bw.writeAll("typedef struct { ");
+        try dg.renderType(bw, child_type);
+        try bw.writeAll(" payload; bool is_null; } ");
+        const name_index = buffer.items.len;
+        try bw.print("zig_Q_{s};\n", .{typeToCIdentifier(child_type)});
+
+        const rendered = buffer.toOwnedSlice();
+        errdefer dg.typedefs.allocator.free(rendered);
+        const name = rendered[name_index .. rendered.len - 2];
+
+        try dg.typedefs.ensureUnusedCapacity(1);
+        dg.typedefs.putAssumeCapacityNoClobber(
+            try t.copy(dg.typedefs_arena),
+            .{ .name = name, .rendered = rendered },
+        );
+
+        return name;
+    }
+
     fn renderType(dg: *DeclGen, w: anytype, t: Type) error{ OutOfMemory, AnalysisFail }!void {
+        const target = dg.module.getTarget();
+
         switch (t.zigTypeTag()) {
             .NoReturn => {
                 try w.writeAll("zig_noreturn void");
@@ -542,7 +739,7 @@ pub const DeclGen = struct {
                     .c_longlong => try w.writeAll("long long"),
                     .c_ulonglong => try w.writeAll("unsigned long long"),
                     .int_signed, .int_unsigned => {
-                        const info = t.intInfo(dg.module.getTarget());
+                        const info = t.intInfo(target);
                         const sign_prefix = switch (info.signedness) {
                             .signed => "",
                             .unsigned => "u",
@@ -554,7 +751,6 @@ pub const DeclGen = struct {
                     else => unreachable,
                 }
             },
-
             .Float => {
                 switch (t.tag()) {
                     .f32 => try w.writeAll("float"),
@@ -565,196 +761,71 @@ pub const DeclGen = struct {
                     else => unreachable,
                 }
             },
-
             .Pointer => {
                 if (t.isSlice()) {
-                    if (dg.typedefs.get(t)) |some| {
-                        return w.writeAll(some.name);
-                    }
+                    const name = dg.getTypedefName(t) orelse
+                        try dg.renderSliceTypedef(t);
 
-                    var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
-                    defer buffer.deinit();
-                    const bw = buffer.writer();
-
-                    try bw.writeAll("typedef struct { ");
-                    const elem_type = t.elemType();
-                    try dg.renderType(bw, elem_type);
-                    try bw.writeAll(" *");
-                    // We skip the const qualifier because C's type system
-                    // would not allow a local mutable variable which is this slice type
-                    // to be overwritten with a new slice type.
-                    if (t.isVolatilePtr()) {
-                        try bw.writeAll("volatile ");
-                    }
-                    try bw.writeAll("ptr; size_t len; } ");
-                    const name_index = buffer.items.len;
-                    try bw.print("zig_L_{s};\n", .{typeToCIdentifier(elem_type)});
-
-                    const rendered = buffer.toOwnedSlice();
-                    errdefer dg.typedefs.allocator.free(rendered);
-                    const name = rendered[name_index .. rendered.len - 2];
-
-                    try dg.typedefs.ensureUnusedCapacity(1);
-                    try w.writeAll(name);
-                    dg.typedefs.putAssumeCapacityNoClobber(
-                        try t.copy(dg.typedefs_arena),
-                        .{ .name = name, .rendered = rendered },
-                    );
-                    return;
+                    return w.writeAll(name);
                 }
+
                 if (t.castPtrToFn()) |fn_ty| {
-                    const fn_info = fn_ty.fnInfo();
-                    try dg.renderType(w, fn_info.return_type);
-                    try w.writeAll(" (*)(");
-                    const param_len = fn_info.param_types.len;
-                    const is_var_args = fn_info.is_var_args;
-                    if (param_len == 0 and !is_var_args)
-                        try w.writeAll("void")
-                    else {
-                        var index: usize = 0;
-                        while (index < param_len) : (index += 1) {
-                            if (index > 0) {
-                                try w.writeAll(", ");
-                            }
-                            try dg.renderType(w, fn_info.param_types[index]);
-                        }
-                    }
-                    if (is_var_args) {
-                        if (param_len != 0) try w.writeAll(", ");
-                        try w.writeAll("...");
-                    }
-                    try w.writeByte(')');
-                    return;
+                    const name = dg.getTypedefName(t) orelse
+                        try dg.renderPtrToFnTypedef(t, fn_ty);
+
+                    return w.writeAll(name);
                 }
 
                 try dg.renderType(w, t.elemType());
-                try w.writeAll(" *");
                 if (t.isConstPtr()) {
-                    try w.writeAll("const ");
+                    try w.writeAll(" const");
                 }
                 if (t.isVolatilePtr()) {
-                    try w.writeAll("volatile ");
+                    try w.writeAll(" volatile");
                 }
+                return w.writeAll(" *");
             },
             .Array => {
+                // We are referencing the array so it will decay to a C pointer.
                 try dg.renderType(w, t.elemType());
-                try w.writeAll(" *");
+                return w.writeAll(" *");
             },
             .Optional => {
                 var opt_buf: Type.Payload.ElemType = undefined;
                 const child_type = t.optionalChild(&opt_buf);
-                const target = dg.module.getTarget();
+
                 if (t.isPtrLikeOptional()) {
                     return dg.renderType(w, child_type);
-                } else if (dg.typedefs.get(t)) |some| {
-                    return w.writeAll(some.name);
-                } else if (child_type.abiSize(target) == 0) {
+                }
+
+                if (child_type.abiSize(target) == 0) {
                     return w.writeAll("bool");
                 }
 
-                var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
-                defer buffer.deinit();
-                const bw = buffer.writer();
-
-                try bw.writeAll("typedef struct { ");
-                try dg.renderType(bw, child_type);
-                try bw.writeAll(" payload; bool is_null; } ");
-                const name_index = buffer.items.len;
-                try bw.print("zig_Q_{s};\n", .{typeToCIdentifier(child_type)});
-
-                const rendered = buffer.toOwnedSlice();
-                errdefer dg.typedefs.allocator.free(rendered);
-                const name = rendered[name_index .. rendered.len - 2];
-
-                try dg.typedefs.ensureUnusedCapacity(1);
-                try w.writeAll(name);
-                dg.typedefs.putAssumeCapacityNoClobber(
-                    try t.copy(dg.typedefs_arena),
-                    .{ .name = name, .rendered = rendered },
-                );
+                const name = dg.getTypedefName(t) orelse
+                    try dg.renderOptionalTypedef(t, child_type);
+
+                return w.writeAll(name);
             },
             .ErrorSet => {
                 comptime std.debug.assert(Type.initTag(.anyerror).abiSize(builtin.target) == 2);
-                try w.writeAll("uint16_t");
+                return w.writeAll("uint16_t");
             },
             .ErrorUnion => {
-                if (dg.typedefs.get(t)) |some| {
-                    return w.writeAll(some.name);
-                }
-                const child_type = t.errorUnionPayload();
-                const err_set_type = t.errorUnionSet();
-
-                if (!child_type.hasCodeGenBits()) {
-                    return dg.renderType(w, err_set_type);
-                }
-
-                var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
-                defer buffer.deinit();
-                const bw = buffer.writer();
-
-                try bw.writeAll("typedef struct { ");
-                try dg.renderType(bw, child_type);
-                try bw.writeAll(" payload; uint16_t error; } ");
-                const name_index = buffer.items.len;
-                if (err_set_type.castTag(.error_set_inferred)) |inf_err_set_payload| {
-                    const func = inf_err_set_payload.data.func;
-                    try bw.writeAll("zig_E_");
-                    try dg.renderDeclName(func.owner_decl, bw);
-                    try bw.writeAll(";\n");
-                } else {
-                    try bw.print("zig_E_{s}_{s};\n", .{
-                        typeToCIdentifier(err_set_type), typeToCIdentifier(child_type),
-                    });
+                if (t.errorUnionPayload().abiSize(target) == 0) {
+                    return dg.renderType(w, t.errorUnionSet());
                 }
 
-                const rendered = buffer.toOwnedSlice();
-                errdefer dg.typedefs.allocator.free(rendered);
-                const name = rendered[name_index .. rendered.len - 2];
+                const name = dg.getTypedefName(t) orelse
+                    try dg.renderErrorUnionTypedef(t);
 
-                try dg.typedefs.ensureUnusedCapacity(1);
-                try w.writeAll(name);
-                dg.typedefs.putAssumeCapacityNoClobber(
-                    try t.copy(dg.typedefs_arena),
-                    .{ .name = name, .rendered = rendered },
-                );
+                return w.writeAll(name);
             },
             .Struct => {
-                if (dg.typedefs.get(t)) |some| {
-                    return w.writeAll(some.name);
-                }
-                const struct_obj = t.castTag(.@"struct").?.data; // Handle 0 bit types elsewhere.
-                const fqn = try struct_obj.getFullyQualifiedName(dg.typedefs.allocator);
-                defer dg.typedefs.allocator.free(fqn);
-
-                var buffer = std.ArrayList(u8).init(dg.typedefs.allocator);
-                defer buffer.deinit();
-
-                try buffer.appendSlice("typedef struct {\n");
-                {
-                    var it = struct_obj.fields.iterator();
-                    while (it.next()) |entry| {
-                        const field_ty = entry.value_ptr.ty;
-                        const name: CValue = .{ .bytes = entry.key_ptr.* };
-                        try buffer.append(' ');
-                        try dg.renderTypeAndName(buffer.writer(), field_ty, name, .Mut);
-                        try buffer.appendSlice(";\n");
-                    }
-                }
-                try buffer.appendSlice("} ");
-
-                const name_start = buffer.items.len;
-                try buffer.writer().print("zig_S_{s};\n", .{fmtIdent(fqn)});
+                const name = dg.getTypedefName(t) orelse
+                    try dg.renderStructTypedef(t);
 
-                const rendered = buffer.toOwnedSlice();
-                errdefer dg.typedefs.allocator.free(rendered);
-                const name = rendered[name_start .. rendered.len - 2];
-
-                try dg.typedefs.ensureUnusedCapacity(1);
-                try w.writeAll(name);
-                dg.typedefs.putAssumeCapacityNoClobber(
-                    try t.copy(dg.typedefs_arena),
-                    .{ .name = name, .rendered = rendered },
-                );
+                return w.writeAll(name);
             },
             .Enum => {
                 // For enums, we simply use the integer tag type.
@@ -763,12 +834,17 @@ pub const DeclGen = struct {
 
                 try dg.renderType(w, int_tag_ty);
             },
-            .Union => return dg.fail("TODO: C backend: implement type Union", .{}),
+
+            .Union,
+            .Frame,
+            .AnyFrame,
+            .Vector,
+            .Opaque,
+            => |tag| return dg.fail("TODO: C backend: implement value of type {s}", .{
+                @tagName(tag),
+            }),
+
             .Fn => unreachable, // This is a function body, not a function pointer.
-            .Opaque => return dg.fail("TODO: C backend: implement type Opaque", .{}),
-            .Frame => return dg.fail("TODO: C backend: implement type Frame", .{}),
-            .AnyFrame => return dg.fail("TODO: C backend: implement type AnyFrame", .{}),
-            .Vector => return dg.fail("TODO: C backend: implement type Vector", .{}),
 
             .Null,
             .Undefined,
@@ -800,100 +876,14 @@ pub const DeclGen = struct {
             render_ty = render_ty.elemType();
         }
 
-        // TODO this is duplicated from the code below and does not handle
-        // arbitrary nesting of pointers. This renderTypeAndName function
-        // needs to be reworked by someone who understands C's insane type syntax. That
-        // person might be future me but it is certainly not present me.
-        if (render_ty.zigTypeTag() == .Pointer and
-            render_ty.childType().zigTypeTag() == .Pointer and
-            render_ty.childType().childType().zigTypeTag() == .Fn)
-        {
-            const ptr2_ty = render_ty.childType();
-            const fn_info = ptr2_ty.childType().fnInfo();
-            const ret_ty = fn_info.return_type;
-            if (ret_ty.zigTypeTag() == .NoReturn) {
-                // noreturn attribute is not allowed here.
-                try w.writeAll("void");
-            } else {
-                try dg.renderType(w, ret_ty);
-            }
-            try w.writeAll(" (*");
-            switch (mutability) {
-                .Const => try w.writeAll("const "),
-                .Mut => {},
-            }
-            if (!ptr2_ty.ptrIsMutable()) {
-                try w.writeAll("*const ");
-            } else {
-                try w.writeAll("*");
-            }
-            try dg.writeCValue(w, name);
-            try w.writeAll(")(");
-            const param_len = fn_info.param_types.len;
-            const is_var_args = fn_info.is_var_args;
-            if (param_len == 0 and !is_var_args)
-                try w.writeAll("void")
-            else {
-                var index: usize = 0;
-                while (index < param_len) : (index += 1) {
-                    if (index > 0) {
-                        try w.writeAll(", ");
-                    }
-                    try dg.renderType(w, fn_info.param_types[index]);
-                }
-            }
-            if (is_var_args) {
-                if (param_len != 0) try w.writeAll(", ");
-                try w.writeAll("...");
-            }
-            try w.writeByte(')');
-            return;
-        }
+        try dg.renderType(w, render_ty);
 
-        if (render_ty.castPtrToFn()) |fn_ty| {
-            const fn_info = fn_ty.fnInfo();
-            const ret_ty = fn_info.return_type;
-            if (ret_ty.zigTypeTag() == .NoReturn) {
-                // noreturn attribute is not allowed here.
-                try w.writeAll("void");
-            } else {
-                try dg.renderType(w, ret_ty);
-            }
-            try w.writeAll(" (*");
-            switch (mutability) {
-                .Const => try w.writeAll("const "),
-                .Mut => {},
-            }
-            try dg.writeCValue(w, name);
-            try w.writeAll(")(");
-            const param_len = fn_info.param_types.len;
-            const is_var_args = fn_info.is_var_args;
-            if (param_len == 0 and !is_var_args)
-                try w.writeAll("void")
-            else {
-                var index: usize = 0;
-                while (index < param_len) : (index += 1) {
-                    if (index > 0) {
-                        try w.writeAll(", ");
-                    }
-                    try dg.renderType(w, fn_info.param_types[index]);
-                }
-            }
-            if (is_var_args) {
-                if (param_len != 0) try w.writeAll(", ");
-                try w.writeAll("...");
-            }
-            try w.writeByte(')');
-        } else {
-            try dg.renderType(w, render_ty);
-
-            const const_prefix = switch (mutability) {
-                .Const => "const ",
-                .Mut => "",
-            };
-            try w.print(" {s}", .{const_prefix});
-            try dg.writeCValue(w, name);
-        }
+        const const_prefix = switch (mutability) {
+            .Const => "const ",
+            .Mut => "",
+        };
+        try w.print(" {s}", .{const_prefix});
+        try dg.writeCValue(w, name);
         try w.writeAll(suffix.items);
     }
 
@@ -1224,10 +1214,11 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
 fn airSliceField(f: *Function, inst: Air.Inst.Index, suffix: []const u8) !CValue {
     if (f.liveness.isUnused(inst)) return CValue.none;
 
+    const inst_ty = f.air.typeOfIndex(inst);
     const ty_op = f.air.instructions.items(.data)[inst].ty_op;
     const operand = try f.resolveInst(ty_op.operand);
     const writer = f.object.writer();
-    const local = try f.allocLocal(Type.initTag(.usize), .Const);
+    const local = try f.allocLocal(inst_ty, .Const);
     try writer.writeAll(" = ");
     try f.writeCValue(writer, operand);
     try writer.writeAll(suffix);
src/link/C.zig
@@ -362,7 +362,7 @@ fn flushDecl(self: *C, f: *Flush, decl: *const Module.Decl) FlushDeclError!void
     const decl_block = self.decl_table.getPtr(decl).?;
     const gpa = self.base.allocator;
 
-    if (decl_block.fwd_decl.items.len != 0) {
+    if (decl_block.typedefs.count() != 0) {
         try f.typedefs.ensureUnusedCapacity(gpa, @intCast(u32, decl_block.typedefs.count()));
         var it = decl_block.typedefs.iterator();
         while (it.next()) |new| {
@@ -371,6 +371,9 @@ fn flushDecl(self: *C, f: *Flush, decl: *const Module.Decl) FlushDeclError!void
                 try f.err_typedef_buf.appendSlice(gpa, new.value_ptr.rendered);
             }
         }
+    }
+
+    if (decl_block.fwd_decl.items.len != 0) {
         const buf = decl_block.fwd_decl.items;
         try f.all_buffers.append(gpa, .{
             .iov_base = buf.ptr,
test/behavior/basic.zig
@@ -250,3 +250,45 @@ test "volatile load and store" {
     ptr.* += 1;
     try expect(ptr.* == 1235);
 }
+
+fn fA() []const u8 {
+    return "a";
+}
+fn fB() []const u8 {
+    return "b";
+}
+
+test "call function pointer in struct" {
+    try expect(mem.eql(u8, f3(true), "a"));
+    try expect(mem.eql(u8, f3(false), "b"));
+}
+
+fn f3(x: bool) []const u8 {
+    var wrapper: FnPtrWrapper = .{
+        .fn_ptr = fB,
+    };
+
+    if (x) {
+        wrapper.fn_ptr = fA;
+    }
+
+    return wrapper.fn_ptr();
+}
+
+const FnPtrWrapper = struct {
+    fn_ptr: fn () []const u8,
+};
+
+test "const ptr from var variable" {
+    var x: u64 = undefined;
+    var y: u64 = undefined;
+
+    x = 78;
+    copy(&x, &y);
+
+    try expect(x == y);
+}
+
+fn copy(src: *const u64, dst: *u64) void {
+    dst.* = src.*;
+}