Skip to content

Commit c26d64a

Browse files
committed
ENH: add() to API standard
* bring the `add()` ufunc up to the API standard and turn the relevant test on in the CI * other changes are similar to other recent binary ufunc PRs; note one drawback related to copies in broadcasting * the changes are similar to those in kokkosgh-156, though more workunit type implementation were needed for this operation to pass the standard test
1 parent ffd607c commit c26d64a

File tree

4 files changed

+428
-35
lines changed

4 files changed

+428
-35
lines changed

.github/workflows/array_api.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
cd /tmp
3131
git clone https://github.com/kokkos/pykokkos-base.git
3232
cd pykokkos-base
33-
python setup.py install -- -DENABLE_LAYOUTS=ON -DENABLE_MEMORY_TRAITS=OFF
33+
python setup.py install -- -DENABLE_LAYOUTS=ON -DENABLE_MEMORY_TRAITS=OFF -DENABLE_VIEW_RANKS=5
3434
- name: Install pykokkos
3535
run: |
3636
python -m pip install .
@@ -49,4 +49,4 @@ jobs:
4949
# for hypothesis-driven test case generation
5050
pytest $GITHUB_WORKSPACE/pre_compile_tools/pre_compile_ufuncs.py -s
5151
# only run a subset of the conformance tests to get started
52-
pytest array_api_tests/meta/test_broadcasting.py array_api_tests/meta/test_equality_mapping.py array_api_tests/meta/test_signatures.py array_api_tests/meta/test_special_cases.py array_api_tests/test_constants.py array_api_tests/meta/test_utils.py array_api_tests/test_creation_functions.py::test_ones array_api_tests/test_creation_functions.py::test_ones_like array_api_tests/test_data_type_functions.py::test_result_type array_api_tests/test_operators_and_elementwise_functions.py::test_log10 array_api_tests/test_operators_and_elementwise_functions.py::test_sqrt array_api_tests/test_operators_and_elementwise_functions.py::test_isfinite array_api_tests/test_operators_and_elementwise_functions.py::test_log2 array_api_tests/test_operators_and_elementwise_functions.py::test_log1p array_api_tests/test_operators_and_elementwise_functions.py::test_isinf array_api_tests/test_operators_and_elementwise_functions.py::test_log array_api_tests/test_array_object.py::test_scalar_casting array_api_tests/test_operators_and_elementwise_functions.py::test_sign array_api_tests/test_operators_and_elementwise_functions.py::test_square array_api_tests/test_operators_and_elementwise_functions.py::test_cos array_api_tests/test_operators_and_elementwise_functions.py::test_round array_api_tests/test_operators_and_elementwise_functions.py::test_trunc array_api_tests/test_operators_and_elementwise_functions.py::test_ceil array_api_tests/test_operators_and_elementwise_functions.py::test_floor
52+
pytest array_api_tests/meta/test_broadcasting.py array_api_tests/meta/test_equality_mapping.py array_api_tests/meta/test_signatures.py array_api_tests/meta/test_special_cases.py array_api_tests/test_constants.py array_api_tests/meta/test_utils.py array_api_tests/test_creation_functions.py::test_ones array_api_tests/test_creation_functions.py::test_ones_like array_api_tests/test_data_type_functions.py::test_result_type array_api_tests/test_operators_and_elementwise_functions.py::test_log10 array_api_tests/test_operators_and_elementwise_functions.py::test_sqrt array_api_tests/test_operators_and_elementwise_functions.py::test_isfinite array_api_tests/test_operators_and_elementwise_functions.py::test_log2 array_api_tests/test_operators_and_elementwise_functions.py::test_log1p array_api_tests/test_operators_and_elementwise_functions.py::test_isinf array_api_tests/test_operators_and_elementwise_functions.py::test_log array_api_tests/test_array_object.py::test_scalar_casting array_api_tests/test_operators_and_elementwise_functions.py::test_sign array_api_tests/test_operators_and_elementwise_functions.py::test_square array_api_tests/test_operators_and_elementwise_functions.py::test_cos array_api_tests/test_operators_and_elementwise_functions.py::test_round array_api_tests/test_operators_and_elementwise_functions.py::test_trunc array_api_tests/test_operators_and_elementwise_functions.py::test_ceil array_api_tests/test_operators_and_elementwise_functions.py::test_floor "array_api_tests/test_operators_and_elementwise_functions.py::test_add[add(x1, x2)]"

.github/workflows/main_ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ jobs:
3030
cd /tmp
3131
git clone https://github.com/kokkos/pykokkos-base.git
3232
cd pykokkos-base
33-
python setup.py install -- -DENABLE_LAYOUTS=ON -DENABLE_MEMORY_TRAITS=OFF
33+
python setup.py install -- -DENABLE_LAYOUTS=ON -DENABLE_MEMORY_TRAITS=OFF -DENABLE_VIEW_RANKS=5
3434
- name: Install pykokkos
3535
run: |
3636
python -m pip install .

pykokkos/lib/ufunc_workunits.py

Lines changed: 352 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,363 @@
11
import pykokkos as pk
22

33

4+
@pk.workunit
5+
def add_impl_1d_int64(tid: int, view1: pk.View1D[pk.int64], view2: pk.View1D[pk.int64], out: pk.View1D[pk.int64]):
6+
out[tid] = view1[tid] + view2[tid]
7+
8+
9+
@pk.workunit
10+
def add_impl_2d_int64(tid: int, view1: pk.View2D[pk.int64], view2: pk.View2D[pk.int64], out: pk.View2D[pk.int64]):
11+
for i in range(view1.extent(1)):
12+
out[tid][i] = view1[tid][i] + view2[tid][i]
13+
14+
15+
@pk.workunit
16+
def add_impl_3d_int64(tid: int, view1: pk.View3D[pk.int64], view2: pk.View3D[pk.int64], out: pk.View3D[pk.int64]):
17+
for i in range(view1.extent(1)):
18+
for j in range(view1.extent(2)):
19+
out[tid][i][j] = view1[tid][i][j] + view2[tid][i][j]
20+
21+
22+
@pk.workunit
23+
def add_impl_4d_int64(tid: int, view1: pk.View4D[pk.int64], view2: pk.View4D[pk.int64], out: pk.View4D[pk.int64]):
24+
for i in range(view1.extent(1)):
25+
for j in range(view1.extent(2)):
26+
for k in range(view1.extent(3)):
27+
out[tid][i][j][k] = view1[tid][i][j][k] + view2[tid][i][j][k]
28+
29+
30+
@pk.workunit
31+
def add_impl_5d_int64(tid: int, view1: pk.View5D[pk.int64], view2: pk.View5D[pk.int64], out: pk.View5D[pk.int64]):
32+
for i in range(view1.extent(1)):
33+
for j in range(view1.extent(2)):
34+
for k in range(view1.extent(3)):
35+
for l in range(view1.extent(4)):
36+
out[tid][i][j][k][l] = view1[tid][i][j][k][l] + view2[tid][i][j][k][l]
37+
38+
39+
@pk.workunit
40+
def add_impl_1d_int32(tid: int, view1: pk.View1D[pk.int32], view2: pk.View1D[pk.int32], out: pk.View1D[pk.int32]):
41+
out[tid] = view1[tid] + view2[tid]
42+
43+
44+
@pk.workunit
45+
def add_impl_2d_int32(tid: int, view1: pk.View2D[pk.int32], view2: pk.View2D[pk.int32], out: pk.View2D[pk.int32]):
46+
for i in range(view1.extent(1)):
47+
out[tid][i] = view1[tid][i] + view2[tid][i]
48+
49+
50+
@pk.workunit
51+
def add_impl_3d_int32(tid: int, view1: pk.View3D[pk.int32], view2: pk.View3D[pk.int32], out: pk.View3D[pk.int32]):
52+
for i in range(view1.extent(1)):
53+
for j in range(view1.extent(2)):
54+
out[tid][i][j] = view1[tid][i][j] + view2[tid][i][j]
55+
56+
57+
@pk.workunit
58+
def add_impl_4d_int32(tid: int, view1: pk.View4D[pk.int32], view2: pk.View4D[pk.int32], out: pk.View4D[pk.int32]):
59+
for i in range(view1.extent(1)):
60+
for j in range(view1.extent(2)):
61+
for k in range(view1.extent(3)):
62+
out[tid][i][j][k] = view1[tid][i][j][k] + view2[tid][i][j][k]
63+
64+
65+
@pk.workunit
66+
def add_impl_5d_int32(tid: int, view1: pk.View5D[pk.int32], view2: pk.View5D[pk.int32], out: pk.View5D[pk.int32]):
67+
for i in range(view1.extent(1)):
68+
for j in range(view1.extent(2)):
69+
for k in range(view1.extent(3)):
70+
for l in range(view1.extent(4)):
71+
out[tid][i][j][k][l] = view1[tid][i][j][k][l] + view2[tid][i][j][k][l]
72+
73+
74+
@pk.workunit
75+
def add_impl_1d_int16(tid: int, view1: pk.View1D[pk.int16], view2: pk.View1D[pk.int16], out: pk.View1D[pk.int16]):
76+
out[tid] = view1[tid] + view2[tid]
77+
78+
79+
@pk.workunit
80+
def add_impl_2d_int16(tid: int, view1: pk.View2D[pk.int16], view2: pk.View2D[pk.int16], out: pk.View2D[pk.int16]):
81+
for i in range(view1.extent(1)):
82+
out[tid][i] = view1[tid][i] + view2[tid][i]
83+
84+
85+
@pk.workunit
86+
def add_impl_3d_int16(tid: int, view1: pk.View3D[pk.int16], view2: pk.View3D[pk.int16], out: pk.View3D[pk.int16]):
87+
for i in range(view1.extent(1)):
88+
for j in range(view1.extent(2)):
89+
out[tid][i][j] = view1[tid][i][j] + view2[tid][i][j]
90+
91+
92+
@pk.workunit
93+
def add_impl_4d_int16(tid: int, view1: pk.View4D[pk.int16], view2: pk.View4D[pk.int16], out: pk.View4D[pk.int16]):
94+
for i in range(view1.extent(1)):
95+
for j in range(view1.extent(2)):
96+
for k in range(view1.extent(3)):
97+
out[tid][i][j][k] = view1[tid][i][j][k] + view2[tid][i][j][k]
98+
99+
100+
@pk.workunit
101+
def add_impl_5d_int16(tid: int, view1: pk.View5D[pk.int16], view2: pk.View5D[pk.int16], out: pk.View5D[pk.int16]):
102+
for i in range(view1.extent(1)):
103+
for j in range(view1.extent(2)):
104+
for k in range(view1.extent(3)):
105+
for l in range(view1.extent(4)):
106+
out[tid][i][j][k][l] = view1[tid][i][j][k][l] + view2[tid][i][j][k][l]
107+
108+
109+
@pk.workunit
110+
def add_impl_1d_int8(tid: int, view1: pk.View1D[pk.int8], view2: pk.View1D[pk.int8], out: pk.View1D[pk.int8]):
111+
out[tid] = view1[tid] + view2[tid]
112+
113+
114+
@pk.workunit
115+
def add_impl_2d_int8(tid: int, view1: pk.View2D[pk.int8], view2: pk.View2D[pk.int8], out: pk.View2D[pk.int8]):
116+
for i in range(view1.extent(1)):
117+
out[tid][i] = view1[tid][i] + view2[tid][i]
118+
119+
120+
@pk.workunit
121+
def add_impl_3d_int8(tid: int, view1: pk.View3D[pk.int8], view2: pk.View3D[pk.int8], out: pk.View3D[pk.int8]):
122+
for i in range(view1.extent(1)):
123+
for j in range(view1.extent(2)):
124+
out[tid][i][j] = view1[tid][i][j] + view2[tid][i][j]
125+
126+
127+
@pk.workunit
128+
def add_impl_4d_int8(tid: int, view1: pk.View4D[pk.int8], view2: pk.View4D[pk.int8], out: pk.View4D[pk.int8]):
129+
for i in range(view1.extent(1)):
130+
for j in range(view1.extent(2)):
131+
for k in range(view1.extent(3)):
132+
out[tid][i][j][k] = view1[tid][i][j][k] + view2[tid][i][j][k]
133+
134+
135+
@pk.workunit
136+
def add_impl_5d_int8(tid: int, view1: pk.View5D[pk.int8], view2: pk.View5D[pk.int8], out: pk.View5D[pk.int8]):
137+
for i in range(view1.extent(1)):
138+
for j in range(view1.extent(2)):
139+
for k in range(view1.extent(3)):
140+
for l in range(view1.extent(4)):
141+
out[tid][i][j][k][l] = view1[tid][i][j][k][l] + view2[tid][i][j][k][l]
142+
143+
144+
@pk.workunit
145+
def add_impl_1d_uint64(tid: int, view1: pk.View1D[pk.uint64], view2: pk.View1D[pk.uint64], out: pk.View1D[pk.uint64]):
146+
out[tid] = view1[tid] + view2[tid]
147+
148+
149+
@pk.workunit
150+
def add_impl_2d_uint64(tid: int, view1: pk.View2D[pk.uint64], view2: pk.View2D[pk.uint64], out: pk.View2D[pk.uint64]):
151+
for i in range(view1.extent(1)):
152+
out[tid][i] = view1[tid][i] + view2[tid][i]
153+
154+
155+
@pk.workunit
156+
def add_impl_3d_uint64(tid: int, view1: pk.View3D[pk.uint64], view2: pk.View3D[pk.uint64], out: pk.View3D[pk.uint64]):
157+
for i in range(view1.extent(1)):
158+
for j in range(view1.extent(2)):
159+
out[tid][i][j] = view1[tid][i][j] + view2[tid][i][j]
160+
161+
162+
@pk.workunit
163+
def add_impl_4d_uint64(tid: int, view1: pk.View4D[pk.uint64], view2: pk.View4D[pk.uint64], out: pk.View4D[pk.uint64]):
164+
for i in range(view1.extent(1)):
165+
for j in range(view1.extent(2)):
166+
for k in range(view1.extent(3)):
167+
out[tid][i][j][k] = view1[tid][i][j][k] + view2[tid][i][j][k]
168+
169+
170+
@pk.workunit
171+
def add_impl_5d_uint64(tid: int, view1: pk.View5D[pk.uint64], view2: pk.View5D[pk.uint64], out: pk.View5D[pk.uint64]):
172+
for i in range(view1.extent(1)):
173+
for j in range(view1.extent(2)):
174+
for k in range(view1.extent(3)):
175+
for l in range(view1.extent(4)):
176+
out[tid][i][j][k][l] = view1[tid][i][j][k][l] + view2[tid][i][j][k][l]
177+
178+
179+
@pk.workunit
180+
def add_impl_1d_uint32(tid: int, view1: pk.View1D[pk.uint32], view2: pk.View1D[pk.uint32], out: pk.View1D[pk.uint32]):
181+
out[tid] = view1[tid] + view2[tid]
182+
183+
184+
@pk.workunit
185+
def add_impl_2d_uint32(tid: int, view1: pk.View2D[pk.uint32], view2: pk.View2D[pk.uint32], out: pk.View2D[pk.uint32]):
186+
for i in range(view1.extent(1)):
187+
out[tid][i] = view1[tid][i] + view2[tid][i]
188+
189+
190+
@pk.workunit
191+
def add_impl_3d_uint32(tid: int, view1: pk.View3D[pk.uint32], view2: pk.View3D[pk.uint32], out: pk.View3D[pk.uint32]):
192+
for i in range(view1.extent(1)):
193+
for j in range(view1.extent(2)):
194+
out[tid][i][j] = view1[tid][i][j] + view2[tid][i][j]
195+
196+
197+
@pk.workunit
198+
def add_impl_4d_uint32(tid: int, view1: pk.View4D[pk.uint32], view2: pk.View4D[pk.uint32], out: pk.View4D[pk.uint32]):
199+
for i in range(view1.extent(1)):
200+
for j in range(view1.extent(2)):
201+
for k in range(view1.extent(3)):
202+
out[tid][i][j][k] = view1[tid][i][j][k] + view2[tid][i][j][k]
203+
204+
205+
@pk.workunit
206+
def add_impl_5d_uint32(tid: int, view1: pk.View5D[pk.uint32], view2: pk.View5D[pk.uint32], out: pk.View5D[pk.uint32]):
207+
for i in range(view1.extent(1)):
208+
for j in range(view1.extent(2)):
209+
for k in range(view1.extent(3)):
210+
for l in range(view1.extent(4)):
211+
out[tid][i][j][k][l] = view1[tid][i][j][k][l] + view2[tid][i][j][k][l]
212+
213+
214+
@pk.workunit
215+
def add_impl_1d_uint16(tid: int, view1: pk.View1D[pk.uint16], view2: pk.View1D[pk.uint16], out: pk.View1D[pk.uint16]):
216+
out[tid] = view1[tid] + view2[tid]
217+
218+
219+
@pk.workunit
220+
def add_impl_2d_uint16(tid: int, view1: pk.View2D[pk.uint16], view2: pk.View2D[pk.uint16], out: pk.View2D[pk.uint16]):
221+
for i in range(view1.extent(1)):
222+
out[tid][i] = view1[tid][i] + view2[tid][i]
223+
224+
225+
@pk.workunit
226+
def add_impl_3d_uint16(tid: int, view1: pk.View3D[pk.uint16], view2: pk.View3D[pk.uint16], out: pk.View3D[pk.uint16]):
227+
for i in range(view1.extent(1)):
228+
for j in range(view1.extent(2)):
229+
out[tid][i][j] = view1[tid][i][j] + view2[tid][i][j]
230+
231+
232+
@pk.workunit
233+
def add_impl_4d_uint16(tid: int, view1: pk.View4D[pk.uint16], view2: pk.View4D[pk.uint16], out: pk.View4D[pk.uint16]):
234+
for i in range(view1.extent(1)):
235+
for j in range(view1.extent(2)):
236+
for k in range(view1.extent(3)):
237+
out[tid][i][j][k] = view1[tid][i][j][k] + view2[tid][i][j][k]
238+
239+
240+
@pk.workunit
241+
def add_impl_5d_uint16(tid: int, view1: pk.View5D[pk.uint16], view2: pk.View5D[pk.uint16], out: pk.View5D[pk.uint16]):
242+
for i in range(view1.extent(1)):
243+
for j in range(view1.extent(2)):
244+
for k in range(view1.extent(3)):
245+
for l in range(view1.extent(4)):
246+
out[tid][i][j][k][l] = view1[tid][i][j][k][l] + view2[tid][i][j][k][l]
247+
248+
249+
@pk.workunit
250+
def add_impl_1d_uint8(tid: int, view1: pk.View1D[pk.uint8], view2: pk.View1D[pk.uint8], out: pk.View1D[pk.uint8]):
251+
out[tid] = view1[tid] + view2[tid]
252+
253+
254+
@pk.workunit
255+
def add_impl_2d_uint8(tid: int, view1: pk.View2D[pk.uint8], view2: pk.View2D[pk.uint8], out: pk.View2D[pk.uint8]):
256+
for i in range(view1.extent(1)):
257+
out[tid][i] = view1[tid][i] + view2[tid][i]
258+
259+
260+
@pk.workunit
261+
def add_impl_3d_uint8(tid: int, view1: pk.View3D[pk.uint8], view2: pk.View3D[pk.uint8], out: pk.View3D[pk.uint8]):
262+
for i in range(view1.extent(1)):
263+
for j in range(view1.extent(2)):
264+
out[tid][i][j] = view1[tid][i][j] + view2[tid][i][j]
265+
266+
267+
@pk.workunit
268+
def add_impl_4d_uint8(tid: int, view1: pk.View4D[pk.uint8], view2: pk.View4D[pk.uint8], out: pk.View4D[pk.uint8]):
269+
for i in range(view1.extent(1)):
270+
for j in range(view1.extent(2)):
271+
for k in range(view1.extent(3)):
272+
out[tid][i][j][k] = view1[tid][i][j][k] + view2[tid][i][j][k]
273+
274+
275+
@pk.workunit
276+
def add_impl_5d_uint8(tid: int, view1: pk.View5D[pk.uint8], view2: pk.View5D[pk.uint8], out: pk.View5D[pk.uint8]):
277+
for i in range(view1.extent(1)):
278+
for j in range(view1.extent(2)):
279+
for k in range(view1.extent(3)):
280+
for l in range(view1.extent(4)):
281+
out[tid][i][j][k][l] = view1[tid][i][j][k][l] + view2[tid][i][j][k][l]
282+
283+
284+
@pk.workunit
285+
def add_impl_1d_float(tid: int, view1: pk.View1D[pk.float], view2: pk.View1D[pk.float], out: pk.View1D[pk.float]):
286+
out[tid] = view1[tid] + view2[tid]
287+
288+
289+
@pk.workunit
290+
def add_impl_2d_float(tid: int, view1: pk.View2D[pk.float], view2: pk.View2D[pk.float], out: pk.View2D[pk.float]):
291+
for i in range(view1.extent(1)):
292+
out[tid][i] = view1[tid][i] + view2[tid][i]
293+
294+
295+
@pk.workunit
296+
def add_impl_3d_float(tid: int, view1: pk.View3D[pk.float], view2: pk.View3D[pk.float], out: pk.View3D[pk.float]):
297+
for i in range(view1.extent(1)):
298+
for j in range(view1.extent(2)):
299+
out[tid][i][j] = view1[tid][i][j] + view2[tid][i][j]
300+
301+
302+
@pk.workunit
303+
def add_impl_4d_float(tid: int, view1: pk.View4D[pk.float], view2: pk.View4D[pk.float], out: pk.View4D[pk.float]):
304+
for i in range(view1.extent(1)):
305+
for j in range(view1.extent(2)):
306+
for k in range(view1.extent(3)):
307+
out[tid][i][j][k] = view1[tid][i][j][k] + view2[tid][i][j][k]
308+
309+
310+
@pk.workunit
311+
def add_impl_5d_float(tid: int, view1: pk.View5D[pk.float], view2: pk.View5D[pk.float], out: pk.View5D[pk.float]):
312+
for i in range(view1.extent(1)):
313+
for j in range(view1.extent(2)):
314+
for k in range(view1.extent(3)):
315+
for l in range(view1.extent(4)):
316+
out[tid][i][j][k][l] = view1[tid][i][j][k][l] + view2[tid][i][j][k][l]
317+
318+
319+
@pk.workunit
320+
def add_impl_1d_double(tid: int, view1: pk.View1D[pk.double], view2: pk.View1D[pk.double], out: pk.View1D[pk.double]):
321+
out[tid] = view1[tid] + view2[tid]
322+
323+
324+
@pk.workunit
325+
def add_impl_2d_double(tid: int, view1: pk.View2D[pk.double], view2: pk.View2D[pk.double], out: pk.View2D[pk.double]):
326+
for i in range(view1.extent(1)):
327+
out[tid][i] = view1[tid][i] + view2[tid][i]
328+
329+
330+
@pk.workunit
331+
def add_impl_3d_double(tid: int, view1: pk.View3D[pk.double], view2: pk.View3D[pk.double], out: pk.View3D[pk.double]):
332+
for i in range(view1.extent(1)):
333+
for j in range(view1.extent(2)):
334+
out[tid][i][j] = view1[tid][i][j] + view2[tid][i][j]
335+
336+
337+
@pk.workunit
338+
def add_impl_4d_double(tid: int, view1: pk.View4D[pk.double], view2: pk.View4D[pk.double], out: pk.View4D[pk.double]):
339+
for i in range(view1.extent(1)):
340+
for j in range(view1.extent(2)):
341+
for k in range(view1.extent(3)):
342+
out[tid][i][j][k] = view1[tid][i][j][k] + view2[tid][i][j][k]
343+
344+
345+
@pk.workunit
346+
def add_impl_5d_double(tid: int, view1: pk.View5D[pk.double], view2: pk.View5D[pk.double], out: pk.View5D[pk.double]):
347+
for i in range(view1.extent(1)):
348+
for j in range(view1.extent(2)):
349+
for k in range(view1.extent(3)):
350+
for l in range(view1.extent(4)):
351+
out[tid][i][j][k][l] = view1[tid][i][j][k][l] + view2[tid][i][j][k][l]
352+
353+
4354
@pk.workunit
5355
def floor_impl_1d_double(tid: int, view: pk.View1D[pk.double], out: pk.View1D[pk.double]):
6356
out[tid] = floor(view[tid])
7357

8358

359+
360+
9361
@pk.workunit
10362
def floor_impl_2d_double(tid: int, view: pk.View2D[pk.double], out: pk.View2D[pk.double]):
11363
for i in range(view.extent(1)):

0 commit comments

Comments
 (0)