Commit 19691c0b17

Andrew Kelley <andrew@ziglang.org>
2021-09-15 21:37:32
stage2: implement `@fence`
1 parent e5fd450
src/codegen/llvm/bindings.zig
@@ -522,6 +522,14 @@ pub const Builder = opaque {
         Else: *const Value,
         Name: [*:0]const u8,
     ) *const Value;
+
+    pub const buildFence = LLVMBuildFence;
+    extern fn LLVMBuildFence(
+        B: *const Builder,
+        ordering: AtomicOrdering,
+        singleThread: Bool,
+        Name: [*:0]const u8,
+    ) *const Value;
 };
 
 pub const IntPredicate = enum(c_uint) {
src/codegen/c.zig
@@ -842,6 +842,7 @@ fn genBody(o: *Object, body: []const Air.Inst.Index) error{ AnalysisFail, OutOfM
 
             .breakpoint => try airBreakpoint(o),
             .unreach    => try airUnreach(o),
+            .fence      => try airFence(o, inst),
 
             // TODO use a different strategy for add that communicates to the optimizer
             // that wrapping is UB.
@@ -1439,6 +1440,17 @@ fn airBreakpoint(o: *Object) !CValue {
     return CValue.none;
 }
 
+fn airFence(o: *Object, inst: Air.Inst.Index) !CValue {
+    const atomic_order = o.air.instructions.items(.data)[inst].fence;
+    const writer = o.writer();
+
+    try writer.writeAll("zig_fence(");
+    try writeMemoryOrder(writer, atomic_order);
+    try writer.writeAll(");\n");
+
+    return CValue.none;
+}
+
 fn airUnreach(o: *Object) !CValue {
     try o.writer().writeAll("zig_unreachable();\n");
     return CValue.none;
src/codegen/llvm.zig
@@ -1059,6 +1059,7 @@ pub const FuncGen = struct {
                 .array_to_slice => try self.airArrayToSlice(inst),
                 .cmpxchg_weak   => try self.airCmpxchg(inst, true),
                 .cmpxchg_strong => try self.airCmpxchg(inst, false),
+                .fence          => try self.airFence(inst),
 
                 .struct_field_ptr => try self.airStructFieldPtr(inst),
                 .struct_field_val => try self.airStructFieldVal(inst),
@@ -2005,6 +2006,14 @@ pub const FuncGen = struct {
         return null;
     }
 
+    fn airFence(self: *FuncGen, inst: Air.Inst.Index) !?*const llvm.Value {
+        const atomic_order = self.air.instructions.items(.data)[inst].fence;
+        const llvm_memory_order = toLlvmAtomicOrdering(atomic_order);
+        const single_threaded = llvm.Bool.fromBool(self.single_threaded);
+        _ = self.builder.buildFence(llvm_memory_order, single_threaded, "");
+        return null;
+    }
+
     fn airCmpxchg(self: *FuncGen, inst: Air.Inst.Index, is_weak: bool) !?*const llvm.Value {
         const ty_pl = self.air.instructions.items(.data)[inst].ty_pl;
         const extra = self.air.extraData(Air.Cmpxchg, ty_pl.payload).data;
src/link/C/zig.h
@@ -64,12 +64,15 @@
 #include <stdatomic.h>
 #define zig_cmpxchg_strong(obj, expected, desired, succ, fail) atomic_compare_exchange_strong_explicit(obj, expected, desired, succ, fail)
 #define zig_cmpxchg_weak(obj, expected, desired, succ, fail) atomic_compare_exchange_weak_explicit(obj, expected, desired, succ, fail)
+#define zig_fence(order) atomic_thread_fence(order)
 #elif __GNUC__
 #define zig_cmpxchg_strong(obj, expected, desired, succ, fail) __sync_val_compare_and_swap(obj, expected, desired)
 #define zig_cmpxchg_weak(obj, expected, desired, succ, fail) __sync_val_compare_and_swap(obj, expected, desired)
+#define zig_fence(order) __sync_synchronize(order)
 #else
 #define zig_cmpxchg_strong(obj, expected, desired, succ, fail) zig_unimplemented()
 #define zig_cmpxchg_weak(obj, expected, desired, succ, fail) zig_unimplemented()
+#define zig_fence(order) zig_unimplemented()
 #endif
 
 #include <stdint.h>
src/Air.zig
@@ -127,6 +127,10 @@ pub const Inst = struct {
         /// Lowers to a hardware trap instruction, or the next best thing.
         /// Result type is always void.
         breakpoint,
+        /// Lowers to a memory fence instruction.
+        /// Result type is always void.
+        /// Uses the `fence` field.
+        fence,
         /// Function call.
         /// Result type is the return type of the function being called.
         /// Uses the `pl_op` field with the `Call` payload. operand is the callee.
@@ -380,6 +384,7 @@ pub const Inst = struct {
             line: u32,
             column: u32,
         },
+        fence: std.builtin.AtomicOrder,
 
         // Make sure we don't accidentally add a field to make this union
         // bigger than expected. Note that in Debug builds, Zig is allowed
@@ -566,6 +571,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
         .breakpoint,
         .dbg_stmt,
         .store,
+        .fence,
         => return Type.initTag(.void),
 
         .ptrtoint,
src/AstGen.zig
@@ -7116,9 +7116,13 @@ fn builtinCall(
             });
             return rvalue(gz, rl, result, node);
         },
+        .fence => {
+            const order = try expr(gz, scope, .{ .coerced_ty = .atomic_order_type }, params[0]);
+            const result = try gz.addUnNode(.fence, order, node);
+            return rvalue(gz, rl, result, node);
+        },
 
         .breakpoint => return simpleNoOpVoid(gz, rl, node, .breakpoint),
-        .fence      => return simpleNoOpVoid(gz, rl, node, .fence),
 
         .This               => return rvalue(gz, rl, try gz.addNodeExtended(.this,               node), node),
         .return_address     => return rvalue(gz, rl, try gz.addNodeExtended(.ret_addr,           node), node),
src/codegen.zig
@@ -833,6 +833,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
                     .block           => try self.airBlock(inst),
                     .br              => try self.airBr(inst),
                     .breakpoint      => try self.airBreakpoint(),
+                    .fence           => try self.airFence(),
                     .call            => try self.airCall(inst),
                     .cond_br         => try self.airCondBr(inst),
                     .dbg_stmt        => try self.airDbgStmt(inst),
@@ -2549,6 +2550,11 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
             return self.finishAirBookkeeping();
         }
 
+        fn airFence(self: *Self) !void {
+            return self.fail("TODO implement fence() for {}", .{self.target.cpu.arch});
+            //return self.finishAirBookkeeping();
+        }
+
         fn airCall(self: *Self, inst: Air.Inst.Index) !void {
             const pl_op = self.air.instructions.items(.data)[inst].pl_op;
             const fn_ty = self.air.typeOf(pl_op.operand);
src/Liveness.zig
@@ -264,6 +264,7 @@ fn analyzeInst(
         .breakpoint,
         .dbg_stmt,
         .unreach,
+        .fence,
         => return trackOperands(a, new_set, inst, main_tomb, .{ .none, .none, .none }),
 
         .not,
src/print_air.zig
@@ -192,6 +192,7 @@ const Writer = struct {
             .cond_br => try w.writeCondBr(s, inst),
             .switch_br => try w.writeSwitchBr(s, inst),
             .cmpxchg_weak, .cmpxchg_strong => try w.writeCmpxchg(s, inst),
+            .fence => try w.writeFence(s, inst),
         }
     }
 
@@ -276,6 +277,12 @@ const Writer = struct {
         });
     }
 
+    fn writeFence(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
+        const atomic_order = w.air.instructions.items(.data)[inst].fence;
+
+        try s.print("{s}", .{@tagName(atomic_order)});
+    }
+
     fn writeConstant(w: *Writer, s: anytype, inst: Air.Inst.Index) @TypeOf(s).Error!void {
         const ty_pl = w.air.instructions.items(.data)[inst].ty_pl;
         const val = w.air.values[ty_pl.payload];
src/Sema.zig
@@ -377,7 +377,9 @@ pub fn analyzeBody(
             // We also know that they cannot be referenced later, so we avoid
             // putting them into the map.
             .breakpoint => {
-                try sema.zirBreakpoint(block, inst);
+                if (!block.is_comptime) {
+                    _ = try block.addNoOp(.breakpoint);
+                }
                 i += 1;
                 continue;
             },
@@ -2308,20 +2310,21 @@ fn zirSetRuntimeSafety(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) C
     block.want_safety = try sema.resolveConstBool(block, operand_src, inst_data.operand);
 }
 
-fn zirBreakpoint(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!void {
-    const tracy = trace(@src());
-    defer tracy.end();
+fn zirFence(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!void {
+    if (block.is_comptime) return;
 
-    const src_node = sema.code.instructions.items(.data)[inst].node;
-    const src: LazySrcLoc = .{ .node_offset = src_node };
-    try sema.requireRuntimeBlock(block, src);
-    _ = try block.addNoOp(.breakpoint);
-}
+    const inst_data = sema.code.instructions.items(.data)[inst].un_node;
+    const order_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
+    const order = try sema.resolveAtomicOrder(block, order_src, inst_data.operand);
 
-fn zirFence(sema: *Sema, block: *Scope.Block, inst: Zir.Inst.Index) CompileError!void {
-    const src_node = sema.code.instructions.items(.data)[inst].node;
-    const src: LazySrcLoc = .{ .node_offset = src_node };
-    return sema.mod.fail(&block.base, src, "TODO: implement Sema.zirFence", .{});
+    if (@enumToInt(order) < @enumToInt(std.builtin.AtomicOrder.Acquire)) {
+        return sema.mod.fail(&block.base, order_src, "atomic ordering must be Acquire or stricter", .{});
+    }
+
+    _ = try block.addInst(.{
+        .tag = .fence,
+        .data = .{ .fence = order },
+    });
 }
 
 fn zirBreak(sema: *Sema, start_block: *Scope.Block, inst: Zir.Inst.Index) CompileError!Zir.Inst.Index {
src/Zir.zig
@@ -731,7 +731,7 @@ pub const Inst = struct {
         size_of,
         /// Implements the `@bitSizeOf` builtin. Uses `un_node`.
         bit_size_of,
-        /// Implements the `@fence` builtin. Uses `node`.
+        /// Implements the `@fence` builtin. Uses `un_node`.
         fence,
 
         /// Implement builtin `@ptrToInt`. Uses `un_node`.
@@ -1416,7 +1416,7 @@ pub const Inst = struct {
                 .type_info = .un_node,
                 .size_of = .un_node,
                 .bit_size_of = .un_node,
-                .fence = .node,
+                .fence = .un_node,
 
                 .ptr_to_int = .un_node,
                 .error_to_int = .un_node,
@@ -3016,6 +3016,7 @@ const Writer = struct {
             .@"resume",
             .@"await",
             .await_nosuspend,
+            .fence,
             => try self.writeUnNode(stream, inst),
 
             .ref,
@@ -3187,7 +3188,6 @@ const Writer = struct {
             .as_node => try self.writeAs(stream, inst),
 
             .breakpoint,
-            .fence,
             .repeat,
             .repeat_inline,
             .alloc_inferred,
test/behavior/atomics.zig
@@ -24,3 +24,9 @@ fn testCmpxchg() !void {
     try expect(@cmpxchgStrong(i32, &x, 5678, 42, .SeqCst, .SeqCst) == null);
     try expect(x == 42);
 }
+
+test "fence" {
+    var x: i32 = 1234;
+    @fence(.SeqCst);
+    x = 5678;
+}
test/behavior/atomics_stage1.zig
@@ -3,12 +3,6 @@ const expect = std.testing.expect;
 const expectEqual = std.testing.expectEqual;
 const builtin = @import("builtin");
 
-test "fence" {
-    var x: i32 = 1234;
-    @fence(.SeqCst);
-    x = 5678;
-}
-
 test "atomicrmw and atomicload" {
     var data: u8 = 200;
     try testAtomicRmw(&data);