Skip to content

Commit

Permalink
Finish implementing branch statements generically
Browse files Browse the repository at this point in the history
  • Loading branch information
LadyCailin committed Feb 14, 2019
1 parent b11ecee commit 9d8c536
Show file tree
Hide file tree
Showing 8 changed files with 398 additions and 88 deletions.
168 changes: 103 additions & 65 deletions src/main/java/com/laytonsmith/core/MethodScriptCompiler.java
@@ -1,12 +1,14 @@
package com.laytonsmith.core;

import com.laytonsmith.PureUtilities.Common.ArrayUtils;
import com.laytonsmith.PureUtilities.Common.StringUtils;
import com.laytonsmith.abstraction.Implementation;
import com.laytonsmith.annotations.breakable;
import com.laytonsmith.annotations.nolinking;
import com.laytonsmith.annotations.unbreakable;
import com.laytonsmith.commandhelper.CommandHelperPlugin;
import com.laytonsmith.core.Optimizable.OptimizationOption;
import com.laytonsmith.core.compiler.BranchStatement;
import com.laytonsmith.core.compiler.FileOptions;
import com.laytonsmith.core.compiler.KeywordList;
import com.laytonsmith.core.compiler.TokenStream;
Expand Down Expand Up @@ -1673,6 +1675,7 @@ public static ParseTree compile(TokenStream stream) throws ConfigCompileExceptio
link(tree, compilerErrors);
checkLabels(tree, compilerErrors);
checkBreaks(tree, compilerErrors);
eliminateDeadCode(tree);
if(!compilerErrors.isEmpty()) {
if(compilerErrors.size() == 1) {
// Just throw the one CCE
Expand Down Expand Up @@ -2080,72 +2083,7 @@ private static void optimize(ParseTree tree, Stack<List<Procedure>> procs, Set<C
return;
}
}
//Loop through the children, and if any of them are functions that are terminal, truncate.
//To explain this further, consider the following:
//For the code: concat(die(), msg('')), this diagram shows the abstract syntax tree:
// (concat)
// / \
// / \
// (die) (msg)
//By looking at the code, we can tell that msg() will never be called, because die() will run first,
//and since it is a "terminal" function, any code after it will NEVER run. However, consider a more complex condition:
// if(@input){ die() msg('1') } else { msg('2') msg('3') }
// if(@input)
// [true]/ \[false]
// / \
// (sconcat) (sconcat)
// / \ / \
// / \ / \
// (die) (msg[1])(msg[2]) (msg[3])
//In this case, only msg('1') is guaranteed not to run, msg('2') and msg('3') will still run in some cases.
//So, we can optimize out msg('1') in this case, which would cause the tree to become much simpler, therefore a worthwile optimization:
// if(@input)
// [true]/ \[false]
// / \
// (die) (sconcat)
// / \
// / \
// (msg[2]) (msg[3])
//We do have to be careful though, because of functions like if, which actually work like this:
//if(@var){ die() } else { msg('') }
// (if)
// / | \
// / | \
// @var (die) (msg)
//We can't git rid of the msg() here, because it is actually in another branch.
//For the time being, we will simply say that if a function uses execs, it
//is a branch (branches always use execs, though using execs doesn't strictly
//mean you are a branch type function).

for(int i = 0; i < children.size(); i++) {
ParseTree t = children.get(i);
if(t.getData() instanceof CFunction) {
if(t.getData().val().startsWith("_") || (func != null && func.useSpecialExec())) {
continue;
}
Function f;
try {
f = (Function) FunctionList.getFunction(((CFunction) t.getData()));
} catch (ConfigCompileException | ClassCastException ex) {
continue;
}
Set<OptimizationOption> options = NO_OPTIMIZATIONS;
if(f instanceof Optimizable) {
options = ((Optimizable) f).optimizationOptions();
}
if(options.contains(OptimizationOption.TERMINAL)) {
if(children.size() > i + 1) {
//First, a compiler warning
CHLog.GetLogger().Log(CHLog.Tags.COMPILER, LogLevel.WARNING, "Unreachable code. Consider removing this code.", children.get(i + 1).getTarget());
//Now, truncate the children
for(int j = children.size() - 1; j > i; j--) {
children.remove(j);
}
break;
}
}
}
}
boolean fullyStatic = true;
boolean hasIVars = false;
for(ParseTree node : children) {
Expand Down Expand Up @@ -2345,6 +2283,106 @@ private static void optimize(ParseTree tree, Stack<List<Procedure>> procs, Set<C
//It doesn't know how to optimize. Oh well.
}

private static boolean eliminateDeadCode(ParseTree tree) {
//Loop through the children, and if any of them are functions that are terminal, truncate.
//To explain this further, consider the following:
//For the code: concat(die(), msg('')), this diagram shows the abstract syntax tree:
// (concat)
// / \
// / \
// (die) (msg)
//By looking at the code, we can tell that msg() will never be called, because die() will run first,
//and since it is a "terminal" function, any code after it will NEVER run. However, consider a more complex condition:
// if(@input){ die() msg('1') } else { msg('2') msg('3') }
// if(@input)
// [true]/ \[false]
// / \
// (sconcat) (sconcat)
// / \ / \
// / \ / \
// (die) (msg[1])(msg[2]) (msg[3])
//In this case, only msg('1') is guaranteed not to run, msg('2') and msg('3') will still run in some cases.
//So, we can optimize out msg('1') in this case, which would cause the tree to become much simpler, therefore a worthwile optimization:
// if(@input)
// [true]/ \[false]
// / \
// (die) (sconcat)
// / \
// / \
// (msg[2]) (msg[3])
//We do have to be careful though, because of functions like if, which actually work like this:
//if(@var){ die() } else { msg('') }
// (if)
// / | \
// / | \
// @var (die) (msg)
//We can't git rid of the msg() here, because it is actually in another branch.
//For the time being, we will simply say that if a function uses execs, it
//is a branch (branches always use execs, though using execs doesn't strictly
//mean you are a branch type function).
if(tree.getData() instanceof CFunction) {
Function f;
try {
f = (Function) FunctionList.getFunction(((CFunction) tree.getData()));
} catch (ConfigCompileException | ClassCastException ex) {
return false;
}
List<ParseTree> children = tree.getChildren();
List<Boolean> branches;
if(f instanceof BranchStatement) {
branches = ((BranchStatement) f).isBranch(children);
if(branches.size() != children.size()) {
throw new Error(f.getName() + " does not properly implement isBranch. It does not return a value"
+ " with the same count as the actual children. Expected: " + children.size() + ";"
+ " Actual: " + branches.size());
}
} else {
branches = new ArrayList<>(children.size());
for(ParseTree child : children) {
branches.add(false);
}
}
boolean doDeletion = false;
for(int m = 0; m < children.size(); m++) {
boolean isBranch = branches.get(m);
if(doDeletion) {
if(isBranch) {
doDeletion = false;
} else {
CHLog.GetLogger().Log(CHLog.Tags.COMPILER, LogLevel.WARNING, "Unreachable code. Consider"
+ " removing this code.", children.get(m).getTarget());
children.remove(m);
m--;
continue;
}
}
ParseTree child = children.get(m);
if(child.getData() instanceof CFunction) {
Function c;
try {
c = (Function) FunctionList.getFunction(((CFunction) child.getData()));
} catch (ConfigCompileException | ClassCastException ex) {
continue;
}
Set<OptimizationOption> options = NO_OPTIMIZATIONS;
if(c instanceof Optimizable) {
options = ((Optimizable) c).optimizationOptions();
}
doDeletion = options.contains(OptimizationOption.TERMINAL);
boolean subDoDelete = eliminateDeadCode(child);
if(subDoDelete) {
doDeletion = true;
}
}
if(isBranch) {
doDeletion = false;
}
}
return doDeletion;
}
return false;
}

/**
* Runs keyword processing on the tree. Note that this is run before optimization, and is a depth first process.
*
Expand Down
17 changes: 9 additions & 8 deletions src/main/java/com/laytonsmith/core/functions/BasicLogic.java
Expand Up @@ -778,15 +778,16 @@ public Set<OptimizationOption> optimizationOptions() {
@Override
public List<Boolean> isBranch(List<ParseTree> children) {
List<Boolean> branches = new ArrayList<>(children.size());
// The first and second arguments are not
if(children.size() > 1) {
branches.add(false);
}
for(int i = 1; i < children.size(); i++) {
branches.add(false);
if(children.size() > 2) {
branches.add(false);
if(children.size() == 2) {
branches.add(true);
} else {
for(int i = 1; i < children.size() - 1; i += 2) {
branches.add(false);
branches.add(true);
}
if(children.size() % 2 == 0) {
branches.add(true);
i++;
}
}
return branches;
Expand Down

0 comments on commit 9d8c536

Please sign in to comment.