diff --git a/src/ast.fs b/src/ast.fs index 7080ab41..f9c3b1e2 100644 --- a/src/ast.fs +++ b/src/ast.fs @@ -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] diff --git a/src/rewriter.fs b/src/rewriter.fs index 908225f7..195f434a 100644 --- a/src/rewriter.fs +++ b/src/rewriter.fs @@ -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() + // The booleans indicate if the variable initialization has dependencies / unsafe dependencies. + let localDefs = Dictionary() // List of expressions in the current block. Do not look in sub-blocks. let mutable localExpr = [] for stmt: Stmt in block do @@ -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 _ -> () @@ -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 @@ -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 @@ -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 ["="; "+="; "-="; "*="; "/="; "%="; @@ -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 | _ -> () @@ -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 diff --git a/tests/real/mandelbulb.expected b/tests/real/mandelbulb.expected index 60ac59ae..d014d8aa 100644 --- a/tests/real/mandelbulb.expected +++ b/tests/real/mandelbulb.expected @@ -1,4 +1,4 @@ -/* File generated with Shader Minifier 1.3 +/* File generated with * http://www.ctrl-alt-test.fr */ #ifndef MANDELBULB_EXPECTED_ @@ -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));" @@ -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;" @@ -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.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;" diff --git a/tests/unit/inline-aggro.expected b/tests/unit/inline-aggro.expected index e75bc1d9..98d584ec 100644 --- a/tests/unit/inline-aggro.expected +++ b/tests/unit/inline-aggro.expected @@ -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) { @@ -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() diff --git a/tests/unit/inline-fn.expected b/tests/unit/inline-fn.expected index fd46a01b..1f478069 100644 --- a/tests/unit/inline-fn.expected +++ b/tests/unit/inline-fn.expected @@ -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() { diff --git a/tests/unit/inline.aggro.expected b/tests/unit/inline.aggro.expected index 8d2448e7..ee3adcdb 100644 --- a/tests/unit/inline.aggro.expected +++ b/tests/unit/inline.aggro.expected @@ -31,3 +31,10 @@ float multiPass2() { return 9.; } +uniform int time; +in int sync; +void dependOnConst() +{ + int x=time+sync; + return x*2*3; +} diff --git a/tests/unit/inline.expected b/tests/unit/inline.expected index 52084a0f..02894bb1 100644 --- a/tests/unit/inline.expected +++ b/tests/unit/inline.expected @@ -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() { @@ -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() { @@ -33,3 +33,10 @@ float multiPass2() { return 9.; } +uniform int time; +in int sync; +void dependOnConst() +{ + int x=time+sync; + return x*2*3; +} diff --git a/tests/unit/inline.frag b/tests/unit/inline.frag index eb0ac50f..d909ba51 100644 --- a/tests/unit/inline.frag +++ b/tests/unit/inline.frag @@ -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; +} \ No newline at end of file diff --git a/tests/unit/inline.no.expected b/tests/unit/inline.no.expected index 88c26c96..25c70fb4 100644 --- a/tests/unit/inline.no.expected +++ b/tests/unit/inline.no.expected @@ -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; +}