Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Inlining: inline more variables #174

Merged
merged 1 commit into from
Dec 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 3 additions & 6 deletions src/ast.fs
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,13 @@ open Options.Globals

type Ident(name: string) =
let mutable newName = name
let mutable inlined = newName.StartsWith("i_")
let mutable lValue = false

member this.Name = newName
member this.OldName = name
member this.Rename(n) = newName <- n
member this.ToBeInlined = inlined
member this.Inline() = inlined <- true
member this.IsLValue = lValue
member this.MarkLValue() = lValue <- true
member val ToBeInlined = newName.StartsWith("i_") with get, set
member val IsLValue = false with get, set
member val IsConst = false with get, set

// Real identifiers cannot start with a digit, but the temporary ids of the rename pass are numbers.
member this.IsUniqueId = System.Char.IsDigit this.Name.[0]
Expand Down
42 changes: 28 additions & 14 deletions src/rewriter.fs
Original file line number Diff line number Diff line change
Expand Up @@ -236,15 +236,18 @@ let collectReferences stmtList =
// Variables are always safe to inline when all of:
// - the variable is used only once in the current block
// - the variable is not used in a sub-block (e.g. inside a loop)
// - the init value is trivial (doesn't depend on a variable)
// - the init value refers only to constants
// When aggressive inlining is enabled, additionally inline when all of:
// - the variable never appears in an lvalue position (is never written to
// after initalization)
// - the init value is trivial
// - the init value is has no dependency
// The init is considered trivial when:
// - it doesn't depend on a variable
// - it depends only on variables proven constants
let findInlinable block =
// Variables that are defined in this scope.
// The boolean indicates if the variable initialization has dependencies.
let localDefs = Dictionary<string, (Ident * bool)>()
// The booleans indicate if the variable initialization has dependencies / unsafe dependencies.
let localDefs = Dictionary<string, (Ident * bool * bool)>()
// List of expressions in the current block. Do not look in sub-blocks.
let mutable localExpr = []
for stmt: Stmt in block do
Expand All @@ -256,9 +259,16 @@ let findInlinable block =
| None -> ()
| Some init ->
localExpr <- init :: localExpr
// Inline only if the init value doesn't depend on other variables.
let deps = collectReferences [Expr init]
localDefs.[def.name.Name] <- (def.name, deps.Count > 0)
let hasUnsafeDep = deps |> Seq.exists (fun kv ->
if localDefs.ContainsKey kv.Key then
// A local variable not reassigned is effectively constant.
let ident, _, _ = localDefs.[kv.Key]
ident.IsLValue
else
true
)
localDefs.[def.name.Name] <- (def.name, deps.Count > 0, hasUnsafeDep)
| Expr e
| Jump (_, Some e) -> localExpr <- e :: localExpr
| Verbatim _ | Jump (_, None) | Block _ | If _| ForE _ | ForD _ | While _ | DoWhile _ | Switch _ -> ()
Expand All @@ -267,13 +277,16 @@ let findInlinable block =
let allReferences = collectReferences block

for def in localDefs do
let ident, hasInitDeps = def.Value
let ident, hasInitDeps, hasUnsafeDeps = def.Value
if not ident.ToBeInlined then
// AggroInlining could in theory do inlining when hasUnsafeDeps=false.
// However, it seems to increase the compressed size, and might affect performance.
if options.aggroInlining && not hasInitDeps && not ident.IsLValue then
ident.Inline()
ident.ToBeInlined <- true

match localReferences.TryGetValue(def.Key), allReferences.TryGetValue(def.Key) with
| (true, 1), (true, 1) when not hasInitDeps -> ident.Inline()
| (false, _), (false, _) -> ident.Inline()
| (true, 1), (true, 1) when not hasUnsafeDeps -> ident.ToBeInlined <- true
| (false, _), (false, _) -> ident.ToBeInlined <- true
| _ -> ()

let private simplifyStmt = function
Expand Down Expand Up @@ -333,7 +346,7 @@ let inlineAllConsts li =
// compiler would have yelled if it weren't really really const, so we
// can brutishly just inline it.
| ({typeQ = tyQ}, defs) as d when List.contains "const" tyQ ->
for (def:DeclElt) in defs do def.name.Inline()
for (def:DeclElt) in defs do def.name.ToBeInlined <- true
d
| d -> d
let mapStmt = function
Expand All @@ -356,7 +369,7 @@ let markLValues li =
let markVars env = function
| Var v as e ->
match env.vars.TryFind v.Name with
| Some (_, {name = vv}) -> vv.MarkLValue(); e
| Some (_, {name = vv}) -> vv.IsLValue <- true; e
| _ -> e
| e -> e
let assignOps = Set.ofList ["="; "+="; "-="; "*="; "/="; "%=";
Expand All @@ -380,7 +393,7 @@ let markLValues li =
let assignQuals = Set.ofList ["out"; "inout"]
let argAssigns (ty, _) =
List.exists (fun tyQ -> Set.contains tyQ assignQuals) ty.typeQ
if List.exists argAssigns args then id.MarkLValue()
if List.exists argAssigns args then id.IsLValue <- true
let processTl = function
| Function(fct, _) -> maybeMarkFct fct
| _ -> ()
Expand All @@ -395,7 +408,8 @@ let simplify li =
// markLValues doesn't change the AST so we could do it unconditionally,
// but we only need the information for aggroInlining so don't bother if
// it's off.
|> if options.aggroInlining then markLValues >> inlineAllConsts else id
|> markLValues
|> if options.aggroInlining then inlineAllConsts else id
|> iterateSimplifyAndInline
|> List.choose (function
| TLDecl (ty, li) -> TLDecl (rwType ty, declsNotToInline li) |> Some
Expand Down
72 changes: 36 additions & 36 deletions tests/real/mandelbulb.expected
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/* File generated with Shader Minifier 1.3
/* File generated with
* http://www.ctrl-alt-test.fr
*/
#ifndef MANDELBULB_EXPECTED_
Expand Down Expand Up @@ -36,10 +36,10 @@ const char *mandelbulb_frag =
"n=n*8.;"
"c=v+z*vec3(sin(s)*sin(n),cos(s),sin(s)*cos(n));"
"\n#else\n"
"float d=c.x,m=d*d,p=m*m,l=c.y,a=l*l,r=a*a,w=c.z,g=w*w,u=g*g,q=m+g,b=inversesqrt(q*q*q*q*q*q*q),F=p+r+u-6.*a*g-6.*m*a+2.*g*m,C=m-a+g;"
"c.x=v.x+64.*d*l*w*(m-g)*C*(p-6.*m*g+u)*F*b;"
"c.y=v.y+-16.*a*q*C*C+F*F;"
"c.z=v.z+-8.*l*C*(p*p-28.*p*m*g+70.*p*u-28.*m*g*u+u*u)*F*b;"
"float d=c.x,m=d*d,p=m*m,l=c.y,r=l*l,a=c.z,w=a*a,g=w*w,u=m+w,q=inversesqrt(u*u*u*u*u*u*u),b=p+r*r+g-6.*r*w-6.*m*r+2.*w*m,F=m-r+w;"
"c.x=v.x+64.*d*l*a*(m-w)*F*(p-6.*m*w+g)*b*q;"
"c.y=v.y+-16.*r*u*F*F+b*b;"
"c.z=v.z+-8.*l*F*(p*p-28.*p*m*w+70.*p*g-28.*m*w*g+g*g)*b*q;"
"\n#endif\n"
"x=dot(c,c);"
"i=min(i,vec4(c.xyz*c.xyz,x));"
Expand All @@ -50,7 +50,7 @@ const char *mandelbulb_frag =
"f=0.;"
"return true;"
"}"
"bool f(vec3 v,vec3 c,out float o,float y,out vec3 x,out vec4 t,float q)"
"bool f(vec3 v,vec3 c,out float o,float y,out vec3 x,out vec4 t,float u)"
"{"
"vec4 i=vec4(0.,0.,0.,1.25);"
"vec2 e;"
Expand All @@ -65,26 +65,26 @@ const char *mandelbulb_frag =
"float n;"
"vec3 s;"
"vec4 m;"
"float g=1./sqrt(1.+q*q);"
"for(float a=e.x;a<e.y;)"
"float w=1./sqrt(1.+u*u);"
"for(float r=e.x;r<e.y;)"
"{"
"vec3 z=v+c*a;"
"float d=clamp(.001*a*g,1e-6,.005),p=d*.1;"
"vec4 u;"
"vec3 z=v+c*r;"
"float d=clamp(.001*r*w,1e-6,.005),p=d*.1;"
"vec4 q;"
"float l;"
"if(f(z,l,m))"
"return o=a,x=normalize(s),t=m,true;"
"float w;"
"f(z+vec3(p,0.,0.),w,u);"
"return o=r,x=normalize(s),t=m,true;"
"float g;"
"f(z+vec3(p,0.,0.),g,q);"
"float a;"
"f(z+vec3(0.,p,0.),a,q);"
"float b;"
"f(z+vec3(0.,p,0.),b,u);"
"float r;"
"f(z+vec3(0.,0.,p),r,u);"
"s=vec3(w-l,b-l,r-l);"
"f(z+vec3(0.,0.,p),b,q);"
"s=vec3(g-l,a-l,b-l);"
"n=.5*l*p/length(s);"
"if(n<d)"
"return t=m,x=normalize(s),o=a,true;"
"a+=n;"
"return t=m,x=normalize(s),o=r,true;"
"r+=n;"
"}"
"return false;"
"}"
Expand All @@ -93,30 +93,30 @@ const char *mandelbulb_frag =
"vec2 v=-1.+2.*gl_FragCoord.xy/resolution.xy,c=v*vec2(1.33,1.);"
"vec3 x=vec3(.577,.577,.577),o=vec3(-.707,0.,.707);"
"float y=1.,s=1.4+.2*cos(6.28318*time/20.);"
"vec3 z=vec3(s*sin(6.28318*time/20.),.3-.4*sin(6.28318*time/20.),s*cos(6.28318*time/20.)),p=vec3(0.,.1,0.),m=normalize(p-z),u=vec3(0.,1.,0.),g=normalize(cross(m,u)),t=normalize(cross(g,m)),e=normalize(c.x*g+c.y*t+1.5*m),l,i;"
"vec4 a;"
"float q;"
"if(!f(z,e,q,1e20,l,a,y))"
"vec3 z=vec3(s*sin(6.28318*time/20.),.3-.4*sin(6.28318*time/20.),s*cos(6.28318*time/20.)),p=vec3(0.,.1,0.),m=normalize(p-z),t=vec3(0.,1.,0.),r=normalize(cross(m,t)),w=normalize(cross(r,m)),e=normalize(c.x*r+c.y*w+1.5*m),l,i;"
"vec4 u;"
"float b;"
"if(!f(z,e,b,1e20,l,u,y))"
"i=1.3*vec3(1.,.98,.9)*(.7+.3*e.y);"
"else"
"{"
"vec3 n=z+q*e;"
"vec3 n=z+b*e;"
"float d=clamp(.2+.8*dot(x,l),0.,1.);"
"d=d*d;"
"float C=clamp(.3+.7*dot(o,l),0.,1.),w=clamp(1.25*a.w-.4,0.,1.);"
"w=w*w*.5+.5*w;"
"float b;"
"vec3 r;"
"vec4 F;"
"float F=clamp(.3+.7*dot(o,l),0.,1.),a=clamp(1.25*u.w-.4,0.,1.);"
"a=a*a*.5+.5*a;"
"float q;"
"vec3 g;"
"vec4 C;"
"if(d>.001)"
"if(f(n,x,b,1e20,r,F,y))"
"if(f(n,x,q,1e20,g,C,y))"
"d=.1;"
"i=vec3(1.,1.,1.);"
"i=mix(i,vec3(.8,.6,.2),sqrt(a.x)*1.25);"
"i=mix(i,vec3(.8,.3,.3),sqrt(a.y)*1.25);"
"i=mix(i,vec3(.7,.4,.3),sqrt(a.z)*1.25);"
"i*=(.5+.5*l.y)*vec3(.14,.15,.16)*.8+d*vec3(1.,.85,.4)+.5*C*vec3(.08,.1,.14);"
"i*=vec3(pow(w,.8),pow(w,1.),pow(w,1.1));"
"i=mix(i,vec3(.8,.6,.2),sqrt(u.x)*1.25);"
"i=mix(i,vec3(.8,.3,.3),sqrt(u.y)*1.25);"
"i=mix(i,vec3(.7,.4,.3),sqrt(u.z)*1.25);"
"i*=(.5+.5*l.y)*vec3(.14,.15,.16)*.8+d*vec3(1.,.85,.4)+.5*F*vec3(.08,.1,.14);"
"i*=vec3(pow(a,.8),pow(a,1.),pow(a,1.1));"
"i=1.5*(i*.15+.85*sqrt(i));"
"}"
"vec2 d=v*.5+.5;"
Expand Down
12 changes: 6 additions & 6 deletions tests/unit/inline-aggro.expected
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
float inl1()
{
float f=42.,g=2.*f;
return f+g;
float f=42.;
return f+2.*f;
}
float inl2(float x)
{
Expand All @@ -14,13 +14,13 @@ float inl2(float x)
}
float inl3()
{
const float f=acos(-1.),g=2.*f;
return.75*g;
const float f=acos(-1.);
return 2.*f*.75;
}
float inl4()
{
const float f=acos(-1.),g=2.*f;
return.75*g;
const float f=acos(-1.);
return 2.*f*.75;
}
const float foo=123.;
float inl5()
Expand Down
4 changes: 2 additions & 2 deletions tests/unit/inline-fn.expected
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ float c()
}
float d()
{
float f=7.,g=f*f+1.;
return g+4.;
float f=7.;
return f*f+1.+4.;
}
float e()
{
Expand Down
7 changes: 7 additions & 0 deletions tests/unit/inline.aggro.expected
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,10 @@ float multiPass2()
{
return 9.;
}
uniform int time;
in int sync;
void dependOnConst()
{
int x=time+sync;
return x*2*3;
}
15 changes: 11 additions & 4 deletions tests/unit/inline.expected
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
float result;
void main()
{
float x=.5,a=.6*x*x;
result=a;
float x=.5;
result=.6*x*x;
}
int arithmetic()
{
Expand All @@ -14,8 +14,8 @@ int vars(int arg,int arg2)
}
int arithmetic2()
{
int a=2,c=a+3;
return 4*a*c;
int a=2;
return 4*a*(a+3);
}
int unusedVars()
{
Expand All @@ -33,3 +33,10 @@ float multiPass2()
{
return 9.;
}
uniform int time;
in int sync;
void dependOnConst()
{
int x=time+sync;
return x*2*3;
}
9 changes: 9 additions & 0 deletions tests/unit/inline.frag
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,12 @@ float multiPass2() {
float b = i_a + 5.0;
return b;
}

uniform int time;
in int sync;

void dependOnConst() {
int x = time + sync;
int y = x * 2;
return y*3;
}
7 changes: 7 additions & 0 deletions tests/unit/inline.no.expected
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,10 @@ float multiPass2()
float b=9.;
return b;
}
uniform int time;
in int sync;
void dependOnConst()
{
int x=time+sync,y=x*2;
return y*3;
}