Crazy JVM stack requirements for compiling big-ish case class?
lihaoyi opened this issue · 0 comments
reproduction steps
Full code and repro commands:
$ cat mill
#!/usr/bin/env sh
# This is a wrapper script, that automatically download mill from GitHub release pages
# You can give the required mill version with MILL_VERSION env variable
# If no version is given, it falls back to the value of DEFAULT_MILL_VERSION
DEFAULT_MILL_VERSION=0.9.5-52-ef6d5d
set -e
if [ -z "$MILL_VERSION" ] ; then
if [ -f ".mill-version" ] ; then
MILL_VERSION="$(head -n 1 .mill-version 2> /dev/null)"
elif [ -f "mill" ] && [ "$BASH_SOURCE" != "mill" ] ; then
MILL_VERSION=$(grep -F "DEFAULT_MILL_VERSION=" "mill" | head -n 1 | cut -d= -f2)
else
MILL_VERSION=$DEFAULT_MILL_VERSION
fi
fi
if [ "x${XDG_CACHE_HOME}" != "x" ] ; then
MILL_DOWNLOAD_PATH="${XDG_CACHE_HOME}/mill/download"
else
MILL_DOWNLOAD_PATH="${HOME}/.cache/mill/download"
fi
MILL_EXEC_PATH="${MILL_DOWNLOAD_PATH}/${MILL_VERSION}"
version_remainder="$MILL_VERSION"
MILL_MAJOR_VERSION="${version_remainder%%.*}"; version_remainder="${version_remainder#*.}"
MILL_MINOR_VERSION="${version_remainder%%.*}"; version_remainder="${version_remainder#*.}"
if [ ! -x "$MILL_EXEC_PATH" ] ; then
mkdir -p $MILL_DOWNLOAD_PATH
if [ "$MILL_MAJOR_VERSION" -gt 0 ] || [ "$MILL_MINOR_VERSION" -ge 5 ] ; then
ASSEMBLY="-assembly"
fi
DOWNLOAD_FILE=$MILL_EXEC_PATH-tmp-download
MILL_DOWNLOAD_URL="https://github.com/lihaoyi/mill/releases/download/${MILL_VERSION%%-*}/$MILL_VERSION${ASSEMBLY}"
curl --fail -L -o "$DOWNLOAD_FILE" "$MILL_DOWNLOAD_URL"
chmod +x "$DOWNLOAD_FILE"
mv "$DOWNLOAD_FILE" "$MILL_EXEC_PATH"
unset DOWNLOAD_FILE
unset MILL_DOWNLOAD_URL
fi
unset MILL_DOWNLOAD_PATH
unset MILL_VERSION
exec $MILL_EXEC_PATH "$@"
lihaoyi test$ cat build.sc
import mill.scalalib._
object test extends ScalaModule{
def scalaVersion = "2.13.4"
}
lihaoyi test$ cat test/src/Big150.scala
case class Big150(_0: Int, _1: Int, _2: Int, _3: Int, _4: Int, _5: Int, _6: Int, _7: Int,
_8: Int, _9: Int, _10: Int, _11: Int, _12: Int, _13: Int, _14: Int,
_15: Int, _16: Int, _17: Int, _18: Int, _19: Int, _20: Int, _21: Int,
_22: Int, _23: Int, _24: Int, _25: Int, _26: Int, _27: Int, _28: Int,
_29: Int, _30: Int, _31: Int, _32: Int, _33: Int, _34: Int, _35: Int,
_36: Int, _37: Int, _38: Int, _39: Int, _40: Int, _41: Int, _42: Int,
_43: Int, _44: Int, _45: Int, _46: Int, _47: Int, _48: Int, _49: Int,
_50: Int, _51: Int, _52: Int, _53: Int, _54: Int, _55: Int, _56: Int,
_57: Int, _58: Int, _59: Int, _60: Int, _61: Int, _62: Int, _63: Int,
_64: Int, _65: Int, _66: Int, _67: Int, _68: Int, _69: Int, _70: Int,
_71: Int, _72: Int, _73: Int, _74: Int, _75: Int, _76: Int, _77: Int,
_78: Int, _79: Int, _80: Int, _81: Int, _82: Int, _83: Int, _84: Int,
_85: Int, _86: Int, _87: Int, _88: Int, _89: Int, _90: Int, _91: Int,
_92: Int, _93: Int, _94: Int, _95: Int, _96: Int, _97: Int, _98: Int,
_99: Int, _100: Int, _101: Int, _102: Int, _103: Int, _104: Int,
_105: Int, _106: Int, _107: Int, _108: Int, _109: Int, _110: Int,
_111: Int, _112: Int, _113: Int, _114: Int, _115: Int, _116: Int,
_117: Int, _118: Int, _119: Int, _120: Int, _121: Int, _122: Int,
_123: Int, _124: Int, _125: Int, _126: Int, _127: Int, _128: Int,
_129: Int, _130: Int, _131: Int, _132: Int, _133: Int, _134: Int,
_135: Int, _136: Int, _137: Int, _138: Int, _139: Int, _140: Int,
_141: Int, _142: Int, _143: Int, _144: Int, _145: Int, _146: Int,
_147: Int, _148: Int, _149: Int)
lihaoyi test$ JAVA_OPTS=-Xss1024M ./mill test.compile
[26/26] test.compile
[info] compiling 1 Scala source to /Users/lihaoyi/Github/test/out/test/compile/dest/classes ...
[error] ## Exception when compiling 1 sources to /Users/lihaoyi/Github/test/out/test/compile/dest/classes
[error] java.lang.StackOverflowError
The exact failure seems non-deterministic. I've had it compile before with the default stack size (1mb?), and have had it pass before with 996mb, but right now i can't get it to compile even with 1024mb of stack, which is the limit the JVM allows you to set.
Making it a normal non-case
class seems to make the problem go away, as does adding an override def equals(other: Any): Boolean = false
It feels like the code being auto-generated for the case class is probably making the compiler work too hard at inferring something? Or maybe we're generating deeply imbalanced ASTs in the .equals
or .hashCode
implementation that can be fixed by adding some strategic parens to group things into a balanced tree of O(log n) depth instead of O(n)?
I can accept that bigger programs/expressions/definitions may require more resources, but requiring >1024mb of stack space is unreasonable.
I bumped into this in the context of com-lihaoyi/upickle#348, where I'm trying to make my JSON library support large case classes, e.g. modelling Github's "repository" JSON response which has ~450 fields. Given this minimal repro falls apart at 150 fields, there's clearly a long way to go to support 450!