Skip to content

Commit

Permalink
Updated inlining strategy that doesn't look at 'const' (#277)
Browse files Browse the repository at this point in the history
Instead of looking at const, check whether the value is mutated.

- By default, inline the trivial values (numbers)
- With aggro-inlining, inline a few more things known to be constant
- Make aggro-inlining safer (the large expressions are no longer copied
everywhere)

As a result, the behavior or default-inlining and aggro-inlining come
much closer than before.
  • Loading branch information
laurentlb authored Mar 27, 2023
1 parent 5c4dba4 commit 3bea2f3
Show file tree
Hide file tree
Showing 34 changed files with 335 additions and 336 deletions.
94 changes: 56 additions & 38 deletions src/analyzer.fs
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,15 @@ module private VariableInlining =
mapExpr (mapEnv collectLocalUses id) expr |> ignore<Expr>
result

let isEffectivelyConst (ident: Ident) =
if ident.AsResolvedVar.IsSome then
// A variable not reassigned is effectively constant.
not ident.AsResolvedVar.Value.isLValue
elif Builtin.pureBuiltinFunctions.Contains ident.Name then
true
else
false

// Mark variables as inlinable when possible.
// Variables are always safe to inline when all of:
// - the variable is used only once in the current block
Expand All @@ -46,8 +55,8 @@ module private VariableInlining =
// - it depends only on variables proven constants
let markInlinableVariables block =
// Variables that are defined in this scope.
// The booleans indicate if the variable initialization has dependencies / unsafe dependencies.
let localDefs = Dictionary<string, (Ident * bool * bool)>()
// The boolean indicate if the variable initialization is const.
let localDefs = Dictionary<string, (Ident * 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 @@ -60,16 +69,8 @@ module private VariableInlining =
| Some init ->
localExpr <- init :: localExpr
let deps = collectReferencesSet init
let hasUnsafeDep = deps |> Seq.exists (fun ident ->
if ident.AsResolvedVar <> None then
// A variable not reassigned is effectively constant.
ident.AsResolvedVar.Value.isLValue
elif Builtin.pureBuiltinFunctions.Contains ident.Name then
false
else
true
)
localDefs.[def.name.Name] <- (def.name, deps.Count > 0, hasUnsafeDep)
let isConst = deps |> Seq.forall isEffectivelyConst
localDefs.[def.name.Name] <- (def.name, isConst)
| Expr e
| Jump (_, Some e) -> localExpr <- e :: localExpr
| Verbatim _ | Jump (_, None) | Block _ | If _| ForE _ | ForD _ | While _ | DoWhile _ | Switch _ -> ()
Expand All @@ -78,48 +79,63 @@ module private VariableInlining =
let allReferences = collectReferences block

for def in localDefs do
let ident, hasInitDeps, hasUnsafeDeps = def.Value
let ident, isConst = def.Value
if not ident.ToBeInlined && not ident.AsResolvedVar.Value.isLValue 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 then
ident.ToBeInlined <- true

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

let maybeInlineVariables li =
let mapInnerDecl = function
| ({typeQ = tyQ}, defs) as d when List.contains "const" tyQ ->
// Inline some variables, regardless of where they are used or how often they are used.
let maybeInlineVariables topLevel =
let isTrivialExpr = function
| Var v when v.Name = "true" || v.Name = "false" -> true
| Int _
| Float _ -> true
| _ -> false

// Detect if a variable can be inlined, based on its value.
let canBeInlined (init: Expr) =
match init with
| e when isTrivialExpr e -> true
| _ when not options.aggroInlining -> false
// Allow a few things to be inlined with aggroInlining
| Var v
| Dot (Var v, _) -> isEffectivelyConst v
| FunCall(Op op, args) ->
not (Builtin.assignOps.Contains op) &&
args |> List.forall isTrivialExpr
| FunCall(Var fct, args) ->
Builtin.pureBuiltinFunctions.Contains fct.Name &&
args |> List.forall isTrivialExpr
| _ -> false

let mapInnerDecl isTopLevel = function
| (ty: Type, defs) as d when not ty.IsExternal ->
for (def:DeclElt) in defs do
// AggroInlining: unconditional inlining of anything marked "const".
// Note: this is unsafe if the init value depends on something mutable.
if options.aggroInlining then
def.name.ToBeInlined <- true
// Otherwise, inline only trivial constants.
else match def.init with
| Some (Var v) when v.Name = "true" || v.Name = "false" ->
if not def.name.AsResolvedVar.Value.isLValue then
if def.init.IsNone then
// Top-level values are special, in particular in HLSL. Keep them for now.
if not isTopLevel then
def.name.ToBeInlined <- true
| Some (Int _)
| Some (Float _) ->
def.name.ToBeInlined <- true
| _ -> ()
elif canBeInlined def.init.Value then
def.name.ToBeInlined <- true
d
| d -> d
let mapStmt = function
| Decl d -> Decl (mapInnerDecl d)
| Decl d -> Decl (mapInnerDecl false d)
| s -> s
let mapExpr _ e = e
let mapTLDecl = function
| TLDecl d -> TLDecl (mapInnerDecl d)
| TLDecl d -> TLDecl (mapInnerDecl true d)
| d -> d
li
topLevel
|> mapTopLevel (mapEnv mapExpr mapStmt)
|> List.map mapTLDecl

let markLValues li =
let markLValues topLevel =

let findWrites (env: MapEnv) = function
| Var v as e when env.isLValue && v.AsResolvedVar <> None ->
v.AsResolvedVar.Value.isLValue <- true
Expand All @@ -137,11 +153,13 @@ module private VariableInlining =
e
| _ -> e
| e -> e
mapTopLevel (mapEnv findWrites id) li
mapTopLevel (mapEnv findWrites id) topLevel

let markInlinableVariables = VariableInlining.markInlinableVariables
let markLValues = VariableInlining.markLValues
let maybeInlineVariables = VariableInlining.maybeInlineVariables
let maybeInlineVariables topLevel =
if options.noInlining then topLevel
else VariableInlining.maybeInlineVariables topLevel


// Create ResolvedIdent for each declaration in the file.
Expand Down
4 changes: 3 additions & 1 deletion src/ast.fs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,9 @@ and Type = {
name: TypeSpec // e.g. int
typeQ: string list // type qualifiers, e.g. const, uniform, out, inout...
arraySizes: Expr list // e.g. [3][5]
}
} with
member this.IsExternal =
List.exists (fun s -> Set.contains s Builtin.externalQualifier) this.typeQ

and DeclElt = {
name: Ident // e.g. foo
Expand Down
4 changes: 4 additions & 0 deletions src/builtin.fs
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,7 @@ let pureBuiltinFunctions =
vectorFunctions +
castFunctions +
textureFunctions

// Type qualifiers telling that a global variables is an 'external' name
// (it may be referenced from other files).
let externalQualifier = set ["in"; "out"; "attribute"; "varying"; "uniform"]
6 changes: 1 addition & 5 deletions src/renamer.fs
Original file line number Diff line number Diff line change
Expand Up @@ -242,11 +242,7 @@ module private RenamerImpl =
let aux (env: Env) (decl: Ast.DeclElt) =
Option.iter (renExpr env) decl.init
Option.iter (renExpr env) decl.size
let ext =
let tyQSet = Set.ofList ty.typeQ
let extQ = ["in"; "out"; "attribute"; "varying"; "uniform"]
List.exists (fun s -> Set.contains s tyQSet) extQ
let isExternal = isTopLevel && (ext || options.hlsl)
let isExternal = isTopLevel && (ty.IsExternal || options.hlsl)

if (isTopLevel && options.preserveAllGlobals) ||
List.contains decl.name.Name options.noRenamingList then
Expand Down
30 changes: 15 additions & 15 deletions tests/compression_results.log
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
clod.frag... 8895 => 1533.510
audio-flight-v2.frag 4586 => 895.242
buoy.frag 4148 => 632.447
controllable-machinery.frag 7771 => 1227.917
clod.frag... 8918 => 1534.693
audio-flight-v2.frag 4584 => 898.967
buoy.frag 4137 => 630.100
controllable-machinery.frag 7760 => 1219.338
ed-209.frag 7860 => 1355.308
elevated.hlsl 3405 => 603.218
endeavour.frag 2642 => 541.090
from-the-seas-to-the-stars.frag 14320 => 2334.199
frozen-wasteland.frag 4601 => 809.115
kinder_painter.frag 2891 => 447.986
leizex.frag 2298 => 512.987
lunaquatic.frag 5231 => 1055.816
endeavour.frag 2627 => 536.451
from-the-seas-to-the-stars.frag 14308 => 2332.018
frozen-wasteland.frag 4597 => 809.000
kinder_painter.frag 2871 => 447.691
leizex.frag 2311 => 510.372
lunaquatic.frag 5257 => 1049.740
mandelbulb.frag 2403 => 547.468
ohanami.frag 3254 => 727.478
orchard.frag 5584 => 1037.403
ohanami.frag 3256 => 722.517
orchard.frag 5574 => 1034.487
oscars_chair.frag 4651 => 986.364
robin.frag 6333 => 1055.863
slisesix.frag 4571 => 930.275
slisesix.frag 4581 => 933.929
terrarium.frag 3634 => 747.342
the_real_party_is_in_your_pocket.frag 12187 => 1813.123
the_real_party_is_in_your_pocket.frag 12168 => 1811.526
valley_ball.glsl 4386 => 888.496
yx_long_way_from_home.frag 2975 => 610.699
Total: 118626 => 21293.347
Total: 118596 => 21265.585
7 changes: 3 additions & 4 deletions tests/real/audio-flight-v2.frag.expected
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ vec2 edge(vec2 p)
-1.:
1.);
}
float ths=13.25;
vec2 map(vec3 p,float sg)
{
vec2 res=vec2(100,-1);
Expand Down Expand Up @@ -162,7 +161,7 @@ vec2 map(vec3 p,float sg)
flight+=.00025/(1e-7+blt*blt);
if(sg==1.&&zprs<.1)
glow+=.00015/(2e-6+dlt*dlt);
if(sg==1.&&tm<ths)
if(sg==1.&&tm<13.25)
objglow+=5e-4/(5e-4+obj*obj);
return res;
}
Expand Down Expand Up @@ -233,12 +232,12 @@ void mainImage(out vec4 O,vec2 F)
vec3(1);
if(m==3.||m==4.)
h=vec3(.012);
if(tm>ths)
if(tm>13.25)
C=h*diff+spec;
else if(m==3.||m==4.)
C=hsv2rgb(vec3(s_hp.z*.01,.8,.6))*diff;
}
if(tm>ths)
if(tm>13.25)
{
if(mod(T,.1)<.05)
FC=vec3(.8);
Expand Down
5 changes: 2 additions & 3 deletions tests/real/controllable-machinery.frag.expected
Original file line number Diff line number Diff line change
Expand Up @@ -154,8 +154,7 @@ float GearDf(vec3 p)
}
float ObjDf(vec3 p)
{
vec4 a4;
vec3 q,bPos;
vec3 q;
float dMin,d,r,a;
dMin=dstFar;
q=p-vec3(1.13+bEdge,bEdge,1);
Expand Down Expand Up @@ -355,7 +354,7 @@ vec3 ShowScene(vec3 ro,vec3 rd)
{
vec4 col4;
vec3 vn,col,q;
float dstObj,dstGear,dstBlk,sh,s,r,a,nDotL;
float dstObj,dstGear,dstBlk,sh,r,a,nDotL;
int idObjT;
bool isMet;
tCyc=18.5;
Expand Down
2 changes: 1 addition & 1 deletion tests/real/disco.expected
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ var var_TEX2 = "D"
var var_TEX3 = "y"
var var_TIME = "v"

var disco_frag = `uniform vec2 s;uniform float v;uniform sampler2D f,C,D,y;vec4 n(vec2 s,float f){float c=3.1415,y=v*sign(f),a=s.x*320.*.0065*f,i=s.y*240.*.006*f,C=sqrt(a*a+i*i);if(C>1.)return vec4(0);{float r=-.4*sign(f)+sin(y*.05),n=sqrt(1.-a*a-i*i),u=i*sin(r)-n*cos(r);i=i*cos(r)+n*sin(r);n=acos(i);r=acos(a/sin(n))/(2.*c)*120.*sign(u)-y;n=n*60./c;u=cos(floor(n/c));C=pow(abs(cos(r)*sin(n)),.2)*.1/(u+sin(float(int((r+c/2.)/c))+y*.6+cos(u*25.)))*pow(1.-C,.9);vec4 g;g=C<0.?vec4(-C/2.*abs(cos(y*.1)),0,-C*2.*abs(sin(y*.04)),1):vec4(C,C*2.,C*2.,1);return g;}}void main(){vec2 f=-1.+2.*gl_FragCoord.xy/s.xy;vec4 r=vec4(0);for(int i=80;i>0;i--)r+=n(f,1.-float(i)/80.)*(.008-float(i)*5e-5);vec4 i=n(f,1.);gl_FragColor=(i.w==0.?n(f,-.2)*.02:i)+sqrt(r);}`
var disco_frag = `uniform vec2 s;uniform float v;uniform sampler2D f,C,D,y;vec4 n(vec2 s,float f){float c=v*sign(f),a=s.x*320.*.0065*f,i=s.y*240.*.006*f,y=sqrt(a*a+i*i);if(y>1.)return vec4(0);{float r=-.4*sign(f)+sin(c*.05),n=sqrt(1.-a*a-i*i),u=i*sin(r)-n*cos(r);i=i*cos(r)+n*sin(r);n=acos(i);r=acos(a/sin(n))/6.283*120.*sign(u)-c;n=n*60./3.1415;u=cos(floor(n/3.1415));y=pow(abs(cos(r)*sin(n)),.2)*.1/(u+sin(float(int((r+1.57075)/3.1415))+c*.6+cos(u*25.)))*pow(1.-y,.9);vec4 g;g=y<0.?vec4(-y/2.*abs(cos(c*.1)),0,-y*2.*abs(sin(c*.04)),1):vec4(y,y*2.,y*2.,1);return g;}}void main(){vec2 f=-1.+2.*gl_FragCoord.xy/s.xy;vec4 r=vec4(0);for(int i=80;i>0;i--)r+=n(f,1.-float(i)/80.)*(.008-float(i)*5e-5);vec4 i=n(f,1.);gl_FragColor=(i.w==0.?n(f,-.2)*.02:i)+sqrt(r);}`
13 changes: 6 additions & 7 deletions tests/real/endeavour.frag.expected
Original file line number Diff line number Diff line change
Expand Up @@ -75,10 +75,9 @@ void scene(vec3 x,out vec2 sdf)
stroke(sdb.x,.003*step(dis,0.),sdb.x);
sdf.x=max(sdf.x,-da);
add(sdf,sdb,sdf);
float dr=.1;
vec3 y=mod(x,dr)-.5*dr;
float guard=-length(max(abs(y)-vec3(.5*dr*c.xx,.6),0.));
guard=abs(guard)+dr*.1;
vec3 y=mod(x,.1)-.05;
float guard=-length(max(abs(y)-vec3(.05*c.xx,.6),0.));
guard=abs(guard)+.01;
sdf.x=min(sdf.x,guard);
}
void normal(vec3 x,out vec3 n)
Expand Down Expand Up @@ -108,16 +107,16 @@ void mainImage(out vec4 fragColor,vec2 fragCoord)
vec2 uv=fragCoord/iResolution.yy-.5*vec2(a,1),s;
vec3 col=c.yyy,o=.5*c.yyx+.3*iTime*c.yxy,t=vec3(uv,0)+.3*iTime*c.yxy+c.yxy,dir=normalize(t-o),x;
float d=(.04-o.z)/dir.z;
int N=450,i;
for(i=0;i<N;++i)
int i;
for(i=0;i<450;++i)
{
x=o+d*dir;
scene(x,s);
if(s.x<1e-4)
break;
d+=s.x;
}
if(i<N)
if(i<450)
{
vec3 n,l=normalize(x+c.yyx);
normal(x,n);
Expand Down
5 changes: 2 additions & 3 deletions tests/real/from-the-seas-to-the-stars.frag.expected
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,13 @@ vec2 disc(vec2 uv)
float zoom=.4,cameraroll=1.,cameraroll2=0;
uvec2 encrypt(uvec2 v)
{
uint k[4],sum=0,delta=2654435769;
uint k[4],sum=0;
k[0]=2738958700;
k[1]=3355524772;
k[2]=2911926141;
k[3]=2123724318;
for(uint i=0;i<4;++i)
sum+=delta,v.x+=(v.y<<4)+k[0]^v.y+sum^(v.y>>5)+k[1],v.y+=(v.x<<4)+k[2]^v.x+sum^(v.x>>5)+k[3];
sum+=2654435769,v.x+=(v.y<<4)+k[0]^v.y+sum^(v.y>>5)+k[1],v.y+=(v.x<<4)+k[2]^v.x+sum^(v.x>>5)+k[3];
return v;
}
mat2 rotmat(float a)
Expand Down Expand Up @@ -336,7 +336,6 @@ void main()
}
p.x-=2;
p.y+=time2/14.;
float c;
}
else if(w.w<.26)
w.x=R(),w.y=R(),w.z=R(),w.w=R(),p=(w.xyz*2-1)*3,p.x-=2,col=vec3(w.w)/2,w.w=R(),col+=vec3(1,1,.5)*pow(.5+.5*cos(w.w*6+time*3),32.)*.1;
Expand Down
4 changes: 2 additions & 2 deletions tests/real/frozen-wasteland.frag.expected
Original file line number Diff line number Diff line change
Expand Up @@ -167,13 +167,13 @@ float fogmap(vec3 p,float d)
}
float march(vec3 ro,vec3 rd,out float drift,vec2 scUV)
{
float precis=.1,mul=.34,h,d=hash12(scUV)*1.5;
float mul=.34,h,d=hash12(scUV)*1.5;
drift=0.;
for(int i=0;i<ITR;i++)
{
vec3 p=ro+rd*d;
h=map(p);
if(h<precis*(1.+d*.05)||d>FAR)
if(h<.1*(1.+d*.05)||d>FAR)
break;
drift+=fogmap(p,d);
d+=h*mul;
Expand Down
15 changes: 6 additions & 9 deletions tests/real/kinder_painter.expected
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,6 @@
"}"
"bool x(vec3 v,vec3 z,float i)"
"{"
"float y;"
"bvec4 b;"
"b.x=d(r[0],v,z,i);"
"b.y=d(r[1],v,z,i);"
Expand Down Expand Up @@ -156,11 +155,9 @@
"}"
"void main()"
"{"
"vec4 f,i,w;"
"vec3 y;"
"vec4 o;"
"vec2 x=-1.+2.*gl_FragCoord.xy/v.xy;"
"x*=vec2(v.x/v.y,1);"
"vec4 f,i,w,o;"
"vec2 y=-1.+2.*gl_FragCoord.xy/v.xy;"
"y*=vec2(v.x/v.y,1);"
"r[0]=vec4(1.2*sin(2.073423*z),0,1.8*sin(2.450409*z+1.),1);"
"r[1]=vec4(1.5*sin(1.947761*z+4.),sin(1.822099*z+1.9),1.8*sin(1.822099*z),1);"
"r[2]=vec4(-1.2,0,0,.4);"
Expand All @@ -173,9 +170,9 @@
"b[3]=vec4(.5,.5,.7,3);"
"b[4]=vec4(1,.9,.9,2);"
"b[5]=vec4(1,.9,.9,2);"
"float a=.15*z-6.2831*s.x/v.x,c=2.+3.*s.y/v.y;"
"vec2 g=vec2(cos(a),sin(a));"
"vec3 n=normalize(vec3(x.x*g.x-g.y,x.y,g.x+x.x*g.y)),t=vec3(c*g.y,0,-c*g.x);"
"float x=.15*z-6.2831*s.x/v.x,a=2.+3.*s.y/v.y;"
"vec2 g=vec2(cos(x),sin(x));"
"vec3 n=normalize(vec3(y.x*g.x-g.y,y.y,g.x+y.x*g.y)),t=vec3(a*g.y,0,-a*g.x);"
"float u=m(t,n,i,w);"
"vec3 e=t+n*u;"
"f.xyz=vec3(0,1.5,-3)-e;"
Expand Down
Loading

0 comments on commit 3bea2f3

Please sign in to comment.