ReductionDriver { public String doReduction( ShaderJob initialState, String shaderJobShortName, int fileCountOffset, int stepLimit) throws IOException { final boolean requiresUniformBindings = initialState.getPipelineInfo().getNumUniforms() == 0 || initialState.hasUniformBindings(); final Optional<String> pushConstant = initialState.getPushConstant(); if (initialState.hasUniformBindings()) { initialState.removeUniformBindings(); } try { if (fileCountOffset > 0) { LOGGER.info("Continuing reduction for {}", shaderJobShortName); } else { LOGGER.info("Starting reduction for {}", shaderJobShortName); for (int i = 1; ; i++) { if (isInterestingNoCache(initialState, requiresUniformBindings, pushConstant, false, false, false, shaderJobShortName)) { break; } LOGGER.info("Result from initial state is not interesting (attempt " + i + ")"); if (i >= NUM_INITIAL_TRIES) { LOGGER.info("Tried " + NUM_INITIAL_TRIES + " times; stopping."); fileOps.createFile(new File(workDir, "NOT_INTERESTING")); return null; } } LOGGER.info("Result from initial state is interesting - proceeding with reduction."); } boolean addGlobalLoopLimiters = false; boolean makeArrayAccessesInBounds = false; boolean addInitializers = false; if (context.reduceEverywhere()) { LOGGER.info("We are not preserving semantics, so see whether adding guards against " + "undefined behaviour preserves interestingness."); if (isInterestingNoCache(initialState, requiresUniformBindings, pushConstant, true, false, false, shaderJobShortName)) { LOGGER.info("The shader is still interesting when global loop limiters are added; these" + " will be used during reduction, so the final reduced shader should be free from " + "long-running or infinite loops."); addGlobalLoopLimiters = true; } else { LOGGER.info("The shader is not interesting when global loop limiters are added; these" + " will not be used during reduction, so the final reduced shader could contain " + "long-running or infinite loops."); } if (isInterestingNoCache(initialState, requiresUniformBindings, pushConstant, addGlobalLoopLimiters, true, false, shaderJobShortName)) { LOGGER.info("The shader is still interesting when array/vector/matrix accesses " + "are made in-bounds. Bounds clamping will be applied during reduction, so " + "the final reduced shader should be free from out-of-bounds accesses."); makeArrayAccessesInBounds = true; } else { LOGGER.info("The shader is not interesting when array/vector/matrix accesses " + "are made in-bounds. Bounds clamping will be not be applied during reduction, " + "so the final reduced shader might exhibit out-of-bounds accesses."); } if (isInterestingNoCache(initialState, requiresUniformBindings, pushConstant, addGlobalLoopLimiters, makeArrayAccessesInBounds, true, shaderJobShortName)) { LOGGER.info("The shader is still interesting when initializers are added to all regular " + "variables. Adding of initializers will be applied during reduction, so the final " + "reduced shader should be free from reads from uninitialized regular variables."); addInitializers = true; } else { LOGGER.info("The shader is not interesting when initializers are added to all regular " + "variables. Adding of initializers will not be applied during reduction, so the " + "final reduced shader should be free from reads from uninitialized regular " + "variables."); } } ShaderJob currentState = initialState; int stepCount = 0; boolean stoppedEarly = false; while (true) { LOGGER.info("Trying reduction attempt " + stepCount + " (" + numSuccessfulReductions + " successful so far)."); final Optional<ShaderJob> maybeNewState = passManager.applyReduction(currentState); if (!maybeNewState.isPresent()) { LOGGER.info("No more to reduce; stopping."); break; } final ShaderJob newState = maybeNewState.get(); stepCount++; final int currentReductionAttempt = stepCount + fileCountOffset; String currentShaderJobShortName = getReductionStepShaderJobShortName( shaderJobShortName, currentReductionAttempt); final boolean interesting = isInterestingWithCache(newState, requiresUniformBindings, pushConstant, addGlobalLoopLimiters, makeArrayAccessesInBounds, addInitializers, currentShaderJobShortName); passManager.notifyInteresting(interesting); final String currentStepShaderJobShortNameWithOutcome = getReductionStepShaderJobShortName( shaderJobShortName, currentReductionAttempt, Optional.of(interesting ? "success" : "fail")); fileOps.moveShaderJobFileTo( new File(workDir, currentShaderJobShortName + ".json"), new File(workDir, currentStepShaderJobShortNameWithOutcome + ".json"), true ); if (interesting) { LOGGER.info("Successful reduction."); numSuccessfulReductions++; currentState = newState; } else { LOGGER.info("Failed reduction."); } if (stepLimit > -1 && stepCount >= stepLimit) { LOGGER.info("Stopping reduction due to hitting step limit {}.", stepLimit); stoppedEarly = true; break; } } ShaderJob finalState = finaliseReduction(currentState); String finalOutputFilePrefix = shaderJobShortName + "_reduced_final"; if (!isInterestingNoCache(finalState, requiresUniformBindings, pushConstant, addGlobalLoopLimiters, makeArrayAccessesInBounds, addInitializers, finalOutputFilePrefix)) { LOGGER.info( "Failed to simplify final reduction state! Reverting to the non-simplified state."); writeState(currentState, new File(workDir, finalOutputFilePrefix + ".json"), requiresUniformBindings, pushConstant, addGlobalLoopLimiters, makeArrayAccessesInBounds, addInitializers); } if (stoppedEarly) { fileOps.createFile(new File(workDir, Constants.REDUCTION_INCOMPLETE)); } LOGGER.info("Total fail hash cache hits: " + failHashCacheHits); return finalOutputFilePrefix; } catch (FileNotFoundException | FileJudgeException exception) { throw new RuntimeException(exception); } } ReductionDriver(ReducerContext context,
                         boolean verbose,
                         ShaderJobFileOperations fileOps,
                         IFileJudge judge,
                         File workDir);  ReductionDriver(ReducerContext context,
                         boolean verbose,
                         ShaderJobFileOperations fileOps,
                         IFileJudge judge,
                         File workDir,
                         boolean literalsToUniforms); String doReduction(
        ShaderJob initialState,
        String shaderJobShortName,
        int fileCountOffset, // Used when continuing a reduction - added on to the number associated
        // with each reduction step during the current reduction.
        int stepLimit); static String getReductionStepShaderJobShortName(String variantPrefix,
                                                          int currentReductionAttempt,
                                                          Optional<String> successIndicator); static final boolean DEBUG_REDUCER; }
@Test public void doReduction() throws Exception { File tempFile = testFolder.newFile("temp.frag"); File tempJsonFile = testFolder.newFile("temp.json"); String program = "void main() { if(_GLF_DEAD(_GLF_FALSE(false, false))) { } }"; FileUtils.writeStringToFile(tempFile, program, StandardCharsets.UTF_8); FileUtils.writeStringToFile(tempJsonFile, "{ }", StandardCharsets.UTF_8); ShadingLanguageVersion version = ShadingLanguageVersion.ESSL_100; IRandom generator = new RandomWrapper(0); TranslationUnit tu = ParseHelper.parse(tempFile); ShaderJob state = new GlslShaderJob( Optional.empty(), new PipelineInfo(tempJsonFile), tu); IFileJudge pessimist = new IFileJudge() { boolean first = true; @Override public boolean isInteresting( File shaderJobFile, File shaderResultFileOutput ) { if (first) { first = false; return true; } return false; } }; List<IReductionOpportunity> ops = ReductionOpportunities.getReductionOpportunities( MakeShaderJobFromFragmentShader.make(tu), new ReducerContext(false, version, generator, new IdGenerator()), fileOps); assertEquals(3, ops.size()); new ReductionDriver( new ReducerContext( false, version, generator, new IdGenerator()), false, fileOps, pessimist, testFolder.getRoot()) .doReduction(state, getPrefix(tempFile), 0, -1); }
@Test public void testInitializersAreInlined() throws Exception { final String original = "void main() {" + " float GLF_live3_x = 3.0 + sin(7.0);\n" + " float GLF_live3_y = GLF_live3_x + cos(8.0);\n" + "}\n"; final String expected = "void main() {" + " float GLF_live3_x = 3.0;\n" + " float GLF_live3_y = sin(7.0) + cos(8.0);\n" + "}\n"; final File tempFile = testFolder.newFile("temp.frag"); final File tempJsonFile = testFolder.newFile("temp.json"); FileUtils.writeStringToFile(tempFile, original, StandardCharsets.UTF_8); FileUtils.writeStringToFile(tempJsonFile, "{ }", StandardCharsets.UTF_8); final ShadingLanguageVersion version = ShadingLanguageVersion.ESSL_100; final IRandom generator = new RandomWrapper(0); final TranslationUnit tu = ParseHelper.parse(tempFile); ShaderJob state = new GlslShaderJob( Optional.empty(), new PipelineInfo(tempJsonFile), tu); IFileJudge referencesSinCosAnd3 = (shaderJobFile, shaderResultFileOutput) -> { try { final String contents = FileUtils.readFileToString( FileHelper.replaceExtension(shaderJobFile, ".frag"), StandardCharsets.UTF_8); return contents.contains("float GLF_live3_x = 3.0 + sin(7.0);") && contents.contains("float GLF_live3_y = GLF_live3_x + cos(8.0);") || contents.contains("float GLF_live3_x = 3.0 + sin(7.0);") && contents.contains("float GLF_live3_y = (3.0 + sin(7.0)) + cos(8.0);") || contents.contains("float GLF_live3_x = 3.0;") && contents.contains("float GLF_live3_y = (3.0 + sin(7.0)) + cos(8.0);") || contents.contains("float GLF_live3_x = 3.0;") && contents.contains("float GLF_live3_y = (sin(7.0)) + cos(8.0);") || contents.contains("float GLF_live3_x = 3.0;") && contents.contains("float GLF_live3_y = sin(7.0) + cos(8.0);"); } catch (IOException e) { return false; } }; final String reducedFilesPrefix = new ReductionDriver( new ReducerContext(false, version, generator, new IdGenerator()), false, fileOps, referencesSinCosAnd3, testFolder.getRoot()) .doReduction(state, getPrefix(tempFile), 0, -1); assertEquals(PrettyPrinterVisitor.prettyPrintAsString(ParseHelper.parse(expected)), PrettyPrinterVisitor.prettyPrintAsString(ParseHelper.parse( new File(testFolder.getRoot(), reducedFilesPrefix + ".frag")))); }
@Test public void testReductionWithUniformBindings() throws Exception { final TranslationUnit fragShader = ParseHelper.parse("layout(location = 0) out vec4 " + "_GLF_color;" + "uniform float a; " + "uniform float b;" + "void main() {" + " if (a > b) {" + " _GLF_color = vec4(1.0);" + " } else {" + " _GLF_color = vec4(0.0);" + " }" + "}"); final String expected = "void main() { }"; PipelineInfo pipelineInfo = new PipelineInfo(); pipelineInfo.addUniform("a", BasicType.FLOAT, Optional.empty(), Arrays.asList(1.0)); pipelineInfo.addUniform("b", BasicType.FLOAT, Optional.empty(), Arrays.asList(2.0)); ShaderJob shaderJob = new GlslShaderJob(Optional.empty(), pipelineInfo, fragShader); shaderJob.makeUniformBindings(Optional.empty()); final File workDir = testFolder.getRoot(); final File tempShaderJobFile = new File(workDir, "temp.json"); fileOps.writeShaderJobFile(shaderJob, tempShaderJobFile); final String resultsPrefix = new ReductionDriver(new ReducerContext(true, ShadingLanguageVersion.ESSL_300, new RandomWrapper(0), new IdGenerator()), false, fileOps, (unused, item) -> true, workDir) .doReduction(shaderJob, "temp", 0, 100); CompareAsts.assertEqualAsts(expected, ParseHelper.parse(new File(testFolder.getRoot(), resultsPrefix + ".frag"))); }
@Test public void testReductionWithPushConstantBinding() throws Exception { final TranslationUnit fragShader = ParseHelper.parse("layout(location = 0) out vec4 " + "_GLF_color;" + "uniform float a; " + "uniform float b;" + "void main() {" + " if (a > b) {" + " _GLF_color = vec4(1.0);" + " } else {" + " _GLF_color = vec4(0.0);" + " }" + "}"); final String expected = "layout(location = 0) out vec4 _GLF_color;\n" + "\n" + "layout(push_constant) uniform buf_push {\n" + " float a;\n" + "};\n" + "layout(set = 0, binding = 0) uniform buf0 {\n" + " float b;\n" + "};\n" + "void main() {\n" + " if(a > b) {\n" + " _GLF_color = vec4(1.0);\n" + " } else {\n" + " _GLF_color = vec4(0.0);\n" + " }\n" + "}\n"; PipelineInfo pipelineInfo = new PipelineInfo(); pipelineInfo.addUniform("a", BasicType.FLOAT, Optional.empty(), Arrays.asList(1.0)); pipelineInfo.addUniform("b", BasicType.FLOAT, Optional.empty(), Arrays.asList(2.0)); ShaderJob shaderJob = new GlslShaderJob(Optional.empty(), pipelineInfo, fragShader); shaderJob.makeUniformBindings(Optional.of("a")); final File workDir = testFolder.getRoot(); final File tempShaderJobFile = new File(workDir, "temp.json"); fileOps.writeShaderJobFile(shaderJob, tempShaderJobFile); final String resultsPrefix = new ReductionDriver(new ReducerContext(false, ShadingLanguageVersion.ESSL_300, new RandomWrapper(0), new IdGenerator()), false, fileOps, (unused, item) -> true, workDir) .doReduction(shaderJob, "temp", 0, 100); CompareAsts.assertEqualAsts(expected, ParseHelper.parse(new File(testFolder.getRoot(), resultsPrefix + ".frag"))); }
@Test public void testReductionOfUnreferencedUniform() throws Exception { final String emptyShader = "void main() { }"; final TranslationUnit fragShader = ParseHelper.parse(emptyShader); final PipelineInfo pipelineInfo = new PipelineInfo(); pipelineInfo.addUniform("a", BasicType.FLOAT, Optional.empty(), Collections.singletonList(1.0)); final ShaderJob shaderJob = new GlslShaderJob(Optional.empty(), pipelineInfo, fragShader); assertEquals(1, shaderJob.getPipelineInfo().getNumUniforms()); final File workDir = testFolder.getRoot(); final File tempShaderJobFile = new File(workDir, "temp.json"); fileOps.writeShaderJobFile(shaderJob, tempShaderJobFile); final String resultsPrefix = new ReductionDriver(new ReducerContext(true, ShadingLanguageVersion.ESSL_100, new RandomWrapper(0), new IdGenerator()), false, fileOps, (unused, item) -> true, workDir) .doReduction(shaderJob, "temp", 0, 100); final ShaderJob after = fileOps.readShaderJobFile(new File(testFolder.getRoot(), resultsPrefix + ".json")); CompareAsts.assertEqualAsts(emptyShader, after.getFragmentShader().get()); assertEquals(0, after.getPipelineInfo().getNumUniforms()); }
@Test public void testSimplificationOfSwitch() throws Exception { final String shader = "#version 310 es\n" + "void main() {\n" + " switch(0) {\n" + " case 0:\n" + " mix(0.0, 1.0, 0.0);\n" + " break;\n" + " default:\n" + " 1;\n" + " }\n" + "}\n"; final String expected = "#version 310 es\n" + "void main() {\n" + " mix(1.0, 1.0, 1.0);\n" + "}\n"; final ShaderJob shaderJob = new GlslShaderJob(Optional.empty(), new PipelineInfo(), ParseHelper.parse(shader)); final File workDir = testFolder.getRoot(); final File tempShaderJobFile = new File(workDir, "temp.json"); fileOps.writeShaderJobFile(shaderJob, tempShaderJobFile); final IFileJudge usesMixFileJudge = new CheckAstFeaturesFileJudge(Collections.singletonList(() -> new CheckAstFeatureVisitor() { @Override public void visitFunctionCallExpr(FunctionCallExpr functionCallExpr) { super.visitFunctionCallExpr(functionCallExpr); if (functionCallExpr.getCallee().equals("mix")) { trigger(); } } }), ShaderKind.FRAGMENT, fileOps); final String resultsPrefix = new ReductionDriver(new ReducerContext(true, ShadingLanguageVersion.ESSL_310, new RandomWrapper(0), new IdGenerator()), false, fileOps, usesMixFileJudge, workDir) .doReduction(shaderJob, "temp", 0, 100); CompareAsts.assertEqualAsts(expected, ParseHelper.parse(new File(testFolder.getRoot(), resultsPrefix + ".frag"))); }
@Test public void testEliminationOfReturns() throws Exception { final String shader = "#version 310 es\n" + "int foo() {\n" + " if (false) {\n" + " return 1;\n" + " }\n" + " return 0;\n" + "}\n" + "void main() {\n" + " if (true) {\n" + " foo();\n" + " return;\n" + " } else {\n" + " return;\n" + " }\n" + " return;\n" + "}\n"; final String expected = "#version 310 es\n" + "int foo() {\n" + " return 1;\n" + "}\n" + "void main() {\n" + " foo();\n" + "}\n"; final ShaderJob shaderJob = new GlslShaderJob(Optional.empty(), new PipelineInfo(), ParseHelper.parse(shader)); final File workDir = testFolder.getRoot(); final File tempShaderJobFile = new File(workDir, "temp.json"); fileOps.writeShaderJobFile(shaderJob, tempShaderJobFile); final IFileJudge usesFooFileJudge = new CheckAstFeaturesFileJudge(Arrays.asList( () -> new CheckAstFeatureVisitor() { @Override public void visitFunctionCallExpr(FunctionCallExpr functionCallExpr) { super.visitFunctionCallExpr(functionCallExpr); if (functionCallExpr.getCallee().equals("foo")) { trigger(); } } }, () -> new CheckAstFeatureVisitor() { @Override public void visitReturnStmt(ReturnStmt returnStmt) { super.visitReturnStmt(returnStmt); if (returnStmt.hasExpr() && returnStmt.getExpr() instanceof IntConstantExpr && ((IntConstantExpr) returnStmt.getExpr()).getNumericValue() == 1) { trigger(); } } }), ShaderKind.FRAGMENT, fileOps); final String resultsPrefix = new ReductionDriver(new ReducerContext(true, ShadingLanguageVersion.ESSL_310, new RandomWrapper(0), new IdGenerator()), false, fileOps, usesFooFileJudge, workDir) .doReduction(shaderJob, "temp", 0, 100); CompareAsts.assertEqualAsts(expected, ParseHelper.parse(new File(testFolder.getRoot(), resultsPrefix + ".frag"))); }
@Test public void testRemoveIfOrLoopButKeepGuard() throws Exception { final String shaderIf = "#version 310 es\n" + "void main() {\n" + " if(1 > 0) {\n" + " }\n" + "}\n"; final String shaderWhile = "#version 310 es\n" + "void main() {\n" + " while(1 > 0) {\n" + " }\n" + "}\n"; final String shaderDoWhile = "#version 310 es\n" + "void main() {\n" + " do {\n" + " } while(1 > 0);\n" + "}\n"; final String shaderFor = "#version 310 es\n" + "void main() {\n" + " for (; 1 > 0; ) {\n" + " }\n" + "}\n"; final String expected = "#version 310 es\n" + "void main() {\n" + " 1 > 0;\n" + "}\n"; final IFileJudge customFileJudge = new CheckAstFeaturesFileJudge(Collections.singletonList( () -> new CheckAstFeatureVisitor() { @Override public void visitBinaryExpr(BinaryExpr binaryExpr) { super.visitBinaryExpr(binaryExpr); if (binaryExpr.getOp() == BinOp.GT && binaryExpr.getLhs() instanceof IntConstantExpr && ((IntConstantExpr) binaryExpr.getLhs()).getNumericValue() == 1 && binaryExpr.getRhs() instanceof IntConstantExpr && ((IntConstantExpr) binaryExpr.getRhs()).getNumericValue() == 0) { trigger(); } } }), ShaderKind.FRAGMENT, fileOps); for (String shader : Arrays.asList(shaderIf, shaderWhile, shaderDoWhile, shaderFor)) { final ShaderJob shaderJob = new GlslShaderJob(Optional.empty(), new PipelineInfo(), ParseHelper.parse(shader)); final File workDir = testFolder.getRoot(); final File tempShaderJobFile = new File(workDir, "temp.json"); fileOps.writeShaderJobFile(shaderJob, tempShaderJobFile); final String resultsPrefix = new ReductionDriver(new ReducerContext(true, ShadingLanguageVersion.ESSL_310, new RandomWrapper(0), new IdGenerator()), false, fileOps, customFileJudge, workDir) .doReduction(shaderJob, "temp", 0, 100); CompareAsts.assertEqualAsts(expected, ParseHelper.parse(new File(testFolder.getRoot(), resultsPrefix + ".frag"))); } }
@Test public void testRemoveSwitchButKeepGuard() throws Exception { final String shaderSwitch = "#version 310 es\n" + "void main() {\n" + " switch(0) {\n" + " case 1:\n" + " break;\n" + " }\n" + "}\n"; final String expected = "#version 310 es\n" + "void main() {\n" + " 0;\n" + "}\n"; final IFileJudge customFileJudge = new CheckAstFeaturesFileJudge(Collections.singletonList( () -> new CheckAstFeatureVisitor() { @Override public void visitIntConstantExpr(IntConstantExpr intConstantExpr) { super.visitIntConstantExpr(intConstantExpr); if (intConstantExpr.getNumericValue() == 0) { trigger(); } } }), ShaderKind.FRAGMENT, fileOps); final ShaderJob shaderJob = new GlslShaderJob(Optional.empty(), new PipelineInfo(), ParseHelper.parse(shaderSwitch)); final File workDir = testFolder.getRoot(); final File tempShaderJobFile = new File(workDir, "temp.json"); fileOps.writeShaderJobFile(shaderJob, tempShaderJobFile); final String resultsPrefix = new ReductionDriver(new ReducerContext(true, ShadingLanguageVersion.ESSL_310, new RandomWrapper(0), new IdGenerator()), false, fileOps, customFileJudge, workDir) .doReduction(shaderJob, "temp", 0, 100); CompareAsts.assertEqualAsts(expected, ParseHelper.parse(new File(testFolder.getRoot(), resultsPrefix + ".frag"))); }
@Test public void testMakingArrayAccessesInBoundsIsValid() throws Exception { final String shaderSwitch = "#version 310 es\n" + "precision highp float;\n" + "void main() {\n" + " int A[3];\n" + " for (int i = 0; i < 12; i++) {\n" + " A[i] = i;\n" + " }\n" + "}\n"; final String expected = "#version 310 es\n" + "void main() {\n" + " 0;\n" + "}\n"; final IFileJudge customFileJudge = new IFileJudge() { @Override public boolean isInteresting(File shaderJobFile, File shaderResultFileOutput) throws FileJudgeException { try { fileOps.areShadersValid(shaderJobFile, true); } catch (IOException | InterruptedException exception) { throw new FileJudgeException(exception); } return true; } }; final ShaderJob shaderJob = new GlslShaderJob(Optional.empty(), new PipelineInfo(), ParseHelper.parse(shaderSwitch)); final File workDir = testFolder.getRoot(); final File tempShaderJobFile = new File(workDir, "temp.json"); fileOps.writeShaderJobFile(shaderJob, tempShaderJobFile); new ReductionDriver(new ReducerContext(true, ShadingLanguageVersion.ESSL_310, new RandomWrapper(0), new IdGenerator()), false, fileOps, customFileJudge, workDir) .doReduction(shaderJob, "temp", 0, 100); }
@Test public void testGranularityAtMinimum() throws Exception { final String shader = "#version 310 es\n" + "void main() {\n" + " int a = 1;\n" + " int b = 2;\n" + " int c = 3;\n" + " int d = 4;\n" + " int e = 5;\n" + " int f = 6;\n" + "}\n"; final String expected = "#version 310 es\n" + "layout(set = 0, binding = 0) uniform buf0 {" + "int _GLF_uniform_int_values[5];" + "};" + "void main() {\n" + " int a = _GLF_uniform_int_values[0];\n" + " int b = _GLF_uniform_int_values[1];\n" + " int c = _GLF_uniform_int_values[2];\n" + " int d = _GLF_uniform_int_values[3];\n" + " int e = _GLF_uniform_int_values[4];\n" + " int f = 6;\n" + "}\n"; class GranularityJudge implements IFileJudge { private int counter; private final ShaderJobFileOperations fileOps; public GranularityJudge(ShaderJobFileOperations fileOps) { this.counter = 0; this.fileOps = fileOps; } @Override public boolean isInteresting( File shaderJobFile, File shaderResultFileOutput ) { try { if (!fileOps.areShadersValid(shaderJobFile, true)) { return false; } } catch (IOException | InterruptedException ex) { throw new RuntimeException(ex); } counter++; if (counter == 2) { return false; } return true; } } final IFileJudge granularityJudge = new GranularityJudge(fileOps); final ShaderJob shaderJob = new GlslShaderJob(Optional.empty(), new PipelineInfo(), ParseHelper.parse(shader)); final File workDir = testFolder.getRoot(); final File tempShaderJobFile = new File(workDir, "temp.json"); fileOps.writeShaderJobFile(shaderJob, tempShaderJobFile); final String resultsPrefix = new ReductionDriver(new ReducerContext(false, ShadingLanguageVersion.ESSL_310, new RandomWrapper(0), new IdGenerator()), false, fileOps, granularityJudge, workDir, true) .doReduction(shaderJob, "temp", 0, 100); CompareAsts.assertEqualAsts(expected, ParseHelper.parse(new File(testFolder.getRoot(), resultsPrefix + ".frag"))); }