Commit f6cd68386d

vegecode <39607947+vegecode@users.noreply.github.com>
2019-01-02 22:47:47
@bitreverse intrinsic, part of #767 (#1865)
* bitreverse - give bswap behavior * bitreverse, comptime_ints, negative values still not working? * bitreverse working for negative comptime ints * Finished bitreverse test cases * Undo exporting a bigint function. @bitreverse test name includes ampersand * added docs entry for @bitreverse
1 parent 6df8e4b
doc/langref.html.in
@@ -5316,6 +5316,18 @@ comptime {
       </p>
       {#header_close#}
 
+      {#header_open|@bitreverse#}
+      <pre>{#syntax#}@bitreverse(comptime T: type, value: T) T{#endsyntax#}</pre>
+      <p>{#syntax#}T{#endsyntax#} accepts any integer type.</p>
+      <p>
+      Reverses the bitpattern of an integer value, including the sign bit if applicable.
+      </p>
+      <p>
+      For example 0b10110110 ({#syntax#}u8 = 182{#endsyntax#}, {#syntax#}i8 = -74{#endsyntax#})
+      becomes 0b01101101 ({#syntax#}u8 = 109{#endsyntax#}, {#syntax#}i8 = 109{#endsyntax#}).
+      </p>
+      {#header_close#}
+
       {#header_open|@byteOffsetOf#}
       <pre>{#syntax#}@byteOffsetOf(comptime T: type, comptime field_name: [] const u8) comptime_int{#endsyntax#}</pre>
       <p>
src/all_types.hpp
@@ -1415,6 +1415,7 @@ enum BuiltinFnId {
     BuiltinFnIdAtomicRmw,
     BuiltinFnIdAtomicLoad,
     BuiltinFnIdBswap,
+    BuiltinFnIdBitReverse,
 };
 
 struct BuiltinFnEntry {
@@ -1488,6 +1489,7 @@ enum ZigLLVMFnId {
     ZigLLVMFnIdCeil,
     ZigLLVMFnIdSqrt,
     ZigLLVMFnIdBswap,
+    ZigLLVMFnIdBitReverse,
 };
 
 enum AddSubMul {
@@ -1520,6 +1522,9 @@ struct ZigLLVMFnKey {
         struct {
             uint32_t bit_count;
         } bswap;
+        struct {
+            uint32_t bit_count;
+        } bit_reverse;
     } data;
 };
 
@@ -2162,6 +2167,7 @@ enum IrInstructionId {
     IrInstructionIdMarkErrRetTracePtr,
     IrInstructionIdSqrt,
     IrInstructionIdBswap,
+    IrInstructionIdBitReverse,
     IrInstructionIdErrSetCast,
     IrInstructionIdToBytes,
     IrInstructionIdFromBytes,
@@ -3262,6 +3268,13 @@ struct IrInstructionBswap {
     IrInstruction *op;
 };
 
+struct IrInstructionBitReverse {
+    IrInstruction base;
+
+    IrInstruction *type;
+    IrInstruction *op;
+};
+
 static const size_t slice_ptr_index = 0;
 static const size_t slice_len_index = 1;
 
src/analyze.cpp
@@ -6121,6 +6121,8 @@ uint32_t zig_llvm_fn_key_hash(ZigLLVMFnKey x) {
             return (uint32_t)(x.data.floating.bit_count) * (uint32_t)2225366385;
         case ZigLLVMFnIdBswap:
             return (uint32_t)(x.data.bswap.bit_count) * (uint32_t)3661994335;
+        case ZigLLVMFnIdBitReverse:
+            return (uint32_t)(x.data.bit_reverse.bit_count) * (uint32_t)2621398431;
         case ZigLLVMFnIdOverflowArithmetic:
             return ((uint32_t)(x.data.overflow_arithmetic.bit_count) * 87135777) +
                 ((uint32_t)(x.data.overflow_arithmetic.add_sub_mul) * 31640542) +
@@ -6141,6 +6143,8 @@ bool zig_llvm_fn_key_eql(ZigLLVMFnKey a, ZigLLVMFnKey b) {
             return a.data.pop_count.bit_count == b.data.pop_count.bit_count;
         case ZigLLVMFnIdBswap:
             return a.data.bswap.bit_count == b.data.bswap.bit_count;
+        case ZigLLVMFnIdBitReverse:
+            return a.data.bit_reverse.bit_count == b.data.bit_reverse.bit_count;
         case ZigLLVMFnIdFloor:
         case ZigLLVMFnIdCeil:
         case ZigLLVMFnIdSqrt:
src/bigint.cpp
@@ -1722,3 +1722,4 @@ void bigint_incr(BigInt *x) {
 
     bigint_add(x, &copy, &one);
 }
+
src/codegen.cpp
@@ -3789,6 +3789,11 @@ static LLVMValueRef get_int_builtin_fn(CodeGen *g, ZigType *int_type, BuiltinFnI
         n_args = 1;
         key.id = ZigLLVMFnIdBswap;
         key.data.bswap.bit_count = (uint32_t)int_type->data.integral.bit_count;
+    } else if (fn_id == BuiltinFnIdBitReverse) {
+        fn_name = "bitreverse";
+        n_args = 1;
+        key.id = ZigLLVMFnIdBitReverse;
+        key.data.bit_reverse.bit_count = (uint32_t)int_type->data.integral.bit_count;
     } else {
         zig_unreachable();
     }
@@ -5096,6 +5101,14 @@ static LLVMValueRef ir_render_bswap(CodeGen *g, IrExecutable *executable, IrInst
     return LLVMBuildTrunc(g->builder, shifted, int_type->type_ref, "");
 }
 
+static LLVMValueRef ir_render_bit_reverse(CodeGen *g, IrExecutable *executable, IrInstructionBitReverse *instruction) {
+    LLVMValueRef op = ir_llvm_value(g, instruction->op);
+    ZigType *int_type = instruction->base.value.type;
+    assert(int_type->id == ZigTypeIdInt);
+    LLVMValueRef fn_val = get_int_builtin_fn(g, instruction->base.value.type, BuiltinFnIdBitReverse);
+    return LLVMBuildCall(g->builder, fn_val, &op, 1, "");
+}
+
 static void set_debug_location(CodeGen *g, IrInstruction *instruction) {
     AstNode *source_node = instruction->source_node;
     Scope *scope = instruction->scope;
@@ -5335,6 +5348,8 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable,
             return ir_render_sqrt(g, executable, (IrInstructionSqrt *)instruction);
         case IrInstructionIdBswap:
             return ir_render_bswap(g, executable, (IrInstructionBswap *)instruction);
+        case IrInstructionIdBitReverse:
+            return ir_render_bit_reverse(g, executable, (IrInstructionBitReverse *)instruction);
     }
     zig_unreachable();
 }
@@ -6758,6 +6773,7 @@ static void define_builtin_fns(CodeGen *g) {
     create_builtin_fn(g, BuiltinFnIdFromBytes, "bytesToSlice", 2);
     create_builtin_fn(g, BuiltinFnIdThis, "This", 0);
     create_builtin_fn(g, BuiltinFnIdBswap, "bswap", 2);
+    create_builtin_fn(g, BuiltinFnIdBitReverse, "bitreverse", 2);
 }
 
 static const char *bool_to_str(bool b) {
src/ir.cpp
@@ -861,6 +861,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionBswap *) {
     return IrInstructionIdBswap;
 }
 
+static constexpr IrInstructionId ir_instruction_id(IrInstructionBitReverse *) {
+    return IrInstructionIdBitReverse;
+}
+
 static constexpr IrInstructionId ir_instruction_id(IrInstructionCheckRuntimeScope *) {
     return IrInstructionIdCheckRuntimeScope;
 }
@@ -2721,6 +2725,17 @@ static IrInstruction *ir_build_bswap(IrBuilder *irb, Scope *scope, AstNode *sour
     return &instruction->base;
 }
 
+static IrInstruction *ir_build_bit_reverse(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *type, IrInstruction *op) {
+    IrInstructionBitReverse *instruction = ir_build_instruction<IrInstructionBitReverse>(irb, scope, source_node);
+    instruction->type = type;
+    instruction->op = op;
+
+    if (type != nullptr) ir_ref_instruction(type, irb->current_basic_block);
+    ir_ref_instruction(op, irb->current_basic_block);
+
+    return &instruction->base;
+}
+
 static IrInstruction *ir_build_check_runtime_scope(IrBuilder *irb, Scope *scope, AstNode *source_node, IrInstruction *scope_is_comptime, IrInstruction *is_comptime) {
     IrInstructionCheckRuntimeScope *instruction = ir_build_instruction<IrInstructionCheckRuntimeScope>(irb, scope, source_node);
     instruction->scope_is_comptime = scope_is_comptime;
@@ -3646,7 +3661,7 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo
     Buf *name = fn_ref_expr->data.symbol_expr.symbol;
     auto entry = irb->codegen->builtin_fn_table.maybe_get(name);
 
-    if (!entry) {
+    if (!entry) { // new built in not found
         add_node_error(irb->codegen, node,
                 buf_sprintf("invalid builtin function: '%s'", buf_ptr(name)));
         return irb->codegen->invalid_instruction;
@@ -4720,6 +4735,21 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo
                 IrInstruction *result = ir_build_bswap(irb, scope, node, arg0_value, arg1_value);
                 return ir_lval_wrap(irb, scope, result, lval);
             }
+        case BuiltinFnIdBitReverse:
+            {
+                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;
+
+                AstNode *arg1_node = node->data.fn_call_expr.params.at(1);
+                IrInstruction *arg1_value = ir_gen_node(irb, arg1_node, scope);
+                if (arg1_value == irb->codegen->invalid_instruction)
+                    return arg1_value;
+
+                IrInstruction *result = ir_build_bit_reverse(irb, scope, node, arg0_value, arg1_value);
+                return ir_lval_wrap(irb, scope, result, lval);
+            }
     }
     zig_unreachable();
 }
@@ -21115,6 +21145,69 @@ static IrInstruction *ir_analyze_instruction_bswap(IrAnalyze *ira, IrInstruction
     return result;
 }
 
+static IrInstruction *ir_analyze_instruction_bit_reverse(IrAnalyze *ira, IrInstructionBitReverse *instruction) {
+    ZigType *int_type = ir_resolve_type(ira, instruction->type->child);
+    if (type_is_invalid(int_type))
+        return ira->codegen->invalid_instruction;
+
+    IrInstruction *op = instruction->op->child;
+    if (type_is_invalid(op->value.type))
+        return ira->codegen->invalid_instruction;
+
+    if (int_type->id != ZigTypeIdInt) {
+        ir_add_error(ira, instruction->type,
+            buf_sprintf("expected integer type, found '%s'", buf_ptr(&int_type->name)));
+        return ira->codegen->invalid_instruction;
+    }
+
+    IrInstruction *casted_op = ir_implicit_cast(ira, op, int_type);
+    if (type_is_invalid(casted_op->value.type))
+        return ira->codegen->invalid_instruction;
+
+    if (int_type->data.integral.bit_count == 0) {
+        IrInstruction *result = ir_const(ira, &instruction->base, int_type);
+        bigint_init_unsigned(&result->value.data.x_bigint, 0);
+        return result;
+    }
+
+    if (instr_is_comptime(casted_op)) {
+        ConstExprValue *val = ir_resolve_const(ira, casted_op, UndefBad);
+        if (!val)
+            return ira->codegen->invalid_instruction;
+
+        IrInstruction *result = ir_const(ira, &instruction->base, int_type);
+        size_t num_bits = int_type->data.integral.bit_count;
+        size_t buf_size = (num_bits + 7) / 8;
+        uint8_t *comptime_buf = allocate_nonzero<uint8_t>(buf_size);
+        uint8_t *result_buf = allocate_nonzero<uint8_t>(buf_size);
+        memset(comptime_buf,0,buf_size);
+        memset(result_buf,0,buf_size);
+
+        bigint_write_twos_complement(&val->data.x_bigint,comptime_buf,num_bits,ira->codegen->is_big_endian);
+
+        size_t bit_i = 0;
+        size_t bit_rev_i = num_bits - 1;
+        for (; bit_i < num_bits; bit_i++, bit_rev_i--) {
+            if (comptime_buf[bit_i / 8] & (1 << (bit_i % 8))) {
+                result_buf[bit_rev_i / 8] |= (1 << (bit_rev_i % 8));
+            }
+        }
+
+        bigint_read_twos_complement(&result->value.data.x_bigint,
+                                    result_buf,
+                                    int_type->data.integral.bit_count,
+                                    ira->codegen->is_big_endian,
+                                    int_type->data.integral.is_signed);
+
+        return result;
+    }
+
+    IrInstruction *result = ir_build_bit_reverse(&ira->new_irb, instruction->base.scope,
+            instruction->base.source_node, nullptr, casted_op);
+    result->value.type = int_type;
+    return result;
+}
+
 
 static IrInstruction *ir_analyze_instruction_enum_to_int(IrAnalyze *ira, IrInstructionEnumToInt *instruction) {
     Error err;
@@ -21453,6 +21546,8 @@ static IrInstruction *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructio
             return ir_analyze_instruction_sqrt(ira, (IrInstructionSqrt *)instruction);
         case IrInstructionIdBswap:
             return ir_analyze_instruction_bswap(ira, (IrInstructionBswap *)instruction);
+        case IrInstructionIdBitReverse:
+            return ir_analyze_instruction_bit_reverse(ira, (IrInstructionBitReverse *)instruction);
         case IrInstructionIdIntToErr:
             return ir_analyze_instruction_int_to_err(ira, (IrInstructionIntToErr *)instruction);
         case IrInstructionIdErrToInt:
@@ -21675,6 +21770,7 @@ bool ir_has_side_effects(IrInstruction *instruction) {
         case IrInstructionIdPromiseResultType:
         case IrInstructionIdSqrt:
         case IrInstructionIdBswap:
+        case IrInstructionIdBitReverse:
         case IrInstructionIdAtomicLoad:
         case IrInstructionIdIntCast:
         case IrInstructionIdFloatCast:
src/ir_print.cpp
@@ -1335,6 +1335,18 @@ static void ir_print_bswap(IrPrint *irp, IrInstructionBswap *instruction) {
     fprintf(irp->f, ")");
 }
 
+static void ir_print_bit_reverse(IrPrint *irp, IrInstructionBitReverse *instruction) {
+    fprintf(irp->f, "@bitreverse(");
+    if (instruction->type != nullptr) {
+        ir_print_other_instruction(irp, instruction->type);
+    } else {
+        fprintf(irp->f, "null");
+    }
+    fprintf(irp->f, ",");
+    ir_print_other_instruction(irp, instruction->op);
+    fprintf(irp->f, ")");
+}
+
 static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
     ir_print_prefix(irp, instruction);
     switch (instruction->id) {
@@ -1751,6 +1763,9 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) {
         case IrInstructionIdBswap:
             ir_print_bswap(irp, (IrInstructionBswap *)instruction);
             break;
+        case IrInstructionIdBitReverse:
+            ir_print_bit_reverse(irp, (IrInstructionBitReverse *)instruction);
+            break;
         case IrInstructionIdAtomicLoad:
             ir_print_atomic_load(irp, (IrInstructionAtomicLoad *)instruction);
             break;
test/cases/bitreverse.zig
@@ -0,0 +1,81 @@
+const std = @import("std");
+const assert = std.debug.assert;
+const minInt = std.math.minInt;
+
+test "@bitreverse" {
+    comptime testBitReverse();
+    testBitReverse();
+}
+
+fn testBitReverse() void {
+    // using comptime_ints, unsigned
+    assert(@bitreverse(u0,   0) == 0);
+    assert(@bitreverse(u5,   0x12) == 0x9);
+    assert(@bitreverse(u8,   0x12) == 0x48);
+    assert(@bitreverse(u16,  0x1234) == 0x2c48);
+    assert(@bitreverse(u24,  0x123456) == 0x6a2c48);
+    assert(@bitreverse(u32,  0x12345678) == 0x1e6a2c48);
+    assert(@bitreverse(u40,  0x123456789a) == 0x591e6a2c48);
+    assert(@bitreverse(u48,  0x123456789abc) == 0x3d591e6a2c48);
+    assert(@bitreverse(u56,  0x123456789abcde) == 0x7b3d591e6a2c48);
+    assert(@bitreverse(u64,  0x123456789abcdef1) == 0x8f7b3d591e6a2c48);
+    assert(@bitreverse(u128, 0x123456789abcdef11121314151617181) == 0x818e868a828c84888f7b3d591e6a2c48);
+
+    // using runtime uints, unsigned
+    var num0: u0 = 0;
+    assert(@bitreverse(u0,   num0) == 0);
+    var num5: u5 = 0x12;
+    assert(@bitreverse(u5,   num5) == 0x9);
+    var num8: u8 = 0x12;
+    assert(@bitreverse(u8,   num8) == 0x48);
+    var num16: u16 = 0x1234;
+    assert(@bitreverse(u16,  num16) == 0x2c48);
+    var num24: u24 = 0x123456;
+    assert(@bitreverse(u24,  num24) == 0x6a2c48);
+    var num32: u32 = 0x12345678;
+    assert(@bitreverse(u32,  num32) == 0x1e6a2c48);
+    var num40: u40 = 0x123456789a;
+    assert(@bitreverse(u40,  num40) == 0x591e6a2c48);
+    var num48: u48 = 0x123456789abc;
+    assert(@bitreverse(u48,  num48) == 0x3d591e6a2c48);
+    var num56: u56 = 0x123456789abcde;
+    assert(@bitreverse(u56,  num56) == 0x7b3d591e6a2c48);
+    var num64: u64 = 0x123456789abcdef1;
+    assert(@bitreverse(u64,  num64) == 0x8f7b3d591e6a2c48);
+    var num128: u128 = 0x123456789abcdef11121314151617181;
+    assert(@bitreverse(u128, num128) == 0x818e868a828c84888f7b3d591e6a2c48);
+
+    // using comptime_ints, signed, positive
+    assert(@bitreverse(i0,   0) == 0);
+    assert(@bitreverse(i8,   @bitCast(i8,  u8(0x92))) == @bitCast(i8, u8( 0x49)));
+    assert(@bitreverse(i16,  @bitCast(i16, u16(0x1234))) == @bitCast(i16, u16( 0x2c48)));
+    assert(@bitreverse(i24,  @bitCast(i24, u24(0x123456))) == @bitCast(i24, u24( 0x6a2c48)));
+    assert(@bitreverse(i32,  @bitCast(i32, u32(0x12345678))) == @bitCast(i32, u32( 0x1e6a2c48)));
+    assert(@bitreverse(i40,  @bitCast(i40, u40(0x123456789a))) == @bitCast(i40, u40( 0x591e6a2c48)));
+    assert(@bitreverse(i48,  @bitCast(i48, u48(0x123456789abc))) == @bitCast(i48, u48( 0x3d591e6a2c48)));
+    assert(@bitreverse(i56,  @bitCast(i56, u56(0x123456789abcde))) == @bitCast(i56, u56( 0x7b3d591e6a2c48)));
+    assert(@bitreverse(i64,  @bitCast(i64, u64(0x123456789abcdef1))) ==  @bitCast(i64,u64(0x8f7b3d591e6a2c48)));
+    assert(@bitreverse(i128, @bitCast(i128,u128(0x123456789abcdef11121314151617181))) ==  @bitCast(i128,u128(0x818e868a828c84888f7b3d591e6a2c48)));
+
+    // using comptime_ints, signed, negative. Compare to runtime ints returned from llvm.
+    var neg5: i5 = minInt(i5) + 1;
+    assert(@bitreverse(i5,   minInt(i5) + 1) == @bitreverse(i5, neg5));
+    var neg8: i8 = -18;
+    assert(@bitreverse(i8,   -18) == @bitreverse(i8, neg8));
+    var neg16: i16 = -32694;
+    assert(@bitreverse(i16,   -32694) == @bitreverse(i16, neg16));
+    var neg24: i24 = -6773785;
+    assert(@bitreverse(i24,   -6773785) == @bitreverse(i24, neg24));
+    var neg32: i32 = -16773785;
+    assert(@bitreverse(i32,   -16773785) == @bitreverse(i32, neg32));
+    var neg40: i40 = minInt(i40) + 12345;
+    assert(@bitreverse(i40,   minInt(i40) + 12345) == @bitreverse(i40, neg40));
+    var neg48: i48 = minInt(i48) + 12345;
+    assert(@bitreverse(i48,   minInt(i48) + 12345) == @bitreverse(i48, neg48));
+    var neg56: i56 = minInt(i56) + 12345;
+    assert(@bitreverse(i56,   minInt(i56) + 12345) == @bitreverse(i56, neg56));
+    var neg64: i64 = minInt(i64) + 12345;
+    assert(@bitreverse(i64,   minInt(i64) + 12345) == @bitreverse(i64, neg64));
+    var neg128: i128 = minInt(i128) + 12345;
+    assert(@bitreverse(i128,   minInt(i128) + 12345) == @bitreverse(i128, neg128));
+}
test/behavior.zig
@@ -9,6 +9,7 @@ comptime {
     _ = @import("cases/bitcast.zig");
     _ = @import("cases/bool.zig");
     _ = @import("cases/bswap.zig");
+    _ = @import("cases/bitreverse.zig");
     _ = @import("cases/bugs/1076.zig");
     _ = @import("cases/bugs/1111.zig");
     _ = @import("cases/bugs/1277.zig");