LuciferYang commented on code in PR #58881:
URL: https://github.com/apache/spark/pull/58881#discussion_r4466842384
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/codegen/CodeGenerator.scala:
##########
@@ -1322,23 +1330,26 @@ class CodegenContext extends Logging {
val args = mutable.LinkedHashMap.empty[String, VariableValue]
val inputsToEvaluate = mutable.ArrayBuffer.empty[ExprCode]
// Whether the code can read `v` from the method: false for a value no
parameter can carry
- // where the policy asks for names. `ExpandExec` hands out a
`VariableValue` naming a slot of a
- // compacted mutable state array, and a `SimpleExprValue` is an expression
rather than a name
+ // where the policy asks for names. A `SimpleExprValue` is an expression
rather than a name
// -- `posexplode_outer` gives its position the nullness `index == -1`,
and a `Byte` or `Short`
// literal's value is `(byte)1`. A field or a literal needs no parameter
and is read as it
- // stands; so is a field handed out as a `VariableValue` where the policy
says so, passing one
- // being refused by the split methods' own check, a slot of a compacted
array included.
+ // stands; so is a `VariableValue` naming a field `addMutableState`
registered, where the
+ // policy says so, passing one being refused by the whole stage split's
own check (`splitCall`)
+ // in tests. A slot of a compacted array is not a name at all, so it has
to be handed out as a
+ // `GlobalValue`, and `INPUT_ROW` cannot be one. Testing checks for one
handed out as a
+ // variable: here, where the policy asks for names, for what the walk
reaches before its first
+ // refusal; elsewhere, in the method taking it (`assertMethodParameters`).
The row a whole stage
+ // split appends is not checked (SPARK-60510).
def take(v: ExprValue): Boolean = v match {
case local: VariableValue =>
val name = local.variableName
- val isName = name.nonEmpty &&
Character.isJavaIdentifierStart(name.head) &&
- name.forall(Character.isJavaIdentifierPart)
- // A slot of a compacted array is a field under every policy, since no
parameter can be
- // named `array[i]`, where the whole stage split is on; with it off,
every policy answers
- // as it did before that split existed. Another field is passed where
the policy says so.
- val isField = (isCompactedSlot(name) && wholeStageSplit !=
WholeStageSplit.Off) ||
- (policy.fieldsNeedNoArgument && mutableStateNames.contains(name))
- val taken = isName || isField || !policy.onlyNames
+ val isName = isJavaName(name)
+ if (Utils.isTesting && policy.onlyNames) {
+ assert(isName, s"$name cannot name a parameter of a method this code
moves into; " +
+ nameFix(name))
+ }
+ val isField = policy.fieldsNeedNoArgument &&
mutableStateNames.contains(name)
+ val taken = isName || !policy.onlyNames
Review Comment:
Done in 97c803e1beb: `CodegenContext.checkParameterNames` defaults to
`Utils.isTesting`, and `collectInputs` and `assertParametersAreNames` use it.
With it off, `CollectInputsSuite` checks that the whole-stage split and common
expression policies refuse the method for a slot and for an `INPUT_ROW` that is
a slot, and `WithExpressionEvalSuite` checks the inline body again with
`markersIn(...) == 4`. With `val taken = true`, those three tests fail.
##########
sql/core/src/main/scala/org/apache/spark/sql/execution/ExpandExec.scala:
##########
@@ -189,9 +189,15 @@ case class ExpandExec(
"resultValue",
v => s"$v = ${CodeGenerator.defaultValue(firstExpr.dataType)};")
+ // `addMutableState` hands back a field: a name of its own, or a
slot in a compacted array
+ // (`mutableStateArray_0[3]`) for a state not inlined. Either is
reachable from every
+ // method of the generated class and is not a local variable, so a
consumer collecting
+ // local variables to pass into a split function must not take it
for one -- a slot
+ // expression is not a legal parameter name. Whole-stage
subexpression elimination wraps
+ // its own mutable state the same way.
Review Comment:
Done in 97c803e1beb: the `@return` of `addMutableState` now says to hand the
result out with `JavaCode.global` or `isNullGlobal`.
##########
sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/expressions/codegen/CodeGenerator.scala:
##########
@@ -1411,13 +1422,15 @@ class CodegenContext extends Logging {
}
}
- /**
- * Whether `name` is a slot of a compacted mutable state array, such as
`mutableStateArray_0[3]`,
- * which `addMutableState` hands out for a state it does not inline: a field
of the class.
- */
- private def isCompactedSlot(name: String): Boolean = name match {
- case CodeGenerator.CompactedSlot(array) =>
mutableStateNames.contains(array)
- case _ => false
+ /** Whether `name` can name a parameter: a slot of a compacted array,
`array[i]`, cannot. */
+ private def isJavaName(name: String): Boolean =
+ SourceVersion.isIdentifier(name) && !SourceVersion.isKeyword(name)
+
+ /** How the producer of `name`, a value that cannot name a parameter, can
hand it out instead. */
+ private def nameFix(name: String): String = if (name == INPUT_ROW) {
+ "set INPUT_ROW to a local or a field added with forceInline = true"
+ } else {
+ "hand out a field with JavaCode.global or isNullGlobal"
}
Review Comment:
Applied in 97c803e1beb, with an assertion on the hint for `false` in
`CollectInputsSuite`.
##########
sql/core/src/test/scala/org/apache/spark/sql/execution/WholeStageCodegenSuite.scala:
##########
@@ -1932,6 +1962,55 @@ class WholeStageCodegenSuite extends SharedSparkSession
query.collect().toSeq
}
+ /**
+ * Checks the query `query` builds from a FROM clause that UNPIVOTs two
string columns into
+ * `v`, with the whole stage split on and off: a method named like `method`
calls `lpad` on the
+ * slot of the compacted mutable state array that `ExpandExec` holds `v` in,
`check` holds of
+ * the code, the code compiles, and the rows match those computed without
whole stage codegen.
+ * With the slot handed out as a local variable instead, building that
method fails the testing
+ * check on its parameters.
+ */
+ private def checkExpandSlotRead(
+ method: String,
+ query: String => String,
+ check: Seq[CodeAndComment] => Unit = _ => ()): Unit = {
+ withTempView("t") {
+ spark.range(10).selectExpr("concat('a', CAST(id AS STRING)) AS a",
+ "concat('b', CAST(id AS STRING)) AS b").createOrReplaceTempView("t")
+ def df: DataFrame = sql(query("FROM t UNPIVOT (v FOR c IN (a, b))"))
+ val expected = withoutWholeStage(df)
+ Seq("true", "false").foreach { split =>
+ withSQLConf(SQLConf.WHOLESTAGE_SPLIT_EXPRESSIONS.key -> split) {
+ val code = genCode(df)
+ // A subexpression method or a switch case also writes into a slot
of the same array,
+ // so the array's name alone does not show a read.
+ assert(code.exists(c => methodsHolding(c,
"].lpad(").exists(_.contains(method))),
+ s"$method $split")
+ withClue(s"$method $split: ")(check(code))
+ code.foreach(CodeGenerator.compile)
+ checkAnswer(df, expected)
+ }
+ }
+ withSQLConf(SQLConf.WHOLESTAGE_SPLIT_EXPRESSIONS.key -> "false") {
+ val e = intercept[AssertionError](genCodeWithLocalExpandOutput(df))
+ assert(s"\\w*$method\\w* takes
\\w+\\[\\d+\\]".r.findFirstIn(e.getMessage).isDefined,
+ e.getMessage)
+ }
+ }
+ }
+
+ /**
+ * `genCode`, with the bottom `ExpandExec` of the plan handing out its
output as local variables,
+ * as it did before SPARK-59589.
+ */
+ private def genCodeWithLocalExpandOutput(df: DataFrame): Seq[CodeAndComment]
=
Review Comment:
Done in 97c803e1beb: `genCode` takes the change of the plan, and the helper
is gone.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]