GCC Code Coverage Report


Directory: ./
Coverage: low: ≥ 0% medium: ≥ 75.0% high: ≥ 90.0%
Coverage Exec / Excl / Total
Lines: 95.1% 77 / 0 / 81
Functions: 100.0% 5 / 0 / 5
Branches: 51.2% 44 / 0 / 86

src/cpu/slice2d.cpp
Line Branch Exec Source
1 // ─── CPU 2D spatial slice / crop ────────────────────────────────────────────
2 //
3 // FP32 scalar host implementation. Extracts the (H_out, W_out) sub-region
4 // of an NCHW tensor starting at (h0, w0); N and C pass through unchanged.
5 // This is the "crop" half of the pad2d / slice2d pair — together they are
6 // inverse operations when pad2d uses zero mode.
7 //
8 // Memory layout (NCHW flat):
9 // X / dX : ((n*C + c)*H + h)*W + w
10 // Y / dY : ((n*C + c)*H_out + h)*W_out + w
11 //
12 // ── ACCUMULATION ────────────────────────────────────────────────────────────
13 // slice2d_forward — Y OVERWRITTEN (straight copy).
14 // slice2d_backward — dX OVERWRITTEN (zeroed, then dY copied into the slice
15 // region). The forward is a pure read so the adjoint
16 // has no aliasing; this is just a memset+memcpy in
17 // disguise.
18
19 #include <brotensor/tensor.h>
20
21 #include <cstring>
22 #include <stdexcept>
23 #include <string>
24
25 namespace brotensor::detail::cpu {
26
27 namespace {
28
29 3 [[noreturn]] inline void fail(const char* op, const std::string& reason) {
30
7/14
✓ Branch 0 taken 3 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 3 times.
✗ Branch 3 not taken.
✓ Branch 4 taken 3 times.
✗ Branch 5 not taken.
✓ Branch 6 taken 3 times.
✗ Branch 7 not taken.
✓ Branch 8 taken 3 times.
✗ Branch 9 not taken.
✗ Branch 10 not taken.
✓ Branch 11 taken 3 times.
✓ Branch 12 taken 3 times.
✗ Branch 13 not taken.
3 throw std::runtime_error(std::string("brotensor: ") + op + ": " + reason);
31 3 }
32
33 13 inline void check_fp32(const ::brotensor::Tensor& t,
34 const char* op, const char* name) {
35
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 if (t.dtype != Dtype::FP32) {
36 fail(op, std::string(name) +
37 " must be FP32 (CPU backend is FP32-only)");
38 }
39 13 }
40
41 13 inline void check_args(const char* op,
42 int N, int C, int H, int W,
43 int h0, int w0, int H_out, int W_out) {
44
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 if (N < 0 || C < 1 || H < 0 || W < 0) {
45
0/2
✗ Branch 0 not taken.
✗ Branch 1 not taken.
3 fail(op, "C must be >=1; N, H, W must be >=0");
46 }
47
1/2
✓ Branch 0 taken 13 times.
✗ Branch 1 not taken.
13 if (H_out < 0 || W_out < 0) {
48 fail(op, "H_out and W_out must be >=0");
49 }
50
2/2
✓ Branch 0 taken 12 times.
✓ Branch 1 taken 1 time.
13 if (h0 < 0 || w0 < 0) {
51
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 fail(op, "h0 and w0 must be >=0");
52 }
53
2/2
✓ Branch 0 taken 11 times.
✓ Branch 1 taken 1 time.
12 if (h0 + H_out > H) {
54
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 fail(op, "h0 + H_out must be <= H");
55 }
56
2/2
✓ Branch 0 taken 10 times.
✓ Branch 1 taken 1 time.
11 if (w0 + W_out > W) {
57
1/2
✗ Branch 0 not taken.
✓ Branch 1 taken 1 time.
1 fail(op, "w0 + W_out must be <= W");
58 }
59 13 }
60
61 } // namespace
62
63 // ─── slice2d_forward ───────────────────────────────────────────────────────
64
65 6 void slice2d_forward(const ::brotensor::Tensor& X,
66 int N, int C, int H, int W,
67 int h0, int w0, int H_out, int W_out,
68 ::brotensor::Tensor& Y) {
69 6 const char* op = "slice2d_forward";
70 6 check_fp32(X, op, "X");
71 6 check_args(op, N, C, H, W, h0, w0, H_out, W_out);
72
1/2
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
6 if (X.rows != N || X.cols != C * H * W) {
73 fail(op, "X shape must be (N, C*H*W)");
74 }
75 6 const int cols_out = C * H_out * W_out;
76
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 6 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
6 if (Y.rows != N || Y.cols != cols_out || Y.dtype != Dtype::FP32) {
77 6 Y.resize(N, cols_out, Dtype::FP32);
78 6 }
79
3/4
✓ Branch 0 taken 6 times.
✗ Branch 1 not taken.
✓ Branch 2 taken 1 time.
✓ Branch 3 taken 5 times.
6 if (N == 0 || cols_out == 0) return;
80
81 5 const float* Xp = X.host_f32();
82 5 float* Yp = Y.host_f32_mut();
83
84
2/2
✓ Branch 0 taken 5 times.
✓ Branch 1 taken 8 times.
13 for (int n = 0; n < N; ++n) {
85
2/2
✓ Branch 0 taken 22 times.
✓ Branch 1 taken 8 times.
30 for (int c = 0; c < C; ++c) {
86 22 const float* x_chan =
87 22 Xp + (static_cast<long>(n) * C + c) * H * W;
88 22 float* y_chan =
89 22 Yp + (static_cast<long>(n) * C + c) * H_out * W_out;
90
2/2
✓ Branch 0 taken 84 times.
✓ Branch 1 taken 22 times.
106 for (int h = 0; h < H_out; ++h) {
91 84 const float* x_row =
92 84 x_chan + static_cast<long>(h0 + h) * W + w0;
93 84 float* y_row = y_chan + static_cast<long>(h) * W_out;
94 168 std::memcpy(y_row, x_row,
95 84 static_cast<size_t>(W_out) * sizeof(float));
96 84 }
97 22 }
98 8 }
99 6 }
100
101 // ─── slice2d_backward ──────────────────────────────────────────────────────
102
103 4 void slice2d_backward(const ::brotensor::Tensor& dY,
104 int N, int C, int H, int W,
105 int h0, int w0, int H_out, int W_out,
106 ::brotensor::Tensor& dX) {
107 4 const char* op = "slice2d_backward";
108 4 check_fp32(dY, op, "dY");
109 4 check_args(op, N, C, H, W, h0, w0, H_out, W_out);
110
1/2
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
4 if (dY.rows != N || dY.cols != C * H_out * W_out) {
111 fail(op, "dY shape must be (N, C*H_out*W_out)");
112 }
113 4 const int cols_in = C * H * W;
114
1/6
✗ Branch 0 not taken.
✓ Branch 1 taken 4 times.
✗ Branch 2 not taken.
✗ Branch 3 not taken.
✗ Branch 4 not taken.
✗ Branch 5 not taken.
4 if (dX.rows != N || dX.cols != cols_in || dX.dtype != Dtype::FP32) {
115 4 dX.resize(N, cols_in, Dtype::FP32);
116 4 }
117
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
4 if (N == 0 || cols_in == 0) return;
118
119 4 const float* dYp = dY.host_f32();
120 4 float* dXp = dX.host_f32_mut();
121
122 // Zero dX, then scatter dY into the slice region.
123 4 const long total_in = static_cast<long>(N) * cols_in;
124
2/2
✓ Branch 0 taken 756 times.
✓ Branch 1 taken 4 times.
760 for (long i = 0; i < total_in; ++i) dXp[i] = 0.0f;
125
126
2/4
✓ Branch 0 taken 4 times.
✗ Branch 1 not taken.
✗ Branch 2 not taken.
✓ Branch 3 taken 4 times.
4 if (H_out == 0 || W_out == 0) return;
127
128
2/2
✓ Branch 0 taken 4 times.
✓ Branch 1 taken 6 times.
10 for (int n = 0; n < N; ++n) {
129
2/2
✓ Branch 0 taken 13 times.
✓ Branch 1 taken 6 times.
19 for (int c = 0; c < C; ++c) {
130 13 const float* dy_chan =
131 13 dYp + (static_cast<long>(n) * C + c) * H_out * W_out;
132 13 float* dx_chan =
133 13 dXp + (static_cast<long>(n) * C + c) * H * W;
134
2/2
✓ Branch 0 taken 39 times.
✓ Branch 1 taken 13 times.
52 for (int h = 0; h < H_out; ++h) {
135 39 const float* dy_row =
136 39 dy_chan + static_cast<long>(h) * W_out;
137 39 float* dx_row =
138 39 dx_chan + static_cast<long>(h0 + h) * W + w0;
139 78 std::memcpy(dx_row, dy_row,
140 39 static_cast<size_t>(W_out) * sizeof(float));
141 39 }
142 13 }
143 6 }
144 4 }
145
146 } // namespace brotensor::detail::cpu
147