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