shader_crusher/shader_crusher/
shadercrusher.rs

1
2use libc::{c_char};
3use std::ffi::CStr;
4use std::collections::HashMap;
5
6use glsl::parser::Parse;
7use glsl::syntax::ShaderStage;
8
9use glsl::syntax::{CompoundStatement, Expr, SingleDeclaration, Statement, TypeSpecifierNonArray};
10use glsl::syntax::*;
11use glsl::visitor::{Host, Visit, Visitor};
12
13use regex::Regex;
14
15include!(concat!(env!("OUT_DIR"), "/glsl_keywords.rs"));
16
17struct IdentEntry{
18	crushed_name: String,
19	count: u32,
20}
21
22impl std::fmt::Debug for IdentEntry {
23    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
24        write!(f, "{} (*{})", self.crushed_name, self.count )
25    }
26}
27
28impl IdentEntry{
29	pub fn new( n: &str ) -> IdentEntry {
30		IdentEntry{
31			crushed_name: n.to_string(),
32			count: 0,
33		}
34	}
35	fn set_crushed_name( &mut self, cn: &str ) {
36		self.crushed_name = cn.to_string();
37	}
38}
39
40struct IdentMap{
41	entries: HashMap<String, IdentEntry>,	
42}
43
44
45impl std::fmt::Debug for IdentMap {
46    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
47        write!(f, "entries: {:#?}", self.entries)
48    }
49}
50
51impl IdentMap{
52	pub fn new() -> IdentMap {
53		IdentMap {
54			entries: HashMap::new(),
55		}
56	}
57	fn contains(&self, k: &str ) -> bool {
58		self.entries.contains_key( k )
59	}
60	fn keys(&self) -> Vec<String> {
61//		users.iter().map(|(_, user)| &user.reference.clone()).collect();
62		self.entries.iter().map(|(k,v)| k.into() ).collect()
63//			self.entries.iter().map( |k, v| k )
64//		self.entries.keys().map( |e| e.clone() ).to_vec()
65	}
66	fn crush(&mut self, used_identifiers: Vec<String>, blacklist: &Vec<String> ) {
67		let mut candidates = Vec::new();
68		// :TODO: be smarter ;)
69		// :TODO: e.g. count frequency of characters in input and use most used ones
70		// :TODO: provide more than 26 candidates, or generate them on the fly when needed
71		for c in (b'a'..=b'z').rev() {
72			let c = c as char;
73			for c2 in (b'a'..=b'z').rev() {
74				let c2 = c2 as char;
75				candidates.push( format!("{}{}", c, c2 ).to_string() );
76			}
77		}
78		for c in (b'a'..=b'z').rev() {
79			let c = c as char;
80			candidates.push( c.to_string() );
81		}
82		// filter out used identifiers to avoid unwanted aliasing
83		let mut candidates = candidates.into_iter().filter(
84			|n|
85			!used_identifiers.contains( &n ) && !blacklist.contains( &n )
86		).collect::<Vec<String>>();
87
88//		println!("Used identifiers {:?}", used_identifiers );
89//		println!("Best candidates {:?}", candidates );
90//		let mut count_index: Vec<(&String, &u32)> = self.entries.iter().map(|a|
91//			(a.0, &a.1.count)	// :TODO: count might be a bit simplistic here, total "cost" might be a better measure
92//		).collect::<Vec<(&String, &u32)>>().clone();
93		let mut count_index = Vec::new();
94		for e in self.entries.iter(){
95			count_index.push( ( e.0.clone(), e.1.count ) );
96		}
97		count_index.sort_by(|a,b|
98			if b.1 != a.1 {
99				b.1.cmp(&a.1)
100			} else {
101				a.0.cmp(&b.0)
102			}
103		);
104//		println!("{:?}", count_index);
105		for k in count_index {
106			match self.entries.get_mut( &k.0 ) {
107				None => {}, // :WTF:
108				Some( e ) => {
109					let cn = match candidates.pop() {
110						None => e.crushed_name.clone(),
111						Some( cn ) => cn,
112					};
113//					println!("Crushing {:?} to {:?}", e, cn );
114					e.set_crushed_name( &cn );
115				}
116			}
117		}
118	}
119	fn get_crushed_name(&self, n: &str ) -> Option< String > {
120		self.entries.get( n ).map(|a| a.crushed_name.clone() )
121	}
122	fn add( &mut self, n: &str ) -> u32 {
123		let mut e = self.entries.entry(n.to_string()).or_insert_with(||IdentEntry::new( &n ));
124		e.count += 1;
125		e.count
126	}
127}
128
129#[derive(Debug,PartialEq)]
130enum CounterPhase {
131	Analysing,
132	Crushing,
133}
134
135struct Counter {
136	phase: CounterPhase,
137	blacklist: Vec<String>,
138	crushing: bool,
139	identifiers_crushed: IdentMap,
140	identifiers_uncrushed: IdentMap,
141}
142
143impl Counter {
144	pub fn new() -> Counter {
145		Counter {
146			phase: CounterPhase::Analysing,
147			blacklist: vec![ "main".to_string() ],
148			crushing: true,
149			identifiers_crushed: IdentMap::new(),
150			identifiers_uncrushed: IdentMap::new(),
151		}
152	}
153
154	pub fn crush_names( &mut self ) {
155		self.identifiers_crushed.crush( self.identifiers_uncrushed.keys().to_vec(), &self.blacklist );
156	}
157}
158impl Visitor for Counter {
159	/*
160	fn visit_translation_unit(&mut self, tu: &mut TranslationUnit) -> Visit {
161		println!("{:?}", tu );
162		Visit::Children
163	}
164	*/
165	/*
166	fn visit_preprocessor(&mut self, p: &mut Preprocessor) -> Visit {
167		println!("Preprocessor: {:?}", p );
168		match p {
169			Preprocessor::Pragma( pragma ) => {
170				match pragma.command.as_ref() {
171					"SHADER_CRUSHER_OFF" => {
172						self.crushing = false;
173						pragma.command = "".to_string();
174					},
175					"SHADER_CRUSHER_ON" => {
176						self.crushing = true;
177						pragma.command = "".to_string();
178					},
179					_ => {
180
181					},
182				};
183			},
184			_ => {},
185		};
186		Visit::Children
187	}
188	*/
189	fn visit_preprocessor_define(&mut self, pd: &mut PreprocessorDefine) -> Visit {
190//		println!("Define: {:?} - {:?}", pd, self.crushing );
191		match pd {
192			PreprocessorDefine::ObjectLike { ident, value } => {
193				println!("{:?}", ident );
194				match ident {
195					Identifier( i ) => {
196						println!("{:?}", i );
197						match self.phase {
198							CounterPhase::Crushing => {
199							},
200							CounterPhase::Analysing => {
201								let c = self.crushing;
202								self.crushing = false;
203								// :HACK: always add #define identifiers as uncrushed, so we don't have to parse all potential usages
204								self.add_identifier( &i );
205								self.crushing = c;
206							}
207						}
208					},
209					_ => {
210
211					},
212				}
213			},
214			PreprocessorDefine::FunctionLike { ident, args, value } => {
215				println!("{:?}", ident );
216				match ident {
217					Identifier( i ) => {
218						println!("{:?}", i );
219						match self.phase {
220							CounterPhase::Crushing => {
221							},
222							CounterPhase::Analysing => {
223								let c = self.crushing;
224								self.crushing = false;
225								// :HACK: always add #define identifiers as uncrushed, so we don't have to parse all potential usages
226								self.add_identifier( &i );
227								self.crushing = c;
228							}
229						}
230					},
231					_ => {
232
233					},
234				}
235			},
236			x => {
237				println!("{:?}", x);
238			},
239
240		};
241		Visit::Children
242	}
243
244	fn visit_preprocessor_pragma(&mut self, pragma: &mut PreprocessorPragma) -> Visit {
245
246//		println!("Pragma: {:?} - {:?}", pragma, self.crushing );
247		match pragma.command.as_ref() {
248			"SHADER_CRUSHER_OFF" => {
249				self.crushing = false;
250				pragma.command = "".to_string();	// no idea how to remove the pragma completely :(
251				println!("== Crusher: Off ==");
252			},
253			"SHADER_CRUSHER_ON" => {
254				self.crushing = true;
255				pragma.command = "".to_string();
256				println!("== Crusher: On ==");
257			},
258			_ => {
259
260			},
261		}
262		Visit::Children
263	}
264	fn visit_identifier(&mut self, e: &mut Identifier) -> Visit {
265//		println!("Identifier: {:?}", e );
266		match e {
267			Identifier( i ) => {
268				match self.phase {
269					CounterPhase::Crushing => {
270//						println!("Expr Identifier {:?}", i );
271						match self.identifiers_crushed.get_crushed_name( i ) {
272							Some( n ) => {
273								println!("Identifier: Replacing {:?} with {:?}", i, n );
274								*e = Identifier( n.to_string() );
275							},
276							None => {
277//								println!("No crushed version of {:?} found", i );
278							},
279						}
280					},
281					CounterPhase::Analysing => {
282						self.add_identifier( &i );
283					}
284				}
285			},
286			_ => {
287
288			},
289		}
290		Visit::Children
291	}
292	fn visit_type_name(&mut self, tn: &mut TypeName) -> Visit {
293//		println!("TypeName {:#?}", tn );
294		match tn {
295			TypeName( i ) => {
296				match self.phase {
297					CounterPhase::Crushing => {
298//						println!("Expr Identifier {:?}", i );
299						match self.identifiers_crushed.get_crushed_name( i ) {
300							Some( n ) => {
301								println!("TypeName/Identifier: Replacing {:?} with {:?}", i, n );
302								*tn = TypeName( n.to_string() );
303							},
304							None => {
305//								println!("No crushed version of {:?} found", i );
306							},
307						}
308					},
309					CounterPhase::Analysing => {
310						self.add_identifier( &i );
311					}
312				}
313			},
314			_ => {
315
316			},
317		}
318    	Visit::Children
319	}
320/*
321	fn visit_single_declaration(&mut self, declaration: &mut SingleDeclaration) -> Visit {
322//		println!("{:#?}", declaration );
323		println!("SingleDeclaration: {:#?}", declaration );
324		match &declaration.name {
325			None => {
326
327			},
328			Some( name ) => {
329				println!("declaration.name {:?}", name );
330				let n = name.to_string();
331				match self.phase {
332					CounterPhase::Analysing => {
333						self.add_identifier( &n );
334					},
335					CounterPhase::Crushing => {
336					}
337				}
338			},
339		}
340		Visit::Children
341//		Visit::Parent
342	}
343*/
344	/*
345	fn visit_arrayed_identifier(&mut self, ai: &mut ArrayedIdentifier) -> Visit {
346		println!("visit_arrayed_identifier {:?}", ai );
347		Visit::Children
348	}
349	*/
350/*	
351	fn visit_function_prototype(&mut self, fp: &mut FunctionPrototype) -> Visit {
352//		println!("{:?}", fp );
353//		println!("{}", fp.name );
354		match self.phase {
355			CounterPhase::Analysing => {
356//				self.add_identifier( &fp.name.as_str() );
357			},
358			CounterPhase::Crushing => {
359				/* :TODO:
360				match self.identifiers_crushed.get_crushed_name( &n ) {
361					Some( cn ) => {
362						println!("Found {:?} for {:?}", cn, n );
363						declaration.name = Some( Identifier( cn.to_string() ) );
364					},
365					None => {
366						println!("No crushed version of {:?} found", n );
367					},
368				}
369				*/
370
371			}
372		}
373		Visit::Children
374	}
375*/
376}
377
378impl Counter {
379	fn add_identifier( &mut self, n: &str ) {
380		let blacklisted = self.blacklist.contains( &n.to_string() );
381		let uncrushed = self.identifiers_uncrushed.contains( &n.to_string() );
382		if self.crushing && !blacklisted && !uncrushed {
383			let c = self.identifiers_crushed.add( &n );
384			println!("{: >8} x {: <20} [-crushed-] {} {} {}",
385				c,
386				&n,
387				if self.crushing { "[--CRUSHING--]" } else { "[NOT CRUSHING]" },
388				if blacklisted { "[--BLACKLISTED--]" } else { "[NOT BLACKLISTED]" },
389				if uncrushed { "[--UNCRUSHED--]" } else { "[NOT UNCRUSHED]" },
390			);
391		} else {
392			let c = self.identifiers_uncrushed.add( &n );
393			println!("{: >8} x {: <20} [uncrushed] {} {} {}",
394				c,
395				&n,
396				if self.crushing { "[--CRUSHING--]" } else { "[NOT CRUSHING]" },
397				if blacklisted { "[--BLACKLISTED--]" } else { "[NOT BLACKLISTED]" },
398				if uncrushed { "[--UNCRUSHED--]" } else { "[NOT UNCRUSHED]" },
399			);
400		}
401	}
402	fn blacklist_identifier( &mut self, n: &str ) {
403		if !self.blacklist.contains( &n.to_string() ) {
404			self.blacklist.push( n.to_string() );
405		}
406	}
407}
408
409pub struct ShaderCrusher {
410	input: String,
411	output: String,
412	input_entropy: f32,
413	output_entropy: f32,
414	blacklist: Vec<String>,
415}
416
417impl ShaderCrusher {
418	pub fn new() -> ShaderCrusher {
419		let blacklist = GlslKeywords::get();
420		ShaderCrusher {
421			input: String::new(),
422			output: String::new(),
423			input_entropy: 0.0,
424			output_entropy: 0.0,
425			blacklist: blacklist,
426		}
427	}
428	pub fn blacklist_identifier( &mut self, n: &str ) {
429		if !self.blacklist.contains( &n.to_string() ) {
430			self.blacklist.push( n.to_string() );
431		}
432	}
433
434	fn recalc_entropy( &mut self ) {
435//		self.input_entropy = entropy::shannon_entropy( self.input.as_bytes() );
436//		self.output_entropy = entropy::shannon_entropy( self.output.as_bytes() );
437		self.input_entropy = entropy::metric_entropy( self.input.as_bytes() );
438		self.output_entropy = entropy::metric_entropy( self.output.as_bytes() );
439	}
440	pub fn set_input( &mut self, input: &str ) {
441		self.input = input.to_string();
442		self.output = self.input.clone();
443
444		self.recalc_entropy();
445	}
446	pub fn get_output( &self ) -> String {
447		self.output.clone()
448	}
449
450	pub fn get_input_entropy( &self ) -> f32 {
451		self.input_entropy
452	}
453
454	pub fn get_output_entropy( &self ) -> f32 {
455		self.output_entropy
456	}
457
458	pub fn crush( &mut self ) {
459		let mut stage = ShaderStage::parse(&self.input);
460//		println!("Stage: {:?}", stage);
461		let mut stage = match stage {
462			Err( e ) => {
463				println!("Error parsing shader {:?}", e );
464				return;
465			},
466			Ok( stage ) => {
467//				println!("Parsed shader {:#?}", stage );
468				stage
469			}
470		};
471
472//		let mut compound = stage.clone();
473		let mut counter = Counter::new();
474//		println!("Blacklist {:?}", self.blacklist );
475		for n in &self.blacklist {
476			counter.blacklist_identifier( n );
477		};
478		stage.visit(&mut counter);
479		counter.crush_names();
480		// :TODO: fixup crushed identifiers names
481		// skip crushing for now
482		counter.phase = CounterPhase::Crushing;
483		stage.visit(&mut counter);
484		println!("Stats:\n-------");
485		println!("Crushed Varnames: {:?}", counter.identifiers_crushed );
486		println!("Uncrushed Varnames: {:?}", counter.identifiers_uncrushed );
487        let mut glsl_buffer = String::new();
488        let r = glsl::transpiler::glsl::show_translation_unit(&mut glsl_buffer, &stage);
489//        println!("r {:?}", r);
490//        println!("r {}", r);
491//        let pr: PrettyPrint = From::from(stage);// as &PrettyPrint;
492//		PrettyPrint::print_shaderstage( &stage );
493//        println!("{:?}", pr);
494
495		// cleanup empty pragmas
496		let re = Regex::new(r"(?m)^\s*#\s*pragma\s*$").unwrap();
497		let glsl_buffer = re.replace_all(
498			&glsl_buffer,
499			|c: &regex::Captures|{
500//				println!("{:?}", c );
501				"".to_string()
502			}
503		);
504
505		// cleanup double braces e.g. "((x))"
506/*		// :TODO: this is to agressive, or maybe even wrong
507		let re = Regex::new(r"(?m)\(\(([^)]*)\)\)").unwrap();
508		let glsl_buffer = re.replace_all(
509			&glsl_buffer,
510			|c: &regex::Captures|{
511//				println!("{:?}", c );
512				let inner = c.get(1).map_or("", |m| m.as_str() );
513//				println!("{}", inner );
514				format!("({}))", inner).clone()
515			}
516		);
517*/		
518		let re = Regex::new(r"(?m)\(\(([a-zA-Z0-9.]+)\)").unwrap();
519		let glsl_buffer = re.replace_all(
520			&glsl_buffer,
521			|c: &regex::Captures|{
522//				println!("{:?}", c );
523				let inner = c.get(1).map_or("", |m| m.as_str() );
524//				println!("{}", inner );
525				format!("({}", inner).clone()
526			}
527		);
528//println!("====");
529		let re = Regex::new(r"(?m)\(\(([a-zA-Z0-9.]+)\)").unwrap();
530		let glsl_buffer = re.replace_all(
531			&glsl_buffer,
532			|c: &regex::Captures|{
533//				println!("{:?}", c );
534				let inner = c.get(1).map_or("", |m| m.as_str() );
535//				println!("{}", inner );
536				format!("({}", inner).clone()
537			}
538		);
539
540//println!("====");
541
542//		let re = Regex::new(r"(?m)([\n\s-+*]+)\(([a-zA-Z0-9.]+)\)").unwrap();
543//		let re = Regex::new(r"(?m)([\n[[:space:]]-+*]+)\(([a-zA-Z0-9.]+)\)").unwrap();
544//		let re = Regex::new(r"(?m)([\n[[:space:]]-+*<>=]+)\(([a-zA-Z0-9.]+)\)").unwrap();
545//		let re = Regex::new(r"(?m)([\n-+*<>=]+)\(([a-zA-Z0-9.]+)\)").unwrap();
546		let re = Regex::new(r"(?m)([-+*<>=]+)\(([a-zA-Z0-9.]+)\)").unwrap();
547		
548		let glsl_buffer = re.replace_all(
549			&glsl_buffer,
550			|c: &regex::Captures|{
551//				println!("{:?}", c );
552				let prefix = c.get(1).map_or("", |m| m.as_str() );
553				let inner = c.get(2).map_or("", |m| m.as_str() );
554//				println!("{}{}", prefix, inner );
555				format!("{}{}", prefix, inner).clone()
556			}
557		);
558
559        self.output = glsl_buffer.to_string();
560        self.recalc_entropy();
561        let il = self.input.len();
562        let ie = self.input_entropy;
563        let it = il as f32 * ie;
564        let ol = self.output.len();
565        let oe = self.output_entropy;
566        let ot = ol as f32 * oe;
567        println!("Input  Size: {}, Entropy: {} => {}", il, ie, it );
568        println!("Output Size: {}, Entropy: {} => {}", ol, oe, ot );
569	}
570}
571
572
573// API
574#[no_mangle]
575pub unsafe extern "C" fn shadercrusher_new() -> *mut ShaderCrusher {
576	Box::into_raw(Box::new(ShaderCrusher::new()))
577}
578
579#[no_mangle]
580pub extern fn shadercrusher_free(ptr: *mut ShaderCrusher) {
581    if ptr.is_null() { return }
582    unsafe { Box::from_raw(ptr); }
583}
584
585#[no_mangle]
586pub extern fn shadercrusher_set_input(ptr: *mut ShaderCrusher, input: *const c_char) {
587	let shadercrusher = unsafe {
588	    assert!(!ptr.is_null());
589	    &mut *ptr
590	};
591	let input = unsafe {
592	    assert!(!input.is_null());
593	    CStr::from_ptr(input)
594	};
595	let input = input.to_str().unwrap();
596	shadercrusher.set_input( input );
597}
598/*
599#[no_mangle]
600pub extern fn theme_song_free(s: *mut c_char) {
601    unsafe {
602        if s.is_null() { return }
603        CString::from_raw(s)
604    };
605}
606*/
607#[no_mangle]
608pub extern fn shadercrusher_get_ouput(ptr: *mut ShaderCrusher) -> *mut c_char {
609	let shadercrusher = unsafe {
610	    assert!(!ptr.is_null());
611	    &mut *ptr
612	};
613	let output = shadercrusher.get_output( );
614
615	let output_cs = std::ffi::CString::new(output).unwrap();
616    output_cs.into_raw()
617}
618
619#[no_mangle]
620pub extern fn shadercrusher_free_ouput(ptr: *mut ShaderCrusher, output_cs: *mut c_char) {
621    unsafe {
622        if output_cs.is_null() {
623        	return
624        }
625        std::ffi::CString::from_raw(output_cs)
626    };
627}
628
629#[no_mangle]
630pub extern fn shadercrusher_crush(ptr: *mut ShaderCrusher) {
631	let shadercrusher = unsafe {
632	    assert!(!ptr.is_null());
633	    &mut *ptr
634	};
635	shadercrusher.crush();
636}