Skip to content

Commit d6067db

Browse files
committed
stage2: implement @popCount for non-vectors
1 parent 5ff01bd commit d6067db

File tree

14 files changed

+209
-176
lines changed

14 files changed

+209
-176
lines changed

lib/std/math/big/int.zig

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -733,6 +733,27 @@ pub const Mutable = struct {
733733
rma.truncate(rma.toConst(), signedness, bit_count);
734734
}
735735

736+
/// r = @popCount(a) with 2s-complement semantics.
737+
/// r and a may be aliases.
738+
///
739+
/// Assets the result fits in `r`. Upper bound on the number of limbs needed by
740+
/// r is `calcTwosCompLimbCount(bit_count)`.
741+
pub fn popCount(r: *Mutable, a: Const, bit_count: usize) void {
742+
r.copy(a);
743+
744+
if (!a.positive) {
745+
r.positive = true; // Negate.
746+
r.bitNotWrap(r.toConst(), .unsigned, bit_count); // Bitwise NOT.
747+
r.addScalar(r.toConst(), 1); // Add one.
748+
}
749+
750+
var sum: Limb = 0;
751+
for (r.limbs[0..r.len]) |limb| {
752+
sum += @popCount(Limb, limb);
753+
}
754+
r.set(sum);
755+
}
756+
736757
/// rma = a * a
737758
///
738759
/// `rma` may not alias with `a`.
@@ -2735,6 +2756,15 @@ pub const Managed = struct {
27352756
m.saturate(a, signedness, bit_count);
27362757
r.setMetadata(m.positive, m.len);
27372758
}
2759+
2760+
/// r = @popCount(a) with 2s-complement semantics.
2761+
/// r and a may be aliases.
2762+
pub fn popCount(r: *Managed, a: Const, bit_count: usize) !void {
2763+
try r.ensureCapacity(calcTwosCompLimbCount(bit_count));
2764+
var m = r.toMutable();
2765+
m.popCount(a, bit_count);
2766+
r.setMetadata(m.positive, m.len);
2767+
}
27382768
};
27392769

27402770
/// Different operators which can be used in accumulation style functions

lib/std/math/big/int_test.zig

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2434,3 +2434,14 @@ test "big.int regression test for realloc with alias" {
24342434

24352435
try testing.expect(a.toConst().orderAgainstScalar(14691098406862188148944207245954912110548093601382197697835) == .eq);
24362436
}
2437+
2438+
test "big int popcount" {
2439+
var a = try Managed.initSet(testing.allocator, -1);
2440+
defer a.deinit();
2441+
var b = try Managed.initSet(testing.allocator, -1);
2442+
defer b.deinit();
2443+
2444+
try a.popCount(b.toConst(), 16);
2445+
2446+
try testing.expect(a.toConst().orderAgainstScalar(16) == .eq);
2447+
}

src/Air.zig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,10 @@ pub const Inst = struct {
202202
/// Result type will always be an unsigned integer big enough to fit the answer.
203203
/// Uses the `ty_op` field.
204204
ctz,
205+
/// Count number of 1 bits in an integer according to its representation in twos complement.
206+
/// Result type will always be an unsigned integer big enough to fit the answer.
207+
/// Uses the `ty_op` field.
208+
popcount,
205209

206210
/// `<`. Result type is always bool.
207211
/// Uses the `bin_op` field.
@@ -744,6 +748,7 @@ pub fn typeOfIndex(air: Air, inst: Air.Inst.Index) Type {
744748
.get_union_tag,
745749
.clz,
746750
.ctz,
751+
.popcount,
747752
=> return air.getRefType(datas[inst].ty_op.ty),
748753

749754
.loop,

src/Liveness.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,7 @@ fn analyzeInst(
313313
.get_union_tag,
314314
.clz,
315315
.ctz,
316+
.popcount,
316317
=> {
317318
const o = inst_datas[inst].ty_op;
318319
return trackOperands(a, new_set, inst, main_tomb, .{ o.operand, .none, .none });

src/Sema.zig

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9904,8 +9904,30 @@ fn zirCtz(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Ins
99049904

99059905
fn zirPopCount(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {
99069906
const inst_data = sema.code.instructions.items(.data)[inst].un_node;
9907-
const src = inst_data.src();
9908-
return sema.fail(block, src, "TODO: Sema.zirPopCount", .{});
9907+
const ty_src: LazySrcLoc = .{ .node_offset_builtin_call_arg0 = inst_data.src_node };
9908+
const operand_src: LazySrcLoc = .{ .node_offset_builtin_call_arg1 = inst_data.src_node };
9909+
const operand = sema.resolveInst(inst_data.operand);
9910+
const operand_ty = sema.typeOf(operand);
9911+
// TODO implement support for vectors
9912+
if (operand_ty.zigTypeTag() != .Int) {
9913+
return sema.fail(block, ty_src, "expected integer type, found '{}'", .{
9914+
operand_ty,
9915+
});
9916+
}
9917+
const target = sema.mod.getTarget();
9918+
const bits = operand_ty.intInfo(target).bits;
9919+
if (bits == 0) return Air.Inst.Ref.zero;
9920+
9921+
const result_ty = try Type.smallestUnsignedInt(sema.arena, bits);
9922+
9923+
const runtime_src = if (try sema.resolveMaybeUndefVal(block, operand_src, operand)) |val| {
9924+
if (val.isUndef()) return sema.addConstUndef(result_ty);
9925+
const result_val = try val.popCount(operand_ty, target, sema.arena);
9926+
return sema.addConstant(result_ty, result_val);
9927+
} else operand_src;
9928+
9929+
try sema.requireRuntimeBlock(block, runtime_src);
9930+
return block.addTyOp(.popcount, result_ty, operand);
99099931
}
99109932

99119933
fn zirByteSwap(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!Air.Inst.Ref {

src/arch/aarch64/CodeGen.zig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,7 @@ fn genBody(self: *Self, body: []const Air.Inst.Index) InnerError!void {
481481
.get_union_tag => try self.airGetUnionTag(inst),
482482
.clz => try self.airClz(inst),
483483
.ctz => try self.airCtz(inst),
484+
.popcount => try self.airPopcount(inst),
484485

485486
.atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
486487
.atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
@@ -1138,6 +1139,12 @@ fn airCtz(self: *Self, inst: Air.Inst.Index) !void {
11381139
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
11391140
}
11401141

1142+
fn airPopcount(self: *Self, inst: Air.Inst.Index) !void {
1143+
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1144+
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else return self.fail("TODO implement airPopcount for {}", .{self.target.cpu.arch});
1145+
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
1146+
}
1147+
11411148
fn reuseOperand(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, op_index: Liveness.OperandInt, mcv: MCValue) bool {
11421149
if (!self.liveness.operandDies(inst, op_index))
11431150
return false;

src/codegen.zig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -836,6 +836,7 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
836836
.get_union_tag => try self.airGetUnionTag(inst),
837837
.clz => try self.airClz(inst),
838838
.ctz => try self.airCtz(inst),
839+
.popcount => try self.airPopcount(inst),
839840

840841
.atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
841842
.atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
@@ -1610,6 +1611,14 @@ fn Function(comptime arch: std.Target.Cpu.Arch) type {
16101611
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
16111612
}
16121613

1614+
fn airPopcount(self: *Self, inst: Air.Inst.Index) !void {
1615+
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
1616+
const result: MCValue = if (self.liveness.isUnused(inst)) .dead else switch (arch) {
1617+
else => return self.fail("TODO implement airPopcount for {}", .{self.target.cpu.arch}),
1618+
};
1619+
return self.finishAir(inst, result, .{ ty_op.operand, .none, .none });
1620+
}
1621+
16131622
fn reuseOperand(self: *Self, inst: Air.Inst.Index, operand: Air.Inst.Ref, op_index: Liveness.OperandInt, mcv: MCValue) bool {
16141623
if (!self.liveness.operandDies(inst, op_index))
16151624
return false;

src/codegen/c.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1107,6 +1107,7 @@ fn genBody(f: *Function, body: []const Air.Inst.Index) error{ AnalysisFail, OutO
11071107
.get_union_tag => try airGetUnionTag(f, inst),
11081108
.clz => try airBuiltinCall(f, inst, "clz"),
11091109
.ctz => try airBuiltinCall(f, inst, "ctz"),
1110+
.popcount => try airBuiltinCall(f, inst, "popcount"),
11101111

11111112
.int_to_float,
11121113
.float_to_int,

src/codegen/llvm.zig

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1777,6 +1777,7 @@ pub const FuncGen = struct {
17771777
.get_union_tag => try self.airGetUnionTag(inst),
17781778
.clz => try self.airClzCtz(inst, "ctlz"),
17791779
.ctz => try self.airClzCtz(inst, "cttz"),
1780+
.popcount => try self.airPopCount(inst, "ctpop"),
17801781

17811782
.atomic_store_unordered => try self.airAtomicStore(inst, .Unordered),
17821783
.atomic_store_monotonic => try self.airAtomicStore(inst, .Monotonic),
@@ -3679,6 +3680,40 @@ pub const FuncGen = struct {
36793680
}
36803681
}
36813682

3683+
fn airPopCount(self: *FuncGen, inst: Air.Inst.Index, prefix: [*:0]const u8) !?*const llvm.Value {
3684+
if (self.liveness.isUnused(inst)) return null;
3685+
3686+
const ty_op = self.air.instructions.items(.data)[inst].ty_op;
3687+
const operand_ty = self.air.typeOf(ty_op.operand);
3688+
const operand = try self.resolveInst(ty_op.operand);
3689+
const target = self.dg.module.getTarget();
3690+
const bits = operand_ty.intInfo(target).bits;
3691+
3692+
var fn_name_buf: [100]u8 = undefined;
3693+
const llvm_fn_name = std.fmt.bufPrintZ(&fn_name_buf, "llvm.{s}.i{d}", .{
3694+
prefix, bits,
3695+
}) catch unreachable;
3696+
const fn_val = self.dg.object.llvm_module.getNamedFunction(llvm_fn_name) orelse blk: {
3697+
const operand_llvm_ty = try self.dg.llvmType(operand_ty);
3698+
const param_types = [_]*const llvm.Type{operand_llvm_ty};
3699+
const fn_type = llvm.functionType(operand_llvm_ty, &param_types, param_types.len, .False);
3700+
break :blk self.dg.object.llvm_module.addFunction(llvm_fn_name, fn_type);
3701+
};
3702+
3703+
const params = [_]*const llvm.Value{operand};
3704+
const wrong_size_result = self.builder.buildCall(fn_val, &params, params.len, .C, .Auto, "");
3705+
const result_ty = self.air.typeOfIndex(inst);
3706+
const result_llvm_ty = try self.dg.llvmType(result_ty);
3707+
const result_bits = result_ty.intInfo(target).bits;
3708+
if (bits > result_bits) {
3709+
return self.builder.buildTrunc(wrong_size_result, result_llvm_ty, "");
3710+
} else if (bits < result_bits) {
3711+
return self.builder.buildZExt(wrong_size_result, result_llvm_ty, "");
3712+
} else {
3713+
return wrong_size_result;
3714+
}
3715+
}
3716+
36823717
fn callFloor(self: *FuncGen, arg: *const llvm.Value, ty: Type) !*const llvm.Value {
36833718
return self.callFloatUnary(arg, ty, "floor");
36843719
}

src/print_air.zig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,7 @@ const Writer = struct {
196196
.get_union_tag,
197197
.clz,
198198
.ctz,
199+
.popcount,
199200
=> try w.writeTyOp(s, inst),
200201

201202
.block,

0 commit comments

Comments
 (0)