You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The old algorithm had two small flaws:
1. It may have generated an arbitrarily large amount of code in the
worst case as it could, in theory, keep invoking recursive code
generators even if it was already over budget.
2. It would generally produce somewhat "unbalanced" code: blocks
generated early on would generally be larger than blocks generated
later as the recursive budget was dependent on the _remaining_ outer
budget.
The new algorithm fixes both of these issues: it now has an explicit
constant (really, a range between two constants) that determines the
budget for recursively generated blocks relative to their parents
_initial_ budget. This ensures that blocks are more balanced. Further,
if the remaining budget becomes "too small", no more recursive code
generators are called to avoid overshooting the budget by a lot.
See the comment in ProgramBuilder.swift for more details.
/// Finalizes and returns the constructed program, then resets this builder so it can be reused for building another program.
@@ -975,61 +956,155 @@ public class ProgramBuilder {
975
956
returntrue
976
957
}
977
958
959
+
// Code Building Algorithm:
960
+
//
961
+
// In theory, the basic building algorithm is simply:
962
+
//
963
+
// var remainingBudget = initialBudget
964
+
// while remainingBudget > 0 {
965
+
// if probability(0.5) {
966
+
// remainingBudget -= runRandomCodeGenerator()
967
+
// } else {
968
+
// remainingBudget -= performSplicing()
969
+
// }
970
+
// }
971
+
//
972
+
// In practice, things become a little more complicated because code generators can be recursive: a function
973
+
// generator will emit the function start and end and recursively call into the code building machinery to fill the
974
+
// body of the function. The size of the recursively generated blocks is determined as a fraction of the parent's
975
+
// *initial budget*. This ensures that the sizes of recursively generated blocks roughly follow the same
976
+
// distribution. However, it also means that the initial budget can be overshot by quite a bit: we may end up
977
+
// invoking a recursive generator near the end of our budget, which may then for example generate another 0.5x
978
+
// initialBudget instructions. However, the benefit of this approach is that there are really only two "knobs" that
979
+
// determine the "shape" of the generated code: the factor that determines the recursive budget relative to the
980
+
// parent budget and the (absolute) threshold for recursive code generation.
981
+
//
982
+
983
+
/// The first "knob": this mainly determines the shape of generated code as it determines how large block bodies are relative to their surrounding code.
984
+
/// This also influences the nesting depth of the generated code, as recursive code generators are only invoked if enough "budget" is still available.
985
+
/// These are writable so they can be reconfigured in tests.
986
+
varminRecursiveBudgetRelativeToParentBudget=0.05
987
+
varmaxRecursiveBudgetRelativeToParentBudget=0.50
988
+
989
+
/// The second "knob": the minimum budget required to be able to invoke recursive code generators.
/// Build random code at the current position in the program.
1025
+
///
1026
+
/// The first parameter controls the number of emitted instructions: as soon as more than that number of instructions have been emitted, building stops.
1027
+
/// This parameter is only a rough estimate as recursive code generators may lead to significantly more code being generated.
1028
+
/// Typically, the actual number of generated instructions will be somewhere between n and 2x n.
984
1029
publicfunc build(n:Int=1, by mode:BuildingMode=.runningGeneratorsAndSplicing){
985
-
currentBuildingBudget = n
986
-
currentBuildingMode = mode
987
-
buildInternal()
1030
+
assert(buildStack.isEmpty)
1031
+
buildInternal(initialBuildingBudget: n, mode: mode)
1032
+
assert(buildStack.isEmpty)
988
1033
}
989
1034
990
1035
/// Recursive code building. Used by CodeGenerators for example to fill the bodies of generated blocks.
991
-
publicfunc buildRecursive(){
992
-
assert(currentBuildingMode !=.splicing)
1036
+
publicfunc buildRecursive(block:Int=1, of numBlocks:Int=1, n optionalBudget:Int?=nil){
1037
+
assert(!buildStack.isEmpty)
1038
+
letparentState= buildStack.last!
993
1039
994
-
// Generate at least one instruction, even if already below budget.
995
-
if currentBuildingBudget <=0{
996
-
currentBuildingBudget =1
997
-
}
1040
+
assert(parentState.mode !=.splicing)
1041
+
assert(parentState.recursiveBuildingAllowed) // If this fails, a recursive CodeGenerator is probably not marked as recursive.
0 commit comments