Commit 1639fd4c45

Cody Tapscott <topolarity@tapscott.me>
2022-02-16 19:40:33
Fix 2D array support for C backend
This updates the C backend to use proper array types. In order to do that, this commit also: - fixes up elem_ptr and field_ptr handling - adds `renderTypecast` (renders in C typecast format, e.g. "int* [10]") - adds a bit special handling for undefined pointers, which is necessary to support slice/elem_ptr to undefined decls
1 parent c4baa66
Changed files (3)
src
codegen
test
src/codegen/c.zig
@@ -34,6 +34,8 @@ pub const CValue = union(enum) {
     /// By-value
     decl: *Decl,
     decl_ref: *Decl,
+    /// An undefined (void *) pointer (cannot be dereferenced)
+    undefined_ptr: void,
     /// Render the slice as an identifier (using fmtIdent)
     identifier: []const u8,
     /// Render these bytes literally.
@@ -311,6 +313,10 @@ pub const Function = struct {
     fn renderType(f: *Function, w: anytype, t: Type) !void {
         return f.object.dg.renderType(w, t);
     }
+
+    fn renderTypecast(f: *Function, w: anytype, t: Type) !void {
+        return f.object.dg.renderTypecast(w, t);
+    }
 };
 
 /// This data is available when outputting .c code for a `Module`.
@@ -365,10 +371,10 @@ pub const DeclGen = struct {
 
         if (ty.isSlice()) {
             try writer.writeByte('(');
-            try dg.renderType(writer, ty);
+            try dg.renderTypecast(writer, ty);
             try writer.writeAll("){");
             var buf: Type.SlicePtrFieldTypeBuffer = undefined;
-            try dg.renderValue(writer, ty.slicePtrFieldType(&buf), val);
+            try dg.renderValue(writer, ty.slicePtrFieldType(&buf), val.slicePtr());
             try writer.writeAll(", ");
             try writer.print("{d}", .{val.sliceLen()});
             try writer.writeAll("}");
@@ -389,7 +395,7 @@ pub const DeclGen = struct {
             }
 
             try writer.writeAll("((");
-            try dg.renderType(writer, ty);
+            try dg.renderTypecast(writer, ty);
             try writer.writeAll(")&");
             try dg.renderDeclName(decl, writer);
             try writer.writeByte(')');
@@ -445,6 +451,57 @@ pub const DeclGen = struct {
         }
     }
 
+    // Renders a "child" pointer (e.g. ElemPtr, FieldPtr) by recursing
+    // to the root decl/variable that acts as its parent
+    //
+    // Used for .elem_ptr, .field_ptr, .opt_payload_ptr, .eu_payload_ptr, since
+    // the Type of their container cannot be retrieved from their own Type
+    fn renderChildPtr(dg: *DeclGen, writer: anytype, ptr_val: Value) error{ OutOfMemory, AnalysisFail }!Type {
+        switch (ptr_val.tag()) {
+            .decl_ref_mut, .decl_ref, .variable => {
+                const decl = switch (ptr_val.tag()) {
+                    .decl_ref => ptr_val.castTag(.decl_ref).?.data,
+                    .decl_ref_mut => ptr_val.castTag(.decl_ref_mut).?.data.decl,
+                    .variable => ptr_val.castTag(.variable).?.data.owner_decl,
+                    else => unreachable,
+                };
+                try dg.renderDeclValue(writer, decl.ty, ptr_val, decl);
+                return decl.ty;
+            },
+            .field_ptr => {
+                const field_ptr = ptr_val.castTag(.field_ptr).?.data;
+                const index = field_ptr.field_index;
+
+                try writer.writeAll("&(");
+                const container_ty = try dg.renderChildPtr(writer, field_ptr.container_ptr);
+
+                const field_name = switch (container_ty.zigTypeTag()) {
+                    .Struct => container_ty.structFields().keys()[index],
+                    .Union => container_ty.unionFields().keys()[index],
+                    else => unreachable,
+                };
+                const field_ty = switch (container_ty.zigTypeTag()) {
+                    .Struct => container_ty.structFields().values()[index].ty,
+                    .Union => container_ty.unionFields().values()[index].ty,
+                    else => unreachable,
+                };
+                try writer.print(").{ }", .{fmtIdent(field_name)});
+
+                return field_ty;
+            },
+            .elem_ptr => {
+                const elem_ptr = ptr_val.castTag(.elem_ptr).?.data;
+                try writer.writeAll("&(*");
+                const container_ty = try dg.renderChildPtr(writer, elem_ptr.array_ptr);
+                try writer.print(")[{d}]", .{elem_ptr.index});
+                return container_ty.childType();
+            },
+            .opt_payload_ptr => return dg.fail("implement renderChildPtr for optional payload", .{}),
+            .eu_payload_ptr => return dg.fail("implement renderChildPtr for error union payload", .{}),
+            else => unreachable,
+        }
+    }
+
     fn renderValue(
         dg: *DeclGen,
         writer: anytype,
@@ -531,24 +588,15 @@ pub const DeclGen = struct {
                     var buf: Type.SlicePtrFieldTypeBuffer = undefined;
 
                     try writer.writeByte('(');
-                    try dg.renderType(writer, ty);
+                    try dg.renderTypecast(writer, ty);
                     try writer.writeAll("){");
                     try dg.renderValue(writer, ty.slicePtrFieldType(&buf), slice.ptr);
                     try writer.writeAll(", ");
                     try dg.renderValue(writer, Type.usize, slice.len);
                     try writer.writeAll("}");
                 },
-                .elem_ptr => {
-                    const elem_ptr = val.castTag(.elem_ptr).?.data;
-                    var arena = std.heap.ArenaAllocator.init(dg.module.gpa);
-                    defer arena.deinit();
-                    const elem_ptr_ty = try ty.elemPtrType(arena.allocator());
-
-                    try writer.writeAll("(&((");
-                    try dg.renderType(writer, ty);
-                    try writer.writeByte(')');
-                    try dg.renderValue(writer, elem_ptr_ty, elem_ptr.array_ptr);
-                    try writer.print(")[{d}])", .{elem_ptr.index});
+                .field_ptr, .elem_ptr, .opt_payload_ptr, .eu_payload_ptr => {
+                    _ = try dg.renderChildPtr(writer, val);
                 },
                 .function => {
                     const func = val.castTag(.function).?.data;
@@ -560,7 +608,7 @@ pub const DeclGen = struct {
                 },
                 .int_u64, .one => {
                     try writer.writeAll("((");
-                    try dg.renderType(writer, ty);
+                    try dg.renderTypecast(writer, ty);
                     try writer.print(")0x{x}u)", .{val.toUnsignedInt()});
                 },
                 else => unreachable,
@@ -605,7 +653,7 @@ pub const DeclGen = struct {
                     return writer.print("{}", .{is_null});
                 }
                 try writer.writeByte('(');
-                try dg.renderType(writer, ty);
+                try dg.renderTypecast(writer, ty);
                 try writer.writeAll("){");
                 if (val.castTag(.opt_payload)) |pl| {
                     const payload_val = pl.data;
@@ -641,7 +689,7 @@ pub const DeclGen = struct {
                 }
 
                 try writer.writeByte('(');
-                try dg.renderType(writer, ty);
+                try dg.renderTypecast(writer, ty);
                 try writer.writeAll("){");
                 if (val.castTag(.eu_payload)) |pl| {
                     const payload_val = pl.data;
@@ -703,7 +751,7 @@ pub const DeclGen = struct {
                 const field_vals = val.castTag(.@"struct").?.data;
 
                 try writer.writeAll("(");
-                try dg.renderType(writer, ty);
+                try dg.renderTypecast(writer, ty);
                 try writer.writeAll("){");
 
                 for (field_vals) |field_val, i| {
@@ -723,7 +771,7 @@ pub const DeclGen = struct {
                 const layout = ty.unionGetLayout(target);
 
                 try writer.writeAll("(");
-                try dg.renderType(writer, ty);
+                try dg.renderTypecast(writer, ty);
                 try writer.writeAll("){");
 
                 if (ty.unionTagType()) |tag_ty| {
@@ -798,8 +846,9 @@ pub const DeclGen = struct {
             if (params_written > 0) {
                 try w.writeAll(", ");
             }
-            try dg.renderType(w, dg.decl.ty.fnParamType(index));
-            try w.print(" a{d}", .{index});
+            const name = CValue{ .arg = index };
+            const alignment = Value.initTag(.abi_align_default);
+            try dg.renderTypeAndName(w, dg.decl.ty.fnParamType(index), name, .Mut, alignment);
             params_written += 1;
         }
 
@@ -837,7 +886,7 @@ pub const DeclGen = struct {
             if (params_written > 0) {
                 try bw.writeAll(", ");
             }
-            try dg.renderType(bw, fn_info.param_types[index]);
+            try dg.renderTypecast(bw, fn_info.param_types[index]);
             params_written += 1;
         }
 
@@ -868,17 +917,16 @@ pub const DeclGen = struct {
         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; } ");
+
+        var ptr_type_buf: Type.SlicePtrFieldTypeBuffer = undefined;
+        const ptr_type = t.slicePtrFieldType(&ptr_type_buf);
+        const ptr_name = CValue{ .bytes = "ptr" };
+        const ptr_alignment = Value.initTag(.abi_align_default);
+        try dg.renderTypeAndName(bw, ptr_type, ptr_name, .Mut, ptr_alignment);
+
+        try bw.writeAll("; size_t len; } ");
         const name_index = buffer.items.len;
+        const elem_type = t.elemType();
         if (t.isConstPtr()) {
             try bw.print("zig_L_{s};\n", .{typeToCIdentifier(elem_type)});
         } else {
@@ -1003,8 +1051,10 @@ pub const DeclGen = struct {
         const bw = buffer.writer();
 
         try bw.writeAll("typedef struct { ");
-        try dg.renderType(bw, child_type);
-        try bw.writeAll(" payload; uint16_t error; } ");
+        const payload_name = CValue{ .bytes = "payload" };
+        const alignment = Value.initTag(.abi_align_default);
+        try dg.renderTypeAndName(bw, child_type, payload_name, .Mut, alignment);
+        try bw.writeAll("; 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;
@@ -1030,14 +1080,47 @@ pub const DeclGen = struct {
         return name;
     }
 
+    fn renderArrayTypedef(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();
+
+        const elem_type = t.elemType();
+        const sentinel_bit = @boolToInt(t.sentinel() != null);
+        const c_len = t.arrayLen() + sentinel_bit;
+
+        try bw.writeAll("typedef ");
+        try dg.renderType(bw, elem_type);
+
+        const name_start = buffer.items.len + 1;
+        try bw.print(" zig_A_{s}_{d}", .{ typeToCIdentifier(elem_type), c_len });
+        const name_end = buffer.items.len;
+
+        try bw.print("[{d}];\n", .{c_len});
+
+        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 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 payload_name = CValue{ .bytes = "payload" };
+        const alignment = Value.initTag(.abi_align_default);
+        try dg.renderTypeAndName(bw, child_type, payload_name, .Mut, alignment);
+        try bw.writeAll("; bool is_null; } ");
         const name_index = buffer.items.len;
         try bw.print("zig_Q_{s};\n", .{typeToCIdentifier(child_type)});
 
@@ -1054,6 +1137,18 @@ pub const DeclGen = struct {
         return name;
     }
 
+    /// Renders a type as a single identifier, generating intermediate typedefs
+    /// if necessary.
+    ///
+    /// This is guaranteed to be valid in both typedefs and declarations/definitions.
+    ///
+    /// There are three type formats in total that we support rendering:
+    ///   | Function            | Example 1 (*u8) | Example 2 ([10]*u8) |
+    ///   |---------------------|-----------------|---------------------|
+    ///   | `renderTypecast`    | "uint8_t *"     | "uint8_t *[10]"     |
+    ///   | `renderTypeAndName` | "uint8_t *name" | "uint8_t *name[10]" |
+    ///   | `renderType`        | "uint8_t *"     | "zig_A_uint8_t_10"  |
+    ///
     fn renderType(dg: *DeclGen, w: anytype, t: Type) error{ OutOfMemory, AnalysisFail }!void {
         const target = dg.module.getTarget();
 
@@ -1130,10 +1225,10 @@ pub const DeclGen = struct {
                 return w.writeAll(" *");
             },
             .Array => {
-                // We are referencing the array so it will decay to a C pointer.
-                // NB: arrays are not really types in C so they are either specified in the declaration
-                // or are already pointed to; our only job is to render the element type.
-                return dg.renderType(w, t.elemType());
+                const name = dg.getTypedefName(t) orelse
+                    try dg.renderArrayTypedef(t);
+
+                return w.writeAll(name);
             },
             .Optional => {
                 var opt_buf: Type.Payload.ElemType = undefined;
@@ -1208,6 +1303,39 @@ pub const DeclGen = struct {
         }
     }
 
+    /// Renders a type in C typecast format.
+    ///
+    /// This is guaranteed to be valid in a typecast expression, but not
+    /// necessarily in a variable/field declaration.
+    ///
+    /// There are three type formats in total that we support rendering:
+    ///   | Function            | Example 1 (*u8) | Example 2 ([10]*u8) |
+    ///   |---------------------|-----------------|---------------------|
+    ///   | `renderTypecast`    | "uint8_t *"     | "uint8_t *[10]"     |
+    ///   | `renderTypeAndName` | "uint8_t *name" | "uint8_t *name[10]" |
+    ///   | `renderType`        | "uint8_t *"     | "zig_A_uint8_t_10"  |
+    ///
+    fn renderTypecast(
+        dg: *DeclGen,
+        w: anytype,
+        ty: Type,
+        //mutability: Mutability,
+        //alignment: Value,
+    ) error{ OutOfMemory, AnalysisFail }!void {
+        const name = CValue{ .bytes = "" };
+        const alignment = Value.initTag(.abi_align_default);
+        return renderTypeAndName(dg, w, ty, name, .Mut, alignment);
+    }
+
+    /// Renders a type and name in field declaration/definition format.
+    ///
+    /// There are three type formats in total that we support rendering:
+    ///   | Function            | Example 1 (*u8) | Example 2 ([10]*u8) |
+    ///   |---------------------|-----------------|---------------------|
+    ///   | `renderTypecast`    | "uint8_t *"     | "uint8_t *[10]"     |
+    ///   | `renderTypeAndName` | "uint8_t *name" | "uint8_t *name[10]" |
+    ///   | `renderType`        | "uint8_t *"     | "zig_A_uint8_t_10"  |
+    ///
     fn renderTypeAndName(
         dg: *DeclGen,
         w: anytype,
@@ -1219,6 +1347,8 @@ pub const DeclGen = struct {
         var suffix = std.ArrayList(u8).init(dg.gpa);
         defer suffix.deinit();
 
+        // Any top-level array types are rendered here as a suffix, which
+        // avoids creating typedefs for every array type
         var render_ty = ty;
         while (render_ty.zigTypeTag() == .Array) {
             const sentinel_bit = @boolToInt(render_ty.sentinel() != null);
@@ -1267,6 +1397,14 @@ pub const DeclGen = struct {
                 try w.writeByte('&');
                 return dg.renderDeclName(decl, w);
             },
+            .undefined_ptr => {
+                const target = dg.module.getTarget();
+                switch (target.cpu.arch.ptrBitWidth()) {
+                    32 => try w.writeAll("(void *)0xaaaaaaaa"),
+                    64 => try w.writeAll("(void *)0xaaaaaaaaaaaaaaaa"),
+                    else => unreachable,
+                }
+            },
             .identifier => |ident| return w.print("{ }", .{fmtIdent(ident)}),
             .bytes => |bytes| return w.writeAll(bytes),
         }
@@ -1285,6 +1423,7 @@ pub const DeclGen = struct {
                 return w.writeByte(')');
             },
             .decl_ref => |decl| return dg.renderDeclName(decl, w),
+            .undefined_ptr => unreachable,
             .identifier => |ident| return w.print("(*{ })", .{fmtIdent(ident)}),
             .bytes => |bytes| {
                 try w.writeAll("(*");
@@ -1676,14 +1815,21 @@ fn airPtrElemPtr(f: *Function, inst: Air.Inst.Index) !CValue {
 
     const ty_pl = f.air.instructions.items(.data)[inst].ty_pl;
     const bin_op = f.air.extraData(Air.Bin, ty_pl.payload).data;
+    const ptr_ty = f.air.typeOf(bin_op.lhs);
 
     const ptr = try f.resolveInst(bin_op.lhs);
     const index = try f.resolveInst(bin_op.rhs);
     const writer = f.object.writer();
     const local = try f.allocLocal(f.air.typeOfIndex(inst), .Const);
-    try writer.writeAll(" = &");
-    try f.writeCValue(writer, ptr);
-    try writer.writeByte('[');
+
+    try writer.writeAll(" = &(");
+    if (ptr_ty.ptrSize() == .One) {
+        // It's a pointer to an array, so we need to de-reference.
+        try f.writeCValueDeref(writer, ptr);
+    } else {
+        try f.writeCValue(writer, ptr);
+    }
+    try writer.writeAll(")[");
     try f.writeCValue(writer, index);
     try writer.writeAll("];\n");
     return local;
@@ -1747,13 +1893,7 @@ fn airAlloc(f: *Function, inst: Air.Inst.Index) !CValue {
     const elem_type = inst_ty.elemType();
     const mutability: Mutability = if (inst_ty.isConstPtr()) .Const else .Mut;
     if (!elem_type.isFnOrHasRuntimeBits()) {
-        const target = f.object.dg.module.getTarget();
-        const literal = switch (target.cpu.arch.ptrBitWidth()) {
-            32 => "(void *)0xaaaaaaaa",
-            64 => "(void *)0xaaaaaaaaaaaaaaaa",
-            else => unreachable,
-        };
-        return CValue{ .bytes = literal };
+        return CValue.undefined_ptr;
     }
 
     const target = f.object.dg.module.getTarget();
@@ -1768,10 +1908,6 @@ fn airAlloc(f: *Function, inst: Air.Inst.Index) !CValue {
     const local = try f.allocAlignedLocal(elem_type, mutability, alignment_value);
     try writer.writeAll(";\n");
 
-    // Arrays are already pointers so they don't need to be referenced.
-    if (elem_type.zigTypeTag() == .Array)
-        return CValue{ .local = local.local };
-
     return CValue{ .local_ref = local.local };
 }
 
@@ -1808,38 +1944,22 @@ fn airLoad(f: *Function, inst: Air.Inst.Index) !CValue {
     // We need to separately initialize arrays with a memcpy so they must be mutable.
     const local = try f.allocLocal(inst_ty, if (is_array) .Mut else .Const);
 
-    switch (operand) {
-        .local_ref => |i| {
-            const wrapped: CValue = .{ .local = i };
-            try writer.writeAll(" = ");
-            try f.writeCValue(writer, wrapped);
-            try writer.writeAll(";\n");
-        },
-        .decl_ref => |decl| {
-            const wrapped: CValue = .{ .decl = decl };
-            try writer.writeAll(" = ");
-            try f.writeCValue(writer, wrapped);
-            try writer.writeAll(";\n");
-        },
-        else => {
-            if (is_array) {
-                // Insert a memcpy to initialize this array. The source operand is always a pointer
-                // and thus we only need to know size/type information from the local type/dest.
-                try writer.writeAll(";");
-                try f.object.indent_writer.insertNewline();
-                try writer.writeAll("memcpy(");
-                try f.writeCValue(writer, local);
-                try writer.writeAll(", ");
-                try f.writeCValue(writer, operand);
-                try writer.writeAll(", sizeof(");
-                try f.writeCValue(writer, local);
-                try writer.writeAll("));\n");
-            } else {
-                try writer.writeAll(" = ");
-                try f.writeCValueDeref(writer, operand);
-                try writer.writeAll(";\n");
-            }
-        },
+    if (is_array) {
+        // Insert a memcpy to initialize this array. The source operand is always a pointer
+        // and thus we only need to know size/type information from the local type/dest.
+        try writer.writeAll(";");
+        try f.object.indent_writer.insertNewline();
+        try writer.writeAll("memcpy(");
+        try f.writeCValue(writer, local);
+        try writer.writeAll(", ");
+        try f.writeCValue(writer, operand);
+        try writer.writeAll(", sizeof(");
+        try f.writeCValue(writer, local);
+        try writer.writeAll("));\n");
+    } else {
+        try writer.writeAll(" = ");
+        try f.writeCValueDeref(writer, operand);
+        try writer.writeAll(";\n");
     }
     return local;
 }
@@ -1884,7 +2004,7 @@ fn airIntCast(f: *Function, inst: Air.Inst.Index) !CValue {
     const inst_ty = f.air.typeOfIndex(inst);
     const local = try f.allocLocal(inst_ty, .Const);
     try writer.writeAll(" = (");
-    try f.renderType(writer, inst_ty);
+    try f.renderTypecast(writer, inst_ty);
     try writer.writeAll(")");
     try f.writeCValue(writer, operand);
     try writer.writeAll(";\n");
@@ -1945,7 +2065,7 @@ fn airBoolToInt(f: *Function, inst: Air.Inst.Index) !CValue {
     return local;
 }
 
-fn airStoreUndefined(f: *Function, dest_ptr: CValue, dest_child_type: Type) !CValue {
+fn airStoreUndefined(f: *Function, dest_ptr: CValue) !CValue {
     const is_debug_build = f.object.dg.module.optimizeMode() == .Debug;
     if (!is_debug_build)
         return CValue.none;
@@ -1954,11 +2074,7 @@ fn airStoreUndefined(f: *Function, dest_ptr: CValue, dest_child_type: Type) !CVa
     try writer.writeAll("memset(");
     try f.writeCValue(writer, dest_ptr);
     try writer.writeAll(", 0xaa, sizeof(");
-    if (dest_child_type.zigTypeTag() == .Array) {
-        try f.writeCValue(writer, dest_ptr);
-    } else {
-        try f.writeCValueDeref(writer, dest_ptr);
-    }
+    try f.writeCValueDeref(writer, dest_ptr);
     try writer.writeAll("));\n");
     return CValue.none;
 }
@@ -1975,58 +2091,40 @@ fn airStore(f: *Function, inst: Air.Inst.Index) !CValue {
     const src_val_is_undefined =
         if (f.air.value(bin_op.rhs)) |v| v.isUndefDeep() else false;
     if (src_val_is_undefined)
-        return try airStoreUndefined(f, dest_ptr, lhs_child_type);
+        return try airStoreUndefined(f, dest_ptr);
 
     const writer = f.object.writer();
-    switch (dest_ptr) {
-        .local_ref => |i| {
-            const dest: CValue = .{ .local = i };
-            try f.writeCValue(writer, dest);
-            try writer.writeAll(" = ");
-            try f.writeCValue(writer, src_val);
-            try writer.writeAll(";\n");
-        },
-        .decl_ref => |decl| {
-            const dest: CValue = .{ .decl = decl };
-            try f.writeCValue(writer, dest);
+    if (lhs_child_type.zigTypeTag() == .Array) {
+        // For this memcpy to safely work we need the rhs to have the same
+        // underlying type as the lhs (i.e. they must both be arrays of the same underlying type).
+        const rhs_type = f.air.typeOf(bin_op.rhs);
+        assert(rhs_type.eql(lhs_child_type));
+
+        // If the source is a constant, writeCValue will emit a brace initialization
+        // so work around this by initializing into new local.
+        // TODO this should be done by manually initializing elements of the dest array
+        const array_src = if (src_val == .constant) blk: {
+            const new_local = try f.allocLocal(rhs_type, .Const);
             try writer.writeAll(" = ");
             try f.writeCValue(writer, src_val);
-            try writer.writeAll(";\n");
-        },
-        else => {
-            if (lhs_child_type.zigTypeTag() == .Array) {
-                // For this memcpy to safely work we need the rhs to have the same
-                // underlying type as the lhs (i.e. they must both be arrays of the same underlying type).
-                const rhs_type = f.air.typeOf(bin_op.rhs);
-                assert(rhs_type.eql(lhs_child_type));
-
-                // If the source is a constant, writeCValue will emit a brace initialization
-                // so work around this by initializing into new local.
-                // TODO this should be done by manually initializing elements of the dest array
-                const array_src = if (src_val == .constant) blk: {
-                    const new_local = try f.allocLocal(rhs_type, .Const);
-                    try writer.writeAll(" = ");
-                    try f.writeCValue(writer, src_val);
-                    try writer.writeAll(";");
-                    try f.object.indent_writer.insertNewline();
-
-                    break :blk new_local;
-                } else src_val;
-
-                try writer.writeAll("memcpy(");
-                try f.writeCValue(writer, dest_ptr);
-                try writer.writeAll(", ");
-                try f.writeCValue(writer, array_src);
-                try writer.writeAll(", sizeof(");
-                try f.writeCValue(writer, array_src);
-                try writer.writeAll("));\n");
-            } else {
-                try f.writeCValueDeref(writer, dest_ptr);
-                try writer.writeAll(" = ");
-                try f.writeCValue(writer, src_val);
-                try writer.writeAll(";\n");
-            }
-        },
+            try writer.writeAll(";");
+            try f.object.indent_writer.insertNewline();
+
+            break :blk new_local;
+        } else src_val;
+
+        try writer.writeAll("memcpy(");
+        try f.writeCValue(writer, dest_ptr);
+        try writer.writeAll(", ");
+        try f.writeCValue(writer, array_src);
+        try writer.writeAll(", sizeof(");
+        try f.writeCValue(writer, array_src);
+        try writer.writeAll("));\n");
+    } else {
+        try f.writeCValueDeref(writer, dest_ptr);
+        try writer.writeAll(" = ");
+        try f.writeCValue(writer, src_val);
+        try writer.writeAll(";\n");
     }
     return CValue.none;
 }
@@ -2382,17 +2480,24 @@ fn airPtrAddSub(f: *Function, inst: Air.Inst.Index, operator: [*:0]const u8) !CV
     const writer = f.object.writer();
     const inst_ty = f.air.typeOfIndex(inst);
     const local = try f.allocLocal(inst_ty, .Const);
+    const elem_ty = switch (inst_ty.ptrSize()) {
+        .One => blk: {
+            const array_ty = inst_ty.childType();
+            break :blk array_ty.childType();
+        },
+        else => inst_ty.childType(),
+    };
 
     // We must convert to and from integer types to prevent UB if the operation results in a NULL pointer,
     // or if LHS is NULL. The operation is only UB if the result is NULL and then dereferenced.
     try writer.writeAll(" = (");
-    try f.renderType(writer, inst_ty);
+    try f.renderTypecast(writer, inst_ty);
     try writer.writeAll(")(((uintptr_t)");
     try f.writeCValue(writer, lhs);
     try writer.print("){s}(", .{operator});
     try f.writeCValue(writer, rhs);
     try writer.writeAll("*sizeof(");
-    try f.renderType(writer, inst_ty.childType());
+    try f.renderTypecast(writer, elem_ty);
     try writer.print(")));\n", .{});
 
     return local;
@@ -2580,7 +2685,7 @@ fn airBitcast(f: *Function, inst: Air.Inst.Index) !CValue {
     {
         const local = try f.allocLocal(inst_ty, .Const);
         try writer.writeAll(" = (");
-        try f.renderType(writer, inst_ty);
+        try f.renderTypecast(writer, inst_ty);
 
         try writer.writeAll(")");
         try f.writeCValue(writer, operand);
@@ -2922,13 +3027,12 @@ fn structFieldPtr(f: *Function, inst: Air.Inst.Index, struct_ptr_ty: Type, struc
         },
         else => unreachable,
     }
-    const addrof = if (field_val_ty.zigTypeTag() == .Array) "" else "&";
     const payload = if (struct_ty.tag() == .union_tagged) "payload." else "";
 
     const inst_ty = f.air.typeOfIndex(inst);
     const local = try f.allocLocal(inst_ty, .Const);
 
-    try writer.print(" = {s}", .{addrof});
+    try writer.print(" = &", .{});
     try f.writeCValueDeref(writer, struct_ptr);
     try writer.print(".{s}{ };\n", .{ payload, fmtIdent(field_name) });
     return local;
@@ -3114,7 +3218,15 @@ fn airArrayToSlice(f: *Function, inst: Air.Inst.Index) !CValue {
     const array_len = f.air.typeOf(ty_op.operand).elemType().arrayLen();
 
     try writer.writeAll(" = { .ptr = ");
-    try f.writeCValue(writer, operand);
+    if (operand == .undefined_ptr) {
+        // Unfortunately, C does not support any equivalent to
+        // &(*(void *)p)[0], although LLVM does via GetElementPtr
+        try f.writeCValue(writer, CValue.undefined_ptr);
+    } else {
+        try writer.writeAll("&(");
+        try f.writeCValueDeref(writer, operand);
+        try writer.writeAll(")[0]");
+    }
     try writer.print(", .len = {d} }};\n", .{array_len});
     return local;
 }
@@ -3146,7 +3258,7 @@ fn airPtrToInt(f: *Function, inst: Air.Inst.Index) !CValue {
     const operand = try f.resolveInst(un_op);
 
     try writer.writeAll(" = (");
-    try f.renderType(writer, inst_ty);
+    try f.renderTypecast(writer, inst_ty);
     try writer.writeAll(")");
     try f.writeCValue(writer, operand);
     try writer.writeAll(";\n");
test/behavior/basic.zig
@@ -436,6 +436,38 @@ test "array 2D const double ptr" {
     try testArray2DConstDoublePtr(&rect_2d_vertexes[0][0]);
 }
 
+test "array 2D const double ptr with offset" {
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest;
+
+    const rect_2d_vertexes = [_][2]f32{
+        [_]f32{ 3.0, 4.239 },
+        [_]f32{ 1.0, 2.0 },
+    };
+    try testArray2DConstDoublePtr(&rect_2d_vertexes[1][0]);
+}
+
+test "array 3D const double ptr with offset" {
+    if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_x86_64) return error.SkipZigTest;
+    if (builtin.zig_backend == .stage2_wasm) return error.SkipZigTest; // TODO
+
+    const rect_3d_vertexes = [_][2][2]f32{
+        [_][2]f32{
+            [_]f32{ 3.0, 4.239 },
+            [_]f32{ 3.5, 7.2 },
+        },
+        [_][2]f32{
+            [_]f32{ 3.0, 4.239 },
+            [_]f32{ 1.0, 2.0 },
+        },
+    };
+    try testArray2DConstDoublePtr(&rect_3d_vertexes[1][1][0]);
+}
+
 fn testArray2DConstDoublePtr(ptr: *const f32) !void {
     const ptr2 = @ptrCast([*]const f32, ptr);
     try expect(ptr2[0] == 1.0);
test/behavior/undefined.zig
@@ -1,4 +1,5 @@
 const std = @import("std");
+const builtin = @import("builtin");
 const expect = std.testing.expect;
 const mem = std.mem;
 
@@ -12,6 +13,10 @@ fn initStaticArray() [10]i32 {
 }
 const static_array = initStaticArray();
 test "init static array to undefined" {
+    // This test causes `initStaticArray()` to be codegen'd, and the
+    // C backend does not yet support returning arrays, so it fails
+    if (builtin.zig_backend == .stage2_c) return error.SkipZigTest;
+
     try expect(static_array[0] == 1);
     try expect(static_array[4] == 2);
     try expect(static_array[7] == 3);