Commit 3f95003d4c

Jacob Young <jacobly0@users.noreply.github.com>
2025-01-08 19:55:34
cbe: fix miscomps of x86_64 backend
1 parent 5b5c60f
Changed files (3)
src
codegen
link
test
src/codegen/c.zig
@@ -105,7 +105,7 @@ pub const CValue = union(enum) {
 };
 
 const BlockData = struct {
-    block_id: usize,
+    block_id: u32,
     result: CValue,
 };
 
@@ -359,8 +359,8 @@ pub const Function = struct {
     liveness: Liveness,
     value_map: CValueMap,
     blocks: std.AutoHashMapUnmanaged(Air.Inst.Index, BlockData) = .empty,
-    next_arg_index: usize = 0,
-    next_block_index: usize = 0,
+    next_arg_index: u32 = 0,
+    next_block_index: u32 = 0,
     object: Object,
     lazy_fns: LazyFnMap,
     func_index: InternPool.Index,
@@ -663,6 +663,7 @@ pub const DeclGen = struct {
     mod: *Module,
     pass: Pass,
     is_naked_fn: bool,
+    expected_block: ?u32,
     /// This is a borrowed reference from `link.C`.
     fwd_decl: std.ArrayList(u8),
     error_msg: ?*Zcu.ErrorMsg,
@@ -1399,12 +1400,24 @@ pub const DeclGen = struct {
                                         .repeated_elem => |elem| elem,
                                     };
 
+                                    const field_int_info: std.builtin.Type.Int = if (field_ty.isAbiInt(zcu))
+                                        field_ty.intInfo(zcu)
+                                    else
+                                        .{ .signedness = .unsigned, .bits = undefined };
+                                    switch (field_int_info.signedness) {
+                                        .signed => {
+                                            try writer.writeByte('(');
+                                            try dg.renderValue(writer, Value.fromInterned(field_val), .Other);
+                                            try writer.writeAll(" & ");
+                                            const field_uint_ty = try pt.intType(.unsigned, field_int_info.bits);
+                                            try dg.renderValue(writer, try field_uint_ty.maxIntScalar(pt, field_uint_ty), .Other);
+                                            try writer.writeByte(')');
+                                        },
+                                        .unsigned => try dg.renderValue(writer, Value.fromInterned(field_val), .Other),
+                                    }
                                     if (bit_offset != 0) {
-                                        try dg.renderValue(writer, Value.fromInterned(field_val), .Other);
                                         try writer.writeAll(" << ");
                                         try dg.renderValue(writer, try pt.intValue(bit_offset_ty, bit_offset), .FunctionArgument);
-                                    } else {
-                                        try dg.renderValue(writer, Value.fromInterned(field_val), .Other);
                                     }
 
                                     bit_offset += field_ty.bitSize(zcu);
@@ -2899,6 +2912,8 @@ pub fn genFunc(f: *Function) !void {
     const main_body = f.air.getMainBody();
     try genBodyResolveState(f, undefined, &.{}, main_body, false);
     try o.indent_writer.insertNewline();
+    if (o.dg.expected_block) |_|
+        return f.fail("runtime code not allowed in naked function", .{});
 
     // Take advantage of the free_locals map to bucket locals per type. All
     // locals corresponding to AIR instructions should be in there due to
@@ -3189,6 +3204,8 @@ fn genBodyInner(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail,
     const air_datas = f.air.instructions.items(.data);
 
     for (body) |inst| {
+        if (f.object.dg.expected_block) |_|
+            return f.fail("runtime code not allowed in naked function", .{});
         if (f.liveness.isUnused(inst) and !f.air.mustLower(inst, ip))
             continue;
 
@@ -4517,6 +4534,7 @@ fn airCall(
 ) !CValue {
     const pt = f.object.dg.pt;
     const zcu = pt.zcu;
+    const ip = &zcu.intern_pool;
     // Not even allowed to call panic in a naked function.
     if (f.object.dg.is_naked_fn) return .none;
 
@@ -4562,11 +4580,12 @@ fn airCall(
     }
 
     const callee_ty = f.typeOf(pl_op.operand);
-    const fn_info = zcu.typeToFunc(switch (callee_ty.zigTypeTag(zcu)) {
-        .@"fn" => callee_ty,
-        .pointer => callee_ty.childType(zcu),
+    const callee_is_ptr = switch (callee_ty.zigTypeTag(zcu)) {
+        .@"fn" => false,
+        .pointer => true,
         else => unreachable,
-    }).?;
+    };
+    const fn_info = zcu.typeToFunc(if (callee_is_ptr) callee_ty.childType(zcu) else callee_ty).?;
     const ret_ty = Type.fromInterned(fn_info.return_type);
     const ret_ctype: CType = if (ret_ty.isNoReturn(zcu))
         CType.void
@@ -4598,20 +4617,29 @@ fn airCall(
     callee: {
         known: {
             const callee_val = (try f.air.value(pl_op.operand, pt)) orelse break :known;
-            const fn_nav = switch (zcu.intern_pool.indexToKey(callee_val.toIntern())) {
-                .@"extern" => |@"extern"| @"extern".owner_nav,
-                .func => |func| func.owner_nav,
+            const fn_nav, const need_cast = switch (ip.indexToKey(callee_val.toIntern())) {
+                .@"extern" => |@"extern"| .{ @"extern".owner_nav, false },
+                .func => |func| .{ func.owner_nav, Type.fromInterned(func.ty).fnCallingConvention(zcu) != .naked and
+                    Type.fromInterned(func.uncoerced_ty).fnCallingConvention(zcu) == .naked },
                 .ptr => |ptr| if (ptr.byte_offset == 0) switch (ptr.base_addr) {
-                    .nav => |nav| nav,
+                    .nav => |nav| .{ nav, Type.fromInterned(ptr.ty).childType(zcu).fnCallingConvention(zcu) != .naked and
+                        zcu.navValue(nav).typeOf(zcu).fnCallingConvention(zcu) == .naked },
                     else => break :known,
                 } else break :known,
                 else => break :known,
             };
+            if (need_cast) {
+                try writer.writeAll("((");
+                try f.renderType(writer, if (callee_is_ptr) callee_ty else try pt.singleConstPtrType(callee_ty));
+                try writer.writeByte(')');
+                if (!callee_is_ptr) try writer.writeByte('&');
+            }
             switch (modifier) {
                 .auto, .always_tail => try f.object.dg.renderNavName(writer, fn_nav),
                 inline .never_tail, .never_inline => |m| try writer.writeAll(try f.getLazyFnName(@unionInit(LazyFnKey, @tagName(m), fn_nav))),
                 else => unreachable,
             }
+            if (need_cast) try writer.writeByte(')');
             break :callee;
         }
         switch (modifier) {
@@ -4712,7 +4740,7 @@ fn lowerBlock(f: *Function, inst: Air.Inst.Index, body: []const Air.Inst.Index)
     const zcu = pt.zcu;
     const liveness_block = f.liveness.getBlock(inst);
 
-    const block_id: usize = f.next_block_index;
+    const block_id = f.next_block_index;
     f.next_block_index += 1;
     const writer = f.object.writer();
 
@@ -4739,7 +4767,13 @@ fn lowerBlock(f: *Function, inst: Air.Inst.Index, body: []const Air.Inst.Index)
     try f.object.indent_writer.insertNewline();
 
     // noreturn blocks have no `br` instructions reaching them, so we don't want a label
-    if (!f.typeOfIndex(inst).isNoReturn(zcu)) {
+    if (f.object.dg.is_naked_fn) {
+        if (f.object.dg.expected_block) |expected_block| {
+            if (block_id != expected_block)
+                return f.fail("runtime code not allowed in naked function", .{});
+            f.object.dg.expected_block = null;
+        }
+    } else if (!f.typeOfIndex(inst).isNoReturn(zcu)) {
         // label must be followed by an expression, include an empty one.
         try writer.print("zig_block_{d}:;\n", .{block_id});
     }
@@ -4803,6 +4837,8 @@ fn lowerTry(
 
         try genBodyResolveState(f, inst, liveness_condbr.else_deaths, body, false);
         try f.object.indent_writer.insertNewline();
+        if (f.object.dg.expected_block) |_|
+            return f.fail("runtime code not allowed in naked function", .{});
     }
 
     // Now we have the "then branch" (in terms of the liveness data); process any deaths.
@@ -4820,9 +4856,7 @@ fn lowerTry(
 
     try reap(f, inst, &.{operand});
 
-    if (f.liveness.isUnused(inst)) {
-        return .none;
-    }
+    if (f.liveness.isUnused(inst)) return .none;
 
     const local = try f.allocLocal(inst, inst_ty);
     const a = try Assignment.start(f, writer, try f.ctypeFromType(inst_ty, .complete));
@@ -4842,6 +4876,12 @@ fn airBr(f: *Function, inst: Air.Inst.Index) !void {
     const result = block.result;
     const writer = f.object.writer();
 
+    if (f.object.dg.is_naked_fn) {
+        if (result != .none) return f.fail("runtime code not allowed in naked function", .{});
+        f.object.dg.expected_block = block.block_id;
+        return;
+    }
+
     // If result is .none then the value of the block is unused.
     if (result != .none) {
         const operand_ty = f.typeOf(branch.operand);
@@ -5096,6 +5136,8 @@ fn airCondBr(f: *Function, inst: Air.Inst.Index) !void {
 
     try genBodyResolveState(f, inst, liveness_condbr.then_deaths, then_body, false);
     try writer.writeByte('\n');
+    if (else_body.len > 0) if (f.object.dg.expected_block) |_|
+        return f.fail("runtime code not allowed in naked function", .{});
 
     // We don't need to use `genBodyResolveState` for the else block, because this instruction is
     // noreturn so must terminate a body, therefore we don't need to leave `value_map` or
@@ -5193,6 +5235,8 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index, is_dispatch_loop: bool) !void
         try genBodyResolveState(f, inst, liveness.deaths[case.idx], case.body, true);
         f.object.indent_writer.popIndent();
         try writer.writeByte('}');
+        if (f.object.dg.expected_block) |_|
+            return f.fail("runtime code not allowed in naked function", .{});
 
         // The case body must be noreturn so we don't need to insert a break.
     }
@@ -5236,6 +5280,8 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index, is_dispatch_loop: bool) !void
             try genBodyResolveState(f, inst, liveness.deaths[case.idx], case.body, true);
             f.object.indent_writer.popIndent();
             try writer.writeByte('}');
+            if (f.object.dg.expected_block) |_|
+                return f.fail("runtime code not allowed in naked function", .{});
         }
     }
     if (is_dispatch_loop) {
@@ -5248,6 +5294,8 @@ fn airSwitchBr(f: *Function, inst: Air.Inst.Index, is_dispatch_loop: bool) !void
             try die(f, inst, death.toRef());
         }
         try genBody(f, else_body);
+        if (f.object.dg.expected_block) |_|
+            return f.fail("runtime code not allowed in naked function", .{});
     } else {
         try writer.writeAll("zig_unreachable();");
     }
src/link/C.zig
@@ -218,6 +218,7 @@ pub fn updateFunc(
                 .error_msg = null,
                 .pass = .{ .nav = func.owner_nav },
                 .is_naked_fn = Type.fromInterned(func.ty).fnCallingConvention(zcu) == .naked,
+                .expected_block = null,
                 .fwd_decl = fwd_decl.toManaged(gpa),
                 .ctype_pool = ctype_pool.*,
                 .scratch = .{},
@@ -272,6 +273,7 @@ fn updateUav(self: *C, pt: Zcu.PerThread, i: usize) !void {
             .error_msg = null,
             .pass = .{ .uav = uav },
             .is_naked_fn = false,
+            .expected_block = null,
             .fwd_decl = fwd_decl.toManaged(gpa),
             .ctype_pool = codegen.CType.Pool.empty,
             .scratch = .{},
@@ -347,6 +349,7 @@ pub fn updateNav(self: *C, pt: Zcu.PerThread, nav_index: InternPool.Nav.Index) !
             .error_msg = null,
             .pass = .{ .nav = nav_index },
             .is_naked_fn = false,
+            .expected_block = null,
             .fwd_decl = fwd_decl.toManaged(gpa),
             .ctype_pool = ctype_pool.*,
             .scratch = .{},
@@ -675,6 +678,7 @@ fn flushErrDecls(self: *C, pt: Zcu.PerThread, ctype_pool: *codegen.CType.Pool) F
             .error_msg = null,
             .pass = .flush,
             .is_naked_fn = false,
+            .expected_block = null,
             .fwd_decl = fwd_decl.toManaged(gpa),
             .ctype_pool = ctype_pool.*,
             .scratch = .{},
@@ -722,6 +726,7 @@ fn flushLazyFn(
             .error_msg = null,
             .pass = .flush,
             .is_naked_fn = false,
+            .expected_block = null,
             .fwd_decl = fwd_decl.toManaged(gpa),
             .ctype_pool = ctype_pool.*,
             .scratch = .{},
@@ -868,6 +873,7 @@ pub fn updateExports(
         .error_msg = null,
         .pass = pass,
         .is_naked_fn = false,
+        .expected_block = null,
         .fwd_decl = fwd_decl.toManaged(gpa),
         .ctype_pool = decl_block.ctype_pool,
         .scratch = .{},
test/behavior/packed-struct.zig
@@ -1317,3 +1317,13 @@ test "packed struct equality" {
     try S.doTest(x, y);
     comptime try S.doTest(x, y);
 }
+
+test "packed struct with signed field" {
+    var s: packed struct {
+        a: i2,
+        b: u6,
+    } = .{ .a = -1, .b = 42 };
+    s = s;
+    try expect(s.a == -1);
+    try expect(s.b == 42);
+}