shader_crusher/shader_crusher/
shadercrusher.rs1
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> {
61self.entries.iter().map(|(k,v)| k.into() ).collect()
63}
66 fn crush(&mut self, used_identifiers: Vec<String>, blacklist: &Vec<String> ) {
67 let mut candidates = Vec::new();
68 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 let mut candidates = candidates.into_iter().filter(
84 |n|
85 !used_identifiers.contains( &n ) && !blacklist.contains( &n )
86 ).collect::<Vec<String>>();
87
88let 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 );
104for k in count_index {
106 match self.entries.get_mut( &k.0 ) {
107 None => {}, Some( e ) => {
109 let cn = match candidates.pop() {
110 None => e.crushed_name.clone(),
111 Some( cn ) => cn,
112 };
113e.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 fn visit_preprocessor_define(&mut self, pd: &mut PreprocessorDefine) -> Visit {
190match 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 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 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
246match pragma.command.as_ref() {
248 "SHADER_CRUSHER_OFF" => {
249 self.crushing = false;
250 pragma.command = "".to_string(); 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 {
265match e {
267 Identifier( i ) => {
268 match self.phase {
269 CounterPhase::Crushing => {
270match 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},
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 {
293match tn {
295 TypeName( i ) => {
296 match self.phase {
297 CounterPhase::Crushing => {
298match 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},
307 }
308 },
309 CounterPhase::Analysing => {
310 self.add_identifier( &i );
311 }
312 }
313 },
314 _ => {
315
316 },
317 }
318 Visit::Children
319 }
320}
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 ) {
435self.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);
460let mut stage = match stage {
462 Err( e ) => {
463 println!("Error parsing shader {:?}", e );
464 return;
465 },
466 Ok( stage ) => {
467stage
469 }
470 };
471
472let mut counter = Counter::new();
474for n in &self.blacklist {
476 counter.blacklist_identifier( n );
477 };
478 stage.visit(&mut counter);
479 counter.crush_names();
480 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);
489let re = Regex::new(r"(?m)^\s*#\s*pragma\s*$").unwrap();
497 let glsl_buffer = re.replace_all(
498 &glsl_buffer,
499 |c: ®ex::Captures|{
500"".to_string()
502 }
503 );
504
505 let re = Regex::new(r"(?m)\(\(([a-zA-Z0-9.]+)\)").unwrap();
519 let glsl_buffer = re.replace_all(
520 &glsl_buffer,
521 |c: ®ex::Captures|{
522let inner = c.get(1).map_or("", |m| m.as_str() );
524format!("({}", inner).clone()
526 }
527 );
528let re = Regex::new(r"(?m)\(\(([a-zA-Z0-9.]+)\)").unwrap();
530 let glsl_buffer = re.replace_all(
531 &glsl_buffer,
532 |c: ®ex::Captures|{
533let inner = c.get(1).map_or("", |m| m.as_str() );
535format!("({}", inner).clone()
537 }
538 );
539
540let re = Regex::new(r"(?m)([-+*<>=]+)\(([a-zA-Z0-9.]+)\)").unwrap();
547
548 let glsl_buffer = re.replace_all(
549 &glsl_buffer,
550 |c: ®ex::Captures|{
551let prefix = c.get(1).map_or("", |m| m.as_str() );
553 let inner = c.get(2).map_or("", |m| m.as_str() );
554format!("{}{}", 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#[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#[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}