Skip to content

Commit 0d47d32

Browse files
committed
Feature: New hint - Detect functions calls that may be replaced with a constant.
1 parent b666fcb commit 0d47d32

17 files changed

+233
-15
lines changed

ShaderShrinker/Shrinker.Lexer/TypeToken.cs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,22 @@ public string Content
7676
"mat3", "mat3x2", "mat3x3", "mat3x4",
7777
"mat4", "mat4x2", "mat4x3", "mat4x4"
7878
};
79-
public static IEnumerable<string> Names { get; } = new[] { "void", "bool", "int", "uint", "float", "sampler1D", "sampler2D", "sampler3D" }.Union(MultiValueTypes);
79+
public static IEnumerable<string> Names { get; } = new[] { "void", "bool", "int", "uint", "float", "sampler1D", "sampler2D", "sampler3D", "samplerCube" }.Union(MultiValueTypes);
80+
81+
public static IEnumerable<string> GlslInputs { get; } = new[]
82+
{
83+
"iResolution",
84+
"iTime",
85+
"iTimeDelta",
86+
"iFrame",
87+
"iFrameRate",
88+
"iChannelTime",
89+
"iChannelResolution",
90+
"iMouse",
91+
"iChannel",
92+
"iDate",
93+
"iSampleRate"
94+
};
8095

8196
public IToken TryJoin(List<IToken> tokens, int tokenIndex, out int deletePrevious, out int deleteTotal)
8297
{

ShaderShrinker/Shrinker.Parser/CodeHint.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,5 +21,7 @@ protected CodeHint(string item, string suggestion)
2121
Item = item;
2222
Suggestion = suggestion;
2323
}
24+
25+
public override string ToString() => $"{Item}|{Suggestion}";
2426
}
2527
}

ShaderShrinker/Shrinker.Parser/Hinter.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ namespace Shrinker.Parser
2222
public static class Hinter
2323
{
2424
// todo - Report when single value passed to any function.
25+
// todo - If function called in 'if' (or 'else') blocks, suggest parameterizing.
2526
public static IEnumerable<CodeHint> GetHints(this SyntaxNode rootNode)
2627
{
2728
if (rootNode.HasEntryPointFunction())
@@ -35,6 +36,9 @@ public static IEnumerable<CodeHint> GetHints(this SyntaxNode rootNode)
3536

3637
foreach (var codeHint in DetectDefinableReferences(rootNode))
3738
yield return codeHint;
39+
40+
foreach (var codeHint in DetectFunctionsCalledWithConstArguments(rootNode))
41+
yield return codeHint;
3842
}
3943

4044
private static IEnumerable<CodeHint> DetectFunctionsToInline(SyntaxNode rootNode)
@@ -93,6 +97,25 @@ private static IEnumerable<CodeHint> DetectDefinableReferences(SyntaxNode rootNo
9397
}
9498
}
9599

100+
private static IEnumerable<CodeHint> DetectFunctionsCalledWithConstArguments(SyntaxNode rootNode)
101+
{
102+
foreach (var function in rootNode.FunctionDefinitions())
103+
{
104+
var functionCallsWithConstParams =
105+
function
106+
.FunctionCalls()
107+
.Where(o => o.Params.IsNumericCsv(true))
108+
.Where(o => !o.ModifiesGlobalVariables());
109+
110+
foreach (var hintableFunctionCall in functionCallsWithConstParams)
111+
{
112+
var callee = hintableFunctionCall.GetCalleeDefinition();
113+
if (callee != null && callee.ReturnType != "void" && !callee.UsesGlslInputs())
114+
yield return new FunctionCalledWithConstParams(hintableFunctionCall);
115+
}
116+
}
117+
}
118+
96119
public class UnusedFunctionHint : CodeHint
97120
{
98121
public UnusedFunctionHint(string function) : base(function, "Function is never called.")
@@ -113,6 +136,13 @@ public FunctionHasUnusedParam(string function, string param) : base(function, $"
113136
{
114137
}
115138
}
139+
140+
public class FunctionCalledWithConstParams : CodeHint
141+
{
142+
public FunctionCalledWithConstParams(SyntaxNode function) : base(function.ToCode(), "Function called with constant arguments. Consider replacing with the result.")
143+
{
144+
}
145+
}
116146

117147
public class IntroduceDefine : CodeHint
118148
{

ShaderShrinker/Shrinker.Parser/Optimizations/CombineAssignmentWithReturnExtension.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,9 @@ public static bool CombineAssignmentWithReturn(this SyntaxNode rootNode)
7373

7474
// Are we assigning from a non-const global variable?
7575
// Bad idea - It might be modified by a function call in the 'return'.
76-
var functionsInReturn = returnNode.TheTree.OfType<FunctionCallSyntaxNode>().ToList();
76+
var functionsInReturn = returnNode.FunctionCalls().ToList();
7777
if (functionsInReturn.Any() ||
78-
assignment.TheTree.OfType<FunctionCallSyntaxNode>().Any())
78+
assignment.FunctionCalls().Any())
7979
{
8080
var globals = rootNode.GlobalVariables()
8181
.Where(o => (o.Parent as VariableDeclarationSyntaxNode)?.VariableType.IsConst != true)

ShaderShrinker/Shrinker.Parser/Optimizations/InlineConstantVariablesExtension.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ public static void InlineConstantVariables(this SyntaxNode rootNode)
6161
definition.ValueNodes.Count() == 2 &&
6262
definition.Children[0]?.Token is TypeToken &&
6363
definition.Children[1] is RoundBracketSyntaxNode brackets &&
64-
brackets.IsSimpleCsv();
64+
brackets.IsNumericCsv();
6565

6666
if (!couldInline)
6767
continue;

ShaderShrinker/Shrinker.Parser/Optimizations/InlineDefinesExtension.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ private static bool CouldInline(PragmaDefineSyntaxNode o)
7373
return o.ValueNodes[0].Token is TypeToken t &&
7474
t.IsGlslType &&
7575
o.ValueNodes[1] is RoundBracketSyntaxNode brackets &&
76-
brackets.IsSimpleCsv();
76+
brackets.IsNumericCsv();
7777
}
7878

7979
// Nope - Can't inline.

ShaderShrinker/Shrinker.Parser/Optimizations/PerformArithmeticExtension.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,7 @@ public static bool PerformArithmetic(this SyntaxNode rootNode)
126126
// pow(1.1, 2.2) => <the result>
127127
var functionCalls = rootNode.TheTree.OfType<GlslFunctionCallSyntaxNode>().ToList();
128128
foreach (var powNode in functionCalls
129-
.Where(o => o.Name == "pow" && o.Params.IsSimpleCsv())
129+
.Where(o => o.Name == "pow" && o.Params.IsNumericCsv())
130130
.ToList())
131131
{
132132
var xy = powNode.Params.Children.Where(o => o.Token is FloatToken).Select(o => ((FloatToken)o.Token).Number).ToList();
@@ -154,7 +154,7 @@ public static bool PerformArithmetic(this SyntaxNode rootNode)
154154
};
155155

156156
foreach (var mathNode in functionCalls
157-
.Where(o => mathOp.Select(op => op.Item1).Contains(o.Name) && o.Params.IsSimpleCsv())
157+
.Where(o => mathOp.Select(op => op.Item1).Contains(o.Name) && o.Params.IsNumericCsv())
158158
.ToList())
159159
{
160160
var x = mathNode.Params.Children.Where(o => o.Token is FloatToken).Select(o => ((FloatToken)o.Token).Number).ToList();
@@ -177,7 +177,7 @@ public static bool PerformArithmetic(this SyntaxNode rootNode)
177177
continue;
178178

179179
// Brackets must be filled with floats.
180-
if (!brackets.IsSimpleCsv())
180+
if (!brackets.IsNumericCsv())
181181
continue;
182182

183183
// Find the math symbol.

ShaderShrinker/Shrinker.Parser/Optimizations/RemoveUnusedVariablesExtension.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ public static void RemoveUnusedVariables(this SyntaxNode rootNode)
4848
{
4949
// If RHS of the assignment calls a function with an 'out/inout' param, it is not safe to remove.
5050
// Instead, replace the assignment with the RHS component.
51-
if (assignment.TheTree.OfType<FunctionCallSyntaxNode>().Any(o => o.HasOutParam || o.ModifiesGlobalVariables()))
51+
if (assignment.FunctionCalls().Any(o => o.HasOutParam || o.ModifiesGlobalVariables()))
5252
{
5353
var rhs = assignment.ValueNodes.ToList();
5454
rhs.Add(new GenericSyntaxNode(new SemicolonToken()));

ShaderShrinker/Shrinker.Parser/SyntaxNodes/FunctionCallSyntaxNode.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,11 @@ private FunctionCallSyntaxNode(string name)
4040

4141
public bool ModifiesGlobalVariables() =>
4242
this.Root().FunctionDefinitions().FirstOrDefault(o => o.Name == Name)?.ModifiesGlobalVariables() == true;
43+
44+
/// <summary>
45+
/// Find the definition of the function being called (which may be null for functions defined in other buffers, or GLSL function calls).
46+
/// </summary>
47+
public FunctionDefinitionSyntaxNode GetCalleeDefinition() =>
48+
this.Root().FunctionDefinitions().FirstOrDefault(o => o.Name == Name && Params.GetCsv().Count() == o.Params.GetCsv().Count());
4349
}
4450
}

ShaderShrinker/Shrinker.Parser/SyntaxNodes/FunctionDefinitionSyntaxNode.cs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,16 +53,21 @@ public bool ModifiesGlobalVariables()
5353
return true;
5454

5555
// Check any calls made by self...
56-
return theTree.OfType<FunctionCallSyntaxNode>().Any(o => o.ModifiesGlobalVariables());
56+
return this.FunctionCalls().Any(o => o.ModifiesGlobalVariables());
5757
}
5858

59+
/// <summary>
60+
/// Whether the function uses iTime, iResolution, etc.
61+
/// </summary>
62+
public bool UsesGlslInputs() => TheTree.Select(o => o?.Token?.Content).Intersect(TypeToken.GlslInputs).Any();
63+
5964
public bool DoesCall(FunctionDefinitionSyntaxNode otherFunction) =>
60-
Braces.TheTree.OfType<FunctionCallSyntaxNode>().Any(o => o.Name == otherFunction.Name);
65+
this.FunctionCalls().Any(o => o.Name == otherFunction.Name);
6166

6267
/// <summary>
6368
/// How many times does this function call another?
6469
/// </summary>
6570
public int CallCount(FunctionDefinitionSyntaxNode otherFunction) =>
66-
Braces.TheTree.OfType<FunctionCallSyntaxNode>().Count(o => o.Name == otherFunction.Name);
71+
this.FunctionCalls().Count(o => o.Name == otherFunction.Name);
6772
}
6873
}

0 commit comments

Comments
 (0)