Skip to content
Permalink
Browse files
Use cache instead of FunctionList lookups (#1206)
Cache the required environments for each function in CFunction and use that for lookups where possible. Also don't do FunctionList lookups where the function is already cached. This makes the flow as follows:
1. postParseRewrite() caches all existing functions.
2. Other compiler steps use the cached functions, or get null when they don't exist.
3. checkFunctionsExist() does a lookup in the FunctionList if the function is not cached, and generates the compile exception if it doesn't exist.
  • Loading branch information
Pieter12345 committed Jun 10, 2020
1 parent d789750 commit 1b754e6ae11595cf5f8a28f84a7313e5c4811ffe
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 47 deletions.
@@ -2189,35 +2189,32 @@ private static void checkLabels(ParseTree tree, Set<ConfigCompileException> comp
* @param compilerErrors
*/
private static void link(ParseTree tree, Set<ConfigCompileException> compilerErrors) {
FunctionBase treeFunction = null;
try {
treeFunction = FunctionList.getFunction((CFunction) tree.getData(), null);
if(treeFunction.getClass().getAnnotation(nolinking.class) != null) {
// Don't link children of a nolinking function.
return;
}
} catch (ConfigCompileException | ClassCastException ex) {
// This can happen if the treeFunction isn't a function, is a proc, etc,
// but we don't care, we just want to continue.
}
if(tree.getData() instanceof CFunction) {
Function function = ((CFunction) tree.getData()).getCachedFunction();

// Check the argument count, and do any custom linking the function may have.
if(treeFunction != null) {
Integer[] numArgs = treeFunction.numArgs();
if(!Arrays.asList(numArgs).contains(Integer.MAX_VALUE)
&& !Arrays.asList(numArgs).contains(tree.getChildren().size())) {
compilerErrors.add(new ConfigCompileException("Incorrect number of arguments passed to "
+ tree.getData().val(), tree.getData().getTarget()));
}
if(treeFunction instanceof Optimizable) {
Optimizable op = (Optimizable) treeFunction;
if(op.optimizationOptions().contains(OptimizationOption.CUSTOM_LINK)) {
try {
op.link(tree.getData().getTarget(), tree.getChildren());
} catch (ConfigRuntimeException ex) {
compilerErrors.add(new ConfigCompileException(ex));
} catch (ConfigCompileException ex) {
compilerErrors.add(ex);
// Check the argument count, and do any custom linking the function may have.
if(function != null) {
if(function.getClass().getAnnotation(nolinking.class) != null) {
// Don't link children of a nolinking function.
return;
}

Integer[] numArgs = function.numArgs();
if(!Arrays.asList(numArgs).contains(Integer.MAX_VALUE)
&& !Arrays.asList(numArgs).contains(tree.getChildren().size())) {
compilerErrors.add(new ConfigCompileException("Incorrect number of arguments passed to "
+ tree.getData().val(), tree.getData().getTarget()));
}
if(function instanceof Optimizable) {
Optimizable op = (Optimizable) function;
if(op.optimizationOptions().contains(OptimizationOption.CUSTOM_LINK)) {
try {
op.link(tree.getData().getTarget(), tree.getChildren());
} catch (ConfigRuntimeException ex) {
compilerErrors.add(new ConfigCompileException(ex));
} catch (ConfigCompileException ex) {
compilerErrors.add(ex);
}
}
}
}
@@ -2247,13 +2244,21 @@ private static void checkFunctionsExist(ParseTree tree, Set<ConfigCompileExcepti
// Check current node, returning if it is a 'nolinking' function.
CFunction cFunc = (CFunction) tree.getData();
if(cFunc.hasFunction()) {
try {
FunctionBase func = FunctionList.getFunction(cFunc, envs);
FunctionBase func = cFunc.getCachedFunction(envs);
lookup: {
if(func == null) {

// Technically, we could be dealing with a FunctionBase that isn't cached. So do another lookup.
try {
func = FunctionList.getFunction(cFunc, envs);
} catch (ConfigCompileException ex) {
compilerErrors.add(ex);
break lookup;
}
}
if(func.getClass().getAnnotation(nolinking.class) != null) {
return; // Don't check children of 'nolinking' functions.
}
} catch (ConfigCompileException ex) {
compilerErrors.add(ex);
}
}

@@ -2298,12 +2303,7 @@ private static void optimize(ParseTree tree, Environment env,
procs.push(new ArrayList<>());
}
CFunction cFunction = (CFunction) tree.getData();
Function func;
try {
func = (Function) FunctionList.getFunction(cFunction, envs);
} catch (ConfigCompileException e) {
func = null;
}
Function func = cFunction.getCachedFunction(envs);
if(func != null) {
if(func.getClass().getAnnotation(nolinking.class) != null) {
//It's an unlinking function, so we need to stop at this point
@@ -2588,10 +2588,8 @@ private static boolean eliminateDeadCode(ParseTree tree, Environment env, Set<Cl
//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 && ((CFunction) tree.getData()).hasFunction()) {
Function f;
try {
f = (Function) FunctionList.getFunction(((CFunction) tree.getData()), envs);
} catch (ConfigCompileException | ClassCastException ex) {
Function f = ((CFunction) tree.getData()).getCachedFunction(envs);
if(f == null) {
return false;
}
List<ParseTree> children = tree.getChildren();
@@ -2637,10 +2635,8 @@ private static boolean eliminateDeadCode(ParseTree tree, Environment env, Set<Cl
if(!((CFunction) child.getData()).hasFunction()) {
continue;
}
Function c;
try {
c = (Function) FunctionList.getFunction(((CFunction) child.getData()), envs);
} catch (ConfigCompileException | ClassCastException ex) {
Function c = ((CFunction) child.getData()).getCachedFunction(envs);
if(c == null) {
continue;
}
Set<OptimizationOption> options = NO_OPTIMIZATIONS;
@@ -1,8 +1,14 @@
package com.laytonsmith.core.constructs;

import com.laytonsmith.PureUtilities.Common.ReflectionUtils;
import com.laytonsmith.annotations.api;

import java.util.Set;

import com.laytonsmith.PureUtilities.Version;
import com.laytonsmith.core.ParseTree;
import com.laytonsmith.core.environments.Environment;
import com.laytonsmith.core.environments.Environment.EnvironmentImpl;
import com.laytonsmith.core.exceptions.ConfigCompileException;
import com.laytonsmith.core.functions.Function;
import com.laytonsmith.core.functions.FunctionBase;
@@ -17,6 +23,7 @@ public class CFunction extends Construct {

public static final long serialVersionUID = 1L;
private transient Function function = null;
private transient Class<? extends EnvironmentImpl>[] envImpls = null;

public CFunction(String name, Target t) {
super(name, ConstructType.FUNCTION, t);
@@ -78,14 +85,43 @@ public Function getFunction() throws ConfigCompileException {
}

/**
* Returns the underlying function for this construct from the cache (which occurs on calling
* Returns the underlying function for this construct from the cache (which is cached on calling
* {@link #getFunction()} or {@link #setFunction(FunctionBase)}).
* @return The cached {@link Function} or {@code null} when the function has not yet been cached or doesn't exist.
*/
public Function getCachedFunction() {
return this.function;
}

/**
* Returns the underlying function for this construct from the cache (which is cached on calling
* {@link #getFunction()} or {@link #setFunction(FunctionBase)}) if the function is known given the passed
* environments.
* @param envs - The environments.
* @return The cached {@link Function} or {@code null} when the function has not yet been cached or doesn't exist.
*/
public Function getCachedFunction(Set<Class<? extends Environment.EnvironmentImpl>> envs) {

// Return null if the function has not yet been cached or does not exist.
if(this.function == null) {
return null;
}

// Cache the environments from the @api annotation if they have not yet been cached.
if(this.envImpls == null) {
api api = this.function.getClass().getAnnotation(api.class);
this.envImpls = api.environments();
}

// Return the cached function if it is known given the passed environments.
for(Class<? extends Environment.EnvironmentImpl> envImpl : this.envImpls) {
if(!envs.contains(envImpl)) {
return null;
}
}
return this.function;
}

/**
* This function should only be called by the compiler.
*

0 comments on commit 1b754e6

Please sign in to comment.