Skip to content

Commit 19ebd8e

Browse files
committed
Decrease stack pressure of derivation macros by partially unrolling loops (#608)
1 parent e00fcf6 commit 19ebd8e

2 files changed

Lines changed: 290 additions & 36 deletions

File tree

.jvmopts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
-Xmx4g
2-
-Xss4M
2+
-Xss1M
33
-XX:MaxMetaspaceSize=1g
44
-XX:ReservedCodeCacheSize=256m
55
-XX:+UseG1GC

derivation/src/main/scala/io/bullet/borer/derivation/Deriver.scala

Lines changed: 289 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -42,9 +42,45 @@ object Derive {
4242
def rec(remaining: List[AdtTypeNode])(using Quotes): Expr[F[T]] =
4343
remaining match
4444
case Nil => macroCall[T]
45+
case x0 :: x1 :: x2 :: x3 :: x4 :: x5 :: x6 :: x7 :: tail =>
46+
(x0.tpe.asType, x1.tpe.asType, x2.tpe.asType, x3.tpe.asType, x4.tpe.asType, x5.tpe.asType, x6.tpe.asType, x7.tpe.asType) match
47+
case ('[t0], '[t1], '[t2], '[t3], '[t4], '[t5], '[t6], '[t7]) =>
48+
'{
49+
given F[t0] = ${ macroCall[t0] }
50+
given F[t1] = ${ macroCall[t1] }
51+
given F[t2] = ${ macroCall[t2] }
52+
given F[t3] = ${ macroCall[t3] }
53+
given F[t4] = ${ macroCall[t4] }
54+
given F[t5] = ${ macroCall[t5] }
55+
given F[t6] = ${ macroCall[t6] }
56+
given F[t7] = ${ macroCall[t7] }
57+
${ rec(tail) }
58+
}
59+
case x0 :: x1 :: x2 :: x3 :: tail =>
60+
(x0.tpe.asType, x1.tpe.asType, x2.tpe.asType, x3.tpe.asType) match
61+
case ('[t0], '[t1], '[t2], '[t3]) =>
62+
'{
63+
given F[t0] = ${ macroCall[t0] }
64+
given F[t1] = ${ macroCall[t1] }
65+
given F[t2] = ${ macroCall[t2] }
66+
given F[t3] = ${ macroCall[t3] }
67+
${ rec(tail) }
68+
}
69+
case x0 :: x1 :: tail =>
70+
(x0.tpe.asType, x1.tpe.asType) match
71+
case ('[t0], '[t1]) =>
72+
'{
73+
given F[t0] = ${ macroCall[t0] }
74+
given F[t1] = ${ macroCall[t1] }
75+
${ rec(tail) }
76+
}
4577
case x :: tail =>
4678
x.tpe.asType match
47-
case '[t] => '{ implicit val x: F[t] = ${ macroCall[t] }; ${ rec(tail) } }
79+
case '[t] =>
80+
'{
81+
given F[t] = ${ macroCall[t] }
82+
${ rec(tail) }
83+
}
4884

4985
rec(subsWithoutImplicitTypeclassInstances[F](rootNode))
5086
}
@@ -518,10 +554,6 @@ abstract private[derivation] class Deriver[F[_]: Type, T: Type, Q <: Quotes](usi
518554
using Quotes): Expr[B] =
519555
'{ val x: A = $initialValue; ${ inner('x) } }
520556

521-
final def withVar[A: Type, B: Type](initialValue: Expr[A])(inner: Quotes ?=> Var { type V = A } => Expr[B])(
522-
using Quotes): Expr[B] =
523-
'{ var x: A = $initialValue; ${ inner(Var.of('x, newX => '{ x = $newX })) } }
524-
525557
sealed abstract class Val {
526558
type V
527559
def tpe: Type[V]
@@ -572,49 +604,271 @@ abstract private[derivation] class Deriver[F[_]: Type, T: Type, Q <: Quotes](usi
572604
next: Quotes ?=> IArray[Val] => Expr[B])(using Quotes): Expr[B] =
573605
val result = new Array[Val](array.size)
574606
def rec(ix: Int)(using Quotes): Expr[B] =
575-
if (ix < array.size) {
576-
val x = initialValue(array(ix))
577-
x.tpe match
578-
case '[t] =>
579-
withVal(x.as[t].get) { y =>
580-
result(ix) = Val.of(y)
581-
rec(ix + 1)
582-
}
583-
} else next(IArray.unsafeFromArray(result))
607+
array.size - ix match
608+
case n if n >= 8 =>
609+
val v0 = initialValue(array(ix + 0))
610+
val v1 = initialValue(array(ix + 1))
611+
val v2 = initialValue(array(ix + 2))
612+
val v3 = initialValue(array(ix + 3))
613+
val v4 = initialValue(array(ix + 4))
614+
val v5 = initialValue(array(ix + 5))
615+
val v6 = initialValue(array(ix + 6))
616+
val v7 = initialValue(array(ix + 7))
617+
(v0.tpe, v1.tpe, v2.tpe, v3.tpe, v4.tpe, v5.tpe, v6.tpe, v7.tpe) match
618+
case ('[t0], '[t1], '[t2], '[t3], '[t4], '[t5], '[t6], '[t7]) =>
619+
'{
620+
val x0: t0 = ${ v0.as[t0].get }
621+
val x1: t1 = ${ v1.as[t1].get }
622+
val x2: t2 = ${ v2.as[t2].get }
623+
val x3: t3 = ${ v3.as[t3].get }
624+
val x4: t4 = ${ v4.as[t4].get }
625+
val x5: t5 = ${ v5.as[t5].get }
626+
val x6: t6 = ${ v6.as[t6].get }
627+
val x7: t7 = ${ v7.as[t7].get }
628+
${
629+
result(ix + 0) = Val.of('x0)
630+
result(ix + 1) = Val.of('x1)
631+
result(ix + 2) = Val.of('x2)
632+
result(ix + 3) = Val.of('x3)
633+
result(ix + 4) = Val.of('x4)
634+
result(ix + 5) = Val.of('x5)
635+
result(ix + 6) = Val.of('x6)
636+
result(ix + 7) = Val.of('x7)
637+
rec(ix + 8)
638+
}
639+
}
640+
641+
case n if n >= 4 =>
642+
val v0 = initialValue(array(ix + 0))
643+
val v1 = initialValue(array(ix + 1))
644+
val v2 = initialValue(array(ix + 2))
645+
val v3 = initialValue(array(ix + 3))
646+
(v0.tpe, v1.tpe, v2.tpe, v3.tpe) match
647+
case ('[t0], '[t1], '[t2], '[t3]) =>
648+
'{
649+
val x0: t0 = ${ v0.as[t0].get }
650+
val x1: t1 = ${ v1.as[t1].get }
651+
val x2: t2 = ${ v2.as[t2].get }
652+
val x3: t3 = ${ v3.as[t3].get }
653+
${
654+
result(ix + 0) = Val.of('x0)
655+
result(ix + 1) = Val.of('x1)
656+
result(ix + 2) = Val.of('x2)
657+
result(ix + 3) = Val.of('x3)
658+
rec(ix + 4)
659+
}
660+
}
661+
662+
case n if n >= 2 =>
663+
val v0 = initialValue(array(ix + 0))
664+
val v1 = initialValue(array(ix + 1))
665+
(v0.tpe, v1.tpe) match
666+
case ('[t0], '[t1]) =>
667+
'{
668+
val x0: t0 = ${ v0.as[t0].get }
669+
val x1: t1 = ${ v1.as[t1].get }
670+
${
671+
result(ix + 0) = Val.of('x0)
672+
result(ix + 1) = Val.of('x1)
673+
rec(ix + 2)
674+
}
675+
}
676+
677+
case n if n >= 1 =>
678+
val v = initialValue(array(ix))
679+
v.tpe match
680+
case '[t] =>
681+
'{
682+
val x: t = ${ v.as[t].get }
683+
${
684+
result(ix) = Val.of('x)
685+
rec(ix + 1)
686+
}
687+
}
688+
689+
case _ => next(IArray.unsafeFromArray(result))
690+
584691
rec(0)
585692

586693
final def withVars[A, B: Type](array: IArray[A])(initialValue: Quotes ?=> A => Val)(
587694
next: Quotes ?=> IArray[Var] => Expr[B])(using Quotes): Expr[B] =
588695
val result = new Array[Var](array.size)
589696
def rec(ix: Int)(using Quotes): Expr[B] =
590-
if (ix < array.size) {
591-
val x = initialValue(array(ix))
592-
x.tpe match
593-
case '[t] =>
594-
withVar(x.as[t].get) { y =>
595-
result(ix) = y
596-
rec(ix + 1)
597-
}
598-
} else next(IArray.unsafeFromArray(result))
697+
array.size - ix match
698+
case n if n >= 8 =>
699+
val v0 = initialValue(array(ix + 0))
700+
val v1 = initialValue(array(ix + 1))
701+
val v2 = initialValue(array(ix + 2))
702+
val v3 = initialValue(array(ix + 3))
703+
val v4 = initialValue(array(ix + 4))
704+
val v5 = initialValue(array(ix + 5))
705+
val v6 = initialValue(array(ix + 6))
706+
val v7 = initialValue(array(ix + 7))
707+
(v0.tpe, v1.tpe, v2.tpe, v3.tpe, v4.tpe, v5.tpe, v6.tpe, v7.tpe) match
708+
case ('[t0], '[t1], '[t2], '[t3], '[t4], '[t5], '[t6], '[t7]) =>
709+
'{
710+
var x0: t0 = ${ v0.as[t0].get }
711+
var x1: t1 = ${ v1.as[t1].get }
712+
var x2: t2 = ${ v2.as[t2].get }
713+
var x3: t3 = ${ v3.as[t3].get }
714+
var x4: t4 = ${ v4.as[t4].get }
715+
var x5: t5 = ${ v5.as[t5].get }
716+
var x6: t6 = ${ v6.as[t6].get }
717+
var x7: t7 = ${ v7.as[t7].get }
718+
${
719+
result(ix + 0) = Var.of('x0, newX => '{ x0 = $newX })
720+
result(ix + 1) = Var.of('x1, newX => '{ x1 = $newX })
721+
result(ix + 2) = Var.of('x2, newX => '{ x2 = $newX })
722+
result(ix + 3) = Var.of('x3, newX => '{ x3 = $newX })
723+
result(ix + 4) = Var.of('x4, newX => '{ x4 = $newX })
724+
result(ix + 5) = Var.of('x5, newX => '{ x5 = $newX })
725+
result(ix + 6) = Var.of('x6, newX => '{ x6 = $newX })
726+
result(ix + 7) = Var.of('x7, newX => '{ x7 = $newX })
727+
rec(ix + 8)
728+
}
729+
}
730+
731+
case n if n >= 4 =>
732+
val v0 = initialValue(array(ix + 0))
733+
val v1 = initialValue(array(ix + 1))
734+
val v2 = initialValue(array(ix + 2))
735+
val v3 = initialValue(array(ix + 3))
736+
(v0.tpe, v1.tpe, v2.tpe, v3.tpe) match
737+
case ('[t0], '[t1], '[t2], '[t3]) =>
738+
'{
739+
var x0: t0 = ${ v0.as[t0].get }
740+
var x1: t1 = ${ v1.as[t1].get }
741+
var x2: t2 = ${ v2.as[t2].get }
742+
var x3: t3 = ${ v3.as[t3].get }
743+
${
744+
result(ix + 0) = Var.of('x0, newX => '{ x0 = $newX })
745+
result(ix + 1) = Var.of('x1, newX => '{ x1 = $newX })
746+
result(ix + 2) = Var.of('x2, newX => '{ x2 = $newX })
747+
result(ix + 3) = Var.of('x3, newX => '{ x3 = $newX })
748+
rec(ix + 4)
749+
}
750+
}
751+
752+
case n if n >= 2 =>
753+
val v0 = initialValue(array(ix + 0))
754+
val v1 = initialValue(array(ix + 1))
755+
(v0.tpe, v1.tpe) match
756+
case ('[t0], '[t1]) =>
757+
'{
758+
var x0: t0 = ${ v0.as[t0].get }
759+
var x1: t1 = ${ v1.as[t1].get }
760+
${
761+
result(ix + 0) = Var.of('x0, newX => '{ x0 = $newX })
762+
result(ix + 1) = Var.of('x1, newX => '{ x1 = $newX })
763+
rec(ix + 2)
764+
}
765+
}
766+
767+
case n if n >= 1 =>
768+
val v = initialValue(array(ix))
769+
v.tpe match
770+
case '[t]=>
771+
'{
772+
var x: t = ${ v.as[t].get }
773+
${
774+
result(ix + 0) = Var.of('x, newX => '{ x = $newX })
775+
rec(ix + 1)
776+
}
777+
}
778+
779+
case _ => next(IArray.unsafeFromArray(result))
780+
599781
rec(0)
600782

601783
final def withOptVals[A, B: Type](array: IArray[A])(initialValue: Quotes ?=> A => Option[Val])(
602784
next: Quotes ?=> IArray[Option[Val]] => Expr[B])(using Quotes): Expr[B] =
603-
val result = new Array[Option[Val]](array.size)
785+
val result = Array.fill[Option[Val]](array.size)(None)
786+
val initialValuesWithIndex = array.zipWithIndex.flatMap { case (x, i) => initialValue(x).map(_ -> i) }
604787
def rec(ix: Int)(using Quotes): Expr[B] =
605-
if (ix < array.size)
606-
initialValue(array(ix)) match
607-
case Some(x) =>
608-
x.tpe match
609-
case '[t] =>
610-
withVal(x.as[t].get) { y =>
611-
result(ix) = Some(Val.of(y))
788+
initialValuesWithIndex.size - ix match
789+
case n if n >= 8 =>
790+
val (v0, i0) = initialValuesWithIndex(ix + 0)
791+
val (v1, i1) = initialValuesWithIndex(ix + 1)
792+
val (v2, i2) = initialValuesWithIndex(ix + 2)
793+
val (v3, i3) = initialValuesWithIndex(ix + 3)
794+
val (v4, i4) = initialValuesWithIndex(ix + 4)
795+
val (v5, i5) = initialValuesWithIndex(ix + 5)
796+
val (v6, i6) = initialValuesWithIndex(ix + 6)
797+
val (v7, i7) = initialValuesWithIndex(ix + 7)
798+
(v0.tpe, v1.tpe, v2.tpe, v3.tpe, v4.tpe, v5.tpe, v6.tpe, v7.tpe) match
799+
case ('[t0], '[t1], '[t2], '[t3], '[t4], '[t5], '[t6], '[t7]) =>
800+
'{
801+
val x0: t0 = ${ v0.as[t0].get }
802+
val x1: t1 = ${ v1.as[t1].get }
803+
val x2: t2 = ${ v2.as[t2].get }
804+
val x3: t3 = ${ v3.as[t3].get }
805+
val x4: t4 = ${ v4.as[t4].get }
806+
val x5: t5 = ${ v5.as[t5].get }
807+
val x6: t6 = ${ v6.as[t6].get }
808+
val x7: t7 = ${ v7.as[t7].get }
809+
${
810+
result(i0) = Some(Val.of('x0))
811+
result(i1) = Some(Val.of('x1))
812+
result(i2) = Some(Val.of('x2))
813+
result(i3) = Some(Val.of('x3))
814+
result(i4) = Some(Val.of('x4))
815+
result(i5) = Some(Val.of('x5))
816+
result(i6) = Some(Val.of('x6))
817+
result(i7) = Some(Val.of('x7))
818+
rec(ix + 8)
819+
}
820+
}
821+
822+
case n if n >= 4 =>
823+
val (v0, i0) = initialValuesWithIndex(ix + 0)
824+
val (v1, i1) = initialValuesWithIndex(ix + 1)
825+
val (v2, i2) = initialValuesWithIndex(ix + 2)
826+
val (v3, i3) = initialValuesWithIndex(ix + 3)
827+
(v0.tpe, v1.tpe, v2.tpe, v3.tpe) match
828+
case ('[t0], '[t1], '[t2], '[t3]) =>
829+
'{
830+
val x0: t0 = ${ v0.as[t0].get }
831+
val x1: t1 = ${ v1.as[t1].get }
832+
val x2: t2 = ${ v2.as[t2].get }
833+
val x3: t3 = ${ v3.as[t3].get }
834+
${
835+
result(i0) = Some(Val.of('x0))
836+
result(i1) = Some(Val.of('x1))
837+
result(i2) = Some(Val.of('x2))
838+
result(i3) = Some(Val.of('x3))
839+
rec(ix + 4)
840+
}
841+
}
842+
843+
case n if n >= 2 =>
844+
val (v0, i0) = initialValuesWithIndex(ix + 0)
845+
val (v1, i1) = initialValuesWithIndex(ix + 1)
846+
(v0.tpe, v1.tpe) match
847+
case ('[t0], '[t1]) =>
848+
'{
849+
val x0: t0 = ${ v0.as[t0].get }
850+
val x1: t1 = ${ v1.as[t1].get }
851+
${
852+
result(i0) = Some(Val.of('x0))
853+
result(i1) = Some(Val.of('x1))
854+
rec(ix + 2)
855+
}
856+
}
857+
858+
case n if n >= 1 =>
859+
val (v, i) = initialValuesWithIndex(ix)
860+
v.tpe match
861+
case '[t] =>
862+
'{
863+
val x: t = ${ v.as[t].get }
864+
${
865+
result(i) = Some(Val.of('x))
612866
rec(ix + 1)
613867
}
614-
case None =>
615-
result(ix) = None
616-
rec(ix + 1)
617-
else next(IArray.unsafeFromArray(result))
868+
}
869+
870+
case _ => next(IArray.unsafeFromArray(result))
871+
618872
rec(0)
619873

620874
final def companionApply(companion: Symbol, typeArgs: List[TypeRepr], args: List[Term]): Term =

0 commit comments

Comments
 (0)