Skip to content
Closed
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
9bf9aa9
Introduce `InvokeLike` to extract common logic from `StaticInvoke`, `…
ueshin Nov 15, 2016
28f6200
Refactor to remove unneeded null checking and fix nullability of `New…
ueshin Nov 15, 2016
2f30f53
Modify to short circuit if arguments have null when propageteNull == …
ueshin Nov 16, 2016
9ac3f28
Add a comment for `prepareArguments()`.
ueshin Nov 17, 2016
5ad6966
Define a variable for `propagateNull && arguments.exists(_.nullable)`.
ueshin Nov 17, 2016
a0ac177
Add a missing param comment for `propagateNull`.
ueshin Nov 17, 2016
757d33e
Rename variable from `argsHaveNull` to `containsNullInArguments`.
ueshin Nov 17, 2016
240fde4
Modify `InvokeLike` not to extend `Expression` because no need to ext…
ueshin Nov 17, 2016
6f6e0b3
Fix comments.
ueshin Nov 17, 2016
2bd6e50
Modify for readability.
ueshin Nov 17, 2016
bcb93db
Make `InvokeLike` extend `Expression` and `NonSQLExpression`.
ueshin Nov 17, 2016
d448b60
Use `propagatingNull`.
ueshin Nov 17, 2016
8894a96
Use `needNullCheck` instread of `propagatingNull`.
ueshin Nov 17, 2016
ca4558f
Modify `prepareArguments()` to return the result of argument null che…
ueshin Nov 17, 2016
4d9a037
Skip evaluate arguments if `obj.isNull == true` in `Invoke`.
ueshin Nov 17, 2016
ebb1241
Modify to prepare for further optimization.
ueshin Nov 17, 2016
99a59b2
Revert a part of last 2 commits.
ueshin Nov 17, 2016
243888a
Revert "Revert a part of last 2 commits."
ueshin Nov 17, 2016
e12a9bd
Revert "Modify to prepare for further optimization."
ueshin Nov 17, 2016
2c52d91
Use `obj.isNull` instead of `ev.isNull`.
ueshin Nov 17, 2016
43d2693
Revert and use a simple case.
ueshin Nov 17, 2016
bd9c09f
Remove unused argument.
ueshin Nov 18, 2016
501095f
Remove unneeded if.
ueshin Nov 18, 2016
1baac55
Modify generated code of `Invoke`.
ueshin Nov 18, 2016
831c521
Use resultIsNull as ev.isNull in `NewInstance`.
ueshin Nov 18, 2016
0b210f8
Remove unneeded zipWithIndex.
ueshin Nov 18, 2016
f8acda6
Use local variables for `resultIsNull`s and `argValue`s.
ueshin Nov 18, 2016
fe2871c
Revert "Use local variables for `resultIsNull`s and `argValue`s."
ueshin Nov 18, 2016
c88a1ff
Optimize a code in `StaticInvoke`.
ueshin Nov 19, 2016
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,78 @@ import org.apache.spark.sql.catalyst.expressions.codegen.{CodegenContext, ExprCo
import org.apache.spark.sql.catalyst.util.{ArrayBasedMapData, GenericArrayData}
import org.apache.spark.sql.types._

/**
* Common base class for [[StaticInvoke]], [[Invoke]], and [[NewInstance]].
*/
trait InvokeLike extends Expression with NonSQLExpression {

def arguments: Seq[Expression]

def propagateNull: Boolean

protected lazy val needNullCheck: Boolean = propagateNull && arguments.exists(_.nullable)

/**
* Prepares codes for arguments.
*
* - generate codes for argument.
* - use ctx.splitExpressions() to not exceed 64kb JVM limit while preparing arguments.
* - avoid some of nullabilty checking which are not needed because the expression is not
* nullable.
* - when needNullCheck == true, short circuit if we found one of arguments is null because
* preparing rest of arguments can be skipped in the case.
*
* @param ctx a [[CodegenContext]]
* @return (code to prepare arguments, argument string, result of argument null check)
*/
def prepareArguments(ctx: CodegenContext): (String, String, String) = {

val resultIsNull = if (needNullCheck) {
val resultIsNull = ctx.freshName("resultIsNull")
ctx.addMutableState("boolean", resultIsNull, "")
resultIsNull
} else {
"false"
}
val argValues = arguments.map { e =>
val argValue = ctx.freshName("argValue")
ctx.addMutableState(ctx.javaType(e.dataType), argValue, "")
argValue
}

val argCodes = if (needNullCheck) {
val reset = s"$resultIsNull = false;"
val argCodes = arguments.zipWithIndex.map { case (e, i) =>
val expr = e.genCode(ctx)
val updateResultIsNull = if (e.nullable) {
s"$resultIsNull = ${expr.isNull};"
} else {
""
}
s"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how about:

val updateContainsNull = if (e.nullable) {
  "$containsNull = ${expr.isNull};"
} else {
  ""
}
s"""
  if (!$containsNull) {
      ${expr.code}
      $updateContainsNull
      ${argValues(i)} = ${expr.value};
  }
"""

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, I'll use it.

if (!$resultIsNull) {
${expr.code}
$updateResultIsNull
${argValues(i)} = ${expr.value};
}
"""
}
reset +: argCodes
} else {
arguments.zipWithIndex.map { case (e, i) =>
val expr = e.genCode(ctx)
s"""
${expr.code}
${argValues(i)} = ${expr.value};
"""
}
}
val argCode = ctx.splitExpressions(ctx.INPUT_ROW, argCodes)

(argCode, argValues.mkString(", "), resultIsNull)
}
}

/**
* Invokes a static function, returning the result. By default, any of the arguments being null
* will result in returning null instead of calling the function.
Expand All @@ -50,7 +122,7 @@ case class StaticInvoke(
dataType: DataType,
functionName: String,
arguments: Seq[Expression] = Nil,
propagateNull: Boolean = true) extends Expression with NonSQLExpression {
propagateNull: Boolean = true) extends InvokeLike {

val objectName = staticObject.getName.stripSuffix("$")

Expand All @@ -62,16 +134,10 @@ case class StaticInvoke(

override def doGenCode(ctx: CodegenContext, ev: ExprCode): ExprCode = {
val javaType = ctx.javaType(dataType)
val argGen = arguments.map(_.genCode(ctx))
val argString = argGen.map(_.value).mkString(", ")

val callFunc = s"$objectName.$functionName($argString)"
val (argCode, argString, resultIsNull) = prepareArguments(ctx)

val setIsNull = if (propagateNull && arguments.nonEmpty) {
s"boolean ${ev.isNull} = ${argGen.map(_.isNull).mkString(" || ")};"
} else {
s"boolean ${ev.isNull} = false;"
}
val callFunc = s"$objectName.$functionName($argString)"

// If the function can return null, we do an extra check to make sure our null bit is still set
// correctly.
Expand All @@ -82,9 +148,9 @@ case class StaticInvoke(
}

val code = s"""
${argGen.map(_.code).mkString("\n")}
$setIsNull
final $javaType ${ev.value} = ${ev.isNull} ? ${ctx.defaultValue(dataType)} : $callFunc;
$argCode
boolean ${ev.isNull} = $resultIsNull;
final $javaType ${ev.value} = $resultIsNull ? ${ctx.defaultValue(dataType)} : $callFunc;
$postNullCheck
"""
ev.copy(code = code)
Expand All @@ -103,13 +169,15 @@ case class StaticInvoke(
* @param functionName The name of the method to call.
* @param dataType The expected return type of the function.
* @param arguments An optional list of expressions, whos evaluation will be passed to the function.
* @param propagateNull When true, and any of the arguments is null, null will be returned instead
* of calling the function.
*/
case class Invoke(
targetObject: Expression,
functionName: String,
dataType: DataType,
arguments: Seq[Expression] = Nil,
propagateNull: Boolean = true) extends Expression with NonSQLExpression {
propagateNull: Boolean = true) extends InvokeLike {

override def nullable: Boolean = true
override def children: Seq[Expression] = targetObject +: arguments
Expand All @@ -131,8 +199,8 @@ case class Invoke(
override def doGenCode(ctx: CodegenContext, ev: ExprCode): ExprCode = {
val javaType = ctx.javaType(dataType)
val obj = targetObject.genCode(ctx)
val argGen = arguments.map(_.genCode(ctx))
val argString = argGen.map(_.value).mkString(", ")

val (argCode, argString, resultIsNull) = prepareArguments(ctx)

val returnPrimitive = method.isDefined && method.get.getReturnType.isPrimitive
val needTryCatch = method.isDefined && method.get.getExceptionTypes.nonEmpty
Expand Down Expand Up @@ -164,28 +232,26 @@ case class Invoke(
"""
}

val setIsNull = if (propagateNull && arguments.nonEmpty) {
s"boolean ${ev.isNull} = ${obj.isNull} || ${argGen.map(_.isNull).mkString(" || ")};"
} else {
s"boolean ${ev.isNull} = ${obj.isNull};"
}

// If the function can return null, we do an extra check to make sure our null bit is still set
// correctly.
val postNullCheck = if (ctx.defaultValue(dataType) == "null") {
s"${ev.isNull} = ${ev.value} == null;"
} else {
""
}

val code = s"""
${obj.code}
${argGen.map(_.code).mkString("\n")}
$setIsNull
boolean ${ev.isNull} = true;
$javaType ${ev.value} = ${ctx.defaultValue(dataType)};
if (!${ev.isNull}) {
$evaluate
if (!${obj.isNull}) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anyway, I think this new code looks clearer than before, what do you think? @ueshin

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I agree with you. Thank you for your suggestion.

$argCode
${ev.isNull} = $resultIsNull;
if (!${ev.isNull}) {
$evaluate
}
$postNullCheck
}
$postNullCheck
"""
ev.copy(code = code)
}
Expand Down Expand Up @@ -223,10 +289,10 @@ case class NewInstance(
arguments: Seq[Expression],
propagateNull: Boolean,
dataType: DataType,
outerPointer: Option[() => AnyRef]) extends Expression with NonSQLExpression {
outerPointer: Option[() => AnyRef]) extends InvokeLike {
private val className = cls.getName

override def nullable: Boolean = propagateNull
override def nullable: Boolean = needNullCheck

override def children: Seq[Expression] = arguments

Expand All @@ -245,52 +311,25 @@ case class NewInstance(

override def doGenCode(ctx: CodegenContext, ev: ExprCode): ExprCode = {
val javaType = ctx.javaType(dataType)
val argIsNulls = ctx.freshName("argIsNulls")
ctx.addMutableState("boolean[]", argIsNulls,
s"$argIsNulls = new boolean[${arguments.size}];")
val argValues = arguments.zipWithIndex.map { case (e, i) =>
val argValue = ctx.freshName("argValue")
ctx.addMutableState(ctx.javaType(e.dataType), argValue, "")
argValue
}

val argCodes = arguments.zipWithIndex.map { case (e, i) =>
val expr = e.genCode(ctx)
expr.code + s"""
$argIsNulls[$i] = ${expr.isNull};
${argValues(i)} = ${expr.value};
"""
}
val argCode = ctx.splitExpressions(ctx.INPUT_ROW, argCodes)
val (argCode, argString, resultIsNull) = prepareArguments(ctx)

val outer = outerPointer.map(func => Literal.fromObject(func()).genCode(ctx))

var isNull = ev.isNull
val setIsNull = if (propagateNull && arguments.nonEmpty) {
s"""
boolean $isNull = false;
for (int idx = 0; idx < ${arguments.length}; idx++) {
if ($argIsNulls[idx]) { $isNull = true; break; }
}
"""
} else {
isNull = "false"
""
}
ev.isNull = resultIsNull

val constructorCall = outer.map { gen =>
s"""${gen.value}.new ${cls.getSimpleName}(${argValues.mkString(", ")})"""
s"${gen.value}.new ${cls.getSimpleName}($argString)"
}.getOrElse {
s"new $className(${argValues.mkString(", ")})"
s"new $className($argString)"
}

val code = s"""
$argCode
${outer.map(_.code).getOrElse("")}
$setIsNull
final $javaType ${ev.value} = $isNull ? ${ctx.defaultValue(javaType)} : $constructorCall;
"""
ev.copy(code = code, isNull = isNull)
final $javaType ${ev.value} = ${ev.isNull} ? ${ctx.defaultValue(javaType)} : $constructorCall;
"""
ev.copy(code = code)
}

override def toString: String = s"newInstance($cls)"
Expand Down