diff --git a/README.md b/README.md index 9a41b850..4a147fed 100644 --- a/README.md +++ b/README.md @@ -463,6 +463,26 @@ Output: return y ? z : w; ``` +### Loops + +`while` loops are rewritten as `for` loops. This sometimes enable removing semi-colons or braces, +either by moving the preceding statement into the initialization of the `for`, +or by moving the last statement of the loop body into the increment part of the `for`. + +Input: +```glsl +i = 0.; +while (i < 50) { + f(i); + i++; +} +``` + +Output: +```glsl +for(i=0.;i<50;i++)f(i); +``` + ### Merge declarations If multiple values of the same type are declared next to each other, we can merge diff --git a/src/rewriter.fs b/src/rewriter.fs index 16582859..71377ff8 100644 --- a/src/rewriter.fs +++ b/src/rewriter.fs @@ -352,6 +352,14 @@ let squeezeBlockWithComma = function else stmt | stmt -> stmt +let hasNoDecl = List.forall (function Decl _ -> false | _ -> true) + +let rec hasNoContinue stmts = stmts |> List.forall (function + | Jump (JumpKeyword.Continue, _) -> false + | If (_cond, bodyT, bodyF) -> hasNoContinue [bodyT] && hasNoContinue (Option.toList bodyF) + | Switch (_e, cases) -> cases |> List.forall (fun (_label, stmts) -> hasNoContinue stmts) + | _ -> true) + let private simplifyBlock stmts = let b = stmts // Avoid some optimizations when there are preprocessor directives. @@ -368,7 +376,22 @@ let private simplifyBlock stmts = | Decl (_, []) -> false | _ -> true) - let hasNoDecl = List.forall (function Decl _ -> false | _ -> true) + // Merge two consecutive items into one, everywhere possible in a list. + let rec squeeze (f : 'a * 'a -> 'a option) = function + | h1 :: h2 :: t -> + match f (h1, h2) with + | Some x -> squeeze f (x :: t) + | None -> h1 :: (squeeze f (h2 :: t)) + | h :: t -> h :: t + | [] -> [] + + // Merge preceding expression into a for's init. + let b = b |> squeeze (function + | (Expr e, ForE (None, cond, inc, body)) -> // a=0;for(;i<5;++i); -> for(a=0;i<5;++i); + Some (ForE (Some e, cond, inc, body)) + | (Expr e, While (cond, body)) -> // a=0;while(i<5); -> for(a=0;i<5;); + Some (ForE(Some e, Some cond, None, body)) + | _ -> None) // Inline inner decl-less blocks. (Presence of decl could lead to redefinitions.) a();{b();}c(); -> a();b();c(); let b = b |> List.collect (function @@ -414,11 +437,27 @@ let private simplifyStmt = function | Decl (ty, li) -> Decl (rwType ty, declsNotToInline li) | ForD ((ty, d), cond, inc, body) -> ForD((rwType ty, declsNotToInline d), cond, inc, squeezeBlockWithComma body) | ForE (init, cond, inc, body) -> ForE(init, cond, inc, squeezeBlockWithComma body) - | While (cond, body) -> While (cond, squeezeBlockWithComma body) + | While (cond, body) -> + match body with + | Expr e -> ForE (None, Some cond, Some e, Block []) // while(c)b(); -> for(;c;b()); + | Block stmts -> + match List.rev stmts with + | Expr last :: revBody when hasNoContinue stmts && hasNoDecl stmts -> + // This rewrite is only valid if: + // * continue is never used in this loop, and + // * the last expression of the body does not use any Decl from the body. + let block = match (List.rev revBody) with + | [stmt] -> stmt + | stmts -> Block stmts + ForE (None, Some cond, Some last, block) // while(c){a();b();} -> for(;c;b())a(); + | _ -> ForE (None, Some cond, None, squeezeBlockWithComma (Block stmts)) + | _ -> ForE (None, Some cond, None, squeezeBlockWithComma body) | DoWhile (cond, body) -> DoWhile (cond, squeezeBlockWithComma body) | If (True, e1, _) -> squeezeBlockWithComma e1 | If (False, _, Some e2) -> squeezeBlockWithComma e2 | If (False, _, None) -> Block [] + | If (cond, Block [], None) -> Expr cond // if(c); -> c; + | If (cond, Block [], Some (Block [])) -> Expr cond // if(c)else{}; -> c; | If (c, b, Some (Block [])) -> If(c, b, None) // "else{}" -> "" | If (cond, body1, body2) -> let (body1, body2) = squeezeBlockWithComma body1, Option.map squeezeBlockWithComma body2 diff --git a/tests/commands.txt b/tests/commands.txt index 9e73185e..f2e018e1 100644 --- a/tests/commands.txt +++ b/tests/commands.txt @@ -22,6 +22,7 @@ --no-renaming --no-inlining --format indented -o tests/unit/vectors.frag.expected tests/unit/vectors.frag --no-renaming --format indented -o tests/unit/deadcode.frag.expected tests/unit/deadcode.frag --no-renaming --no-inlining --format indented -o tests/unit/augmented.frag.expected tests/unit/augmented.frag +--no-remove-unused --no-renaming -o tests/unit/loop.frag.expected tests/unit/loop.frag # Inlining unit tests diff --git a/tests/compression_results.log b/tests/compression_results.log index 172723ae..94179913 100644 --- a/tests/compression_results.log +++ b/tests/compression_results.log @@ -1,4 +1,4 @@ -from-the-seas-to-the-stars.frag 14313 => 2358.823 +from-the-seas-to-the-stars.frag 14305 => 2358.450 the_real_party_is_in_your_pocket.frag 12196 => 1829.311 ed-209.frag 7888 => 1361.583 valley_ball.glsl 4369 => 890.945 @@ -14,4 +14,4 @@ elevated.hlsl 3406 => 611.302 buoy.frag 4194 => 622.602 orchard.frag 5588 => 1051.886 robin.frag 6293 => 1066.421 -Total: 87663 => 15860.542 +Total: 87655 => 15860.170 diff --git a/tests/real/from-the-seas-to-the-stars.frag.expected b/tests/real/from-the-seas-to-the-stars.frag.expected index 10cea3f3..ad3cd07b 100644 --- a/tests/real/from-the-seas-to-the-stars.frag.expected +++ b/tests/real/from-the-seas-to-the-stars.frag.expected @@ -432,8 +432,7 @@ void main() w.y=R(); w.z=R(); w.w=R(); - if(w.w<.5) - ; + w.w<.5; } } p.xyz+=(w.xyz*2-1)*pow(length(p),2)*.001; @@ -485,8 +484,7 @@ void main() w.y=R(); w.z=R(); w.w=R(); - if(w.w<.5) - ; + w.w<.5; } } p.xyz+=(w.xyz*2-1)*pow(length(p),2)*.001; diff --git a/tests/real/sult.expected b/tests/real/sult.expected index 621aa51a..35c5a9cc 100644 --- a/tests/real/sult.expected +++ b/tests/real/sult.expected @@ -18,18 +18,18 @@ const char *sult_frag = "float f=0.,w=10.;" "float e(vec3 m)" "{" - "float y=c,z,a=0.,g,o,t;" + "float y=c,g,z=0.,o,a,t;" "vec3 r;" "m+=(sin(m.zxy*1.7+y)+sin(m.yzx+y*3.))*.2;" - "a=v<6.?" + "z=v<6.?" "length(m.xyz*vec3(1,1,.1)-vec3(0,-.1,y*.15-.3))-.34:" "length(m.xy+vec2(0,.7))-.3+(sin(m.z*17.+y*.6)+sin(m.z*2.)*6.)*.01;" "m.xy=vec2(atan(m.x,m.y)*1.113,1.6-length(m.xy)-sin(y*2.)*.3);" "r=fract(m.xzz+.5).xyz-.5;" "r.y=(m.y-.35)*1.3;" - "z=max(abs(m.y-.3)-.05,abs(length(fract(m.xz)-.5)-.4)-.03);" - "f=step(a,z);" - "return min(min(z,a),m.y-.2);" + "g=max(abs(m.y-.3)-.05,abs(length(fract(m.xz)-.5)-.4)-.03);" + "f=step(z,g);" + "return min(min(g,z),m.y-.2);" "}" "vec3 d=vec3(.19,.2,.24),o=vec3(1),t=vec3(.45,.01,0),g=vec3(.17,0,0);" "void main()" @@ -39,14 +39,11 @@ const char *sult_frag = "float C=1.,b=0.,Z,Y,X=0.,W,V,U,T;" "u=vec3(.01,0,0);" "h=u.yyy;" - "while(C>.1)" - "{" - "for(Z=X,Y=1.;Z.005;Z+=Y)" - "Y=e(p+i*Z);" - "Z.1;Z.005;Z+=Y)" + "Y=e(p+i*Z);" "gl_FragColor.xyz=h;" "gl_FragColor.w=1.;" "}"; diff --git a/tests/unit/blocks.expected b/tests/unit/blocks.expected index eaff26cb..6514e124 100644 --- a/tests/unit/blocks.expected +++ b/tests/unit/blocks.expected @@ -33,8 +33,7 @@ const char *blocks_frag = "}" "int test_block()" "{" - "if(k==0)" - ";" + "k==0;" "for(int i=0;i<2;i++)" "{" "if(k==1)" @@ -72,10 +71,8 @@ const char *blocks_frag = "removeUselessElseAfterReturn2(0.);" "replaceIfReturnsByReturnTernary1(0.);" "gl_FragColor=vec4(.2,a,b,0);" - "if(a0) + for(;--i>0;) ; return 1; } diff --git a/tests/unit/inline-aggro.expected b/tests/unit/inline-aggro.expected index db9f4a65..db16cdd9 100644 --- a/tests/unit/inline-aggro.expected +++ b/tests/unit/inline-aggro.expected @@ -120,7 +120,7 @@ int noinl11(ivec3 x) int noinl12() { int i=10; - while(--i>0) + for(;--i>0;) ; return 1; } diff --git a/tests/unit/loop.frag b/tests/unit/loop.frag new file mode 100644 index 00000000..b5df06f9 --- /dev/null +++ b/tests/unit/loop.frag @@ -0,0 +1,48 @@ +float f(){return 0.;} +void main() +{ + float c; + for (float a=0; a<50.; ++a) + { + c+=cos(a); + } + float b; + for (b=0; b<50.; ++b) + { + c+=cos(c); + } + b = a; + while (b<50.) + { + c+=cos(c); + b++; + } + b = a; + while (b<50.) + { + b++; + } + b = a; + while (b<50.) + { + c+=cos(c); + float d=f(); // d prevents moving b+=d to a for + b+=d; + } + b = a; + while (b<50.) + { + if (a < b) continue; // continue prevents moving b++ to a for + b++; + } + b = a; + while (b<50.) + { + if (a < b) a=b; else continue; // continue prevents moving b++ to a for + b++; + } + for (; b<50.; ++b) + { + c+=cos(b); + } +} \ No newline at end of file diff --git a/tests/unit/loop.frag.expected b/tests/unit/loop.frag.expected new file mode 100644 index 00000000..ffb683e7 --- /dev/null +++ b/tests/unit/loop.frag.expected @@ -0,0 +1,47 @@ +/* File generated with + * http://www.ctrl-alt-test.fr + */ +#ifndef LOOP_FRAG_EXPECTED_ +# define LOOP_FRAG_EXPECTED_ + +const char *loop_frag = + "float f()" + "{" + "return 0.;" + "}" + "void main()" + "{" + "float c,b;" + "for(float a=0;a<50.;++a)" + "c+=cos(a);" + "for(b=0;b<50.;++b)" + "c+=cos(c);" + "for(b=a;b<50.;b++)" + "c+=cos(c);" + "for(b=a;b<50.;b++)" + ";" + "for(b=a;b<50.;)" + "{" + "c+=cos(c);" + "float d=f();" + "b+=d;" + "}" + "for(b=a;b<50.;)" + "{" + "if(a