scala-native icon indicating copy to clipboard operation
scala-native copied to clipboard

`AnyVal` causes segfault

Open armanbilge opened this issue 3 years ago • 9 comments

I apologize that I don't have time to minimize this at the moment.

To replicate, check out https://github.com/fthomas/refined/commit/3916b07c1e6934a2c8fe83d75dc10e36d4165d28 and run catsNative/test.

armanbilge avatar Jul 06 '22 20:07 armanbilge

Related? https://github.com/scala-native/scala-native/issues/2597

armanbilge avatar Jul 06 '22 21:07 armanbilge

Isolated, but not minimized. Must be run with scala-cli test bug.scala.

//> using scala "2.13.8"
//> using platform "native"
//> using lib "org.typelevel::cats-kernel-laws::2.8.0"
//> using lib "eu.timepit::refined-scalacheck::0.10.1"

import cats.kernel._
import cats.kernel.laws.discipline._
import eu.timepit.refined._
import eu.timepit.refined.api._
import eu.timepit.refined.scalacheck.numeric._
import eu.timepit.refined.types.numeric._
import eu.timepit.refined.numeric.Positive

import org.scalacheck._

class BugSpec extends Properties("bugged") {

  implicit val posByteCommutativeSemigroup: CommutativeSemigroup[PosByte] =
    getPosIntegralCommutativeSemigroup[Byte]

  private def getPosIntegralCommutativeSemigroup[A: CommutativeSemigroup: NonNegShift](implicit
      integral: Integral[A],
      v: Validate[A, Positive]
  ): CommutativeSemigroup[A Refined Positive] =
    CommutativeSemigroup.instance { (x, y) =>
      val combined: A = Semigroup[A].combine(x.value, y.value)

      refineV[Positive](combined).getOrElse {
        val result: A =
          CommutativeSemigroup[A].combine(NonNegShift[A].shift(combined), integral.one)
        refineV[Positive].unsafeFrom(result)
      }
    }

  implicit def eq: Eq[PosByte] = Eq.by(_.value)

  property("propped") = CommutativeSemigroupTests[PosByte].commutativeSemigroup.props(0)._2

}


/**
 * Typeclass to shift Negative values to Non Negative values.
 */
trait NonNegShift[T] extends Serializable {
  def shift(t: T): T
}

object NonNegShift {
  def apply[T](implicit ev: NonNegShift[T]): NonNegShift[T] = ev

  def instance[T](function: T => T): NonNegShift[T] =
    new NonNegShift[T] {
      def shift(t: T): T = function(t)
    }

  // Instances
  implicit val byteNonNegShift: NonNegShift[Byte] = instance(t => (t & Byte.MaxValue).toByte)
  implicit val shortNonNegShift: NonNegShift[Short] = instance(t => (t & Short.MaxValue).toShort)
  implicit val intNonNegShift: NonNegShift[Int] = instance(t => t & Int.MaxValue)
  implicit val longNonNegShift: NonNegShift[Long] = instance(t => t & Long.MaxValue)
}

armanbilge avatar Jul 06 '22 22:07 armanbilge

Minimized to no external dependencies. Now it just segfaults.

//> using scala "2.13.8"
//> using platform "native"

trait Eq[@specialized(Byte) A] {
  def eqv(x: A, y: A): Boolean
}

object Eq {
  def by[A, B](f: A => B)(implicit eqB: Eq[B]): Eq[A] = (x, y) => eqB.eqv(f(x), f(y))
  implicit def forByte: Eq[Byte] = _ == _
}

trait CommutativeSemigroup[@specialized(Byte) A] {
  def combine(x: A, y: A): A
}

object CommutativeSemigroup {
  implicit def forByte: CommutativeSemigroup[Byte] = (x, y) => (x + y).toByte
}

final class Refined[T, P] private (val value: T) extends AnyVal with Serializable {

  override def toString: String =
    value.toString
}

object Refined {

  def unsafeApply[T, P](t: T): Refined[T, P] =
    new Refined(t)

}


object Bug {

  type Positive

  implicit val posByteCommutativeSemigroup: CommutativeSemigroup[Byte Refined Positive] =
    getPosIntegralCommutativeSemigroup[Byte]

  private def getPosIntegralCommutativeSemigroup[A: CommutativeSemigroup: NonNegShift](implicit
      integral: Integral[A],
  ): CommutativeSemigroup[A Refined Positive] = { (x, y) =>
      val combined: A = implicitly[CommutativeSemigroup[A]].combine(x.value, y.value)
      Refined.unsafeApply(implicitly[CommutativeSemigroup[A]].combine(implicitly[NonNegShift[A]].shift(combined), integral.one))
    }

  implicit def eq: Eq[Byte Refined Positive] = Eq.by(_.value)

  def main(args: Array[String]): Unit = {
    val x = Refined.unsafeApply[Byte, Positive](126.toByte)
    val y = Refined.unsafeApply[Byte, Positive](62.toByte)
    println(implicitly[Eq[Byte Refined Positive]].eqv(implicitly[CommutativeSemigroup[Byte Refined Positive]].combine(x, y), implicitly[CommutativeSemigroup[Byte Refined Positive]].combine(y, x)))
  }

}


/**
 * Typeclass to shift Negative values to Non Negative values.
 */
trait NonNegShift[T] {
  def shift(t: T): T
}

object NonNegShift {
  implicit val byteNonNegShift: NonNegShift[Byte] = t => (t & Byte.MaxValue).toByte
}

armanbilge avatar Jul 06 '22 23:07 armanbilge

Ok, I think this is as far as I can get it.

//> using scala "2.13.8"
//> using platform "native"

// removing the AnyVal makes the problem go away
final class Refined[A] (val value: A) extends AnyVal

object Bug {

  def f[A]: Refined[A] => Refined[A] =
    x => new Refined(x.value)

  def g: Refined[Byte] => Boolean =
    x => (x.value == x.value)

  def main(args: Array[String]): Unit = {
    val x = new Refined[Byte](126.toByte)
    println(g.apply(f[Byte].apply(x)))
  }

}
$ scala-cli bug.scala 
Segmentation fault (core dumped)

armanbilge avatar Jul 07 '22 00:07 armanbilge

Thank you for reporting this Issue. Sorry you encountered it.

This looks like a problem at the Scala level but it helps to know the hardware architecture, OS, and JVM involved. Quirks abound (& rule!).

Am I correct in my memory from Discord that this Issue does not appear with Scala 3.1.1?

Others are more qualified to address the presenting issue. In the background, I am on the hunt for quirks in macOS unit-tests both my personal experience & Issue #2701

LeeTibbert avatar Jul 07 '22 15:07 LeeTibbert

Yes, that's right, this issue does not appear in Scala 3. I suspect it is because Scala 3 does not support function specialization, which may be interacting poorly with AnyVal in this particular instance.

$ uname -a
Linux 5.15.0-40-generic #43-Ubuntu SMP Wed Jun 15 12:54:21 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

armanbilge avatar Jul 07 '22 15:07 armanbilge

With address sanitizer you get a little extra info:

AddressSanitizer:DEADLYSIGNAL
=================================================================
==1656==ERROR: AddressSanitizer: SEGV on unknown address 0x00000000007e (pc 0x00010278da24 bp 0x0001027dbabc sp 0x00016d6cf2b0 T0)
==1656==The signal is caused by a UNKNOWN memory access.
==1656==Hint: address points to the zero page.
    #0 0x10278da24 in _SM27scala.runtime.BoxesRunTime$D11unboxToByteL16java.lang.ObjectbEO+0x30 (arman-scala-native-bug-out:arm64+0x10005da24)

==1656==Register values:
 x[0] = 0x0000000000000000   x[1] = 0x000000000000007e   x[2] = 0x0000000000000000   x[3] = 0x0000000280000a00
 x[4] = 0x0000000280000140   x[5] = 0x0000000000000000   x[6] = 0x00000001027498c0   x[7] = 0x0000000000000000
 x[8] = 0x000000000000007e   x[9] = 0x000000000000007e  x[10] = 0x0000000000000001  x[11] = 0x00000000000000fe
x[12] = 0x0000000000000000  x[13] = 0x0000000000000020  x[14] = 0x0000000000000020  x[15] = 0x0000000073647e85
x[16] = 0x000000018d0b2e00  x[17] = 0x00000001030845e8  x[18] = 0x0000000000000000  x[19] = 0x00000001028fc060
x[20] = 0x000000010278e4c4  x[21] = 0x0000000102c94070  x[22] = 0x0000000000000000  x[23] = 0x0000000000000000
x[24] = 0x0000000000000000  x[25] = 0x0000000000000000  x[26] = 0x0000000000000000  x[27] = 0x0000000000000000
x[28] = 0x0000000000000000     fp = 0x000000016d6cf5c0     lr = 0x00000001027dbabc     sp = 0x000000016d6cf2b0
AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (arman-scala-native-bug-out:arm64+0x10005da24) in _SM27scala.runtime.BoxesRunTime$D11unboxToByteL16java.lang.ObjectbEO+0x30

keynmol avatar Jul 07 '22 18:07 keynmol

_SM27scala.runtime.BoxesRunTime$D11unboxToByteL16java.lang.ObjectbEO

is this: https://github.com/scala-native/scala-native/blob/main/auxlib/src/main/scala/scala/runtime/BoxesRunTime.scala#L30-L31

Which is nothing special, just probably shows the address is wrong.

keynmol avatar Jul 07 '22 18:07 keynmol

I hope this is where the problem happens..

With AnyVal

def @"M4Bug$D12$anonfun$g$1L14java.lang.BytezEPT4Bug$" : (@"T4Bug$", @"T14java.lang.Byte") => bool {
%3(%1 : @"T4Bug$", %2 : @"T14java.lang.Byte"):
  %4 = unbox[@"T14java.lang.Byte"] %2 : @"T14java.lang.Byte"
  %5 = sext[int] %4 : byte
  %6 = unbox[@"T14java.lang.Byte"] %2 : @"T14java.lang.Byte"
  %7 = sext[int] %6 : byte
  %8 = ieq[int] %5 : int, %7 : int
  ret %8 : bool
}

Without AnyVal

def @"M4Bug$D12$anonfun$g$1L7RefinedzEPT4Bug$" : (@"T4Bug$", @"T7Refined") => bool {
%3(%1 : @"T4Bug$", %2 : @"T7Refined"):
  %4 = method %2 : @"T7Refined", "D5valueL16java.lang.ObjectEO"
  %5 = call[(@"T7Refined") => @"T16java.lang.Object"] %4 : ptr(%2 : @"T7Refined")
  %6 = unbox[@"T14java.lang.Byte"] %5 : @"T16java.lang.Object"
  %7 = sext[int] %6 : byte
  %8 = method %2 : @"T7Refined", "D5valueL16java.lang.ObjectEO"
  %9 = call[(@"T7Refined") => @"T16java.lang.Object"] %8 : ptr(%2 : @"T7Refined")
  %10 = unbox[@"T14java.lang.Byte"] %9 : @"T16java.lang.Object"
  %11 = sext[int] %10 : byte
  %12 = ieq[int] %7 : int, %11 : int
  ret %12 : bool
}

keynmol avatar Jul 07 '22 18:07 keynmol