From d8295c188946b0f07d62420c2f08c940f70b03ac Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 7 Jul 2018 00:25:32 -0400 Subject: [PATCH] add @popCount intrinsic --- doc/langref.html.in | 15 +++++++-- src/all_types.hpp | 12 ++++++++ src/analyze.cpp | 4 +++ src/bigint.cpp | 31 +++++++++++++++++++ src/bigint.hpp | 2 ++ src/codegen.cpp | 21 ++++++++++++- src/ir.cpp | 68 +++++++++++++++++++++++++++++++++++++++++ src/ir_print.cpp | 9 ++++++ test/behavior.zig | 7 +++-- test/cases/popcount.zig | 24 +++++++++++++++ test/compile_errors.zig | 18 +++++++++++ 11 files changed, 205 insertions(+), 6 deletions(-) create mode 100644 test/cases/popcount.zig diff --git a/doc/langref.html.in b/doc/langref.html.in index 5c1cc130ac7e0b2c7c703fd607d0e4959fa6c581..8eaffb64ad9c8156fd1538d89d84344a782303cc 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -5013,7 +5013,7 @@ comptime {

If x is zero, @clz returns T.bit_count.

- + {#see_also|@ctz|@popCount#} {#header_close#} {#header_open|@cmpxchgStrong#}
@cmpxchgStrong(comptime T: type, ptr: *T, expected_value: T, new_value: T, success_order: AtomicOrder, fail_order: AtomicOrder) ?T
@@ -5149,6 +5149,7 @@ test "main" {

If x is zero, @ctz returns T.bit_count.

+ {#see_also|@clz|@popCount#} {#header_close#} {#header_open|@divExact#}
@divExact(numerator: T, denominator: T) T
@@ -5631,6 +5632,16 @@ test "call foo" { {#see_also|Root Source File#} {#header_close#} + {#header_open|@popCount#} +
@popCount(integer: var) var
+

Counts the number of bits set in an integer.

+

+ If integer is known at {#link|comptime#}, the return type is comptime_int. + Otherwise, the return type is an unsigned integer with the minimum number + of bits that can represent the bit count of the integer type. +

+ {#see_also|@ctz|@clz#} + {#header_close#} {#header_open|@ptrCast#}
@ptrCast(comptime DestType: type, value: var) DestType

@@ -7337,7 +7348,7 @@ hljs.registerLanguage("zig", function(t) { a = t.IR + "\\s*\\(", c = { keyword: "const align var extern stdcallcc nakedcc volatile export pub noalias inline struct packed enum union break return try catch test continue unreachable comptime and or asm defer errdefer if else switch while for fn use bool f32 f64 void type noreturn error i8 u8 i16 u16 i32 u32 i64 u64 isize usize i8w u8w i16w i32w u32w i64w u64w isizew usizew c_short c_ushort c_int c_uint c_long c_ulong c_longlong c_ulonglong resume cancel await async orelse", - built_in: "atomicLoad breakpoint returnAddress frameAddress fieldParentPtr setFloatMode IntType OpaqueType compileError compileLog setCold setRuntimeSafety setEvalBranchQuota offsetOf memcpy inlineCall setGlobalLinkage divTrunc divFloor enumTagName intToPtr ptrToInt panic ptrCast intCast floatCast intToFloat floatToInt boolToInt bytesToSlice sliceToBytes errSetCast bitCast rem mod memset sizeOf alignOf alignCast maxValue minValue memberCount memberName memberType typeOf addWithOverflow subWithOverflow mulWithOverflow shlWithOverflow shlExact shrExact cInclude cDefine cUndef ctz clz import cImport errorName embedFile cmpxchgStrong cmpxchgWeak fence divExact truncate atomicRmw sqrt field typeInfo typeName newStackCall errorToInt intToError enumToInt intToEnum", + built_in: "atomicLoad breakpoint returnAddress frameAddress fieldParentPtr setFloatMode IntType OpaqueType compileError compileLog setCold setRuntimeSafety setEvalBranchQuota offsetOf memcpy inlineCall setGlobalLinkage divTrunc divFloor enumTagName intToPtr ptrToInt panic ptrCast intCast floatCast intToFloat floatToInt boolToInt bytesToSlice sliceToBytes errSetCast bitCast rem mod memset sizeOf alignOf alignCast maxValue minValue memberCount memberName memberType typeOf addWithOverflow subWithOverflow mulWithOverflow shlWithOverflow shlExact shrExact cInclude cDefine cUndef ctz clz popCount import cImport errorName embedFile cmpxchgStrong cmpxchgWeak fence divExact truncate atomicRmw sqrt field typeInfo typeName newStackCall errorToInt intToError enumToInt intToEnum", literal: "true false null undefined" }, n = [e, t.CLCM, t.CBCM, s, r]; diff --git a/src/all_types.hpp b/src/all_types.hpp index 4d97be468cfc1da29e56857d8dab9c7c1fb2199e..6dcf1894d85e3f903be81432858f512bdf1ea1d5 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1352,6 +1352,7 @@ enum BuiltinFnId { BuiltinFnIdCompileLog, BuiltinFnIdCtz, BuiltinFnIdClz, + BuiltinFnIdPopCount, BuiltinFnIdImport, BuiltinFnIdCImport, BuiltinFnIdErrName, @@ -1477,6 +1478,7 @@ bool type_id_eql(TypeId a, TypeId b); enum ZigLLVMFnId { ZigLLVMFnIdCtz, ZigLLVMFnIdClz, + ZigLLVMFnIdPopCount, ZigLLVMFnIdOverflowArithmetic, ZigLLVMFnIdFloor, ZigLLVMFnIdCeil, @@ -1499,6 +1501,9 @@ struct ZigLLVMFnKey { struct { uint32_t bit_count; } clz; + struct { + uint32_t bit_count; + } pop_count; struct { uint32_t bit_count; } floating; @@ -2050,6 +2055,7 @@ enum IrInstructionId { IrInstructionIdUnionTag, IrInstructionIdClz, IrInstructionIdCtz, + IrInstructionIdPopCount, IrInstructionIdImport, IrInstructionIdCImport, IrInstructionIdCInclude, @@ -2545,6 +2551,12 @@ struct IrInstructionClz { IrInstruction *value; }; +struct IrInstructionPopCount { + IrInstruction base; + + IrInstruction *value; +}; + struct IrInstructionUnionTag { IrInstruction base; diff --git a/src/analyze.cpp b/src/analyze.cpp index 643a85634e81bd195b982b2514d980acf65861d4..9b60f7374a617ec89507a6359443c912f7f253f2 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -5976,6 +5976,8 @@ uint32_t zig_llvm_fn_key_hash(ZigLLVMFnKey x) { return (uint32_t)(x.data.ctz.bit_count) * (uint32_t)810453934; case ZigLLVMFnIdClz: return (uint32_t)(x.data.clz.bit_count) * (uint32_t)2428952817; + case ZigLLVMFnIdPopCount: + return (uint32_t)(x.data.clz.bit_count) * (uint32_t)101195049; case ZigLLVMFnIdFloor: return (uint32_t)(x.data.floating.bit_count) * (uint32_t)1899859168; case ZigLLVMFnIdCeil: @@ -5998,6 +6000,8 @@ bool zig_llvm_fn_key_eql(ZigLLVMFnKey a, ZigLLVMFnKey b) { return a.data.ctz.bit_count == b.data.ctz.bit_count; case ZigLLVMFnIdClz: return a.data.clz.bit_count == b.data.clz.bit_count; + case ZigLLVMFnIdPopCount: + return a.data.pop_count.bit_count == b.data.pop_count.bit_count; case ZigLLVMFnIdFloor: case ZigLLVMFnIdCeil: case ZigLLVMFnIdSqrt: diff --git a/src/bigint.cpp b/src/bigint.cpp index bb227a7c3d5bb266257cae23c781445419b7f07f..bf18b9a1bfde5123468773b68a18e9d81c194029 100644 --- a/src/bigint.cpp +++ b/src/bigint.cpp @@ -1593,6 +1593,37 @@ void bigint_append_buf(Buf *buf, const BigInt *op, uint64_t base) { } } +size_t bigint_popcount_unsigned(const BigInt *bi) { + assert(!bi->is_negative); + if (bi->digit_count == 0) + return 0; + + size_t count = 0; + size_t bit_count = bi->digit_count * 64; + for (size_t i = 0; i < bit_count; i += 1) { + if (bit_at_index(bi, i)) + count += 1; + } + return count; +} + +size_t bigint_popcount_signed(const BigInt *bi, size_t bit_count) { + if (bit_count == 0) + return 0; + if (bi->digit_count == 0) + return 0; + + BigInt twos_comp = {0}; + to_twos_complement(&twos_comp, bi, bit_count); + + size_t count = 0; + for (size_t i = 0; i < bit_count; i += 1) { + if (bit_at_index(&twos_comp, i)) + count += 1; + } + return count; +} + size_t bigint_ctz(const BigInt *bi, size_t bit_count) { if (bit_count == 0) return 0; diff --git a/src/bigint.hpp b/src/bigint.hpp index 9f044c87229df8fd2e8e597fa720668cfff3c021..48b222a22721f19e44d204af6a1ccc24dff74269 100644 --- a/src/bigint.hpp +++ b/src/bigint.hpp @@ -81,6 +81,8 @@ void bigint_append_buf(Buf *buf, const BigInt *op, uint64_t base); size_t bigint_ctz(const BigInt *bi, size_t bit_count); size_t bigint_clz(const BigInt *bi, size_t bit_count); +size_t bigint_popcount_signed(const BigInt *bi, size_t bit_count); +size_t bigint_popcount_unsigned(const BigInt *bi); size_t bigint_bits_needed(const BigInt *op); diff --git a/src/codegen.cpp b/src/codegen.cpp index 26ee1069593699687bdc68b0e9f6fbca8f0fe530..54e2da7d617e3109c190d575ea0d8a7f635a6de5 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -3426,14 +3426,22 @@ static LLVMValueRef ir_render_unwrap_maybe(CodeGen *g, IrExecutable *executable, static LLVMValueRef get_int_builtin_fn(CodeGen *g, TypeTableEntry *int_type, BuiltinFnId fn_id) { ZigLLVMFnKey key = {}; const char *fn_name; + uint32_t n_args; if (fn_id == BuiltinFnIdCtz) { fn_name = "cttz"; + n_args = 2; key.id = ZigLLVMFnIdCtz; key.data.ctz.bit_count = (uint32_t)int_type->data.integral.bit_count; } else if (fn_id == BuiltinFnIdClz) { fn_name = "ctlz"; + n_args = 2; key.id = ZigLLVMFnIdClz; key.data.clz.bit_count = (uint32_t)int_type->data.integral.bit_count; + } else if (fn_id == BuiltinFnIdPopCount) { + fn_name = "ctpop"; + n_args = 1; + key.id = ZigLLVMFnIdPopCount; + key.data.pop_count.bit_count = (uint32_t)int_type->data.integral.bit_count; } else { zig_unreachable(); } @@ -3448,7 +3456,7 @@ static LLVMValueRef get_int_builtin_fn(CodeGen *g, TypeTableEntry *int_type, Bui int_type->type_ref, LLVMInt1Type(), }; - LLVMTypeRef fn_type = LLVMFunctionType(int_type->type_ref, param_types, 2, false); + LLVMTypeRef fn_type = LLVMFunctionType(int_type->type_ref, param_types, n_args, false); LLVMValueRef fn_val = LLVMAddFunction(g->module, llvm_name, fn_type); assert(LLVMGetIntrinsicID(fn_val)); @@ -3481,6 +3489,14 @@ static LLVMValueRef ir_render_ctz(CodeGen *g, IrExecutable *executable, IrInstru return gen_widen_or_shorten(g, false, int_type, instruction->base.value.type, wrong_size_int); } +static LLVMValueRef ir_render_pop_count(CodeGen *g, IrExecutable *executable, IrInstructionPopCount *instruction) { + TypeTableEntry *int_type = instruction->value->value.type; + LLVMValueRef fn_val = get_int_builtin_fn(g, int_type, BuiltinFnIdPopCount); + LLVMValueRef operand = ir_llvm_value(g, instruction->value); + LLVMValueRef wrong_size_int = LLVMBuildCall(g->builder, fn_val, &operand, 1, ""); + return gen_widen_or_shorten(g, false, int_type, instruction->base.value.type, wrong_size_int); +} + static LLVMValueRef ir_render_switch_br(CodeGen *g, IrExecutable *executable, IrInstructionSwitchBr *instruction) { LLVMValueRef target_value = ir_llvm_value(g, instruction->target_value); LLVMBasicBlockRef else_block = instruction->else_block->llvm_block; @@ -4831,6 +4847,8 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable, return ir_render_clz(g, executable, (IrInstructionClz *)instruction); case IrInstructionIdCtz: return ir_render_ctz(g, executable, (IrInstructionCtz *)instruction); + case IrInstructionIdPopCount: + return ir_render_pop_count(g, executable, (IrInstructionPopCount *)instruction); case IrInstructionIdSwitchBr: return ir_render_switch_br(g, executable, (IrInstructionSwitchBr *)instruction); case IrInstructionIdPhi: @@ -6342,6 +6360,7 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn(g, BuiltinFnIdCUndef, "cUndef", 1); create_builtin_fn(g, BuiltinFnIdCtz, "ctz", 1); create_builtin_fn(g, BuiltinFnIdClz, "clz", 1); + create_builtin_fn(g, BuiltinFnIdPopCount, "popCount", 1); create_builtin_fn(g, BuiltinFnIdImport, "import", 1); create_builtin_fn(g, BuiltinFnIdCImport, "cImport", 1); create_builtin_fn(g, BuiltinFnIdErrName, "errorName", 1); diff --git a/src/ir.cpp b/src/ir.cpp index 3ad7c77645bb590d1d39671a3d6c2aec4c4b3dc9..98b1bd85ad1aef58e88a46e87b94d63d9969412d 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -427,6 +427,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionCtz *) { return IrInstructionIdCtz; } +static constexpr IrInstructionId ir_instruction_id(IrInstructionPopCount *) { + return IrInstructionIdPopCount; +} + static constexpr IrInstructionId ir_instruction_id(IrInstructionUnionTag *) { return IrInstructionIdUnionTag; } @@ -1725,6 +1729,15 @@ static IrInstruction *ir_build_ctz_from(IrBuilder *irb, IrInstruction *old_instr return new_instruction; } +static IrInstruction *ir_build_pop_count(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *value) { + IrInstructionPopCount *instruction = ir_build_instruction(irb, scope, source_node); + instruction->value = value; + + ir_ref_instruction(value, irb->current_basic_block); + + return &instruction->base; +} + static IrInstruction *ir_build_switch_br(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *target_value, IrBasicBlock *else_block, size_t case_count, IrInstructionSwitchBrCase *cases, IrInstruction *is_comptime, IrInstruction *switch_prongs_void) @@ -3841,6 +3854,16 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo IrInstruction *ctz = ir_build_ctz(irb, scope, node, arg0_value); return ir_lval_wrap(irb, scope, ctz, lval); } + case BuiltinFnIdPopCount: + { + AstNode *arg0_node = node->data.fn_call_expr.params.at(0); + IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, scope); + if (arg0_value == irb->codegen->invalid_instruction) + return arg0_value; + + IrInstruction *instr = ir_build_pop_count(irb, scope, node, arg0_value); + return ir_lval_wrap(irb, scope, instr, lval); + } case BuiltinFnIdClz: { AstNode *arg0_node = node->data.fn_call_expr.params.at(0); @@ -15275,6 +15298,48 @@ static TypeTableEntry *ir_analyze_instruction_clz(IrAnalyze *ira, IrInstructionC } } +static TypeTableEntry *ir_analyze_instruction_pop_count(IrAnalyze *ira, IrInstructionPopCount *instruction) { + IrInstruction *value = instruction->value->other; + if (type_is_invalid(value->value.type)) + return ira->codegen->builtin_types.entry_invalid; + + if (value->value.type->id != TypeTableEntryIdInt && value->value.type->id != TypeTableEntryIdComptimeInt) { + ir_add_error(ira, value, + buf_sprintf("expected integer type, found '%s'", buf_ptr(&value->value.type->name))); + return ira->codegen->builtin_types.entry_invalid; + } + + if (instr_is_comptime(value)) { + ConstExprValue *val = ir_resolve_const(ira, value, UndefBad); + if (!val) + return ira->codegen->builtin_types.entry_invalid; + if (bigint_cmp_zero(&val->data.x_bigint) != CmpLT) { + size_t result = bigint_popcount_unsigned(&val->data.x_bigint); + ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base); + bigint_init_unsigned(&out_val->data.x_bigint, result); + return ira->codegen->builtin_types.entry_num_lit_int; + } + if (value->value.type->id == TypeTableEntryIdComptimeInt) { + Buf *val_buf = buf_alloc(); + bigint_append_buf(val_buf, &val->data.x_bigint, 10); + ir_add_error(ira, &instruction->base, + buf_sprintf("@popCount on negative %s value %s", + buf_ptr(&value->value.type->name), buf_ptr(val_buf))); + return ira->codegen->builtin_types.entry_invalid; + } + size_t result = bigint_popcount_signed(&val->data.x_bigint, value->value.type->data.integral.bit_count); + ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base); + bigint_init_unsigned(&out_val->data.x_bigint, result); + return ira->codegen->builtin_types.entry_num_lit_int; + } + + IrInstruction *result = ir_build_pop_count(&ira->new_irb, instruction->base.scope, + instruction->base.source_node, value); + result->value.type = get_smallest_unsigned_int_type(ira->codegen, value->value.type->data.integral.bit_count); + ir_link_new_instruction(result, &instruction->base); + return result->value.type; +} + static IrInstruction *ir_analyze_union_tag(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *value) { if (type_is_invalid(value->value.type)) return ira->codegen->invalid_instruction; @@ -20534,6 +20599,8 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi return ir_analyze_instruction_clz(ira, (IrInstructionClz *)instruction); case IrInstructionIdCtz: return ir_analyze_instruction_ctz(ira, (IrInstructionCtz *)instruction); + case IrInstructionIdPopCount: + return ir_analyze_instruction_pop_count(ira, (IrInstructionPopCount *)instruction); case IrInstructionIdSwitchBr: return ir_analyze_instruction_switch_br(ira, (IrInstructionSwitchBr *)instruction); case IrInstructionIdSwitchTarget: @@ -20892,6 +20959,7 @@ bool ir_has_side_effects(IrInstruction *instruction) { case IrInstructionIdUnwrapOptional: case IrInstructionIdClz: case IrInstructionIdCtz: + case IrInstructionIdPopCount: case IrInstructionIdSwitchVar: case IrInstructionIdSwitchTarget: case IrInstructionIdUnionTag: diff --git a/src/ir_print.cpp b/src/ir_print.cpp index 5e5a71382cdd2e37f148549e76fe52219c92ff91..780cf9e756a0f642bdee7c0a3d4e2af047020048 100644 --- a/src/ir_print.cpp +++ b/src/ir_print.cpp @@ -501,6 +501,12 @@ static void ir_print_ctz(IrPrint *irp, IrInstructionCtz *instruction) { fprintf(irp->f, ")"); } +static void ir_print_pop_count(IrPrint *irp, IrInstructionPopCount *instruction) { + fprintf(irp->f, "@popCount("); + ir_print_other_instruction(irp, instruction->value); + fprintf(irp->f, ")"); +} + static void ir_print_switch_br(IrPrint *irp, IrInstructionSwitchBr *instruction) { fprintf(irp->f, "switch ("); ir_print_other_instruction(irp, instruction->target_value); @@ -1425,6 +1431,9 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { case IrInstructionIdCtz: ir_print_ctz(irp, (IrInstructionCtz *)instruction); break; + case IrInstructionIdPopCount: + ir_print_pop_count(irp, (IrInstructionPopCount *)instruction); + break; case IrInstructionIdClz: ir_print_clz(irp, (IrInstructionClz *)instruction); break; diff --git a/test/behavior.zig b/test/behavior.zig index d47eb8fd6cb9883fc7baccf4c1eddf15727922c2..450dded56c9c8b7ee55f7bf3dbd1dcc714cc85e6 100644 --- a/test/behavior.zig +++ b/test/behavior.zig @@ -8,17 +8,17 @@ comptime { _ = @import("cases/atomics.zig"); _ = @import("cases/bitcast.zig"); _ = @import("cases/bool.zig"); + _ = @import("cases/bugs/1111.zig"); _ = @import("cases/bugs/394.zig"); _ = @import("cases/bugs/655.zig"); _ = @import("cases/bugs/656.zig"); _ = @import("cases/bugs/828.zig"); _ = @import("cases/bugs/920.zig"); - _ = @import("cases/bugs/1111.zig"); _ = @import("cases/byval_arg_var.zig"); _ = @import("cases/cast.zig"); _ = @import("cases/const_slice_child.zig"); - _ = @import("cases/coroutines.zig"); _ = @import("cases/coroutine_await_struct.zig"); + _ = @import("cases/coroutines.zig"); _ = @import("cases/defer.zig"); _ = @import("cases/enum.zig"); _ = @import("cases/enum_with_members.zig"); @@ -36,11 +36,12 @@ comptime { _ = @import("cases/math.zig"); _ = @import("cases/merge_error_sets.zig"); _ = @import("cases/misc.zig"); - _ = @import("cases/optional.zig"); _ = @import("cases/namespace_depends_on_compile_var/index.zig"); _ = @import("cases/new_stack_call.zig"); _ = @import("cases/null.zig"); + _ = @import("cases/optional.zig"); _ = @import("cases/pointers.zig"); + _ = @import("cases/popcount.zig"); _ = @import("cases/pub_enum/index.zig"); _ = @import("cases/ref_var_in_if_after_if_2nd_switch_prong.zig"); _ = @import("cases/reflection.zig"); diff --git a/test/cases/popcount.zig b/test/cases/popcount.zig new file mode 100644 index 0000000000000000000000000000000000000000..7dc7f28c0e9cf48733267aa5b6cf961c33880e73 --- /dev/null +++ b/test/cases/popcount.zig @@ -0,0 +1,24 @@ +const assert = @import("std").debug.assert; + +test "@popCount" { + comptime testPopCount(); + testPopCount(); +} + +fn testPopCount() void { + { + var x: u32 = 0xaa; + assert(@popCount(x) == 4); + } + { + var x: u32 = 0xaaaaaaaa; + assert(@popCount(x) == 16); + } + { + var x: i16 = -1; + assert(@popCount(x) == 16); + } + comptime { + assert(@popCount(0b11111111000110001100010000100001000011000011100101010001) == 24); + } +} diff --git a/test/compile_errors.zig b/test/compile_errors.zig index d508c7c36cdc4cb2b390fa588e4ec304980b1a8f..9071f0ad7e6be2aa814881aaa4e2ffffbccba21a 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -1,6 +1,24 @@ const tests = @import("tests.zig"); pub fn addCases(cases: *tests.CompileErrorContext) void { + cases.add( + "@popCount - non-integer", + \\export fn entry(x: f32) u32 { + \\ return @popCount(x); + \\} + , + ".tmp_source.zig:2:22: error: expected integer type, found 'f32'", + ); + + cases.add( + "@popCount - negative comptime_int", + \\comptime { + \\ _ = @popCount(-1); + \\} + , + ".tmp_source.zig:2:9: error: @popCount on negative comptime_int value -1", + ); + cases.addCase(x: { const tc = cases.create( "wrong same named struct", -- 2.54.0