Flutter Engine
The Flutter Engine
SkSLMinify.cpp
Go to the documentation of this file.
1/*
2 * Copyright 2022 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
9#include "src/core/SkOpts.h"
12#include "src/sksl/SkSLLexer.h"
16#include "src/sksl/SkSLUtil.h"
20#include "src/utils/SkOSPath.h"
23
24#include <cctype>
25#include <forward_list>
26#include <fstream>
27#include <limits.h>
28#include <stdarg.h>
29#include <stdio.h>
30
31static bool gUnoptimized = false;
32static bool gStringify = false;
34
35void SkDebugf(const char format[], ...) {
36 va_list args;
38 vfprintf(stderr, format, args);
39 va_end(args);
40}
41
42namespace SkOpts {
44}
45
46static std::string base_name(const std::string& path) {
47 size_t slashPos = path.find_last_of("/\\");
48 return path.substr(slashPos == std::string::npos ? 0 : slashPos + 1);
49}
50
51static std::string remove_extension(const std::string& path) {
52 size_t dotPos = path.find_last_of('.');
53 return path.substr(0, dotPos);
54}
55
56/**
57 * Displays a usage banner; used when the command line arguments don't make sense.
58 */
59static void show_usage() {
60 printf("usage: sksl-minify <output> <input> [--frag|--vert|--compute|--shader|"
61 "--colorfilter|--blender|--meshfrag|--meshvert] [dependencies...]\n");
62}
63
64static std::string_view stringize(const SkSL::Token& token, std::string_view text) {
65 return text.substr(token.fOffset, token.fLength);
66}
67
68static bool maybe_identifier(char c) {
69 return std::isalnum(c) || c == '$' || c == '_';
70}
71
72static bool is_plus_or_minus(char c) {
73 return c == '+' || c == '-';
74}
75
76static std::forward_list<std::unique_ptr<const SkSL::Module>> compile_module_list(
78 std::forward_list<std::unique_ptr<const SkSL::Module>> modules;
79
80 // If we are compiling a Runtime Effect...
82 // ... the parent modules still need to be compiled as Fragment programs.
83 // If no modules are explicitly specified, we automatically include the built-in modules for
84 // runtime effects (sksl_shared, sksl_public) so that casual users don't need to always
85 // remember to specify these modules.
86 if (paths.size() == 1) {
87 const std::string minifyDir = SkOSPath::Dirname(SkGetExecutablePath().c_str()).c_str();
88 std::string defaultRuntimeShaderPaths[] = {
89 minifyDir + SkOSPath::SEPARATOR + "sksl_public.sksl",
90 minifyDir + SkOSPath::SEPARATOR + "sksl_shared.sksl",
91 };
92 modules = compile_module_list(defaultRuntimeShaderPaths, SkSL::ProgramKind::kFragment);
93 } else {
94 // The parent modules were listed on the command line; we need to compile them as
95 // fragment programs. The final module keeps the Runtime Shader program-kind.
97 paths = paths.first(1);
98 }
99 // Set up the public type aliases so that Runtime Shader code with GLSL types works as-is.
101 }
102
103 // Load in each input as a module, from right to left.
104 // Each module inherits the symbols from its parent module.
106 for (auto modulePath = paths.rbegin(); modulePath != paths.rend(); ++modulePath) {
107 std::ifstream in(*modulePath);
108 std::string moduleSource{std::istreambuf_iterator<char>(in),
109 std::istreambuf_iterator<char>()};
110 if (in.rdstate()) {
111 printf("error reading '%s'\n", modulePath->c_str());
112 return {};
113 }
114
115 const SkSL::Module* parent = modules.empty() ? SkSL::ModuleLoader::Get().rootModule()
116 : modules.front().get();
117 std::unique_ptr<SkSL::Module> m = compiler.compileModule(kind,
118 modulePath->c_str(),
119 std::move(moduleSource),
120 parent,
121 /*shouldInline=*/false);
122 if (!m) {
123 return {};
124 }
125 // We need to optimize every module in the chain. We rename private functions at global
126 // scope, and we need to make sure there are no name collisions between nested modules.
127 // (i.e., if module A claims names `$a` and `$b` at global scope, module B will need to
128 // start at `$c`. The most straightforward way to handle this is to actually perform the
129 // renames.)
130 compiler.optimizeModuleBeforeMinifying(kind, *m, /*shrinkSymbols=*/!gUnoptimized);
131 modules.push_front(std::move(m));
132 }
133 // Return all of the modules to transfer their ownership to the caller.
134 return modules;
135}
136
137static bool generate_minified_text(std::string_view inputPath,
138 std::string_view text,
140 using TokenKind = SkSL::Token::Kind;
141
142 SkSL::Lexer lexer;
143 lexer.start(text);
144
145 SkSL::Token token;
146 std::string_view lastTokenText = " ";
147 int lineWidth = 1;
148 for (;;) {
149 token = lexer.next();
150 if (token.fKind == TokenKind::TK_END_OF_FILE) {
151 break;
152 }
153 if (token.fKind == TokenKind::TK_LINE_COMMENT ||
154 token.fKind == TokenKind::TK_BLOCK_COMMENT ||
155 token.fKind == TokenKind::TK_WHITESPACE) {
156 continue;
157 }
158 std::string_view thisTokenText = stringize(token, text);
159 if (token.fKind == TokenKind::TK_INVALID) {
160 printf("%.*s: unable to parse '%.*s' at offset %d\n",
161 (int)inputPath.size(), inputPath.data(),
162 (int)thisTokenText.size(), thisTokenText.data(),
163 token.fOffset);
164 return false;
165 }
166 if (thisTokenText.empty()) {
167 continue;
168 }
169 if (token.fKind == TokenKind::TK_FLOAT_LITERAL) {
170 // We can reduce `3.0` to `3.` safely.
171 if (skstd::contains(thisTokenText, '.')) {
172 while (thisTokenText.back() == '0' && thisTokenText.size() >= 3) {
173 thisTokenText.remove_suffix(1);
174 }
175 }
176 // We can reduce `0.5` to `.5` safely.
177 if (skstd::starts_with(thisTokenText, "0.") && thisTokenText.size() >= 3) {
178 thisTokenText.remove_prefix(1);
179 }
180 }
181 SkASSERT(!lastTokenText.empty());
182 if (gStringify && lineWidth > 75) {
183 // We're getting full-ish; wrap to a new line.
184 out.writeText("\"\n\"");
185 lineWidth = 1;
186 }
187
188 // Detect tokens with abutting alphanumeric characters side-by-side.
189 bool adjacentIdentifiers =
190 maybe_identifier(lastTokenText.back()) && maybe_identifier(thisTokenText.front());
191
192 // Detect potentially ambiguous preincrement/postincrement operators.
193 // For instance, `x + ++y` and `x++ + y` require whitespace for differentiation.
194 bool adjacentPlusOrMinus =
195 is_plus_or_minus(lastTokenText.back()) && is_plus_or_minus(thisTokenText.front());
196
197 // Insert whitespace when it is necessary for program correctness.
198 if (adjacentIdentifiers || adjacentPlusOrMinus) {
199 out.writeText(" ");
200 lineWidth++;
201 }
202 out.write(thisTokenText.data(), thisTokenText.size());
203 lineWidth += thisTokenText.size();
204 lastTokenText = thisTokenText;
205 }
206
207 return true;
208}
209
210static bool find_boolean_flag(SkSpan<std::string>* args, std::string_view flagName) {
211 size_t startingCount = args->size();
212 auto iter = std::remove_if(args->begin(), args->end(),
213 [&](const std::string& a) { return a == flagName; });
214 *args = args->subspan(0, std::distance(args->begin(), iter));
215 return args->size() < startingCount;
216}
217
219 // Returns true if more than one boolean is set.
220 return std::count(flags.begin(), flags.end(), true) > 1;
221}
222
224 // Ignore the process name.
225 SkASSERT(!args.empty());
226 args = args.subspan(1);
227
228 // Process command line flags.
229 gUnoptimized = find_boolean_flag(&args, "--unoptimized");
230 gStringify = find_boolean_flag(&args, "--stringify");
231 bool isFrag = find_boolean_flag(&args, "--frag");
232 bool isVert = find_boolean_flag(&args, "--vert");
233 bool isCompute = find_boolean_flag(&args, "--compute");
234 bool isShader = find_boolean_flag(&args, "--shader");
235 bool isPrivateShader = find_boolean_flag(&args, "--privshader");
236 bool isColorFilter = find_boolean_flag(&args, "--colorfilter");
237 bool isBlender = find_boolean_flag(&args, "--blender");
238 bool isMeshFrag = find_boolean_flag(&args, "--meshfrag");
239 bool isMeshVert = find_boolean_flag(&args, "--meshvert");
240 if (has_overlapping_flags({isFrag, isVert, isCompute, isShader, isColorFilter,
241 isBlender, isMeshFrag, isMeshVert})) {
242 show_usage();
244 }
245 if (isFrag) {
247 } else if (isVert) {
249 } else if (isCompute) {
251 } else if (isColorFilter) {
253 } else if (isBlender) {
255 } else if (isMeshFrag) {
257 } else if (isMeshVert) {
259 } else if (isPrivateShader) {
261 } else {
262 // Default case, if no option is specified.
264 }
265
266 // We expect, at a minimum, an output path and one or more input paths.
267 if (args.size() < 2) {
268 show_usage();
270 }
271 const std::string& outputPath = args[0];
272 SkSpan inputPaths = args.subspan(1);
273
274 // Compile the original SkSL from the input path.
275 std::forward_list<std::unique_ptr<const SkSL::Module>> modules =
277 if (modules.empty()) {
279 }
280 const SkSL::Module* module = modules.front().get();
281
282 // Emit the minified SkSL into our output path.
283 SkSL::FileOutputStream out(outputPath.c_str());
284 if (!out.isValid()) {
285 printf("error writing '%s'\n", outputPath.c_str());
287 }
288
289 std::string baseName = remove_extension(base_name(inputPaths.front()));
290 if (gStringify) {
291 out.printf("static constexpr char SKSL_MINIFIED_%s[] =\n\"", baseName.c_str());
292 }
293
294 // Generate the program text by getting the program's description.
295 std::string text;
296 for (const std::unique_ptr<SkSL::ProgramElement>& element : module->fElements) {
297 if ((isMeshFrag || isMeshVert) && element->is<SkSL::StructDefinition>()) {
298 std::string_view name = element->as<SkSL::StructDefinition>().type().name();
299 if (name == "Attributes" || name == "Varyings") {
300 // Don't emit the Attributes or Varyings structs from a mesh program into the
301 // minified output; those are synthesized via the SkMeshSpecification.
302 continue;
303 }
304 }
305 text += element->description();
306 }
307
308 // Eliminate whitespace and perform other basic simplifications via a lexer pass.
309 if (!generate_minified_text(inputPaths.front(), text, out)) {
311 }
312
313 if (gStringify) {
314 out.writeText("\";");
315 }
316 out.writeText("\n");
317
318 if (!out.close()) {
319 printf("error writing '%s'\n", outputPath.c_str());
321 }
322
324}
325
326int main(int argc, const char** argv) {
327 if (argc == 2) {
328 // Worklists are the only two-argument case for sksl-minify, and we don't intend to support
329 // nested worklists, so we can process them here.
330 return (int)ProcessWorklist(argv[1], process_command);
331 } else {
332 // Process non-worklist inputs.
333 std::vector<std::string> args;
334 for (int index=0; index<argc; ++index) {
335 args.push_back(argv[index]);
336 }
337
338 return (int)process_command(args);
339 }
340}
int count
Definition: FontMgrTest.cpp:50
ResultCode ProcessWorklist(const char *worklistPath, const std::function< ResultCode(SkSpan< std::string > args)> &processCommandFn)
ResultCode
#define SkASSERT(cond)
Definition: SkAssert.h:116
std::string SkGetExecutablePath()
int main(int argc, const char **argv)
Definition: SkSLMinify.cpp:326
static void show_usage()
Definition: SkSLMinify.cpp:59
static std::string base_name(const std::string &path)
Definition: SkSLMinify.cpp:46
static ResultCode process_command(SkSpan< std::string > args)
Definition: SkSLMinify.cpp:223
static bool find_boolean_flag(SkSpan< std::string > *args, std::string_view flagName)
Definition: SkSLMinify.cpp:210
void SkDebugf(const char format[],...)
Definition: SkSLMinify.cpp:35
static std::string_view stringize(const SkSL::Token &token, std::string_view text)
Definition: SkSLMinify.cpp:64
static std::forward_list< std::unique_ptr< const SkSL::Module > > compile_module_list(SkSpan< const std::string > paths, SkSL::ProgramKind kind)
Definition: SkSLMinify.cpp:76
static bool gUnoptimized
Definition: SkSLMinify.cpp:31
static bool maybe_identifier(char c)
Definition: SkSLMinify.cpp:68
static std::string remove_extension(const std::string &path)
Definition: SkSLMinify.cpp:51
static bool generate_minified_text(std::string_view inputPath, std::string_view text, SkSL::FileOutputStream &out)
Definition: SkSLMinify.cpp:137
static SkSL::ProgramKind gProgramKind
Definition: SkSLMinify.cpp:33
static bool is_plus_or_minus(char c)
Definition: SkSLMinify.cpp:72
static bool has_overlapping_flags(SkSpan< const bool > flags)
Definition: SkSLMinify.cpp:218
static bool gStringify
Definition: SkSLMinify.cpp:32
GLenum type
static constexpr char SEPARATOR
Definition: SkOSPath.h:21
static SkString Dirname(const char *fullPath)
Definition: SkOSPath.cpp:36
void start(std::string_view text)
Definition: SkSLLexer.h:125
Token next()
Definition: SkSLLexer.cpp:800
const Module * rootModule()
void addPublicTypeAliases(const SkSL::Module *module)
static ModuleLoader Get()
constexpr SkSpan< T > first(size_t prefixLen) const
Definition: SkSpan_impl.h:98
constexpr T & front() const
Definition: SkSpan_impl.h:88
constexpr SkSpan< T > subspan(size_t offset) const
Definition: SkSpan_impl.h:105
constexpr auto rbegin() const
Definition: SkSpan_impl.h:92
constexpr auto rend() const
Definition: SkSpan_impl.h:93
constexpr size_t size() const
Definition: SkSpan_impl.h:95
const char * c_str() const
Definition: SkString.h:133
struct MyStruct a[10]
FlutterSemanticsFlag flags
G_BEGIN_DECLS G_MODULE_EXPORT FlValue * args
uint32_t uint32_t * format
std::u16string text
char ** argv
Definition: library.h:9
size_t raster_pipeline_highp_stride
Definition: SkOpts.cpp:26
std::string printf(const char *fmt,...) SK_PRINTF_LIKE(1
Definition: SkSLString.cpp:83
va_start(args, format)
va_end(args)
DEF_SWITCHES_START aot vmservice shared library Name of the *so containing AOT compiled Dart assets for launching the service isolate vm snapshot The VM snapshot data that will be memory mapped as read only SnapshotAssetPath must be present isolate snapshot The isolate snapshot data that will be memory mapped as read only SnapshotAssetPath must be present cache dir path
Definition: switches.h:57
DEF_SWITCHES_START aot vmservice shared library name
Definition: switches.h:32
compiler
Definition: malisc.py:17
constexpr bool starts_with(std::string_view str, std::string_view prefix)
Definition: SkStringView.h:17
constexpr bool contains(std::string_view str, std::string_view needle)
Definition: SkStringView.h:41
std::vector< std::unique_ptr< ProgramElement > > fElements
Definition: SkSLCompiler.h:59
static bool IsRuntimeEffect(ProgramKind kind)
int32_t fOffset
Definition: SkSLLexer.h:119
Kind fKind
Definition: SkSLLexer.h:118
int32_t fLength
Definition: SkSLLexer.h:120