xref: /aosp_15_r20/external/XNNPACK/test/workspace.cc (revision 4bdc94577ba0e567308109d787f7fec7b531ce36)
1 // Copyright 2022 Google LLC
2 //
3 // This source code is licensed under the BSD-style license found in the
4 // LICENSE file in the root directory of this source tree.
5 
6 #include <algorithm>
7 #include <array>
8 #include <cstddef>
9 #include <cstdint>
10 #include <limits>
11 #include <memory>
12 
13 #include <xnnpack.h>
14 #include <xnnpack/subgraph.h>
15 
16 #include <gtest/gtest.h>
17 
18 namespace {
DefineGraphWithoutInternalTensors(xnn_subgraph_t * subgraph,std::array<size_t,4> dims)19 void DefineGraphWithoutInternalTensors(xnn_subgraph_t* subgraph, std::array<size_t, 4> dims)
20 {
21   xnn_create_subgraph(/*external_value_ids=*/0, /*flags=*/0, subgraph);
22   uint32_t input_id = XNN_INVALID_VALUE_ID;
23   xnn_define_tensor_value(
24     *subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, XNN_INVALID_VALUE_ID,
25     XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id);
26   ASSERT_NE(input_id, XNN_INVALID_VALUE_ID);
27 
28   uint32_t output_id = XNN_INVALID_VALUE_ID;
29   xnn_define_tensor_value(
30     *subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, XNN_INVALID_VALUE_ID,
31     XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id);
32   ASSERT_NE(output_id, XNN_INVALID_VALUE_ID);
33 
34   ASSERT_EQ(xnn_status_success, xnn_define_abs(*subgraph, input_id, output_id, /*flags=*/0));
35 }
36 
37 // Helper function to create a subgraph with 1 input, 1 output, and 1 intermediate tensor.
38 // input -> (abs) -> intermediate -> (hard swish) -> output
39 // The size of the tensors are all the same, specified by `dims`.
DefineGraph(xnn_subgraph_t * subgraph,std::array<size_t,4> dims)40 void DefineGraph(xnn_subgraph_t* subgraph, std::array<size_t, 4> dims)
41 {
42   xnn_create_subgraph(/*external_value_ids=*/0, /*flags=*/0, subgraph);
43   uint32_t input_id = XNN_INVALID_VALUE_ID;
44   xnn_define_tensor_value(
45     *subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, XNN_INVALID_VALUE_ID,
46     XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id);
47   ASSERT_NE(input_id, XNN_INVALID_VALUE_ID);
48 
49   uint32_t intermediate_id = XNN_INVALID_VALUE_ID;
50   xnn_define_tensor_value(
51     *subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, XNN_INVALID_VALUE_ID, /*flags=*/0,
52     &intermediate_id);
53   ASSERT_NE(intermediate_id, XNN_INVALID_VALUE_ID);
54 
55   uint32_t output_id = XNN_INVALID_VALUE_ID;
56   xnn_define_tensor_value(
57     *subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, XNN_INVALID_VALUE_ID,
58     XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id);
59   ASSERT_NE(output_id, XNN_INVALID_VALUE_ID);
60 
61   ASSERT_EQ(xnn_status_success, xnn_define_abs(*subgraph, input_id, intermediate_id, /*flags=*/0));
62   ASSERT_EQ(xnn_status_success, xnn_define_hardswish(*subgraph, intermediate_id, output_id, /*flags=*/0));
63 }
64 
DefineGraphWithStaticData(xnn_subgraph_t * subgraph,std::array<size_t,4> dims,const std::vector<float> * static_value)65 void DefineGraphWithStaticData(xnn_subgraph_t* subgraph, std::array<size_t, 4> dims, const std::vector<float>* static_value)
66 {
67   xnn_create_subgraph(/*external_value_ids=*/0, /*flags=*/0, subgraph);
68   uint32_t input_id = XNN_INVALID_VALUE_ID;
69   xnn_define_tensor_value(
70     *subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, XNN_INVALID_VALUE_ID,
71     XNN_VALUE_FLAG_EXTERNAL_INPUT, &input_id);
72   ASSERT_NE(input_id, XNN_INVALID_VALUE_ID);
73 
74   uint32_t static_value_id = XNN_INVALID_VALUE_ID;
75   xnn_define_tensor_value(
76     *subgraph, xnn_datatype_fp32, dims.size(), dims.data(), static_value->data(), XNN_INVALID_VALUE_ID, /*flags=*/0,
77     &static_value_id);
78   ASSERT_NE(static_value_id, XNN_INVALID_VALUE_ID);
79 
80   uint32_t output_id = XNN_INVALID_VALUE_ID;
81   xnn_define_tensor_value(
82     *subgraph, xnn_datatype_fp32, dims.size(), dims.data(), nullptr, XNN_INVALID_VALUE_ID,
83     XNN_VALUE_FLAG_EXTERNAL_OUTPUT, &output_id);
84   ASSERT_NE(output_id, XNN_INVALID_VALUE_ID);
85 
86   ASSERT_EQ(xnn_status_success,
87             xnn_define_add2(*subgraph, -std::numeric_limits<float>::infinity(),
88                             std::numeric_limits<float>::infinity(), input_id,
89                             static_value_id, output_id, /*flags=*/0));
90 }
91 
BlobInWorkspace(xnn_blob * blob,xnn_workspace_t workspace)92 testing::AssertionResult BlobInWorkspace(xnn_blob* blob, xnn_workspace_t workspace) {
93   if ((blob->data >= workspace->data) &&
94          ((uintptr_t) blob->data + blob->size) <= ((uintptr_t) workspace->data + workspace->size)) {
95     return testing::AssertionSuccess();
96   } else {
97     return testing::AssertionFailure()
98         << "blob at " << blob->data << " of size " << blob->size
99         << "is outside of workspace at " << workspace->data << " of size " << workspace->size;
100   }
101 }
102 
Contains(std::vector<xnn_runtime_t> workspace_users,xnn_runtime_t runtime)103 testing::AssertionResult Contains(std::vector<xnn_runtime_t> workspace_users, xnn_runtime_t runtime) {
104   if (std::find(workspace_users.begin(), workspace_users.end(), runtime) != workspace_users.end()) {
105     return testing::AssertionSuccess();
106   } else {
107     return testing::AssertionFailure() << "runtime " << runtime << " not found in list of workspace users";
108   }
109 }
110 
workspace_user_to_list(xnn_workspace_t workspace)111 std::vector<xnn_runtime_t> workspace_user_to_list(xnn_workspace_t workspace)
112 {
113   std::vector<xnn_runtime_t> users;
114   for (xnn_runtime_t rt = workspace->first_user; rt != NULL; rt = rt->next_workspace_user) {
115     users.push_back(rt);
116   }
117   return users;
118 }
119 }  // namespace
120 
TEST(WORKSPACE,static_data_not_moved_does_not_segv)121 TEST(WORKSPACE, static_data_not_moved_does_not_segv)
122 {
123   std::array<size_t, 4> dims = {2, 20, 20, 3};
124   size_t num_elements = dims[0] * dims[1] * dims[2] * dims[3];
125 
126   xnn_initialize(/*allocator=*/nullptr);
127   xnn_workspace_t workspace = nullptr;
128   xnn_create_workspace(&workspace);
129   std::unique_ptr<xnn_workspace, decltype(&xnn_release_workspace)> auto_workspace(workspace, xnn_release_workspace);
130 
131   // Create a graph that with static data.
132   xnn_subgraph_t subgraph1 = nullptr;
133   std::vector<float> static_data = std::vector<float>(num_elements, 1.0f);
134   DefineGraphWithStaticData(&subgraph1, dims, &static_data);
135   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph1(subgraph1, xnn_delete_subgraph);
136   xnn_runtime_t runtime1 = nullptr;
137   ASSERT_EQ(xnn_status_success, xnn_create_runtime_v4(subgraph1, nullptr, workspace, nullptr, 0, &runtime1));
138   std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime1(runtime1, xnn_delete_runtime);
139 
140   // The workspace remains at size 0, without any memory allocated, since we don't have any internal tensors.
141   size_t old_workspace_size = workspace->size;
142   ASSERT_EQ(old_workspace_size, 0);
143   void* old_runtime_workspace = runtime1->workspace->data;
144   ASSERT_EQ(old_runtime_workspace, nullptr);
145 
146   // Then create a graph that has internal tensors, we will need to resize the workspace.
147   xnn_subgraph_t subgraph2 = nullptr;
148   DefineGraph(&subgraph2, dims);
149   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph2(subgraph2, xnn_delete_subgraph);
150   xnn_runtime_t runtime2 = nullptr;
151   ASSERT_EQ(xnn_status_success, xnn_create_runtime_v4(subgraph2, nullptr, workspace, nullptr, 0, &runtime2));
152   std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime2(runtime2, xnn_delete_runtime);
153 
154   // Check that the workspace grew.
155   ASSERT_GE(workspace->size, num_elements * sizeof(float));
156   ASSERT_NE(runtime2->workspace->data, nullptr);
157 
158   // Try to access all the blobs and ensure that we don't segfault.
159   for (size_t i = 0; i < runtime1->num_blobs; i++) {
160     xnn_blob* blob = &runtime1->blobs[i];
161     if (blob->allocation_type == xnn_allocation_type_external) {
162       continue;
163     }
164     ASSERT_GT(blob->size, 0);
165     char access = *((char *)blob->data);
166     (void) access;
167   }
168 
169   for (size_t i = 0; i < runtime2->num_blobs; i++) {
170     xnn_blob* blob = &runtime2->blobs[i];
171     if (blob->allocation_type == xnn_allocation_type_external) {
172       continue;
173     }
174     ASSERT_GT(blob->size, 0);
175     char access = *((char *)blob->data);
176     (void) access;
177   }
178 }
179 
TEST(WORKSPACE,workspace_no_growth)180 TEST(WORKSPACE, workspace_no_growth)
181 {
182   xnn_initialize(/*allocator=*/nullptr);
183   xnn_workspace_t workspace = nullptr;
184   xnn_create_workspace(&workspace);
185   std::unique_ptr<xnn_workspace, decltype(&xnn_release_workspace)> auto_workspace(workspace, xnn_release_workspace);
186 
187   std::array<size_t, 4> dims = {2, 20, 20, 3};
188 
189   xnn_subgraph_t subgraph1 = nullptr;
190   DefineGraph(&subgraph1, dims);
191   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph1(subgraph1, xnn_delete_subgraph);
192 
193   xnn_runtime_t runtime1 = nullptr;
194   ASSERT_EQ(xnn_status_success, xnn_create_runtime_v4(subgraph1, nullptr, workspace, nullptr, 0, &runtime1));
195   std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime1(runtime1, xnn_delete_runtime);
196 
197   size_t old_workspace_size = workspace->size;
198   ASSERT_GE(old_workspace_size, 0);
199   void* old_runtime_workspace = runtime1->workspace->data;
200   ASSERT_NE(old_runtime_workspace, nullptr);
201 
202   // Create the same graph again with a different runtime that shares the workspace.
203   xnn_subgraph_t subgraph2 = nullptr;
204   DefineGraph(&subgraph2, dims);
205   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph2(subgraph2, xnn_delete_subgraph);
206 
207   xnn_runtime_t runtime2 = nullptr;
208   ASSERT_EQ(xnn_status_success, xnn_create_runtime_v4(subgraph2, nullptr, workspace, nullptr, 0, &runtime2));
209   std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime2(runtime2, xnn_delete_runtime);
210 
211   // Check that the workspace did not grow.
212   ASSERT_EQ(workspace->size, old_workspace_size);
213   // Check that runtime 2 uses the same workspace.
214   ASSERT_EQ(runtime2->workspace->data, old_runtime_workspace);
215 
216   ASSERT_EQ(runtime1->num_blobs, runtime2->num_blobs);
217   for (size_t i = 0; i < runtime1->num_blobs; i++) {
218     xnn_blob* blob1 = &runtime1->blobs[i];
219     if (blob1->allocation_type != xnn_allocation_type_workspace) {
220       continue;
221     }
222     ASSERT_TRUE(BlobInWorkspace(blob1, runtime1->workspace));
223     xnn_blob* blob2 = &runtime2->blobs[i];
224     ASSERT_TRUE(BlobInWorkspace(blob2, runtime2->workspace));
225   }
226 
227   std::vector<xnn_runtime_t> workspace_users = workspace_user_to_list(workspace);
228   ASSERT_EQ(workspace_users.size(), 2);
229   ASSERT_TRUE(Contains(workspace_users, runtime1));
230   ASSERT_TRUE(Contains(workspace_users, runtime2));
231   ASSERT_EQ(workspace->ref_count, 3);
232 }
233 
TEST(WORKSPACE,workspace_grow)234 TEST(WORKSPACE, workspace_grow)
235 {
236   xnn_initialize(/*allocator=*/nullptr);
237   xnn_workspace_t workspace = nullptr;
238   ASSERT_EQ(xnn_status_success, xnn_create_workspace(&workspace));
239   std::unique_ptr<xnn_workspace, decltype(&xnn_release_workspace)> auto_workspace(workspace, xnn_release_workspace);
240 
241   std::array<size_t, 4> dims1 = {2, 20, 20, 3};
242 
243   xnn_subgraph_t subgraph1 = nullptr;
244   DefineGraph(&subgraph1, dims1);
245   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph1(subgraph1, xnn_delete_subgraph);
246 
247   xnn_runtime_t runtime1 = nullptr;
248   ASSERT_EQ(xnn_status_success, xnn_create_runtime_v4(subgraph1, nullptr, workspace, nullptr, 0, &runtime1));
249   std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime1(runtime1, xnn_delete_runtime);
250 
251   size_t old_workspace_size = workspace->size;
252   ASSERT_GE(old_workspace_size, 0);
253   void* old_runtime_workspace = runtime1->workspace->data;
254   ASSERT_NE(old_runtime_workspace, nullptr);
255 
256   std::array<size_t, 4> dims2 = dims1;
257   // Create the same graph but with larger tensors, this will require a larger workspace.
258   std::transform(dims2.begin(), dims2.end(), dims2.begin(), [](size_t i) { return i * 2; });
259   xnn_subgraph_t subgraph2 = nullptr;
260   DefineGraph(&subgraph2, dims2);
261   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph2(subgraph2, xnn_delete_subgraph);
262 
263   xnn_runtime_t runtime2 = nullptr;
264   ASSERT_EQ(xnn_status_success, xnn_create_runtime_v4(subgraph2, nullptr, workspace, nullptr, 0, &runtime2));
265   std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime2(runtime2, xnn_delete_runtime);
266 
267   // Check that the workspace grew.
268   ASSERT_GE(workspace->size, old_workspace_size);
269   // Check that runtime 2 uses the same workspace.
270   ASSERT_NE(runtime2->workspace->data, old_runtime_workspace);
271   // Check that runtime1's workspace has been updated as well.
272   ASSERT_EQ(runtime1->workspace->data, runtime2->workspace->data);
273   ASSERT_EQ(runtime1->workspace->size, runtime2->workspace->size);
274 
275   // Check that both runtime's blob pointers are within range.
276   for (size_t i = 0; i < runtime1->num_blobs; i++) {
277     xnn_blob* blob = &runtime1->blobs[i];
278     if (blob->allocation_type != xnn_allocation_type_workspace) {
279       continue;
280     }
281     ASSERT_TRUE(BlobInWorkspace(blob, runtime1->workspace));
282   }
283   for (size_t i = 0; i < runtime2->num_blobs; i++) {
284     xnn_blob* blob = &runtime2->blobs[i];
285     if (blob->allocation_type != xnn_allocation_type_workspace) {
286       continue;
287     }
288     ASSERT_TRUE(BlobInWorkspace(blob, runtime2->workspace));
289   }
290 
291   std::vector<xnn_runtime_t> workspace_users = workspace_user_to_list(workspace);
292   ASSERT_EQ(workspace_users.size(), 2);
293   ASSERT_TRUE(Contains(workspace_users, runtime1));
294   ASSERT_TRUE(Contains(workspace_users, runtime2));
295   ASSERT_EQ(workspace->ref_count, 3);
296 }
297 
TEST(WORKSPACE,workspace_runtime_delete_head_runtime_first)298 TEST(WORKSPACE, workspace_runtime_delete_head_runtime_first)
299 {
300   xnn_initialize(/*allocator=*/nullptr);
301   xnn_workspace_t workspace = nullptr;
302   ASSERT_EQ(xnn_status_success, xnn_create_workspace(&workspace));
303   std::unique_ptr<xnn_workspace, decltype(&xnn_release_workspace)> auto_workspace(workspace, xnn_release_workspace);
304 
305   const std::array<size_t, 4> dims = {2, 20, 20, 3};
306 
307   xnn_subgraph_t subgraph1 = nullptr;
308   DefineGraph(&subgraph1, dims);
309   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph1(subgraph1, xnn_delete_subgraph);
310 
311   xnn_runtime_t runtime1 = nullptr;
312   ASSERT_EQ(xnn_status_success, xnn_create_runtime_v4(subgraph1, nullptr, workspace, nullptr, 0, &runtime1));
313   std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime1(runtime1, xnn_delete_runtime);
314 
315   xnn_subgraph_t subgraph2 = nullptr;
316   DefineGraph(&subgraph2, dims);
317   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph2(subgraph2, xnn_delete_subgraph);
318 
319   xnn_runtime_t runtime2 = nullptr;
320   ASSERT_EQ(xnn_status_success, xnn_create_runtime_v4(subgraph2, nullptr, workspace, nullptr, 0, &runtime2));
321   std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime2(runtime2, xnn_delete_runtime);
322 
323   ASSERT_EQ(workspace->first_user, runtime2);
324   ASSERT_EQ(runtime2->next_workspace_user, runtime1);
325   ASSERT_EQ(runtime1->next_workspace_user, nullptr);
326 
327   ASSERT_EQ(workspace->ref_count, 3);
328   xnn_delete_runtime(auto_runtime2.release());
329   ASSERT_EQ(workspace->first_user, runtime1);
330   ASSERT_EQ(runtime1->next_workspace_user, nullptr);
331   ASSERT_EQ(workspace->ref_count, 2);
332 
333   xnn_delete_runtime(auto_runtime1.release());
334   ASSERT_EQ(workspace->first_user, nullptr);
335   ASSERT_EQ(workspace->ref_count, 1);
336 }
337 
TEST(WORKSPACE,workspace_runtime_delete_tail_runtime_first)338 TEST(WORKSPACE, workspace_runtime_delete_tail_runtime_first)
339 {
340   xnn_initialize(/*allocator=*/nullptr);
341   xnn_workspace_t workspace = nullptr;
342   ASSERT_EQ(xnn_status_success, xnn_create_workspace(&workspace));
343   std::unique_ptr<xnn_workspace, decltype(&xnn_release_workspace)> auto_workspace(workspace, xnn_release_workspace);
344 
345   std::array<size_t, 4> dims = {2, 20, 20, 3};
346 
347   xnn_subgraph_t subgraph1 = nullptr;
348   DefineGraph(&subgraph1, dims);
349   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph1(subgraph1, xnn_delete_subgraph);
350 
351   xnn_runtime_t runtime1 = nullptr;
352   ASSERT_EQ(xnn_status_success, xnn_create_runtime_v4(subgraph1, nullptr, workspace, nullptr, 0, &runtime1));
353   std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime1(runtime1, xnn_delete_runtime);
354 
355   xnn_subgraph_t subgraph2 = nullptr;
356   DefineGraph(&subgraph2, dims);
357   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph2(subgraph2, xnn_delete_subgraph);
358 
359   xnn_runtime_t runtime2 = nullptr;
360   ASSERT_EQ(xnn_status_success, xnn_create_runtime_v4(subgraph2, nullptr, workspace, nullptr, 0, &runtime2));
361   std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime2(runtime2, xnn_delete_runtime);
362 
363   ASSERT_EQ(workspace->first_user, runtime2);
364   ASSERT_EQ(runtime2->next_workspace_user, runtime1);
365   ASSERT_EQ(runtime1->next_workspace_user, nullptr);
366 
367   ASSERT_EQ(workspace->ref_count, 3);
368   xnn_delete_runtime(auto_runtime1.release());
369 
370   ASSERT_EQ(workspace->first_user, runtime2);
371   ASSERT_EQ(runtime2->next_workspace_user, nullptr);
372   ASSERT_EQ(workspace->ref_count, 2);
373 
374   xnn_delete_runtime(auto_runtime2.release());
375   ASSERT_EQ(workspace->first_user, nullptr);
376   ASSERT_EQ(workspace->ref_count, 1);
377 }
378 
TEST(WORKSPACE,workspace_runtime_delete_middle_runtime_first)379 TEST(WORKSPACE, workspace_runtime_delete_middle_runtime_first)
380 {
381   xnn_initialize(/*allocator=*/nullptr);
382   xnn_workspace_t workspace = nullptr;
383   ASSERT_EQ(xnn_status_success, xnn_create_workspace(&workspace));
384   std::unique_ptr<xnn_workspace, decltype(&xnn_release_workspace)> auto_workspace(workspace, xnn_release_workspace);
385 
386   std::array<size_t, 4> dims = {2, 20, 20, 3};
387 
388   xnn_subgraph_t subgraph1 = nullptr;
389   DefineGraph(&subgraph1, dims);
390   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph1(subgraph1, xnn_delete_subgraph);
391 
392   xnn_runtime_t runtime1 = nullptr;
393   ASSERT_EQ(xnn_status_success, xnn_create_runtime_v4(subgraph1, nullptr, workspace, nullptr, 0, &runtime1));
394   std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime1(runtime1, xnn_delete_runtime);
395 
396   xnn_subgraph_t subgraph2 = nullptr;
397   DefineGraph(&subgraph2, dims);
398   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph2(subgraph2, xnn_delete_subgraph);
399 
400   xnn_runtime_t runtime2 = nullptr;
401   ASSERT_EQ(xnn_status_success, xnn_create_runtime_v4(subgraph2, nullptr, workspace, nullptr, 0, &runtime2));
402   std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime2(runtime2, xnn_delete_runtime);
403 
404   xnn_subgraph_t subgraph3 = nullptr;
405   DefineGraph(&subgraph3, dims);
406   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph3(subgraph3, xnn_delete_subgraph);
407 
408   xnn_runtime_t runtime3 = nullptr;
409   ASSERT_EQ(xnn_status_success, xnn_create_runtime_v4(subgraph3, nullptr, workspace, nullptr, 0, &runtime3));
410   std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime3(runtime3, xnn_delete_runtime);
411 
412   ASSERT_EQ(workspace->first_user, runtime3);
413   ASSERT_EQ(runtime3->next_workspace_user, runtime2);
414   ASSERT_EQ(runtime2->next_workspace_user, runtime1);
415   ASSERT_EQ(runtime1->next_workspace_user, nullptr);
416 
417   ASSERT_EQ(workspace->ref_count, 4);
418   xnn_delete_runtime(auto_runtime2.release());
419 
420   ASSERT_EQ(workspace->first_user, runtime3);
421   ASSERT_EQ(runtime3->next_workspace_user, runtime1);
422   ASSERT_EQ(runtime1->next_workspace_user, nullptr);
423   ASSERT_EQ(workspace->ref_count, 3);
424 
425   xnn_delete_runtime(auto_runtime3.release());
426   ASSERT_EQ(workspace->first_user, runtime1);
427   ASSERT_EQ(runtime1->next_workspace_user, nullptr);
428   ASSERT_EQ(workspace->ref_count, 2);
429 
430   xnn_delete_runtime(auto_runtime1.release());
431   ASSERT_EQ(workspace->first_user, nullptr);
432   ASSERT_EQ(workspace->ref_count, 1);
433 }
434 
TEST(WORKSPACE,zero_sized_workspace_for_graph_without_internal_tensors)435 TEST(WORKSPACE, zero_sized_workspace_for_graph_without_internal_tensors)
436 {
437   xnn_initialize(/*allocator=*/nullptr);
438   xnn_workspace_t workspace = nullptr;
439   xnn_create_workspace(&workspace);
440   std::unique_ptr<xnn_workspace, decltype(&xnn_release_workspace)> auto_workspace(workspace, xnn_release_workspace);
441 
442   std::array<size_t, 4> dims = {2, 20, 20, 3};
443 
444   xnn_subgraph_t subgraph = nullptr;
445   DefineGraphWithoutInternalTensors(&subgraph, dims);
446   std::unique_ptr<xnn_subgraph, decltype(&xnn_delete_subgraph)> auto_subgraph(subgraph, xnn_delete_subgraph);
447 
448   xnn_runtime_t runtime = nullptr;
449   ASSERT_EQ(xnn_status_success, xnn_create_runtime_v4(subgraph, nullptr, workspace, nullptr, 0, &runtime));
450   std::unique_ptr<xnn_runtime, decltype(&xnn_delete_runtime)> auto_runtime(runtime, xnn_delete_runtime);
451 
452   ASSERT_EQ(0, workspace->size);
453   ASSERT_EQ(nullptr, workspace->data);
454   ASSERT_EQ(std::vector<xnn_runtime_t>({runtime}), workspace_user_to_list(workspace));
455   ASSERT_EQ(workspace->ref_count, 2);
456 }
457