1 // Copyright (c) 2017 Google Inc.
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 #include <memory>
16 #include <vector>
17 
18 #include "gmock/gmock.h"
19 #include "spirv-tools/libspirv.hpp"
20 #include "spirv-tools/optimizer.hpp"
21 #include "test/opt/pass_fixture.h"
22 #include "test/opt/pass_utils.h"
23 
24 namespace spvtools {
25 namespace opt {
26 namespace {
27 
28 using CompactIdsTest = PassTest<::testing::Test>;
29 
TEST_F(CompactIdsTest,PassOff)30 TEST_F(CompactIdsTest, PassOff) {
31   const std::string before =
32       R"(OpCapability Addresses
33 OpCapability Kernel
34 OpCapability GenericPointer
35 OpCapability Linkage
36 OpMemoryModel Physical32 OpenCL
37 %99 = OpTypeInt 32 0
38 %10 = OpTypeVector %99 2
39 %20 = OpConstant %99 2
40 %30 = OpTypeArray %99 %20
41 )";
42 
43   const std::string after = before;
44 
45   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
46   SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
47   SinglePassRunAndCheck<NullPass>(before, after, false, false);
48 }
49 
TEST_F(CompactIdsTest,PassOn)50 TEST_F(CompactIdsTest, PassOn) {
51   const std::string before =
52       R"(OpCapability Addresses
53 OpCapability Kernel
54 OpCapability GenericPointer
55 OpCapability Linkage
56 OpMemoryModel Physical32 OpenCL
57 OpEntryPoint Kernel %3 "simple_kernel"
58 %99 = OpTypeInt 32 0
59 %10 = OpTypeVector %99 2
60 %20 = OpConstant %99 2
61 %30 = OpTypeArray %99 %20
62 %40 = OpTypeVoid
63 %50 = OpTypeFunction %40
64  %3 = OpFunction %40 None %50
65 %70 = OpLabel
66 OpReturn
67 OpFunctionEnd
68 )";
69 
70   const std::string after =
71       R"(OpCapability Addresses
72 OpCapability Kernel
73 OpCapability GenericPointer
74 OpCapability Linkage
75 OpMemoryModel Physical32 OpenCL
76 OpEntryPoint Kernel %1 "simple_kernel"
77 %2 = OpTypeInt 32 0
78 %3 = OpTypeVector %2 2
79 %4 = OpConstant %2 2
80 %5 = OpTypeArray %2 %4
81 %6 = OpTypeVoid
82 %7 = OpTypeFunction %6
83 %1 = OpFunction %6 None %7
84 %8 = OpLabel
85 OpReturn
86 OpFunctionEnd
87 )";
88 
89   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
90   SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
91   SinglePassRunAndCheck<CompactIdsPass>(before, after, false, false);
92 }
93 
TEST_F(CompactIdsTest,DebugScope)94 TEST_F(CompactIdsTest, DebugScope) {
95   const std::string text =
96       R"(OpCapability Addresses
97 OpCapability Kernel
98 OpCapability GenericPointer
99 OpCapability Linkage
100 %5 = OpExtInstImport "OpenCL.DebugInfo.100"
101 OpMemoryModel Physical32 OpenCL
102 OpEntryPoint Kernel %3 "simple_kernel"
103 %2 = OpString "test"
104 %99 = OpTypeInt 32 0
105 %10 = OpTypeVector %99 2
106 %20 = OpConstant %99 2
107 %30 = OpTypeArray %99 %20
108 %40 = OpTypeVoid
109 %50 = OpTypeFunction %40
110 %11 = OpExtInst %40 %5 DebugSource %2
111 %12 = OpExtInst %40 %5 DebugCompilationUnit 1 4 %11 HLSL
112 %13 = OpExtInst %40 %5 DebugTypeFunction FlagIsProtected|FlagIsPrivate %40
113 
114 ; CHECK: [[fn:%\w+]] = OpExtInst {{%\w+}} {{%\w+}} DebugFunction
115 %14 = OpExtInst %40 %5 DebugFunction %2 %13 %11 0 0 %12 %2 FlagIsProtected|FlagIsPrivate 0 %3
116  %3 = OpFunction %40 None %50
117 %70 = OpLabel
118 
119 ; CHECK: DebugScope [[fn]]
120 %19 = OpExtInst %40 %5 DebugScope %14
121 OpReturn
122 OpFunctionEnd
123 )";
124 
125   SetAssembleOptions(SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
126   SetDisassembleOptions(SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
127   SinglePassRunAndMatch<CompactIdsPass>(text, true);
128 }
129 
TEST(CompactIds,InstructionResultIsUpdated)130 TEST(CompactIds, InstructionResultIsUpdated) {
131   // For https://github.com/KhronosGroup/SPIRV-Tools/issues/827
132   // In that bug, the compact Ids pass was directly updating the result Id
133   // word for an OpFunction instruction, but not updating the cached
134   // result_id_ in that Instruction object.
135   //
136   // This test is a bit cheesy.  We don't expose internal interfaces enough
137   // to see the inconsistency.  So reproduce the original scenario, with
138   // compact ids followed by a pass that trips up on the inconsistency.
139 
140   const std::string input(R"(OpCapability Shader
141 OpMemoryModel Logical Simple
142 OpEntryPoint GLCompute %100 "main"
143 %200 = OpTypeVoid
144 %300 = OpTypeFunction %200
145 %100 = OpFunction %200 None %300
146 %400 = OpLabel
147 OpReturn
148 OpFunctionEnd
149 )");
150 
151   std::vector<uint32_t> binary;
152   const spv_target_env env = SPV_ENV_UNIVERSAL_1_0;
153   spvtools::SpirvTools tools(env);
154   auto assembled = tools.Assemble(
155       input, &binary, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
156   EXPECT_TRUE(assembled);
157 
158   spvtools::Optimizer optimizer(env);
159   optimizer.RegisterPass(CreateCompactIdsPass());
160   // The exhaustive inliner will use the result_id
161   optimizer.RegisterPass(CreateInlineExhaustivePass());
162 
163   // This should not crash!
164   optimizer.Run(binary.data(), binary.size(), &binary);
165 
166   std::string disassembly;
167   tools.Disassemble(binary, &disassembly, SPV_BINARY_TO_TEXT_OPTION_NO_HEADER);
168 
169   const std::string expected(R"(OpCapability Shader
170 OpMemoryModel Logical Simple
171 OpEntryPoint GLCompute %1 "main"
172 %2 = OpTypeVoid
173 %3 = OpTypeFunction %2
174 %1 = OpFunction %2 None %3
175 %4 = OpLabel
176 OpReturn
177 OpFunctionEnd
178 )");
179 
180   EXPECT_THAT(disassembly, ::testing::Eq(expected));
181 }
182 
TEST(CompactIds,HeaderIsUpdated)183 TEST(CompactIds, HeaderIsUpdated) {
184   const std::string input(R"(OpCapability Shader
185 OpMemoryModel Logical Simple
186 OpEntryPoint GLCompute %100 "main"
187 %200 = OpTypeVoid
188 %300 = OpTypeFunction %200
189 %100 = OpFunction %200 None %300
190 %400 = OpLabel
191 OpReturn
192 OpFunctionEnd
193 )");
194 
195   std::vector<uint32_t> binary;
196   const spv_target_env env = SPV_ENV_UNIVERSAL_1_0;
197   spvtools::SpirvTools tools(env);
198   auto assembled = tools.Assemble(
199       input, &binary, SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
200   EXPECT_TRUE(assembled);
201 
202   spvtools::Optimizer optimizer(env);
203   optimizer.RegisterPass(CreateCompactIdsPass());
204   // The exhaustive inliner will use the result_id
205   optimizer.RegisterPass(CreateInlineExhaustivePass());
206 
207   // This should not crash!
208   optimizer.Run(binary.data(), binary.size(), &binary);
209 
210   std::string disassembly;
211   tools.Disassemble(binary, &disassembly, SPV_BINARY_TO_TEXT_OPTION_NONE);
212 
213   const std::string expected(R"(; SPIR-V
214 ; Version: 1.0
215 ; Generator: Khronos SPIR-V Tools Assembler; 0
216 ; Bound: 5
217 ; Schema: 0
218 OpCapability Shader
219 OpMemoryModel Logical Simple
220 OpEntryPoint GLCompute %1 "main"
221 %2 = OpTypeVoid
222 %3 = OpTypeFunction %2
223 %1 = OpFunction %2 None %3
224 %4 = OpLabel
225 OpReturn
226 OpFunctionEnd
227 )");
228 
229   EXPECT_THAT(disassembly, ::testing::Eq(expected));
230 }
231 
232 // Test context consistency check after invalidating
233 // CFG and others by compact IDs Pass.
234 // Uses a GLSL shader with named labels for variety
TEST(CompactIds,ConsistentCheck)235 TEST(CompactIds, ConsistentCheck) {
236   const std::string input(R"(OpCapability Shader
237 OpMemoryModel Logical GLSL450
238 OpEntryPoint Fragment %main "main" %in_var_A %out_var_SV_TARGET
239 OpExecutionMode %main OriginUpperLeft
240 OpSource HLSL 600
241 OpName %main "main"
242 OpName %in_var_A "in.var.A"
243 OpName %out_var_SV_TARGET "out.var.SV_TARGET"
244 OpDecorate %in_var_A Location 0
245 OpDecorate %out_var_SV_TARGET Location 0
246 %void = OpTypeVoid
247 %3 = OpTypeFunction %void
248 %float = OpTypeFloat 32
249 %v4float = OpTypeVector %float 4
250 %_ptr_Input_v4float = OpTypePointer Input %v4float
251 %_ptr_Output_v4float = OpTypePointer Output %v4float
252 %in_var_A = OpVariable %_ptr_Input_v4float Input
253 %out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output
254 %main = OpFunction %void None %3
255 %5 = OpLabel
256 %12 = OpLoad %v4float %in_var_A
257 %23 = OpVectorShuffle %v4float %12 %12 0 0 0 1
258 OpStore %out_var_SV_TARGET %23
259 OpReturn
260 OpFunctionEnd
261 )");
262 
263   spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_1);
264   std::unique_ptr<IRContext> context =
265       BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, input,
266                   SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
267   ASSERT_NE(context, nullptr);
268 
269   CompactIdsPass compact_id_pass;
270   context->BuildInvalidAnalyses(compact_id_pass.GetPreservedAnalyses());
271   const auto status = compact_id_pass.Run(context.get());
272   ASSERT_NE(status, Pass::Status::Failure);
273   EXPECT_TRUE(context->IsConsistent());
274 
275   // Test output just in case
276   std::vector<uint32_t> binary;
277   context->module()->ToBinary(&binary, false);
278   std::string disassembly;
279   tools.Disassemble(binary, &disassembly,
280                     SpirvTools::kDefaultDisassembleOption);
281 
282   const std::string expected(R"(OpCapability Shader
283 OpMemoryModel Logical GLSL450
284 OpEntryPoint Fragment %main "main" %in_var_A %out_var_SV_TARGET
285 OpExecutionMode %main OriginUpperLeft
286 OpSource HLSL 600
287 OpName %main "main"
288 OpName %in_var_A "in.var.A"
289 OpName %out_var_SV_TARGET "out.var.SV_TARGET"
290 OpDecorate %in_var_A Location 0
291 OpDecorate %out_var_SV_TARGET Location 0
292 %void = OpTypeVoid
293 %5 = OpTypeFunction %void
294 %float = OpTypeFloat 32
295 %v4float = OpTypeVector %float 4
296 %_ptr_Input_v4float = OpTypePointer Input %v4float
297 %_ptr_Output_v4float = OpTypePointer Output %v4float
298 %in_var_A = OpVariable %_ptr_Input_v4float Input
299 %out_var_SV_TARGET = OpVariable %_ptr_Output_v4float Output
300 %main = OpFunction %void None %5
301 %10 = OpLabel
302 %11 = OpLoad %v4float %in_var_A
303 %12 = OpVectorShuffle %v4float %11 %11 0 0 0 1
304 OpStore %out_var_SV_TARGET %12
305 OpReturn
306 OpFunctionEnd
307 )");
308 
309   EXPECT_THAT(disassembly, ::testing::Eq(expected));
310 }
311 
TEST(CompactIds,ResetIdBound)312 TEST(CompactIds, ResetIdBound) {
313   const std::string input(R"(OpCapability Shader
314 OpMemoryModel Logical GLSL450
315 OpEntryPoint Fragment %1 "main"
316 OpExecutionMode %1 OriginUpperLeft
317 %void = OpTypeVoid
318 %3 = OpTypeFunction %void
319 %1 = OpFunction %void None %3
320 %4 = OpLabel
321 OpReturn
322 OpFunctionEnd
323 )");
324 
325   spvtools::SpirvTools tools(SPV_ENV_UNIVERSAL_1_1);
326   std::unique_ptr<IRContext> context =
327       BuildModule(SPV_ENV_UNIVERSAL_1_1, nullptr, input,
328                   SPV_TEXT_TO_BINARY_OPTION_PRESERVE_NUMERIC_IDS);
329   ASSERT_NE(context, nullptr);
330 
331   CompactIdsPass compact_id_pass;
332   context->module()->SetIdBound(20000);
333   const auto status = compact_id_pass.Run(context.get());
334   EXPECT_EQ(status, Pass::Status::SuccessWithChange);
335   EXPECT_EQ(context->module()->id_bound(), 5);
336 
337   // Test output just in case
338   std::vector<uint32_t> binary;
339   context->module()->ToBinary(&binary, false);
340   std::string disassembly;
341   tools.Disassemble(binary, &disassembly,
342                     SpirvTools::kDefaultDisassembleOption);
343 
344   EXPECT_THAT(disassembly, ::testing::Eq(input));
345 }
346 
347 }  // namespace
348 }  // namespace opt
349 }  // namespace spvtools
350