diff --git a/.gitignore b/.gitignore index 5ebd21a1..5344bd22 100644 --- a/.gitignore +++ b/.gitignore @@ -33,6 +33,8 @@ local.properties ## Visual Studio ################# +.vs/ + ## Ignore Visual Studio temporary files, build results, and ## files generated by popular Visual Studio add-ons. diff --git a/src/api.fs b/src/api.fs index ac6f4250..a9c8e6fd 100644 --- a/src/api.fs +++ b/src/api.fs @@ -23,6 +23,8 @@ let minify (files: (string*string)[]) = vprintf "File parsed. "; printSize shaders for shader in shaders do + if not options.noRemoveUnused then + shader.code <- Rewriter.removeUnused shader.code if shader.reorderFunctions then shader.code <- Rewriter.reorder shader.code shader.code <- Rewriter.simplify shader.code diff --git a/src/options.fs b/src/options.fs index b8f89348..37692f2d 100644 --- a/src/options.fs +++ b/src/options.fs @@ -35,6 +35,7 @@ type CliArguments = | [] NoRenamingList of string | [] NoSequence | [] Smoothstep + | [] NoRemoveUnused | [] Filenames of filename:string list interface IArgParserTemplate with @@ -53,6 +54,7 @@ type CliArguments = | NoRenamingList _ -> "Comma-separated list of functions to preserve" | NoSequence -> "Do not use the comma operator trick" | Smoothstep -> "Use IQ's smoothstep trick" + | NoRemoveUnused -> "Do not remove unused code" | Filenames _ -> "List of files to minify" type Options() = @@ -69,6 +71,7 @@ type Options() = member val noSequence = false with get, set member val noRenaming = false with get, set member val noRenamingList = ["main"] with get, set + member val noRemoveUnused = false with get, set member val filenames = [||]: string[] with get, set module Globals = @@ -109,6 +112,7 @@ let private initPrivate argv needFiles = options.aggroInlining <- args.Contains(AggroInlining) && not (args.Contains(NoInlining)) options.noSequence <- args.Contains(NoSequence) options.noRenaming <- args.Contains(NoRenaming) + options.noRemoveUnused <- args.Contains(NoRemoveUnused) options.noRenamingList <- noRenamingList options.filenames <- filenames true diff --git a/src/rewriter.fs b/src/rewriter.fs index 0d9643ef..2e3e8994 100644 --- a/src/rewriter.fs +++ b/src/rewriter.fs @@ -289,25 +289,32 @@ let simplify li = (* Reorder functions because of forward declarations *) +type CallGraphNode = { + func: TopLevel + funcType: FunctionType + name: string + callees: string list +} + let rec private findRemove callback = function - | (name, [], content) :: l -> + | node :: l when node.callees.IsEmpty -> //printfn "=> %s" name - callback name content + callback node l | [] -> failwith "Cannot reorder functions (probably because of a recursion)." | x :: l -> x :: findRemove callback l // slow, but who cares? -let private graphReorder deps = +let private graphReorder nodes = let mutable list = [] let mutable lastName = "" - let rec loop deps = - let deps = findRemove (fun (s: Ident) x -> lastName <- s.Name; list <- x :: list) deps - let deps = deps |> List.map (fun (n, d, c) -> n, List.filter ((<>) lastName) d, c) - if deps <> [] then loop deps + let rec loop nodes = + let nodes = findRemove (fun node -> lastName <- node.name; list <- node.func :: list) nodes + let nodes = nodes |> List.map (fun n -> { n with callees = List.filter ((<>) lastName) n.callees }) + if nodes <> [] then loop nodes - if deps <> [] then loop deps + if nodes <> [] then loop nodes list |> List.rev @@ -324,14 +331,27 @@ let private computeDependencies block = // This function assumes that functions are NOT overloaded let private computeAllDependencies code = - let fct = code |> List.choose (function - | Function(fct, block) as f -> Some (fct.fName, block, f) + let functions = code |> List.choose (function + | Function(funcType, block) as f -> Some (funcType, funcType.fName.Name, block, f) | _ -> None) - let deps = fct |> List.map (fun (name, block, f) -> - let dep = computeDependencies block - |> List.filter (fun name -> fct |> List.exists (fun (x,_,_) -> name = x.Name)) - name, dep, f) - deps + let nodes = functions |> List.map (fun (funcType, name, block, f) -> + let callees = computeDependencies block + |> List.filter (fun name2 -> functions |> List.exists (fun (_,n,_,_) -> name2 = n)) + { CallGraphNode.func = f; funcType = funcType; name = name; callees = callees }) + nodes + + +let removeUnused code = + let nodes = computeAllDependencies code + let isUnused node = + let canBeRenamed = not (options.noRenamingList |> List.contains node.name) // noRenamingList includes "main" + let isCalled = (nodes |> List.exists (fun n -> n.callees |> List.contains node.name)) + let isExternal = options.hlsl && node.funcType.semantics <> [] + canBeRenamed && not isCalled && not isExternal + let unused = set [for node in nodes do if isUnused node then yield node.func] + code |> List.filter (function + | Function _ as t -> not (unused |> Set.contains t) + | _ -> true) // reorder functions if there were forward declarations let reorder code = diff --git a/tests/commands.txt b/tests/commands.txt index 018a620c..e143335d 100644 --- a/tests/commands.txt +++ b/tests/commands.txt @@ -8,52 +8,52 @@ # Unit tests ---no-renaming --format c-variables -o tests/unit/blocks.expected tests/unit/blocks.frag ---hlsl --no-inlining --no-renaming --format c-variables -o tests/unit/geometry.hlsl.expected tests/unit/geometry.hlsl ---no-renaming --no-inlining --format c-array -o tests/unit/operators.expected tests/unit/operators.frag +--no-remove-unused --no-renaming --format c-variables -o tests/unit/blocks.expected tests/unit/blocks.frag +--no-remove-unused --hlsl --no-inlining --no-renaming --format c-variables -o tests/unit/geometry.hlsl.expected tests/unit/geometry.hlsl +--no-remove-unused --no-renaming --no-inlining --format c-array -o tests/unit/operators.expected tests/unit/operators.frag --no-renaming --no-inlining --format c-array -o tests/unit/minus-zero.expected tests/unit/minus-zero.frag ---no-renaming --format c-array --no-inlining -o tests/unit/float.frag.expected tests/unit/float.frag ---no-renaming --format indented --no-inlining -o tests/unit/nested_if.frag.expected tests/unit/nested_if.frag +--no-remove-unused --no-renaming --format c-array --no-inlining -o tests/unit/float.frag.expected tests/unit/float.frag +--no-remove-unused --no-renaming --format indented --no-inlining -o tests/unit/nested_if.frag.expected tests/unit/nested_if.frag --format indented -o tests/unit/precision.frag.expected tests/unit/precision.frag ---format c-array --no-inlining --no-renaming -o tests/unit/verbatim.frag.expected tests/unit/verbatim.frag ---format indented --no-renaming -o tests/unit/forward_declaration.frag.expected tests/unit/forward_declaration.frag +--no-remove-unused --format c-array --no-inlining --no-renaming -o tests/unit/verbatim.frag.expected tests/unit/verbatim.frag +--no-remove-unused --format indented --no-renaming -o tests/unit/forward_declaration.frag.expected tests/unit/forward_declaration.frag # Inlining unit tests ---no-renaming --format indented -o tests/unit/inline.expected tests/unit/inline.frag ---no-renaming --format indented --aggressive-inlining -o tests/unit/inline.aggro.expected tests/unit/inline.frag ---no-renaming --format indented --no-inlining -o tests/unit/inline.no.expected tests/unit/inline.frag ---no-renaming --format indented -o tests/unit/inline-aggro.expected tests/unit/inline-aggro.frag ---no-renaming --format indented --aggressive-inlining -o tests/unit/inline-aggro.aggro.expected tests/unit/inline-aggro.frag ---no-renaming --format indented -o tests/unit/inline-fn.expected tests/unit/inline-fn.frag ---no-renaming --format indented --aggressive-inlining -o tests/unit/inline-fn.aggro.expected tests/unit/inline-fn.frag +--no-remove-unused --no-renaming --format indented -o tests/unit/inline.expected tests/unit/inline.frag +--no-remove-unused --no-renaming --format indented --aggressive-inlining -o tests/unit/inline.aggro.expected tests/unit/inline.frag +--no-remove-unused --no-renaming --format indented --no-inlining -o tests/unit/inline.no.expected tests/unit/inline.frag +--no-remove-unused --no-renaming --format indented -o tests/unit/inline-aggro.expected tests/unit/inline-aggro.frag +--no-remove-unused --no-renaming --format indented --aggressive-inlining -o tests/unit/inline-aggro.aggro.expected tests/unit/inline-aggro.frag +--no-remove-unused --no-renaming --format indented -o tests/unit/inline-fn.expected tests/unit/inline-fn.frag +--no-remove-unused --no-renaming --format indented --aggressive-inlining -o tests/unit/inline-fn.aggro.expected tests/unit/inline-fn.frag # Partial renaming tests ---no-renaming --format c-variables -o tests/unit/function_comma.expected tests/unit/function_comma.frag +--no-remove-unused --no-renaming --format c-variables -o tests/unit/function_comma.expected tests/unit/function_comma.frag --preserve-externals --format c-variables -o tests/real/mandelbulb.expected tests/real/mandelbulb.frag --preserve-all-globals --format c-variables -o tests/real/to_the_road_of_ribbon.expected tests/real/to_the_road_of_ribbon.frag --no-renaming-list rotatey --format c-variables -o tests/real/sult.expected tests/real/sult.frag ---preserve-externals -o tests/unit/externals.preserved.expected tests/unit/externals.frag +--no-remove-unused --preserve-externals -o tests/unit/externals.preserved.expected tests/unit/externals.frag # Multifile tests ---format c-array -o tests/unit/inout.expected tests/unit/inout.frag tests/unit/inout2.frag +--no-remove-unused --format c-array -o tests/unit/inout.expected tests/unit/inout.frag tests/unit/inout2.frag # Tests with full renaming ---format c-array --no-inlining -o tests/unit/many_variables.expected tests/unit/many_variables.frag +--no-remove-unused --format c-array --no-inlining -o tests/unit/many_variables.expected tests/unit/many_variables.frag --hlsl -o tests/real/elevated.hlsl.expected tests/real/elevated.hlsl -o tests/real/lunaquatic.frag.expected tests/real/lunaquatic.frag -o tests/real/yx_long_way_from_home.frag.expected tests/real/yx_long_way_from_home.frag -o tests/real/oscars_chair.frag.expected tests/real/oscars_chair.frag -o tests/real/the_real_party_is_in_your_pocket.frag.expected tests/real/the_real_party_is_in_your_pocket.frag --o tests/unit/function_overload.expected tests/unit/function_overload.frag --o tests/unit/externals.expected tests/unit/externals.frag --o tests/unit/qualifiers.expected tests/unit/qualifiers.frag --o tests/unit/macros.expected --no-inlining tests/unit/macros.frag ---format indented -o tests/unit/switch.expected tests/unit/switch.frag ---format indented -o tests/unit/forbidden.expected tests/unit/forbidden.frag +--no-remove-unused -o tests/unit/function_overload.expected tests/unit/function_overload.frag +--no-remove-unused -o tests/unit/externals.expected tests/unit/externals.frag +--no-remove-unused -o tests/unit/qualifiers.expected tests/unit/qualifiers.frag +--no-remove-unused -o tests/unit/macros.expected --no-inlining tests/unit/macros.frag +--no-remove-unused --format indented -o tests/unit/switch.expected tests/unit/switch.frag +--no-remove-unused --format indented -o tests/unit/forbidden.expected tests/unit/forbidden.frag --format c-variables -o tests/real/heart.frag.expected tests/real/heart.frag # Optimization tests diff --git a/tests/real/sult.expected b/tests/real/sult.expected index f0db069a..808a7b11 100644 --- a/tests/real/sult.expected +++ b/tests/real/sult.expected @@ -4,56 +4,34 @@ #ifndef SULT_EXPECTED_ # define SULT_EXPECTED_ # define VAR_resolution "m" -# define VAR_time "c" +# define VAR_time "l" const char *sult_frag = - "float v=5.,z=.9,y=0.,a=90.,x=0.;" - "vec3 r=vec3(1,1,1),n=vec3(0,0,1),s=vec3(0,0,1.5);" + "float v=5.,y=.9,a=0.,f=90.,e=0.;" + "vec3 r=vec3(1,1,1),c=vec3(0,0,1),t=vec3(0,0,1.5);" "uniform vec2 m;" - "uniform float c;" - "vec3 rotatey(vec3 v,float r)" + "uniform float l;" + "vec3 rotatey(vec3 v,float m)" "{" - "return vec3(v.x*cos(r)+v.z*sin(r),v.y,v.z*cos(r)-v.x*sin(r));" + "return vec3(v.x*cos(m)+v.z*sin(m),v.y,v.z*cos(m)-v.x*sin(m));" "}" - "vec3 e(vec3 v,float r)" + "float u=0.,n=10.;" + "float s(vec3 m)" "{" - "return vec3(v.y*cos(r)+v.z*sin(r),v.x,v.z*cos(r)-v.y*sin(r));" - "}" - "float f=0.,w=10.;" - "float e(vec3 r)" - "{" - "float y=c,w,z=0.,g,x,a;" - "vec3 m;" - "r+=(sin(r.zxy*1.7+y)+sin(r.yzx+y*3.))*.2;" + "float z=l,y,a=0.,f,e,c;" + "vec3 r;" + "m+=(sin(m.zxy*1.7+z)+sin(m.yzx+z*3.))*.2;" "if(v<6.)" - "z=length(r.xyz*vec3(1,1,.1)-vec3(0,-.1,y*.15-.3))-.34;" + "a=length(m.xyz*vec3(1,1,.1)-vec3(0,-.1,z*.15-.3))-.34;" "else" - " z=length(r.xy+vec2(0.,.7))-.3+(sin(r.z*17.+y*.6)+sin(r.z*2.)*6.)*.01;" - "r.xy=vec2(atan(r.x,r.y)*1.113,1.6-length(r.xy)-sin(y*2.)*.3);" - "m=fract(r.xzz+.5).xyz-.5;" - "m.y=(r.y-.35)*1.3;" - "w=max(abs(r.y-.3)-.05,abs(length(fract(r.xz)-.5)-.4)-.03);" - "f=step(z,w);" - "return min(min(w,z),r.y-.2);" + " a=length(m.xy+vec2(0.,.7))-.3+(sin(m.z*17.+z*.6)+sin(m.z*2.)*6.)*.01;" + "m.xy=vec2(atan(m.x,m.y)*1.113,1.6-length(m.xy)-sin(z*2.)*.3);" + "r=fract(m.xzz+.5).xyz-.5;" + "r.y=(m.y-.35)*1.3;" + "y=max(abs(m.y-.3)-.05,abs(length(fract(m.xz)-.5)-.4)-.03);" + "u=step(a,y);" + "return min(min(y,a),m.y-.2);" "}" - "vec3 d=vec3(.19,.2,.24),o=vec3(1),t=vec3(.45,.01,0),g=vec3(.17,0,0);" - "void e()" - "{" - "vec2 l=-1.+2.*gl_FragCoord.xy/m.xy;" - "vec3 i=normalize(rotatey(rotatey(vec3(l.y*z,l.x*z*1.33,1),-y*.035).yxz,(a+x*c)*.035)),p=n+s*c;" - "float u=1.,h=0.,F,C,b=0.,Z,Y,X,W;" - "vec3 V=vec3(.01,0,0),U=V.yyy,T;" - "while(u>.1)" - "{" - "for(F=b,C=1.;F.005;F+=C)" - "C=e(p+i*F);" - "if(F0.&&!d(a+o*.002,h,b,k,S))" - "c+=z*u*n;" + "if(h>0.&&!d(t+l*.002,o,b,k,S))" + "c+=r*h*n;" "i=s(i.y);" "}" "else" - " if(abs(p)>.1)" - "return c+l(m)*z;" + " if(abs(a)>.1)" + "return c+d(x)*r;" "else" " break;" "}" @@ -192,26 +170,26 @@ const char *yx_long_way_from_home_frag = "void main()" "{" "vec2 v=gl_FragCoord.xy/iResolution.xy-.5;" - "float y=iTime+(v.x+iResolution.x*v.y)*1.51269341231;" - "i=s(y);" + "float x=iTime+(v.x+iResolution.x*v.y)*1.51269341231;" + "i=s(x);" "v+=(i-.5)/iResolution.xy;" "v.x*=iResolution.x/iResolution.y;" - "const vec3 m=vec3(-4,2,3),f=vec3(0,0,0);" - "const float x=distance(m,f);" - "const vec2 z=vec2(1,2)*.015;" - "vec3 c=vec3(0),r=normalize(vec3(v,2.));" + "const vec3 f=vec3(-4,2,3),y=vec3(0,0,0);" + "const float z=distance(f,y);" + "const vec2 r=vec2(1,2)*.015;" + "vec3 c=vec3(0),o=normalize(vec3(v,2.));" "vec2 t=d();" - "c.xy+=t*z;" - "r.xy-=t*z*r.z/x;" - "vec3 l=f-m;" - "float p=-atan(l.y,length(l.xz)),o=-atan(l.x,l.z);" - "c.yz*=n(p);" - "r.yz*=n(p);" - "c.xz*=n(o);" - "r.xz*=n(o);" - "c+=m;" - "vec4 a=vec4(h(c,r),1);" - "gl_FragColor=!isnan(a.x)&&a.x>=0.?a:vec4(0);" + "c.xy+=t*r;" + "o.xy-=t*r*o.z/z;" + "vec3 l=y-f;" + "float a=-atan(l.y,length(l.xz)),m=-atan(l.x,l.z);" + "c.yz*=n(a);" + "o.yz*=n(a);" + "c.xz*=n(m);" + "o.xz*=n(m);" + "c+=f;" + "vec4 g=vec4(h(c,o),1);" + "gl_FragColor=!isnan(g.x)&&g.x>=0.?g:vec4(0);" "}"; #endif // YX_LONG_WAY_FROM_HOME_FRAG_EXPECTED_ diff --git a/tests/unit/unused_removal.frag b/tests/unit/unused_removal.frag new file mode 100644 index 00000000..ca912b16 --- /dev/null +++ b/tests/unit/unused_removal.frag @@ -0,0 +1,15 @@ +float f(); + +float f(){ + float r = 1.; + + return r; +} + +vec3 g() { return vec3(0.); } + +vec2 pos = vec2(0.5); + +void main(){ + gl_FragColor = vec4(1., g()); +} diff --git a/tests/unit/unused_removal.frag.expected b/tests/unit/unused_removal.frag.expected new file mode 100644 index 00000000..a1527c45 --- /dev/null +++ b/tests/unit/unused_removal.frag.expected @@ -0,0 +1 @@ +vec2 v=vec2(.5);vec3 n(){return vec3(0.);}void main(){gl_FragColor=vec4(1.,n());}