Commit 67d565136a

Andrew Kelley <superjoe30@gmail.com>
2016-11-21 19:27:44
IR: implement ctz and clz builtins
1 parent 71d95c6
doc/langref.md
@@ -538,12 +538,12 @@ expression is not known at compile time.
 
 The result of the function is the result of the expression.
 
-### @ctz(inline T: type, x: T) -> T
+### @ctz(x: T) -> T
 
 This function counts the number of trailing zeroes in x which is an integer
 type T.
 
-### @clz(inline T: type, x: T) -> T
+### @clz(x: T) -> T
 
 This function counts the number of leading zeroes in x which is an integer
 type T.
src/all_types.hpp
@@ -1455,6 +1455,8 @@ enum IrInstructionId {
     IrInstructionIdSizeOf,
     IrInstructionIdTestNull,
     IrInstructionIdUnwrapMaybe,
+    IrInstructionIdClz,
+    IrInstructionIdCtz,
 };
 
 struct IrInstruction {
@@ -1766,6 +1768,18 @@ struct IrInstructionUnwrapMaybe {
     bool safety_check_on;
 };
 
+struct IrInstructionCtz {
+    IrInstruction base;
+
+    IrInstruction *value;
+};
+
+struct IrInstructionClz {
+    IrInstruction base;
+
+    IrInstruction *value;
+};
+
 enum LValPurpose {
     LValPurposeNone,
     LValPurposeAssign,
src/bignum.cpp
@@ -360,3 +360,37 @@ bool bignum_multiply_by_scalar(BigNum *bignum, uint64_t scalar) {
     assert(!bignum->is_negative);
     return __builtin_umulll_overflow(bignum->data.x_uint, scalar, &bignum->data.x_uint);
 }
+
+uint32_t bignum_ctz(BigNum *bignum, uint32_t bit_count) {
+    assert(bignum->kind == BigNumKindInt);
+
+    uint64_t x = bignum_to_twos_complement(bignum);
+    uint32_t result = 0;
+    for (uint32_t i = 0; i < bit_count; i += 1) {
+        if ((x & 0x1) != 0)
+            break;
+
+        result += 1;
+        x = x >> 1;
+    }
+    return result;
+}
+
+uint32_t bignum_clz(BigNum *bignum, uint32_t bit_count) {
+    assert(bignum->kind == BigNumKindInt);
+
+    if (bit_count == 0)
+        return 0;
+
+    uint64_t x = bignum_to_twos_complement(bignum);
+    uint64_t mask = ((uint64_t)1) << ((uint64_t)bit_count - 1);
+    uint32_t result = 0;
+    for (uint32_t i = 0; i < bit_count; i += 1) {
+        if ((x & mask) != 0)
+            break;
+
+        result += 1;
+        x = x << 1;
+    }
+    return result;
+}
src/bignum.hpp
@@ -66,4 +66,7 @@ bool bignum_multiply_by_scalar(BigNum *bignum, uint64_t scalar);
 struct Buf;
 Buf *bignum_to_buf(BigNum *bn);
 
+uint32_t bignum_ctz(BigNum *bignum, uint32_t bit_count);
+uint32_t bignum_clz(BigNum *bignum, uint32_t bit_count);
+
 #endif
src/codegen.cpp
@@ -1478,6 +1478,46 @@ static LLVMValueRef ir_render_unwrap_maybe(CodeGen *g, IrExecutable *executable,
     }
 }
 
+static LLVMValueRef get_int_builtin_fn(CodeGen *g, TypeTableEntry *int_type, BuiltinFnId fn_id) {
+    // [0-ctz,1-clz][0-8,1-16,2-32,3-64]
+    size_t index0 = (fn_id == BuiltinFnIdCtz) ? 0 : 1;
+    size_t index1 = bits_index(int_type->data.integral.bit_count);
+    LLVMValueRef *fn = &g->int_builtin_fns[index0][index1];
+    if (!*fn) {
+        const char *fn_name = (fn_id == BuiltinFnIdCtz) ? "cttz" : "ctlz";
+        Buf *llvm_name = buf_sprintf("llvm.%s.i%zu", fn_name, int_type->data.integral.bit_count);
+        LLVMTypeRef param_types[] = {
+            int_type->type_ref,
+            LLVMInt1Type(),
+        };
+        LLVMTypeRef fn_type = LLVMFunctionType(int_type->type_ref, param_types, 2, false);
+        *fn = LLVMAddFunction(g->module, buf_ptr(llvm_name), fn_type);
+    }
+    return *fn;
+}
+
+static LLVMValueRef ir_render_clz(CodeGen *g, IrExecutable *executable, IrInstructionClz *instruction) {
+    TypeTableEntry *int_type = instruction->base.type_entry;
+    LLVMValueRef fn_val = get_int_builtin_fn(g, int_type, BuiltinFnIdClz);
+    LLVMValueRef operand = ir_llvm_value(g, instruction->value);
+    LLVMValueRef params[] {
+        operand,
+        LLVMConstNull(LLVMInt1Type()),
+    };
+    return LLVMBuildCall(g->builder, fn_val, params, 2, "");
+}
+
+static LLVMValueRef ir_render_ctz(CodeGen *g, IrExecutable *executable, IrInstructionCtz *instruction) {
+    TypeTableEntry *int_type = instruction->base.type_entry;
+    LLVMValueRef fn_val = get_int_builtin_fn(g, int_type, BuiltinFnIdCtz);
+    LLVMValueRef operand = ir_llvm_value(g, instruction->value);
+    LLVMValueRef params[] {
+        operand,
+        LLVMConstNull(LLVMInt1Type()),
+    };
+    return LLVMBuildCall(g->builder, fn_val, params, 2, "");
+}
+
 static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable, IrInstruction *instruction) {
     set_debug_source_node(g, instruction->source_node);
 
@@ -1529,6 +1569,10 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable,
             return ir_render_test_null(g, executable, (IrInstructionTestNull *)instruction);
         case IrInstructionIdUnwrapMaybe:
             return ir_render_unwrap_maybe(g, executable, (IrInstructionUnwrapMaybe *)instruction);
+        case IrInstructionIdClz:
+            return ir_render_clz(g, executable, (IrInstructionClz *)instruction);
+        case IrInstructionIdCtz:
+            return ir_render_ctz(g, executable, (IrInstructionCtz *)instruction);
         case IrInstructionIdSwitchBr:
         case IrInstructionIdPhi:
         case IrInstructionIdContainerInitList:
@@ -2774,8 +2818,8 @@ static void define_builtin_fns(CodeGen *g) {
     create_builtin_fn_with_arg_count(g, BuiltinFnIdCUndef, "cUndef", 1);
     create_builtin_fn_with_arg_count(g, BuiltinFnIdCompileVar, "compileVar", 1);
     create_builtin_fn_with_arg_count(g, BuiltinFnIdConstEval, "constEval", 1);
-    create_builtin_fn_with_arg_count(g, BuiltinFnIdCtz, "ctz", 2);
-    create_builtin_fn_with_arg_count(g, BuiltinFnIdClz, "clz", 2);
+    create_builtin_fn_with_arg_count(g, BuiltinFnIdCtz, "ctz", 1);
+    create_builtin_fn_with_arg_count(g, BuiltinFnIdClz, "clz", 1);
     create_builtin_fn_with_arg_count(g, BuiltinFnIdImport, "import", 1);
     create_builtin_fn_with_arg_count(g, BuiltinFnIdCImport, "cImport", 1);
     create_builtin_fn_with_arg_count(g, BuiltinFnIdErrName, "errorName", 1);
src/ir.cpp
@@ -234,6 +234,14 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionUnwrapMaybe *) {
     return IrInstructionIdUnwrapMaybe;
 }
 
+static constexpr IrInstructionId ir_instruction_id(IrInstructionClz *) {
+    return IrInstructionIdClz;
+}
+
+static constexpr IrInstructionId ir_instruction_id(IrInstructionCtz *) {
+    return IrInstructionIdCtz;
+}
+
 template<typename T>
 static T *ir_create_instruction(IrExecutable *exec, AstNode *source_node) {
     T *special_instruction = allocate<T>(1);
@@ -924,6 +932,36 @@ static IrInstruction *ir_build_unwrap_maybe_from(IrBuilder *irb, IrInstruction *
     return new_instruction;
 }
 
+static IrInstruction *ir_build_clz(IrBuilder *irb, AstNode *source_node, IrInstruction *value) {
+    IrInstructionClz *instruction = ir_build_instruction<IrInstructionClz>(irb, source_node);
+    instruction->value = value;
+
+    ir_ref_instruction(value);
+
+    return &instruction->base;
+}
+
+static IrInstruction *ir_build_clz_from(IrBuilder *irb, IrInstruction *old_instruction, IrInstruction *value) {
+    IrInstruction *new_instruction = ir_build_clz(irb, old_instruction->source_node, value);
+    ir_link_new_instruction(new_instruction, old_instruction);
+    return new_instruction;
+}
+
+static IrInstruction *ir_build_ctz(IrBuilder *irb, AstNode *source_node, IrInstruction *value) {
+    IrInstructionCtz *instruction = ir_build_instruction<IrInstructionCtz>(irb, source_node);
+    instruction->value = value;
+
+    ir_ref_instruction(value);
+
+    return &instruction->base;
+}
+
+static IrInstruction *ir_build_ctz_from(IrBuilder *irb, IrInstruction *old_instruction, IrInstruction *value) {
+    IrInstruction *new_instruction = ir_build_ctz(irb, old_instruction->source_node, value);
+    ir_link_new_instruction(new_instruction, old_instruction);
+    return new_instruction;
+}
+
 static void ir_gen_defers_for_block(IrBuilder *irb, BlockContext *inner_block, BlockContext *outer_block,
         bool gen_error_defers, bool gen_maybe_defers)
 {
@@ -964,9 +1002,9 @@ static IrInstruction *ir_gen_return(IrBuilder *irb, AstNode *node) {
                 return ir_build_return(irb, node, return_value);
             }
         case ReturnKindError:
-            zig_panic("TODO %%return");
+            zig_panic("TODO gen IR for %%return");
         case ReturnKindMaybe:
-            zig_panic("TODO ?return");
+            zig_panic("TODO gen IR for ?return");
     }
     zig_unreachable();
 }
@@ -1188,7 +1226,7 @@ static IrInstruction *ir_gen_bin_op(IrBuilder *irb, AstNode *node) {
         case BinOpTypeArrayMult:
             return ir_gen_bin_op_id(irb, node, IrBinOpArrayMult);
         case BinOpTypeUnwrapMaybe:
-            zig_panic("TODO gen IR for unwrap maybe");
+            zig_panic("TODO gen IR for unwrap maybe binary operation");
     }
     zig_unreachable();
 }
@@ -1357,7 +1395,7 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, AstNode *node) {
 
     if (builtin_fn->param_count != actual_param_count) {
         add_node_error(irb->codegen, node,
-                buf_sprintf("expected %zu arguments, got %zu",
+                buf_sprintf("expected %zu arguments, found %zu",
                     builtin_fn->param_count, actual_param_count));
         return irb->codegen->invalid_instruction;
     }
@@ -1423,6 +1461,24 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, AstNode *node) {
 
                 return ir_build_size_of(irb, node, arg0_value);
             }
+        case BuiltinFnIdCtz:
+            {
+                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
+                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, node->block_context);
+                if (arg0_value == irb->codegen->invalid_instruction)
+                    return arg0_value;
+
+                return ir_build_ctz(irb, node, arg0_value);
+            }
+        case BuiltinFnIdClz:
+            {
+                AstNode *arg0_node = node->data.fn_call_expr.params.at(0);
+                IrInstruction *arg0_value = ir_gen_node(irb, arg0_node, node->block_context);
+                if (arg0_value == irb->codegen->invalid_instruction)
+                    return arg0_value;
+
+                return ir_build_clz(irb, node, arg0_value);
+            }
         case BuiltinFnIdMemcpy:
         case BuiltinFnIdMemset:
         case BuiltinFnIdAlignof:
@@ -1438,8 +1494,6 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, AstNode *node) {
         case BuiltinFnIdCUndef:
         case BuiltinFnIdCompileErr:
         case BuiltinFnIdConstEval:
-        case BuiltinFnIdCtz:
-        case BuiltinFnIdClz:
         case BuiltinFnIdImport:
         case BuiltinFnIdCImport:
         case BuiltinFnIdErrName:
@@ -1547,13 +1601,17 @@ static IrInstruction *ir_gen_prefix_op_id(IrBuilder *irb, AstNode *node, IrUnOp
     return ir_gen_prefix_op_id_lval(irb, node, op_id, LValPurposeNone);
 }
 
-static IrInstruction *ir_gen_prefix_op_unwrap_maybe(IrBuilder *irb, AstNode *node) {
+static IrInstruction *ir_gen_prefix_op_unwrap_maybe(IrBuilder *irb, AstNode *node, LValPurpose lval) {
     AstNode *expr = node->data.prefix_op_expr.primary_expr;
-    IrInstruction *value = ir_gen_node(irb, expr, node->block_context);
+    IrInstruction *value = ir_gen_node_extra(irb, expr, node->block_context, LValPurposeAddressOf);
     if (value == irb->codegen->invalid_instruction)
         return value;
 
-    return ir_build_unwrap_maybe(irb, node, value, true);
+    IrInstruction *unwrapped_ptr = ir_build_unwrap_maybe(irb, node, value, true);
+    if (lval == LValPurposeNone)
+        return ir_build_load_ptr(irb, node, unwrapped_ptr);
+    else
+        return unwrapped_ptr;
 }
 
 static IrInstruction *ir_gen_prefix_op_expr(IrBuilder *irb, AstNode *node, LValPurpose lval) {
@@ -1585,7 +1643,7 @@ static IrInstruction *ir_gen_prefix_op_expr(IrBuilder *irb, AstNode *node, LValP
         case PrefixOpUnwrapError:
             return ir_gen_prefix_op_id(irb, node, IrUnOpUnwrapError);
         case PrefixOpUnwrapMaybe:
-            return ir_gen_prefix_op_unwrap_maybe(irb, node);
+            return ir_gen_prefix_op_unwrap_maybe(irb, node, lval);
     }
     zig_unreachable();
 }
@@ -2349,7 +2407,7 @@ static IrInstruction *ir_resolve_cast(IrAnalyze *ira, IrInstruction *source_inst
         return result;
     } else {
         IrInstruction *result = ir_build_cast(&ira->new_irb, source_instr->source_node,
-                dest_type->other, value->other, cast_op);
+                dest_type, value, cast_op);
         result->type_entry = wanted_type;
         if (need_alloca && source_instr->source_node->block_context->fn_entry) {
             IrInstructionCast *cast_instruction = (IrInstructionCast *)result;
@@ -2776,7 +2834,7 @@ static IrInstruction *ir_get_casted_value(IrAnalyze *ira, IrInstruction *value,
     switch (result) {
         case ImplicitCastMatchResultNo:
             add_node_error(ira->codegen, first_executing_node(value->source_node),
-                buf_sprintf("expected type '%s', got '%s'",
+                buf_sprintf("expected type '%s', found '%s'",
                     buf_ptr(&expected_type->name),
                     buf_ptr(&value->type_entry->name)));
             return ira->codegen->invalid_instruction;
@@ -3346,7 +3404,7 @@ static TypeTableEntry *ir_analyze_instruction_call(IrAnalyze *ira, IrInstruction
                 return ira->codegen->builtin_types.entry_invalid;
             }
 
-            IrInstruction *arg = call_instruction->args[0];
+            IrInstruction *arg = call_instruction->args[0]->other;
             IrInstruction *cast_instruction = ir_analyze_cast(ira, &call_instruction->base, fn_ref, arg);
             if (cast_instruction == ira->codegen->invalid_instruction)
                 return ira->codegen->builtin_types.entry_invalid;
@@ -3701,7 +3759,7 @@ static TypeTableEntry *ir_analyze_instruction_un_op(IrAnalyze *ira, IrInstructio
             //        return type_entry->data.error.child_type;
             //    } else {
             //        add_node_error(g, *expr_node,
-            //            buf_sprintf("expected error type, got '%s'", buf_ptr(&type_entry->name)));
+            //            buf_sprintf("expected error type, found '%s'", buf_ptr(&type_entry->name)));
             //        return g->builtin_types.entry_invalid;
             //    }
             //}
@@ -4175,6 +4233,7 @@ static TypeTableEntry *ir_analyze_instruction_store_ptr(IrAnalyze *ira, IrInstru
     if (ptr->static_value.special != ConstValSpecialRuntime) {
         // This memory location is transforming from known at compile time to known at runtime.
         // We must emit our own var ptr instruction.
+        // TODO can we delete this code now that we have inline var?
         ptr->static_value.special = ConstValSpecialRuntime;
         IrInstruction *new_ptr_inst;
         if (ptr->id == IrInstructionIdVarPtr) {
@@ -4347,12 +4406,12 @@ static TypeTableEntry *ir_analyze_instruction_set_debug_safety(IrAnalyze *ira,
             target_context = type_arg->data.unionation.block_context;
         } else {
             add_node_error(ira->codegen, target_instruction->source_node,
-                buf_sprintf("expected scope reference, got type '%s'", buf_ptr(&type_arg->name)));
+                buf_sprintf("expected scope reference, found type '%s'", buf_ptr(&type_arg->name)));
             return ira->codegen->builtin_types.entry_invalid;
         }
     } else {
         add_node_error(ira->codegen, target_instruction->source_node,
-            buf_sprintf("expected scope reference, got type '%s'", buf_ptr(&target_type->name)));
+            buf_sprintf("expected scope reference, found type '%s'", buf_ptr(&target_type->name)));
         return ira->codegen->builtin_types.entry_invalid;
     }
 
@@ -4682,6 +4741,54 @@ static TypeTableEntry *ir_analyze_instruction_unwrap_maybe(IrAnalyze *ira,
     return result_type;
 }
 
+static TypeTableEntry *ir_analyze_instruction_ctz(IrAnalyze *ira, IrInstructionCtz *ctz_instruction) {
+    IrInstruction *value = ctz_instruction->value->other;
+    if (value->type_entry->id == TypeTableEntryIdInvalid) {
+        return ira->codegen->builtin_types.entry_invalid;
+    } else if (value->type_entry->id == TypeTableEntryIdInt) {
+        if (value->static_value.special != ConstValSpecialRuntime) {
+            uint32_t result = bignum_ctz(&value->static_value.data.x_bignum,
+                    value->type_entry->data.integral.bit_count);
+            bool depends_on_compile_var = value->static_value.depends_on_compile_var;
+            ConstExprValue *out_val = ir_build_const_from(ira, &ctz_instruction->base,
+                    depends_on_compile_var);
+            bignum_init_unsigned(&out_val->data.x_bignum, result);
+            return value->type_entry;
+        }
+
+        ir_build_ctz_from(&ira->new_irb, &ctz_instruction->base, value);
+        return value->type_entry;
+    } else {
+        add_node_error(ira->codegen, ctz_instruction->base.source_node,
+            buf_sprintf("expected integer type, found '%s'", buf_ptr(&value->type_entry->name)));
+        return ira->codegen->builtin_types.entry_invalid;
+    }
+}
+
+static TypeTableEntry *ir_analyze_instruction_clz(IrAnalyze *ira, IrInstructionClz *clz_instruction) {
+    IrInstruction *value = clz_instruction->value->other;
+    if (value->type_entry->id == TypeTableEntryIdInvalid) {
+        return ira->codegen->builtin_types.entry_invalid;
+    } else if (value->type_entry->id == TypeTableEntryIdInt) {
+        if (value->static_value.special != ConstValSpecialRuntime) {
+            uint32_t result = bignum_clz(&value->static_value.data.x_bignum,
+                    value->type_entry->data.integral.bit_count);
+            bool depends_on_compile_var = value->static_value.depends_on_compile_var;
+            ConstExprValue *out_val = ir_build_const_from(ira, &clz_instruction->base,
+                    depends_on_compile_var);
+            bignum_init_unsigned(&out_val->data.x_bignum, result);
+            return value->type_entry;
+        }
+
+        ir_build_clz_from(&ira->new_irb, &clz_instruction->base, value);
+        return value->type_entry;
+    } else {
+        add_node_error(ira->codegen, clz_instruction->base.source_node,
+            buf_sprintf("expected integer type, found '%s'", buf_ptr(&value->type_entry->name)));
+        return ira->codegen->builtin_types.entry_invalid;
+    }
+}
+
 static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstruction *instruction) {
     switch (instruction->id) {
         case IrInstructionIdInvalid:
@@ -4742,6 +4849,10 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi
             return ir_analyze_instruction_test_null(ira, (IrInstructionTestNull *)instruction);
         case IrInstructionIdUnwrapMaybe:
             return ir_analyze_instruction_unwrap_maybe(ira, (IrInstructionUnwrapMaybe *)instruction);
+        case IrInstructionIdClz:
+            return ir_analyze_instruction_clz(ira, (IrInstructionClz *)instruction);
+        case IrInstructionIdCtz:
+            return ir_analyze_instruction_ctz(ira, (IrInstructionCtz *)instruction);
         case IrInstructionIdSwitchBr:
         case IrInstructionIdCast:
         case IrInstructionIdContainerInitList:
@@ -4854,6 +4965,8 @@ bool ir_has_side_effects(IrInstruction *instruction) {
         case IrInstructionIdSizeOf:
         case IrInstructionIdTestNull:
         case IrInstructionIdUnwrapMaybe:
+        case IrInstructionIdClz:
+        case IrInstructionIdCtz:
             return false;
         case IrInstructionIdAsm:
             {
@@ -5117,7 +5230,7 @@ IrInstruction *ir_exec_const_result(IrExecutable *exec) {
 //        return g->builtin_types.entry_invalid;
 //    } else if (ptr_type->id != TypeTableEntryIdPointer) {
 //        add_node_error(g, *ptr_arg,
-//            buf_sprintf("expected pointer argument, got '%s'", buf_ptr(&ptr_type->name)));
+//            buf_sprintf("expected pointer argument, found '%s'", buf_ptr(&ptr_type->name)));
 //        return g->builtin_types.entry_invalid;
 //    }
 //
@@ -5223,7 +5336,7 @@ IrInstruction *ir_exec_const_result(IrExecutable *exec) {
 //        zig_panic("TODO");
 //    } else {
 //        add_node_error(g, node,
-//                buf_sprintf("expected integer type, got '%s'", buf_ptr(&result_type->name)));
+//                buf_sprintf("expected integer type, found '%s'", buf_ptr(&result_type->name)));
 //        return g->builtin_types.entry_invalid;
 //    }
 //}
@@ -5243,16 +5356,16 @@ IrInstruction *ir_exec_const_result(IrExecutable *exec) {
 //        return g->builtin_types.entry_invalid;
 //    } else if (dest_type->id != TypeTableEntryIdInt) {
 //        add_node_error(g, *op1,
-//                buf_sprintf("expected integer type, got '%s'", buf_ptr(&dest_type->name)));
+//                buf_sprintf("expected integer type, found '%s'", buf_ptr(&dest_type->name)));
 //        return g->builtin_types.entry_invalid;
 //    } else if (src_type->id != TypeTableEntryIdInt) {
 //        add_node_error(g, *op2,
-//                buf_sprintf("expected integer type, got '%s'", buf_ptr(&src_type->name)));
+//                buf_sprintf("expected integer type, found '%s'", buf_ptr(&src_type->name)));
 //        return g->builtin_types.entry_invalid;
 //    } else if (src_type->data.integral.is_signed != dest_type->data.integral.is_signed) {
 //        const char *sign_str = dest_type->data.integral.is_signed ? "signed" : "unsigned";
 //        add_node_error(g, *op2,
-//                buf_sprintf("expected %s integer type, got '%s'", sign_str, buf_ptr(&src_type->name)));
+//                buf_sprintf("expected %s integer type, found '%s'", sign_str, buf_ptr(&src_type->name)));
 //        return g->builtin_types.entry_invalid;
 //    } else if (src_type->data.integral.bit_count <= dest_type->data.integral.bit_count) {
 //        add_node_error(g, *op2,
@@ -5459,7 +5572,7 @@ IrInstruction *ir_exec_const_result(IrExecutable *exec) {
 //                            result_node);
 //                } else {
 //                    add_node_error(g, type_node,
-//                        buf_sprintf("expected integer type, got '%s'", buf_ptr(&int_type->name)));
+//                        buf_sprintf("expected integer type, found '%s'", buf_ptr(&int_type->name)));
 //                }
 //
 //                // TODO constant expression evaluation
@@ -5479,14 +5592,14 @@ IrInstruction *ir_exec_const_result(IrExecutable *exec) {
 //                    dest_type->id != TypeTableEntryIdPointer)
 //                {
 //                    add_node_error(g, dest_node,
-//                            buf_sprintf("expected pointer argument, got '%s'", buf_ptr(&dest_type->name)));
+//                            buf_sprintf("expected pointer argument, found '%s'", buf_ptr(&dest_type->name)));
 //                }
 //
 //                if (src_type->id != TypeTableEntryIdInvalid &&
 //                    src_type->id != TypeTableEntryIdPointer)
 //                {
 //                    add_node_error(g, src_node,
-//                            buf_sprintf("expected pointer argument, got '%s'", buf_ptr(&src_type->name)));
+//                            buf_sprintf("expected pointer argument, found '%s'", buf_ptr(&src_type->name)));
 //                }
 //
 //                if (dest_type->id == TypeTableEntryIdPointer &&
@@ -5517,7 +5630,7 @@ IrInstruction *ir_exec_const_result(IrExecutable *exec) {
 //                    dest_type->id != TypeTableEntryIdPointer)
 //                {
 //                    add_node_error(g, dest_node,
-//                            buf_sprintf("expected pointer argument, got '%s'", buf_ptr(&dest_type->name)));
+//                            buf_sprintf("expected pointer argument, found '%s'", buf_ptr(&dest_type->name)));
 //                }
 //
 //                return builtin_fn->return_type;
@@ -5622,29 +5735,6 @@ IrInstruction *ir_exec_const_result(IrExecutable *exec) {
 //
 //                return resolved_type;
 //            }
-//        case BuiltinFnIdCtz:
-//        case BuiltinFnIdClz:
-//            {
-//                AstNode *type_node = node->data.fn_call_expr.params.at(0);
-//                TypeTableEntry *int_type = analyze_type_expr(g, import, context, type_node);
-//                if (int_type->id == TypeTableEntryIdInvalid) {
-//                    return int_type;
-//                } else if (int_type->id == TypeTableEntryIdInt) {
-//                    AstNode **expr_node = node->data.fn_call_expr.params.at(1)->parent_field;
-//                    TypeTableEntry *resolved_type = analyze_expression(g, import, context, int_type, *expr_node);
-//                    if (resolved_type->id == TypeTableEntryIdInvalid) {
-//                        return resolved_type;
-//                    }
-//
-//                    // TODO const expr eval
-//
-//                    return resolved_type;
-//                } else {
-//                    add_node_error(g, type_node,
-//                        buf_sprintf("expected integer type, got '%s'", buf_ptr(&int_type->name)));
-//                    return g->builtin_types.entry_invalid;
-//                }
-//            }
 //        case BuiltinFnIdImport:
 //            return analyze_import(g, import, context, node);
 //        case BuiltinFnIdCImport:
@@ -6192,7 +6282,7 @@ IrInstruction *ir_exec_const_result(IrExecutable *exec) {
 //                    return child_type;
 //                } else {
 //                    add_node_error(g, op1,
-//                        buf_sprintf("expected maybe type, got '%s'",
+//                        buf_sprintf("expected maybe type, found '%s'",
 //                            buf_ptr(&lhs_type->name)));
 //                    return g->builtin_types.entry_invalid;
 //                }
@@ -6212,7 +6302,7 @@ IrInstruction *ir_exec_const_result(IrExecutable *exec) {
 //                           op1_type->data.pointer.child_type == g->builtin_types.entry_u8) {
 //                    child_type = op1_type->data.pointer.child_type;
 //                } else {
-//                    add_node_error(g, *op1, buf_sprintf("expected array or C string literal, got '%s'",
+//                    add_node_error(g, *op1, buf_sprintf("expected array or C string literal, found '%s'",
 //                                buf_ptr(&op1_type->name)));
 //                    return g->builtin_types.entry_invalid;
 //                }
@@ -6223,7 +6313,7 @@ IrInstruction *ir_exec_const_result(IrExecutable *exec) {
 //                    return g->builtin_types.entry_invalid;
 //                } else if (op2_type->id == TypeTableEntryIdArray) {
 //                    if (op2_type->data.array.child_type != child_type) {
-//                        add_node_error(g, *op2, buf_sprintf("expected array of type '%s', got '%s'",
+//                        add_node_error(g, *op2, buf_sprintf("expected array of type '%s', found '%s'",
 //                                    buf_ptr(&child_type->name),
 //                                    buf_ptr(&op2_type->name)));
 //                        return g->builtin_types.entry_invalid;
@@ -6231,7 +6321,7 @@ IrInstruction *ir_exec_const_result(IrExecutable *exec) {
 //                } else if (op2_type->id == TypeTableEntryIdPointer &&
 //                        op2_type->data.pointer.child_type == g->builtin_types.entry_u8) {
 //                } else {
-//                    add_node_error(g, *op2, buf_sprintf("expected array or C string literal, got '%s'",
+//                    add_node_error(g, *op2, buf_sprintf("expected array or C string literal, found '%s'",
 //                                buf_ptr(&op2_type->name)));
 //                    return g->builtin_types.entry_invalid;
 //                }
@@ -6271,12 +6361,12 @@ IrInstruction *ir_exec_const_result(IrExecutable *exec) {
 //                } else if (op1_type->id == TypeTableEntryIdPointer) {
 //                    if (!op1_val->data.x_ptr.is_c_str) {
 //                        add_node_error(g, *op1,
-//                                buf_sprintf("expected array or C string literal, got '%s'",
+//                                buf_sprintf("expected array or C string literal, found '%s'",
 //                                    buf_ptr(&op1_type->name)));
 //                        return g->builtin_types.entry_invalid;
 //                    } else if (!op2_val->data.x_ptr.is_c_str) {
 //                        add_node_error(g, *op2,
-//                                buf_sprintf("expected array or C string literal, got '%s'",
+//                                buf_sprintf("expected array or C string literal, found '%s'",
 //                                    buf_ptr(&op2_type->name)));
 //                        return g->builtin_types.entry_invalid;
 //                    }
@@ -6510,12 +6600,12 @@ IrInstruction *ir_exec_const_result(IrExecutable *exec) {
 //        if (call_param_count < expect_arg_count) {
 //            ok_invocation = false;
 //            add_node_error(g, node,
-//                buf_sprintf("expected at least %zu arguments, got %zu", src_param_count, call_param_count));
+//                buf_sprintf("expected at least %zu arguments, found %zu", src_param_count, call_param_count));
 //        }
 //    } else if (expect_arg_count != call_param_count) {
 //        ok_invocation = false;
 //        add_node_error(g, node,
-//                buf_sprintf("expected %zu arguments, got %zu", expect_arg_count, call_param_count));
+//                buf_sprintf("expected %zu arguments, found %zu", expect_arg_count, call_param_count));
 //    }
 //
 //    bool all_args_const_expr = true;
@@ -6631,7 +6721,7 @@ IrInstruction *ir_exec_const_result(IrExecutable *exec) {
 //
 //    if (src_param_count != call_param_count + struct_node_1_or_0) {
 //        add_node_error(g, call_node,
-//            buf_sprintf("expected %zu arguments, got %zu", src_param_count - struct_node_1_or_0, call_param_count));
+//            buf_sprintf("expected %zu arguments, found %zu", src_param_count - struct_node_1_or_0, call_param_count));
 //        return g->builtin_types.entry_invalid;
 //    }
 //
@@ -6759,7 +6849,7 @@ IrInstruction *ir_exec_const_result(IrExecutable *exec) {
 //
 //    if (actual_param_count != expected_param_count) {
 //        add_node_error(g, first_executing_node(node),
-//                buf_sprintf("expected %zu arguments, got %zu", expected_param_count, actual_param_count));
+//                buf_sprintf("expected %zu arguments, found %zu", expected_param_count, actual_param_count));
 //        return g->builtin_types.entry_invalid;
 //    }
 //
@@ -7180,7 +7270,7 @@ IrInstruction *ir_exec_const_result(IrExecutable *exec) {
 //                    return resolved_type->data.error.child_type;
 //                } else {
 //                    add_node_error(g, node->data.return_expr.expr,
-//                        buf_sprintf("expected error type, got '%s'", buf_ptr(&resolved_type->name)));
+//                        buf_sprintf("expected error type, found '%s'", buf_ptr(&resolved_type->name)));
 //                    return g->builtin_types.entry_invalid;
 //                }
 //            }
@@ -7208,7 +7298,7 @@ IrInstruction *ir_exec_const_result(IrExecutable *exec) {
 //                    return resolved_type->data.maybe.child_type;
 //                } else {
 //                    add_node_error(g, node->data.return_expr.expr,
-//                        buf_sprintf("expected maybe type, got '%s'", buf_ptr(&resolved_type->name)));
+//                        buf_sprintf("expected maybe type, found '%s'", buf_ptr(&resolved_type->name)));
 //                    return g->builtin_types.entry_invalid;
 //                }
 //            }
@@ -7502,14 +7592,14 @@ IrInstruction *ir_exec_const_result(IrExecutable *exec) {
 //
 //    if (op1_type->id != TypeTableEntryIdArray) {
 //        add_node_error(g, *op1,
-//            buf_sprintf("expected array type, got '%s'", buf_ptr(&op1_type->name)));
+//            buf_sprintf("expected array type, found '%s'", buf_ptr(&op1_type->name)));
 //        return g->builtin_types.entry_invalid;
 //    }
 //
 //    if (op2_type->id != TypeTableEntryIdNumLitInt &&
 //        op2_type->id != TypeTableEntryIdInt)
 //    {
-//        add_node_error(g, *op2, buf_sprintf("expected integer type, got '%s'", buf_ptr(&op2_type->name)));
+//        add_node_error(g, *op2, buf_sprintf("expected integer type, found '%s'", buf_ptr(&op2_type->name)));
 //        return g->builtin_types.entry_invalid;
 //    }
 //
@@ -7576,7 +7666,7 @@ IrInstruction *ir_exec_const_result(IrExecutable *exec) {
 //        return child_type;
 //    } else {
 //        add_node_error(g, op1,
-//            buf_sprintf("expected error type, got '%s'", buf_ptr(&lhs_type->name)));
+//            buf_sprintf("expected error type, found '%s'", buf_ptr(&lhs_type->name)));
 //        return g->builtin_types.entry_invalid;
 //    }
 //}
@@ -8076,21 +8166,6 @@ static void analyze_goto_pass2(CodeGen *g, ImportTableEntry *import, AstNode *no
 //        case BuiltinFnIdCompileErr:
 //        case BuiltinFnIdIntType:
 //            zig_unreachable();
-//        case BuiltinFnIdCtz:
-//        case BuiltinFnIdClz:
-//            {
-//                size_t fn_call_param_count = node->data.fn_call_expr.params.length;
-//                assert(fn_call_param_count == 2);
-//                TypeTableEntry *int_type = get_type_for_type_node(node->data.fn_call_expr.params.at(0));
-//                assert(int_type->id == TypeTableEntryIdInt);
-//                LLVMValueRef fn_val = get_int_builtin_fn(g, int_type, builtin_fn->id);
-//                LLVMValueRef operand = gen_expr(g, node->data.fn_call_expr.params.at(1));
-//                LLVMValueRef params[] {
-//                    operand,
-//                    LLVMConstNull(LLVMInt1Type()),
-//                };
-//                return LLVMBuildCall(g->builder, fn_val, params, 2, "");
-//            }
 //        case BuiltinFnIdAddWithOverflow:
 //        case BuiltinFnIdSubWithOverflow:
 //        case BuiltinFnIdMulWithOverflow:
@@ -9539,24 +9614,6 @@ static void analyze_goto_pass2(CodeGen *g, ImportTableEntry *import, AstNode *no
 //    return gen_var_decl_raw(g, node, &node->data.variable_declaration, false, &init_val, &init_val_type, false);
 //}
 //
-//static LLVMValueRef get_int_builtin_fn(CodeGen *g, TypeTableEntry *int_type, BuiltinFnId fn_id) {
-//    // [0-ctz,1-clz][0-8,1-16,2-32,3-64]
-//    size_t index0 = (fn_id == BuiltinFnIdCtz) ? 0 : 1;
-//    size_t index1 = bits_index(int_type->data.integral.bit_count);
-//    LLVMValueRef *fn = &g->int_builtin_fns[index0][index1];
-//    if (!*fn) {
-//        const char *fn_name = (fn_id == BuiltinFnIdCtz) ? "cttz" : "ctlz";
-//        Buf *llvm_name = buf_sprintf("llvm.%s.i%zu", fn_name, int_type->data.integral.bit_count);
-//        LLVMTypeRef param_types[] = {
-//            int_type->type_ref,
-//            LLVMInt1Type(),
-//        };
-//        LLVMTypeRef fn_type = LLVMFunctionType(int_type->type_ref, param_types, 2, false);
-//        *fn = LLVMAddFunction(g->module, buf_ptr(llvm_name), fn_type);
-//    }
-//    return *fn;
-//}
-//
 //static LLVMValueRef gen_fence(CodeGen *g, AstNode *node) {
 //    assert(node->type == NodeTypeFnCallExpr);
 //
src/ir_print.cpp
@@ -511,6 +511,18 @@ static void ir_print_unwrap_maybe(IrPrint *irp, IrInstructionUnwrapMaybe *instru
     }
 }
 
+static void ir_print_clz(IrPrint *irp, IrInstructionClz *instruction) {
+    fprintf(irp->f, "@clz(");
+    ir_print_other_instruction(irp, instruction->value);
+    fprintf(irp->f, ")");
+}
+
+static void ir_print_ctz(IrPrint *irp, IrInstructionCtz *instruction) {
+    fprintf(irp->f, "@ctz(");
+    ir_print_other_instruction(irp, instruction->value);
+    fprintf(irp->f, ")");
+}
+
 static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
     ir_print_prefix(irp, instruction);
     switch (instruction->id) {
@@ -612,6 +624,12 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
         case IrInstructionIdUnwrapMaybe:
             ir_print_unwrap_maybe(irp, (IrInstructionUnwrapMaybe *)instruction);
             break;
+        case IrInstructionIdCtz:
+            ir_print_ctz(irp, (IrInstructionCtz *)instruction);
+            break;
+        case IrInstructionIdClz:
+            ir_print_clz(irp, (IrInstructionClz *)instruction);
+            break;
         case IrInstructionIdSwitchBr:
             zig_panic("TODO print more IR instructions");
     }