Skip to content

Commit

Permalink
surface (fix): Reduce generated code size for MethodSurface in Scala 3 (
Browse files Browse the repository at this point in the history
#3146)

- surface (fix): Do not use internal cache to save the byte code size
- Cache Surface only for LazySurface

This PR addresses a huge byte code issue, failed with: `[error]
Generated bytecode for method (extending RxRouterProvider) is too large.
Size: 159519 bytes. Limit is 64KB` message
  • Loading branch information
xerial authored Aug 20, 2023
1 parent 85035e0 commit f721dcb
Show file tree
Hide file tree
Showing 3 changed files with 43 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,42 +76,53 @@ private[surface] class CompileTimeSurfaceFactory[Q <: Quotes](using quotes: Q) {
surfaceOf(TypeRepr.of(using tpe))
}

private val seen = scala.collection.mutable.Set[TypeRepr]()
private val memo = scala.collection.mutable.Map[TypeRepr, Expr[Surface]]()
private val seen = scala.collection.mutable.Set[TypeRepr]()
private val memo = scala.collection.mutable.Map[TypeRepr, Expr[Surface]]()
private val lazySurface = scala.collection.mutable.Set[TypeRepr]()

private def surfaceOf(t: TypeRepr): Expr[Surface] = {
if (seen.contains(t)) {
if (memo.contains(t)) {
memo(t)
} else {
lazySurface += t
'{ LazySurface(${ clsOf(t) }, ${ Expr(fullTypeNameOf(t)) }) }
}
} else {
seen += t
// For debugging
// println(s"[${typeNameOf(t)}]\n ${t}\nfull type name: ${fullTypeNameOf(t)}\nclass: ${t.getClass}")
val generator = factory.andThen { expr =>
val cacheKey = if (typeNameOf(t) == "scala.Any") {
t match {
case ParamRef(TypeLambda(typeNames, _, _), _) =>
// Distinguish scala.Any and type bounds (such as _)
s"${fullTypeNameOf(t)} for ${t}"
case TypeBounds(_, _) =>
// This ensures different cache key for each Type Parameter (such as T and U).
// This is required because fullTypeNameOf of every Type Parameters is `scala.Any`.
s"${fullTypeNameOf(t)} for ${t}"
case _ =>
fullTypeNameOf(t)
}
if (!lazySurface.contains(t)) {
expr
} else {
fullTypeNameOf(t)
}
'{
val key = ${ Expr(cacheKey) }
if (!wvlet.airframe.surface.surfaceCache.contains(key)) {
wvlet.airframe.surface.surfaceCache += key -> ${ expr }
// Need to cache the recursive Surface to be referenced in a LazySurface
val cacheKey = if (typeNameOf(t) == "scala.Any") {
t match {
case ParamRef(TypeLambda(typeNames, _, _), _) =>
// Distinguish scala.Any and type bounds (such as _)
s"${fullTypeNameOf(t)} for ${t}"
case TypeBounds(_, _) =>
// This ensures different cache key for each Type Parameter (such as T and U).
// This is required because fullTypeNameOf of every Type Parameters is `scala.Any`.
s"${fullTypeNameOf(t)} for ${t}"
case _ =>
fullTypeNameOf(t)
}
} else {
fullTypeNameOf(t)
}
'{
val key = ${
Expr(cacheKey)
}
if (!wvlet.airframe.surface.surfaceCache.contains(key)) {
wvlet.airframe.surface.surfaceCache += key -> ${
expr
}
}
wvlet.airframe.surface.surfaceCache(key)
}
wvlet.airframe.surface.surfaceCache(key)
}
}
val surface = generator(t)
Expand Down Expand Up @@ -693,6 +704,7 @@ private[surface] class CompileTimeSurfaceFactory[Q <: Quotes](using quotes: Q) {
}
}
val expr = Expr.ofSeq(methodSurfaces)
// println(s"methodOf: ${targetType.typeSymbol.fullName} => \n${expr.show}")
methodMemo += targetType -> expr
expr
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,9 @@ object RecursiveSurfaceTest {
class RecursiveSurfaceTest extends SurfaceSpec {
import RecursiveSurfaceTest._

test("find surface from full type name string") {
val s = Surface.of[Leaf]
assert(wvlet.airframe.surface.getCached("wvlet.airframe.surface.RecursiveSurfaceTest.Leaf") == s)
test("find recursive surface cache from the full type name string") {
val s = Surface.of[Cons]
assert(wvlet.airframe.surface.getCached("wvlet.airframe.surface.RecursiveSurfaceTest.Cons") == s)
}

test("support recursive type") {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,20 +90,24 @@ class SurfaceTest extends SurfaceSpec {
test("be equal") {
val a1 = Surface.of[A]
val a2 = Surface.of[A]
assert(a1 eq a2)

// In Scala 3, Surface instance identity is not guaranteed
// assert(a1 eq a2)

// equality
assert(a1 == a2)
assert(a1.hashCode() == a2.hashCode())

val b = Surface.of[B]
val a3 = b.params.head.surface
assert(a1 eq a3)

// assert(a1 eq a3)

// Generic surface
val c1 = Surface.of[Seq[Int]]
val c2 = Surface.of[Seq[Int]]
assert(c1.equals(c2) == true)
assert(c1 eq c2)
// assert(c1 eq c2)
assert(c1.hashCode() == c2.hashCode())

assert(c1 ne a1)
Expand Down

0 comments on commit f721dcb

Please sign in to comment.