Skip to content

Commit d3a1085

Browse files
committed
Update compiler_rt to use std.atomic.Op for feature detection
1 parent 6c92465 commit d3a1085

File tree

4 files changed

+199
-105
lines changed

4 files changed

+199
-105
lines changed

lib/compiler_rt/atomics.zig

Lines changed: 44 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -12,43 +12,17 @@ pub const panic = common.panic;
1212
// Some architectures support atomic load/stores but no CAS, but we ignore this
1313
// detail to keep the export logic clean and because we need some kind of CAS to
1414
// implement the spinlocks.
15-
const supports_atomic_ops = switch (arch) {
16-
.msp430, .avr, .bpfel, .bpfeb => false,
17-
.arm, .armeb, .thumb, .thumbeb =>
18-
// The ARM v6m ISA has no ldrex/strex and so it's impossible to do CAS
19-
// operations (unless we're targeting Linux, the kernel provides a way to
20-
// perform CAS operations).
21-
// XXX: The Linux code path is not implemented yet.
22-
!builtin.cpu.has(.arm, .has_v6m),
23-
else => true,
24-
};
25-
26-
// The size (in bytes) of the biggest object that the architecture can
27-
// load/store atomically.
28-
// Objects bigger than this threshold require the use of a lock.
29-
const largest_atomic_size = switch (arch) {
30-
// On SPARC systems that lacks CAS and/or swap instructions, the only
31-
// available atomic operation is a test-and-set (`ldstub`), so we force
32-
// every atomic memory access to go through the lock.
33-
.sparc => if (builtin.cpu.has(.sparc, .hasleoncasa)) @sizeOf(usize) else 0,
34-
35-
// XXX: On x86/x86_64 we could check the presence of cmpxchg8b/cmpxchg16b
36-
// and set this parameter accordingly.
37-
else => @sizeOf(usize),
38-
};
39-
40-
// The size (in bytes) of the smallest atomic object that the architecture can
41-
// perform fetch/exchange atomically. Note, this does not encompass load and store.
42-
// Objects smaller than this threshold are implemented in terms of compare-exchange
43-
// of a larger value.
44-
const smallest_atomic_fetch_exch_size = switch (arch) {
45-
// On AMDGCN, there are no instructions for atomic operations other than load and store
46-
// (as of LLVM 15), and so these need to be implemented in terms of atomic CAS.
47-
.amdgcn => @sizeOf(u32),
48-
else => @sizeOf(u8),
49-
};
50-
51-
const cache_line_size = 64;
15+
const supports_atomic_ops =
16+
std.atomic.Op.supported(.{ .cmpxchg = .strong }, usize) or
17+
// We have a specialized SPARC spinlock implementation
18+
builtin.cpu.arch.isSPARC();
19+
20+
// This is the size of the smallest value that the target can perform a compare-and-swap on.
21+
// The function `wideUpdate` can be used to implement RMW operations on types smaller than this.
22+
const wide_update_size = std.atomic.Op.supportedSizes(
23+
.{ .cmpxchg = .weak },
24+
builtin.cpu.arch,
25+
).findMin(builtin.cpu.features);
5226

5327
const SpinlockTable = struct {
5428
// Allocate ~4096 bytes of memory for the spinlock table
@@ -63,7 +37,7 @@ const SpinlockTable = struct {
6337

6438
// Prevent false sharing by providing enough padding between two
6539
// consecutive spinlock elements
66-
v: if (arch.isSPARC()) sparc_lock else other_lock align(cache_line_size) = .Unlocked,
40+
v: if (arch.isSPARC()) sparc_lock else other_lock align(std.atomic.cache_line) = .Unlocked,
6741

6842
fn acquire(self: *@This()) void {
6943
while (true) {
@@ -165,12 +139,12 @@ fn __atomic_compare_exchange(
165139
// aligned.
166140
inline fn atomic_load_N(comptime T: type, src: *T, model: i32) T {
167141
_ = model;
168-
if (@sizeOf(T) > largest_atomic_size) {
142+
if (comptime std.atomic.Op.supported(.load, T)) {
143+
return @atomicLoad(T, src, .seq_cst);
144+
} else {
169145
var sl = spinlocks.get(@intFromPtr(src));
170146
defer sl.release();
171147
return src.*;
172-
} else {
173-
return @atomicLoad(T, src, .seq_cst);
174148
}
175149
}
176150

@@ -196,12 +170,12 @@ fn __atomic_load_16(src: *u128, model: i32) callconv(.c) u128 {
196170

197171
inline fn atomic_store_N(comptime T: type, dst: *T, value: T, model: i32) void {
198172
_ = model;
199-
if (@sizeOf(T) > largest_atomic_size) {
173+
if (comptime std.atomic.Op.supported(.store, T)) {
174+
@atomicStore(T, dst, value, .seq_cst);
175+
} else {
200176
var sl = spinlocks.get(@intFromPtr(dst));
201177
defer sl.release();
202178
dst.* = value;
203-
} else {
204-
@atomicStore(T, dst, value, .seq_cst);
205179
}
206180
}
207181

@@ -226,13 +200,14 @@ fn __atomic_store_16(dst: *u128, value: u128, model: i32) callconv(.c) void {
226200
}
227201

228202
fn wideUpdate(comptime T: type, ptr: *T, val: T, update: anytype) T {
229-
const WideAtomic = std.meta.Int(.unsigned, smallest_atomic_fetch_exch_size * 8);
203+
comptime std.debug.assert(@sizeOf(T) < wide_update_size);
204+
const WideAtomic = std.meta.Int(.unsigned, wide_update_size * 8);
230205

231206
const addr = @intFromPtr(ptr);
232-
const wide_addr = addr & ~(@as(T, smallest_atomic_fetch_exch_size) - 1);
233-
const wide_ptr: *align(smallest_atomic_fetch_exch_size) WideAtomic = @alignCast(@as(*WideAtomic, @ptrFromInt(wide_addr)));
207+
const wide_addr = addr & ~(@as(T, wide_update_size) - 1);
208+
const wide_ptr: *align(wide_update_size) WideAtomic = @alignCast(@as(*WideAtomic, @ptrFromInt(wide_addr)));
234209

235-
const inner_offset = addr & (@as(T, smallest_atomic_fetch_exch_size) - 1);
210+
const inner_offset = addr & (@as(T, wide_update_size) - 1);
236211
const inner_shift = @as(std.math.Log2Int(T), @intCast(inner_offset * 8));
237212

238213
const mask = @as(WideAtomic, std.math.maxInt(T)) << inner_shift;
@@ -252,13 +227,9 @@ fn wideUpdate(comptime T: type, ptr: *T, val: T, update: anytype) T {
252227

253228
inline fn atomic_exchange_N(comptime T: type, ptr: *T, val: T, model: i32) T {
254229
_ = model;
255-
if (@sizeOf(T) > largest_atomic_size) {
256-
var sl = spinlocks.get(@intFromPtr(ptr));
257-
defer sl.release();
258-
const value = ptr.*;
259-
ptr.* = val;
260-
return value;
261-
} else if (@sizeOf(T) < smallest_atomic_fetch_exch_size) {
230+
if (comptime std.atomic.Op.supported(.{ .rmw = .Xchg }, T)) {
231+
return @atomicRmw(T, ptr, .Xchg, val, .seq_cst);
232+
} else if (@sizeOf(T) < wide_update_size) {
262233
// Machine does not support this type, but it does support a larger type.
263234
const Updater = struct {
264235
fn update(new: T, old: T) T {
@@ -268,7 +239,11 @@ inline fn atomic_exchange_N(comptime T: type, ptr: *T, val: T, model: i32) T {
268239
};
269240
return wideUpdate(T, ptr, val, Updater.update);
270241
} else {
271-
return @atomicRmw(T, ptr, .Xchg, val, .seq_cst);
242+
var sl = spinlocks.get(@intFromPtr(ptr));
243+
defer sl.release();
244+
const value = ptr.*;
245+
ptr.* = val;
246+
return value;
272247
}
273248
}
274249

@@ -302,7 +277,13 @@ inline fn atomic_compare_exchange_N(
302277
) i32 {
303278
_ = success;
304279
_ = failure;
305-
if (@sizeOf(T) > largest_atomic_size) {
280+
if (comptime std.atomic.Op.supported(.{ .cmpxchg = .strong }, T)) {
281+
if (@cmpxchgStrong(T, ptr, expected.*, desired, .seq_cst, .seq_cst)) |old_value| {
282+
expected.* = old_value;
283+
return 0;
284+
}
285+
return 1;
286+
} else {
306287
var sl = spinlocks.get(@intFromPtr(ptr));
307288
defer sl.release();
308289
const value = ptr.*;
@@ -312,12 +293,6 @@ inline fn atomic_compare_exchange_N(
312293
}
313294
expected.* = value;
314295
return 0;
315-
} else {
316-
if (@cmpxchgStrong(T, ptr, expected.*, desired, .seq_cst, .seq_cst)) |old_value| {
317-
expected.* = old_value;
318-
return 0;
319-
}
320-
return 1;
321296
}
322297
}
323298

@@ -359,19 +334,19 @@ inline fn fetch_op_N(comptime T: type, comptime op: std.builtin.AtomicRmwOp, ptr
359334
}
360335
};
361336

362-
if (@sizeOf(T) > largest_atomic_size) {
337+
if (comptime std.atomic.Op.supported(.{ .rmw = op }, T)) {
338+
return @atomicRmw(T, ptr, op, val, .seq_cst);
339+
} else if (@sizeOf(T) < wide_update_size) {
340+
// Machine does not support this type, but it does support a larger type.
341+
return wideUpdate(T, ptr, val, Updater.update);
342+
} else {
363343
var sl = spinlocks.get(@intFromPtr(ptr));
364344
defer sl.release();
365345

366346
const value = ptr.*;
367347
ptr.* = Updater.update(val, value);
368348
return value;
369-
} else if (@sizeOf(T) < smallest_atomic_fetch_exch_size) {
370-
// Machine does not support this type, but it does support a larger type.
371-
return wideUpdate(T, ptr, val, Updater.update);
372349
}
373-
374-
return @atomicRmw(T, ptr, op, val, .seq_cst);
375350
}
376351

377352
fn __atomic_fetch_add_1(ptr: *u8, val: u8, model: i32) callconv(.c) u8 {

lib/std/Target.zig

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1245,6 +1245,11 @@ pub const Cpu = struct {
12451245
set.ints = @as(@Vector(usize_count, usize), set.ints) & ~@as(@Vector(usize_count, usize), other_set.ints);
12461246
}
12471247

1248+
/// Removes all features that are not in the specified set.
1249+
pub fn intersectFeatureSet(set: *Set, other_set: Set) void {
1250+
set.ints = @as(@Vector(usize_count, usize), set.ints) & @as(@Vector(usize_count, usize), other_set.ints);
1251+
}
1252+
12481253
pub fn populateDependencies(set: *Set, all_features_list: []const Cpu.Feature) void {
12491254
@setEvalBranchQuota(1000000);
12501255

@@ -1277,6 +1282,13 @@ pub const Cpu = struct {
12771282
return @reduce(.And, (set_v & other_v) == other_v);
12781283
}
12791284

1285+
pub fn intersectsWith(set: Set, other_set: Set) bool {
1286+
const V = @Vector(usize_count, usize);
1287+
const set_v: V = set.ints;
1288+
const other_v: V = other_set.ints;
1289+
return @reduce(.Or, (set_v & other_v) != @as(V, @splat(0)));
1290+
}
1291+
12801292
/// Formatter to print the feature set as a comma-separated list, ending with a conjunction
12811293
pub fn fmtList(set: Set, family: Arch.Family, conjunction: []const u8) FormatList {
12821294
return .{ .set = set, .family = family, .conjunction = conjunction };

0 commit comments

Comments
 (0)