From da330a8a7b39b1d20ca9647c15dbbfd7cbac127d Mon Sep 17 00:00:00 2001
From: Sijawusz Pur Rahnama <sija@sija.pl>
Date: Fri, 8 Mar 2019 02:10:34 +0100
Subject: [PATCH] Implement Int#{leading,trailing}_zeros_count

---
 spec/std/big/big_int_spec.cr |  4 ++
 spec/std/int_spec.cr         | 16 +++++++
 src/big/big_int.cr           |  4 ++
 src/big/lib_gmp.cr           |  2 +
 src/int.cr                   | 93 ++++++++++++++++++++++++++++++++++++
 src/intrinsics.cr            | 12 +++++
 6 files changed, 131 insertions(+)

diff --git a/spec/std/big/big_int_spec.cr b/spec/std/big/big_int_spec.cr
index ccfcdd4466d5..497a2dbff8fc 100644
--- a/spec/std/big/big_int_spec.cr
+++ b/spec/std/big/big_int_spec.cr
@@ -326,6 +326,10 @@ describe "BigInt" do
     5.to_big_i.popcount.should eq(2)
   end
 
+  it "#trailing_zeros_count" do
+    "00000000000000001000000000001000".to_big_i(base: 2).trailing_zeros_count.should eq(3)
+  end
+
   it "#hash" do
     b1 = 5.to_big_i
     b2 = 5.to_big_i
diff --git a/spec/std/int_spec.cr b/spec/std/int_spec.cr
index 0a1c5abf4db2..f0d71f460d12 100644
--- a/spec/std/int_spec.cr
+++ b/spec/std/int_spec.cr
@@ -646,6 +646,22 @@ describe "Int" do
     it { 18446744073709551615_u64.popcount.should eq(64) }
   end
 
+  describe "#leading_zeros_count" do
+    {% for width in %w(8 16 32 64).map(&.id) %}
+      it { -1_i{{width}}.leading_zeros_count.should eq(0) }
+      it { 0_i{{width}}.leading_zeros_count.should eq({{width}}) }
+      it { 0_u{{width}}.leading_zeros_count.should eq({{width}}) }
+    {% end %}
+  end
+
+  describe "#trailing_zeros_count" do
+    {% for width in %w(8 16 32 64).map(&.id) %}
+      it { -2_i{{width}}.trailing_zeros_count.should eq(1) }
+      it { 2_i{{width}}.trailing_zeros_count.should eq(1) }
+      it { 2_u{{width}}.trailing_zeros_count.should eq(1) }
+    {% end %}
+  end
+
   it "compares signed vs. unsigned integers" do
     signed_ints = [Int8::MAX, Int16::MAX, Int32::MAX, Int64::MAX, Int8::MIN, Int16::MIN, Int32::MIN, Int64::MIN, 0_i8, 0_i16, 0_i32, 0_i64]
     unsigned_ints = [UInt8::MAX, UInt16::MAX, UInt32::MAX, UInt64::MAX, 0_u8, 0_u16, 0_u32, 0_u64]
diff --git a/src/big/big_int.cr b/src/big/big_int.cr
index 8b0b71533f51..d3430bdf2d54 100644
--- a/src/big/big_int.cr
+++ b/src/big/big_int.cr
@@ -385,6 +385,10 @@ struct BigInt < Int
     LibGMP.popcount(self)
   end
 
+  def trailing_zeros_count
+    LibGMP.scan1(self, 0)
+  end
+
   def to_i
     to_i32
   end
diff --git a/src/big/lib_gmp.cr b/src/big/lib_gmp.cr
index 6104825dbbb6..59c4ee464f7b 100644
--- a/src/big/lib_gmp.cr
+++ b/src/big/lib_gmp.cr
@@ -93,6 +93,8 @@ lib LibGMP
   # # Logic
 
   fun popcount = __gmpz_popcount(op : MPZ*) : BitcntT
+  fun scan0 = __gmpz_scan0(op : MPZ*, starting_bit : BitcntT) : BitcntT
+  fun scan1 = __gmpz_scan1(op : MPZ*, starting_bit : BitcntT) : BitcntT
 
   # # Comparison
 
diff --git a/src/int.cr b/src/int.cr
index 64758aa4c82b..d78e151eea18 100644
--- a/src/int.cr
+++ b/src/int.cr
@@ -542,6 +542,9 @@ struct Int
   # ```
   abstract def popcount
 
+  # Returns the number of trailing `0`-bits.
+  abstract def trailing_zeros_count
+
   private class TimesIterator(T)
     include Iterator(T)
 
@@ -629,6 +632,15 @@ struct Int8
     Intrinsics.popcount8(self)
   end
 
+  # Returns the number of leading `0`-bits.
+  def leading_zeros_count
+    Intrinsics.countleading8(self, false)
+  end
+
+  def trailing_zeros_count
+    Intrinsics.counttrailing8(self, false)
+  end
+
   def clone
     self
   end
@@ -656,6 +668,15 @@ struct Int16
     Intrinsics.popcount16(self)
   end
 
+  # Returns the number of leading `0`-bits.
+  def leading_zeros_count
+    Intrinsics.countleading16(self, false)
+  end
+
+  def trailing_zeros_count
+    Intrinsics.counttrailing16(self, false)
+  end
+
   def clone
     self
   end
@@ -683,6 +704,15 @@ struct Int32
     Intrinsics.popcount32(self)
   end
 
+  # Returns the number of leading `0`-bits.
+  def leading_zeros_count
+    Intrinsics.countleading32(self, false)
+  end
+
+  def trailing_zeros_count
+    Intrinsics.counttrailing32(self, false)
+  end
+
   def clone
     self
   end
@@ -710,6 +740,15 @@ struct Int64
     Intrinsics.popcount64(self)
   end
 
+  # Returns the number of leading `0`-bits.
+  def leading_zeros_count
+    Intrinsics.countleading64(self, false)
+  end
+
+  def trailing_zeros_count
+    Intrinsics.counttrailing64(self, false)
+  end
+
   def clone
     self
   end
@@ -739,6 +778,15 @@ struct Int128
     Intrinsics.popcount128(self)
   end
 
+  # Returns the number of leading `0`-bits.
+  def leading_zeros_count
+    Intrinsics.countleading128(self, false)
+  end
+
+  def trailing_zeros_count
+    Intrinsics.counttrailing128(self, false)
+  end
+
   def clone
     self
   end
@@ -766,6 +814,15 @@ struct UInt8
     Intrinsics.popcount8(self)
   end
 
+  # Returns the number of leading `0`-bits.
+  def leading_zeros_count
+    Intrinsics.countleading8(self, false)
+  end
+
+  def trailing_zeros_count
+    Intrinsics.counttrailing8(self, false)
+  end
+
   def clone
     self
   end
@@ -793,6 +850,15 @@ struct UInt16
     Intrinsics.popcount16(self)
   end
 
+  # Returns the number of leading `0`-bits.
+  def leading_zeros_count
+    Intrinsics.countleading16(self, false)
+  end
+
+  def trailing_zeros_count
+    Intrinsics.counttrailing16(self, false)
+  end
+
   def clone
     self
   end
@@ -820,6 +886,15 @@ struct UInt32
     Intrinsics.popcount32(self)
   end
 
+  # Returns the number of leading `0`-bits.
+  def leading_zeros_count
+    Intrinsics.countleading32(self, false)
+  end
+
+  def trailing_zeros_count
+    Intrinsics.counttrailing32(self, false)
+  end
+
   def clone
     self
   end
@@ -847,6 +922,15 @@ struct UInt64
     Intrinsics.popcount64(self)
   end
 
+  # Returns the number of leading `0`-bits.
+  def leading_zeros_count
+    Intrinsics.countleading64(self, false)
+  end
+
+  def trailing_zeros_count
+    Intrinsics.counttrailing64(self, false)
+  end
+
   def clone
     self
   end
@@ -875,6 +959,15 @@ struct UInt128
     Intrinsics.popcount128(self)
   end
 
+  # Returns the number of leading `0`-bits.
+  def leading_zeros_count
+    Intrinsics.countleading128(self, false)
+  end
+
+  def trailing_zeros_count
+    Intrinsics.counttrailing128(self, false)
+  end
+
   def clone
     self
   end
diff --git a/src/intrinsics.cr b/src/intrinsics.cr
index edf4f14548cb..d47781a15e16 100644
--- a/src/intrinsics.cr
+++ b/src/intrinsics.cr
@@ -18,6 +18,18 @@ lib Intrinsics
   fun popcount64 = "llvm.ctpop.i64"(src : Int64) : Int64
   fun popcount128 = "llvm.ctpop.i128"(src : Int128) : Int128
 
+  fun countleading8 = "llvm.ctlz.i8"(src : Int8, zero_is_undef : Bool) : Int8
+  fun countleading16 = "llvm.ctlz.i16"(src : Int16, zero_is_undef : Bool) : Int16
+  fun countleading32 = "llvm.ctlz.i32"(src : Int32, zero_is_undef : Bool) : Int32
+  fun countleading64 = "llvm.ctlz.i64"(src : Int64, zero_is_undef : Bool) : Int64
+  fun countleading128 = "llvm.ctlz.i128"(src : Int128, zero_is_undef : Bool) : Int128
+
+  fun counttrailing8 = "llvm.cttz.i8"(src : Int8, zero_is_undef : Bool) : Int8
+  fun counttrailing16 = "llvm.cttz.i16"(src : Int16, zero_is_undef : Bool) : Int16
+  fun counttrailing32 = "llvm.cttz.i32"(src : Int32, zero_is_undef : Bool) : Int32
+  fun counttrailing64 = "llvm.cttz.i64"(src : Int64, zero_is_undef : Bool) : Int64
+  fun counttrailing128 = "llvm.cttz.i128"(src : Int128, zero_is_undef : Bool) : Int128
+
   fun va_start = "llvm.va_start"(ap : Void*)
   fun va_end = "llvm.va_end"(ap : Void*)
 end