Skip to content

Commit 40d9810

Browse files
committed
Feature: Replace function calls with a calculatable result.
1 parent 0d47d32 commit 40d9810

File tree

15 files changed

+257
-62
lines changed

15 files changed

+257
-62
lines changed

README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,7 @@ In most cases they can be worked-around using a set of 'custom' settings which d
141141
* [Introduce +=, -=, /=, *=](#introduce-+=,--=,-/=,-*=)
142142
* [Simplify Mathematical Expressions](#simplify-mathematical-expressions)
143143
* [Perform Simple Arithmetic](#perform-simple-arithmetic)
144+
* [Replace Functions Calls With Result](#replace-functions-calls-with-result)
144145
## Remove Comments
145146
Remove all C/C++ -style comments from the code.
146147
#### Before
@@ -564,3 +565,23 @@ E.g.
564565
* Change ```vec2 f = vec2(1.1, 2.2) + 3.3 * 4.4;``` to ```vec2 f = vec2(15.62, 16.72);```
565566

566567
---
568+
## Replace Functions Calls With Result
569+
If the result of a function call can be calculated, replace the call with the result.
570+
#### Before
571+
```c
572+
float doMaths(float a, float b, float c) {
573+
return a * b + a + sin(c);
574+
}
575+
576+
float f() {
577+
float result = doMaths(1.0, 2.0, 3.14159);
578+
}
579+
```
580+
#### After
581+
```c
582+
float f() {
583+
float result = 3.0;
584+
}
585+
```
586+
587+
---

ShaderShrinker/Shrinker.Parser/CustomOptions.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ public class CustomOptions
4545
public bool SimplifyArithmetic { get; set; }
4646
public bool PerformArithmetic { get; set; }
4747
public bool SimplifyBranching { get; set; }
48+
public bool ReplaceFunctionCallsWithResult { get; set; }
4849

4950
private CustomOptions()
5051
{

ShaderShrinker/Shrinker.Parser/Hinter.cs

Lines changed: 14 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using System.Collections.Generic;
1313
using System.Linq;
1414
using Shrinker.Lexer;
15+
using Shrinker.Parser.Optimizations;
1516
using Shrinker.Parser.SyntaxNodes;
1617

1718
namespace Shrinker.Parser
@@ -73,7 +74,7 @@ private static IEnumerable<CodeHint> DetectUnusedFunctionParam(SyntaxNode rootNo
7374
.Select(o => o.Token?.Content ?? (o as VariableAssignmentSyntaxNode)?.Name)
7475
.Any(o => o?.StartsWithVarName(paramName) == true);
7576
if (!isUsed)
76-
yield return new FunctionHasUnusedParam(function.UiName, paramName);
77+
yield return new FunctionHasUnusedParamHint(function.UiName, paramName);
7778
}
7879
}
7980
}
@@ -93,28 +94,15 @@ private static IEnumerable<CodeHint> DetectDefinableReferences(SyntaxNode rootNo
9394
var newSize = 8 + candidate.Length + usageCount;
9495

9596
if (newSize < oldSize)
96-
yield return new IntroduceDefine(candidate, replacement[candidates.ToList().IndexOf(candidate)]);
97+
yield return new IntroduceDefineHint(candidate, replacement[candidates.ToList().IndexOf(candidate)]);
9798
}
9899
}
99100

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-
}
101+
private static IEnumerable<CodeHint> DetectFunctionsCalledWithConstArguments(SyntaxNode rootNode) =>
102+
rootNode
103+
.FunctionDefinitions()
104+
.SelectMany(o => o.FunctionCalls().FunctionCallsMadeWithConstParams())
105+
.Select(function => new FunctionCalledWithConstParamsHint(function));
118106

119107
public class UnusedFunctionHint : CodeHint
120108
{
@@ -130,23 +118,23 @@ public FunctionToInlineHint(string function) : base(function, "Function is calle
130118
}
131119
}
132120

133-
public class FunctionHasUnusedParam : CodeHint
121+
public class FunctionHasUnusedParamHint : CodeHint
134122
{
135-
public FunctionHasUnusedParam(string function, string param) : base(function, $"Function parameter '{param}' is unused.")
123+
public FunctionHasUnusedParamHint(string function, string param) : base(function, $"Function parameter '{param}' is unused.")
136124
{
137125
}
138126
}
139127

140-
public class FunctionCalledWithConstParams : CodeHint
128+
public class FunctionCalledWithConstParamsHint : CodeHint
141129
{
142-
public FunctionCalledWithConstParams(SyntaxNode function) : base(function.ToCode(), "Function called with constant arguments. Consider replacing with the result.")
130+
public FunctionCalledWithConstParamsHint(SyntaxNode function) : base(function.ToCode(), "Function called with constant arguments. Consider replacing with the result.")
143131
{
144132
}
145133
}
146134

147-
public class IntroduceDefine : CodeHint
135+
public class IntroduceDefineHint : CodeHint
148136
{
149-
public IntroduceDefine(string originalName, string defineNameAndValue) : base(originalName, $"[GOLF] Consider adding '#define {defineNameAndValue}'")
137+
public IntroduceDefineHint(string originalName, string defineNameAndValue) : base(originalName, $"[GOLF] Consider adding '#define {defineNameAndValue}'")
150138
{
151139
}
152140
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
// -----------------------------------------------------------------------
2+
// <copyright file="ReplaceFunctionCallsWithResultExtension.cs" company="Global Graphics Software Ltd">
3+
// Copyright (c) 2021 Global Graphics Software Ltd. All rights reserved.
4+
// </copyright>
5+
// <summary>
6+
// This example is provided on an "as is" basis and without warranty of any kind.
7+
// Global Graphics Software Ltd. does not warrant or make any representations regarding the use or
8+
// results of use of this example.
9+
// </summary>
10+
// -----------------------------------------------------------------------
11+
12+
using System.Collections.Generic;
13+
using System.Linq;
14+
using Shrinker.Lexer;
15+
using Shrinker.Parser.SyntaxNodes;
16+
17+
namespace Shrinker.Parser.Optimizations
18+
{
19+
public static class ReplaceFunctionCallsWithResultExtension
20+
{
21+
/// <summary>
22+
/// Returns true if all optimizations should be re-run.
23+
/// </summary>
24+
public static bool ReplaceFunctionCallsWithResult(this SyntaxNode rootNode)
25+
{
26+
var repeatSimplifications = false;
27+
28+
while (true)
29+
{
30+
var didChange = false;
31+
32+
foreach (var functionCall in rootNode.FunctionCalls().Where(o => o.IsFunctionCallWithConstParams()).ToList())
33+
{
34+
// Clone the callee function, so we can attempt to simplify it.
35+
var callee = (FunctionDefinitionSyntaxNode)functionCall.GetCalleeDefinition().Clone();
36+
37+
var callerParams = functionCall.Params.Children.Select(o => o.Clone()).ToList();
38+
39+
// Convert params to local variables.
40+
var nodesToPrefix = new List<VariableDeclarationSyntaxNode>();
41+
var paramIndex = 0;
42+
var i = 0;
43+
while (i < callee.Params.Children.Count)
44+
{
45+
if (callee.Params.Children[i].Token is CommaToken)
46+
{
47+
// Skip the commas.
48+
i++;
49+
continue;
50+
}
51+
52+
var typeNode = (GenericSyntaxNode)callee.Params.Children[i++].Clone();
53+
var nameNode = (GenericSyntaxNode)callee.Params.Children[i++].Clone();
54+
55+
var declNode = new VariableDeclarationSyntaxNode(typeNode);
56+
declNode.Adopt(new VariableAssignmentSyntaxNode(nameNode, new List<SyntaxNode> { callerParams[paramIndex * 2] }));
57+
paramIndex++;
58+
59+
nodesToPrefix.Add(declNode);
60+
}
61+
62+
// Move new nodes to head of function body.
63+
callee.Params.ReplaceWith(new RoundBracketSyntaxNode());
64+
for (i = 0; i < nodesToPrefix.Count; i++)
65+
callee.Braces.InsertChild(i, nodesToPrefix[i]);
66+
67+
// Re-parse and our cloned function.
68+
var lexer = new Lexer.Lexer();
69+
if (!lexer.Load(callee.Braces.ToCode()))
70+
continue;
71+
var reparsed = new Parser(lexer).Parse();
72+
73+
// 'Shrink' our cloned function.
74+
var simplified = reparsed.Simplify();
75+
if (simplified.Children.FirstOrDefault() is BraceSyntaxNode braces &&
76+
braces.Children.FirstOrDefault() is ReturnSyntaxNode returnNode)
77+
{
78+
// Inline the function call with its result.
79+
functionCall.ReplaceWith(returnNode.Children.ToList());
80+
81+
didChange = true;
82+
break;
83+
}
84+
}
85+
86+
if (!didChange)
87+
break;
88+
89+
repeatSimplifications = true;
90+
}
91+
92+
return repeatSimplifications;
93+
}
94+
95+
public static IEnumerable<FunctionCallSyntaxNode> FunctionCallsMadeWithConstParams(this IEnumerable<FunctionCallSyntaxNode> functionCalls) =>
96+
functionCalls.Where(o => o.IsFunctionCallWithConstParams());
97+
98+
private static bool IsFunctionCallWithConstParams(this FunctionCallSyntaxNode functionCall)
99+
{
100+
if (!functionCall.Params.IsNumericCsv(true) ||
101+
functionCall.ModifiesGlobalVariables() ||
102+
functionCall.HasOutParam)
103+
return false;
104+
105+
var callee = functionCall.GetCalleeDefinition();
106+
return callee != null && callee.ReturnType != "void" && !callee.UsesGlslInputs() && !callee.CallsLocalFunctions();
107+
}
108+
}
109+
}

ShaderShrinker/Shrinker.Parser/Shrinker.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ public static SyntaxNode Simplify(this SyntaxNode rootNode, CustomOptions option
9999

100100
if (options.PerformArithmetic)
101101
repeatSimplifications |= rootNode.PerformArithmetic();
102+
103+
if (options.ReplaceFunctionCallsWithResult)
104+
repeatSimplifications |= rootNode.ReplaceFunctionCallsWithResult();
102105
}
103106

104107
return rootNode;

ShaderShrinker/Shrinker.Parser/SyntaxNodes/FileSyntaxNode.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -306,8 +306,7 @@ private void FindNegativeNumbers()
306306
continue;
307307

308308
if (node.Previous == null ||
309-
node.Previous?.Token is SymbolOperatorToken ||
310-
node.Previous?.Token is CommaToken)
309+
node.Previous.Token is SymbolOperatorToken or CommaToken or AssignmentOperatorToken)
311310
{
312311
node.Remove();
313312
numberToken.MakeNegative();

ShaderShrinker/Shrinker.Parser/SyntaxNodes/FunctionCallSyntaxNode.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@ private FunctionCallSyntaxNode(string name)
3838

3939
public virtual bool HasOutParam => this.Root().Children.OfType<FunctionSyntaxNodeBase>().Any(o => o.Name == Name && o.HasOutParam);
4040

41-
public bool ModifiesGlobalVariables() =>
42-
this.Root().FunctionDefinitions().FirstOrDefault(o => o.Name == Name)?.ModifiesGlobalVariables() == true;
41+
public bool ModifiesGlobalVariables() => GetCalleeDefinition()?.ModifiesGlobalVariables() == true;
4342

4443
/// <summary>
4544
/// Find the definition of the function being called (which may be null for functions defined in other buffers, or GLSL function calls).

ShaderShrinker/Shrinker.Parser/SyntaxNodes/FunctionDefinitionSyntaxNode.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ public bool ModifiesGlobalVariables()
6464
public bool DoesCall(FunctionDefinitionSyntaxNode otherFunction) =>
6565
this.FunctionCalls().Any(o => o.Name == otherFunction.Name);
6666

67+
public bool CallsLocalFunctions() =>
68+
this.FunctionCalls().Any(o => o.GetCalleeDefinition() != null);
69+
6770
/// <summary>
6871
/// How many times does this function call another?
6972
/// </summary>

ShaderShrinker/Shrinker.WpfApp/OptionsDialog.xaml

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,31 @@
570570
</MdXaml:MarkdownScrollViewer>
571571
</CheckBox.ToolTip>
572572
</CheckBox>
573+
574+
<CheckBox Content="Replace functions calls with result" IsChecked="{Binding CustomOptions.ReplaceFunctionCallsWithResult}">
575+
<CheckBox.ToolTip>
576+
<MdXaml:MarkdownScrollViewer xml:space="preserve">
577+
### Replace Functions Calls With Result
578+
If the result of a function call can be calculated, replace the call with the result.
579+
#### Before
580+
```c
581+
float doMaths(float a, float b, float c) {
582+
return a * b + a + sin(c);
583+
}
584+
585+
float f() {
586+
float result = doMaths(1.0, 2.0, 3.14159);
587+
}
588+
```
589+
#### After
590+
```c
591+
float f() {
592+
float result = 3.0;
593+
}
594+
```
595+
</MdXaml:MarkdownScrollViewer>
596+
</CheckBox.ToolTip>
597+
</CheckBox>
573598
</StackPanel>
574599

575600
<StackPanel Grid.Row="1" Grid.Column="0" Orientation="Horizontal" Margin="0,16,0,0" VerticalAlignment="Center">

ShaderShrinker/UnitTests/HinterTests.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ public void CheckDetectingFunctionWithUnusedParam(
5555

5656
var rootNode = new Parser(lexer).Parse();
5757

58-
var hints = rootNode.GetHints().OfType<Hinter.FunctionHasUnusedParam>().ToList();
58+
var hints = rootNode.GetHints().OfType<Hinter.FunctionHasUnusedParamHint>().ToList();
5959
Assert.That(hints, Has.Count.EqualTo(1));
6060
Assert.That(() => hints.Single().Suggestion, Does.Contain("'b'"));
6161
}
@@ -79,7 +79,7 @@ public void CheckMultiUseResolutionTriggersHint()
7979

8080
var rootNode = new Parser(lexer).Parse();
8181

82-
Assert.That(() => rootNode.GetHints().OfType<Hinter.IntroduceDefine>().ToList(), Has.Count.EqualTo(1));
82+
Assert.That(() => rootNode.GetHints().OfType<Hinter.IntroduceDefineHint>().ToList(), Has.Count.EqualTo(1));
8383
}
8484

8585
[Test]
@@ -101,7 +101,7 @@ public void CheckMultiUseSmoothstepTriggersHint()
101101

102102
var rootNode = new Parser(lexer).Parse();
103103

104-
Assert.That(() => rootNode.GetHints().OfType<Hinter.IntroduceDefine>().ToList(), Has.Count.EqualTo(1));
104+
Assert.That(() => rootNode.GetHints().OfType<Hinter.IntroduceDefineHint>().ToList(), Has.Count.EqualTo(1));
105105
}
106106

107107
[Test]
@@ -123,7 +123,7 @@ public void CheckMultiUseMouseTriggersHint()
123123

124124
var rootNode = new Parser(lexer).Parse();
125125

126-
Assert.That(() => rootNode.GetHints().OfType<Hinter.IntroduceDefine>().ToList(), Has.Count.EqualTo(1));
126+
Assert.That(() => rootNode.GetHints().OfType<Hinter.IntroduceDefineHint>().ToList(), Has.Count.EqualTo(1));
127127
}
128128

129129
[Test, Sequential]
@@ -140,7 +140,7 @@ public void CheckCallingVoidFunctionWithConstParamDoesNotTriggerHint(
140140

141141
var rootNode = new Parser(lexer).Parse();
142142

143-
Assert.That(() => rootNode.GetHints().OfType<Hinter.FunctionCalledWithConstParams>(), Is.Empty);
143+
Assert.That(() => rootNode.GetHints().OfType<Hinter.FunctionCalledWithConstParamsHint>(), Is.Empty);
144144
}
145145

146146
[Test]
@@ -151,7 +151,7 @@ public void CheckCallingFunctionWithConstParamUsingGlobalDoesNotTriggerHint()
151151

152152
var rootNode = new Parser(lexer).Parse();
153153

154-
Assert.That(() => rootNode.GetHints().OfType<Hinter.FunctionCalledWithConstParams>(), Is.Empty);
154+
Assert.That(() => rootNode.GetHints().OfType<Hinter.FunctionCalledWithConstParamsHint>(), Is.Empty);
155155
}
156156

157157
[Test]
@@ -162,7 +162,7 @@ public void CheckCallingFunctionWithConstParamUsingTimeDoesNotTriggerHint()
162162

163163
var rootNode = new Parser(lexer).Parse();
164164

165-
Assert.That(() => rootNode.GetHints().OfType<Hinter.FunctionCalledWithConstParams>(), Is.Empty);
165+
Assert.That(() => rootNode.GetHints().OfType<Hinter.FunctionCalledWithConstParamsHint>(), Is.Empty);
166166
}
167167

168168
[Test]
@@ -173,7 +173,7 @@ public void CheckCallingFunctionWithNonNumericVectorDoesNotTriggerHint()
173173

174174
var rootNode = new Parser(lexer).Parse();
175175

176-
Assert.That(() => rootNode.GetHints().OfType<Hinter.FunctionCalledWithConstParams>(), Is.Empty);
176+
Assert.That(() => rootNode.GetHints().OfType<Hinter.FunctionCalledWithConstParamsHint>(), Is.Empty);
177177
}
178178

179179
[Test]
@@ -190,7 +190,7 @@ public void CheckCallingFunctionWithConstParamTriggersHint(
190190

191191
var rootNode = new Parser(lexer).Parse();
192192

193-
Assert.That(() => rootNode.GetHints().OfType<Hinter.FunctionCalledWithConstParams>().ToList(), Has.Count.EqualTo(1));
193+
Assert.That(() => rootNode.GetHints().OfType<Hinter.FunctionCalledWithConstParamsHint>().ToList(), Has.Count.EqualTo(1));
194194
}
195195
}
196196
}

0 commit comments

Comments
 (0)