@@ -113,7 +113,7 @@ class CodegenContext {
113
113
val idx = references.length
114
114
references += obj
115
115
val clsName = Option (className).getOrElse(obj.getClass.getName)
116
- addMutableState(clsName, term, s " this. $term = ( $clsName) references[ $idx]; " )
116
+ addMutableState(clsName, term, s " $term = ( $clsName) references[ $idx]; " )
117
117
term
118
118
}
119
119
@@ -202,16 +202,6 @@ class CodegenContext {
202
202
partitionInitializationStatements.mkString(" \n " )
203
203
}
204
204
205
- /**
206
- * Holding all the functions those will be added into generated class.
207
- */
208
- val addedFunctions : mutable.Map [String , String ] =
209
- mutable.Map .empty[String , String ]
210
-
211
- def addNewFunction (funcName : String , funcCode : String ): Unit = {
212
- addedFunctions += ((funcName, funcCode))
213
- }
214
-
215
205
/**
216
206
* Holds expressions that are equivalent. Used to perform subexpression elimination
217
207
* during codegen.
@@ -233,10 +223,118 @@ class CodegenContext {
233
223
// The collection of sub-expression result resetting methods that need to be called on each row.
234
224
val subexprFunctions = mutable.ArrayBuffer .empty[String ]
235
225
236
- def declareAddedFunctions (): String = {
237
- addedFunctions.map { case (funcName, funcCode) => funcCode }.mkString(" \n " )
226
+ private val outerClassName = " OuterClass"
227
+
228
+ /**
229
+ * Holds the class and instance names to be generated, where `OuterClass` is a placeholder
230
+ * standing for whichever class is generated as the outermost class and which will contain any
231
+ * nested sub-classes. All other classes and instance names in this list will represent private,
232
+ * nested sub-classes.
233
+ */
234
+ private val classes : mutable.ListBuffer [(String , String )] =
235
+ mutable.ListBuffer [(String , String )](outerClassName -> null )
236
+
237
+ // A map holding the current size in bytes of each class to be generated.
238
+ private val classSize : mutable.Map [String , Int ] =
239
+ mutable.Map [String , Int ](outerClassName -> 0 )
240
+
241
+ // Nested maps holding function names and their code belonging to each class.
242
+ private val classFunctions : mutable.Map [String , mutable.Map [String , String ]] =
243
+ mutable.Map (outerClassName -> mutable.Map .empty[String , String ])
244
+
245
+ // Returns the size of the most recently added class.
246
+ private def currClassSize (): Int = classSize(classes.head._1)
247
+
248
+ // Returns the class name and instance name for the most recently added class.
249
+ private def currClass (): (String , String ) = classes.head
250
+
251
+ // Adds a new class. Requires the class' name, and its instance name.
252
+ private def addClass (className : String , classInstance : String ): Unit = {
253
+ classes.prepend(className -> classInstance)
254
+ classSize += className -> 0
255
+ classFunctions += className -> mutable.Map .empty[String , String ]
238
256
}
239
257
258
+ /**
259
+ * Adds a function to the generated class. If the code for the `OuterClass` grows too large, the
260
+ * function will be inlined into a new private, nested class, and a instance-qualified name for
261
+ * the function will be returned. Otherwise, the function will be inined to the `OuterClass` the
262
+ * simple `funcName` will be returned.
263
+ *
264
+ * @param funcName the class-unqualified name of the function
265
+ * @param funcCode the body of the function
266
+ * @param inlineToOuterClass whether the given code must be inlined to the `OuterClass`. This
267
+ * can be necessary when a function is declared outside of the context
268
+ * it is eventually referenced and a returned qualified function name
269
+ * cannot otherwise be accessed.
270
+ * @return the name of the function, qualified by class if it will be inlined to a private,
271
+ * nested sub-class
272
+ */
273
+ def addNewFunction (
274
+ funcName : String ,
275
+ funcCode : String ,
276
+ inlineToOuterClass : Boolean = false ): String = {
277
+ // The number of named constants that can exist in the class is limited by the Constant Pool
278
+ // limit, 65,536. We cannot know how many constants will be inserted for a class, so we use a
279
+ // threshold of 1600k bytes to determine when a function should be inlined to a private, nested
280
+ // sub-class.
281
+ val (className, classInstance) = if (inlineToOuterClass) {
282
+ outerClassName -> " "
283
+ } else if (currClassSize > 1600000 ) {
284
+ val className = freshName(" NestedClass" )
285
+ val classInstance = freshName(" nestedClassInstance" )
286
+
287
+ addClass(className, classInstance)
288
+
289
+ className -> classInstance
290
+ } else {
291
+ currClass()
292
+ }
293
+
294
+ classSize(className) += funcCode.length
295
+ classFunctions(className) += funcName -> funcCode
296
+
297
+ if (className == outerClassName) {
298
+ funcName
299
+ } else {
300
+
301
+ s " $classInstance. $funcName"
302
+ }
303
+ }
304
+
305
+ /**
306
+ * Instantiates all nested, private sub-classes as objects to the `OuterClass`
307
+ */
308
+ private [sql] def initNestedClasses (): String = {
309
+ // Nested, private sub-classes have no mutable state (though they do reference the outer class'
310
+ // mutable state), so we declare and initialize them inline to the OuterClass.
311
+ classes.filter(_._1 != outerClassName).map {
312
+ case (className, classInstance) =>
313
+ s " private $className $classInstance = new $className(); "
314
+ }.mkString(" \n " )
315
+ }
316
+
317
+ /**
318
+ * Declares all function code that should be inlined to the `OuterClass`.
319
+ */
320
+ private [sql] def declareAddedFunctions (): String = {
321
+ classFunctions(outerClassName).values.mkString(" \n " )
322
+ }
323
+
324
+ /**
325
+ * Declares all nested, private sub-classes and the function code that should be inlined to them.
326
+ */
327
+ private [sql] def declareNestedClasses (): String = {
328
+ classFunctions.filterKeys(_ != outerClassName).map {
329
+ case (className, functions) =>
330
+ s """
331
+ |private class $className {
332
+ | ${functions.values.mkString(" \n " )}
333
+ |}
334
+ """ .stripMargin
335
+ }
336
+ }.mkString(" \n " )
337
+
240
338
final val JAVA_BOOLEAN = " boolean"
241
339
final val JAVA_BYTE = " byte"
242
340
final val JAVA_SHORT = " short"
@@ -556,8 +654,7 @@ class CodegenContext {
556
654
return 0;
557
655
}
558
656
"""
559
- addNewFunction(compareFunc, funcCode)
560
- s " this. $compareFunc( $c1, $c2) "
657
+ s " ${addNewFunction(compareFunc, funcCode)}( $c1, $c2) "
561
658
case schema : StructType =>
562
659
val comparisons = GenerateOrdering .genComparisons(this , schema)
563
660
val compareFunc = freshName(" compareStruct" )
@@ -573,8 +670,7 @@ class CodegenContext {
573
670
return 0;
574
671
}
575
672
"""
576
- addNewFunction(compareFunc, funcCode)
577
- s " this. $compareFunc( $c1, $c2) "
673
+ s " ${addNewFunction(compareFunc, funcCode)}( $c1, $c2) "
578
674
case other if other.isInstanceOf [AtomicType ] => s " $c1.compare( $c2) "
579
675
case udt : UserDefinedType [_] => genComp(udt.sqlType, c1, c2)
580
676
case _ =>
@@ -689,7 +785,6 @@ class CodegenContext {
689
785
|}
690
786
""" .stripMargin
691
787
addNewFunction(name, code)
692
- name
693
788
}
694
789
695
790
foldFunctions(functions.map(name => s " $name( ${arguments.map(_._2).mkString(" , " )}) " ))
@@ -773,8 +868,6 @@ class CodegenContext {
773
868
|}
774
869
""" .stripMargin
775
870
776
- addNewFunction(fnName, fn)
777
-
778
871
// Add a state and a mapping of the common subexpressions that are associate with this
779
872
// state. Adding this expression to subExprEliminationExprMap means it will call `fn`
780
873
// when it is code generated. This decision should be a cost based one.
@@ -792,7 +885,7 @@ class CodegenContext {
792
885
addMutableState(javaType(expr.dataType), value,
793
886
s " $value = ${defaultValue(expr.dataType)}; " )
794
887
795
- subexprFunctions += s " $fnName( $INPUT_ROW); "
888
+ subexprFunctions += s " ${addNewFunction( fnName, fn)} ( $INPUT_ROW); "
796
889
val state = SubExprEliminationState (isNull, value)
797
890
e.foreach(subExprEliminationExprs.put(_, state))
798
891
}
0 commit comments