Skip to content

Commit ec0c8e6

Browse files
committed
refactor so contents of ss struct is in array buffer
1 parent 887d6e5 commit ec0c8e6

File tree

6 files changed

+133
-128
lines changed

6 files changed

+133
-128
lines changed

stringdtype/stringdtype/src/casts.c

Lines changed: 23 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -40,30 +40,26 @@ string_to_string_resolve_descriptors(PyObject *NPY_UNUSED(self),
4040
}
4141

4242
static int
43-
string_to_string(PyArrayMethod_Context *context, char *const data[],
44-
npy_intp const dimensions[], npy_intp const strides[],
45-
NpyAuxData *NPY_UNUSED(auxdata))
43+
string_to_string(PyArrayMethod_Context *NPY_UNUSED(context),
44+
char *const data[], npy_intp const dimensions[],
45+
npy_intp const strides[], NpyAuxData *NPY_UNUSED(auxdata))
4646
{
4747
npy_intp N = dimensions[0];
48-
ss **in = (ss **)data[0];
49-
ss **out = (ss **)data[1];
50-
// strides are in bytes but pointer offsets are in pointer widths, so
51-
// divide by the element size (one pointer width) to get the pointer offset
52-
npy_intp in_stride = strides[0] / context->descriptors[0]->elsize;
53-
npy_intp out_stride = strides[1] / context->descriptors[1]->elsize;
48+
char *in = data[0];
49+
char *out = data[1];
50+
npy_intp in_stride = strides[0];
51+
npy_intp out_stride = strides[1];
52+
53+
ss *s = NULL, *os = NULL;
5454

5555
while (N--) {
56-
ss *s = empty_if_null(in);
57-
out[0] = ssdup(s);
58-
if (out[0] == NULL) {
56+
load_string(in, &s);
57+
load_string(out, &os);
58+
if (ssdup(s, os) == -1) {
5959
gil_error(PyExc_MemoryError, "ssdup failed");
6060
return -1;
6161
}
6262

63-
if (*in == NULL) {
64-
free(s);
65-
}
66-
6763
in += in_stride;
6864
out += out_stride;
6965
}
@@ -120,7 +116,7 @@ unicode_to_string_resolve_descriptors(PyObject *NPY_UNUSED(self),
120116
// is the number of codepoints that are not trailing null codepoints. Returns
121117
// 0 on success and -1 when an invalid code point is found.
122118
static int
123-
utf8_size(Py_UCS4 *codepoints, long max_length, size_t *num_codepoints,
119+
utf8_size(const Py_UCS4 *codepoints, long max_length, size_t *num_codepoints,
124120
size_t *utf8_bytes)
125121
{
126122
size_t ucs4len = max_length;
@@ -132,7 +128,7 @@ utf8_size(Py_UCS4 *codepoints, long max_length, size_t *num_codepoints,
132128

133129
size_t num_bytes = 0;
134130

135-
for (int i = 0; i < ucs4len; i++) {
131+
for (size_t i = 0; i < ucs4len; i++) {
136132
Py_UCS4 code = codepoints[i];
137133

138134
if (code <= 0x7F) {
@@ -207,13 +203,11 @@ unicode_to_string(PyArrayMethod_Context *context, char *const data[],
207203

208204
npy_intp N = dimensions[0];
209205
Py_UCS4 *in = (Py_UCS4 *)data[0];
210-
ss **out = (ss **)data[1];
206+
char *out = data[1];
211207

212208
// 4 bytes per UCS4 character
213209
npy_intp in_stride = strides[0] / 4;
214-
// strides are in bytes but pointer offsets are in pointer widths, so
215-
// divide by the element size (one pointer width) to get the pointer offset
216-
npy_intp out_stride = strides[1] / context->descriptors[1]->elsize;
210+
npy_intp out_stride = strides[1];
217211

218212
while (N--) {
219213
size_t out_num_bytes = 0;
@@ -223,8 +217,8 @@ unicode_to_string(PyArrayMethod_Context *context, char *const data[],
223217
gil_error(PyExc_TypeError, "Invalid unicode code point found");
224218
return -1;
225219
}
226-
ss *out_ss = ssnewemptylen(out_num_bytes);
227-
if (out_ss == NULL) {
220+
ss *out_ss = (ss *)out;
221+
if (ssnewemptylen(out_num_bytes, out_ss) == -1) {
228222
gil_error(PyExc_MemoryError, "ssnewemptylen failed");
229223
return -1;
230224
}
@@ -253,9 +247,6 @@ unicode_to_string(PyArrayMethod_Context *context, char *const data[],
253247
// pad string with null character
254248
out_buf[out_num_bytes] = '\0';
255249

256-
// set out to the address of the beginning of the string
257-
out[0] = out_ss;
258-
259250
in += in_stride;
260251
out += out_stride;
261252
}
@@ -335,18 +326,18 @@ string_to_unicode(PyArrayMethod_Context *context, char *const data[],
335326
NpyAuxData *NPY_UNUSED(auxdata))
336327
{
337328
npy_intp N = dimensions[0];
338-
ss **in = (ss **)data[0];
329+
char *in = data[0];
339330
Py_UCS4 *out = (Py_UCS4 *)data[1];
340-
// strides are in bytes but pointer offsets are in pointer widths, so
341-
// divide by the element size (one pointer width) to get the pointer offset
342-
npy_intp in_stride = strides[0] / context->descriptors[0]->elsize;
331+
npy_intp in_stride = strides[0];
343332
// 4 bytes per UCS4 character
344333
npy_intp out_stride = strides[1] / 4;
345334
// max number of 4 byte UCS4 characters that can fit in the output
346335
long max_out_size = (context->descriptors[1]->elsize) / 4;
347336

337+
ss *s = NULL;
338+
348339
while (N--) {
349-
ss *s = empty_if_null(in);
340+
load_string(in, &s);
350341
unsigned char *this_string = (unsigned char *)(s->buf);
351342
size_t n_bytes = s->len;
352343
size_t tot_n_bytes = 0;
@@ -371,10 +362,6 @@ string_to_unicode(PyArrayMethod_Context *context, char *const data[],
371362
}
372363
}
373364

374-
if (*in == NULL) {
375-
free(s);
376-
}
377-
378365
in += in_stride;
379366
out += out_stride;
380367
}

stringdtype/stringdtype/src/dtype.c

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ new_stringdtype_instance(void)
1616
if (new == NULL) {
1717
return NULL;
1818
}
19-
new->base.elsize = sizeof(ss *);
20-
new->base.alignment = _Alignof(ss *);
19+
new->base.elsize = sizeof(ss);
20+
new->base.alignment = _Alignof(ss);
2121
new->base.flags |= NPY_NEEDS_INIT;
2222
new->base.flags |= NPY_LIST_PICKLE;
2323
new->base.flags |= NPY_ITEM_REFCOUNT;
@@ -119,18 +119,18 @@ stringdtype_setitem(StringDTypeObject *NPY_UNUSED(descr), PyObject *obj,
119119
return -1;
120120
}
121121

122-
ss *str_val = ssnewlen(val, length);
123-
if (str_val == NULL) {
122+
// free if dataptr holds preexisting string data,
123+
// ssfree does a NULL check
124+
ssfree((ss *)dataptr);
125+
126+
// copies contents of val into item_val->buf
127+
if (ssnewlen(val, length, (ss *)dataptr) == -1) {
124128
PyErr_SetString(PyExc_MemoryError, "ssnewlen failed");
125129
return -1;
126130
}
127-
// the dtype instance has the NPY_NEEDS_INIT flag set,
128-
// so if *dataptr is NULL, that means we're initializing
129-
// the array and don't need to free an existing string
130-
if (*dataptr != NULL) {
131-
free((ss *)*dataptr);
132-
}
133-
*dataptr = (char *)str_val;
131+
132+
// val_obj must stay alive until here to ensure *val* doesn't get
133+
// deallocated
134134
Py_DECREF(val_obj);
135135
return 0;
136136
}
@@ -140,14 +140,11 @@ stringdtype_getitem(StringDTypeObject *NPY_UNUSED(descr), char **dataptr)
140140
{
141141
char *data;
142142

143-
// in the future this could represent missing data too, but we'd
144-
// need to make it so np.empty and np.zeros take their initial value
145-
// from an API hook that doesn't exist yet
146143
if (*dataptr == NULL) {
147144
data = "\0";
148145
}
149146
else {
150-
data = ((ss *)*dataptr)->buf;
147+
data = ((ss *)dataptr)->buf;
151148
}
152149

153150
PyObject *val_obj = PyUnicode_FromString(data);
@@ -173,8 +170,8 @@ stringdtype_getitem(StringDTypeObject *NPY_UNUSED(descr), char **dataptr)
173170
int
174171
compare_strings(char **a, char **b, PyArrayObject *NPY_UNUSED(arr))
175172
{
176-
ss *ss_a = (ss *)*a;
177-
ss *ss_b = (ss *)*b;
173+
ss *ss_a = (ss *)a;
174+
ss *ss_b = (ss *)b;
178175
return strcmp(ss_a->buf, ss_b->buf);
179176
}
180177

@@ -193,8 +190,8 @@ stringdtype_clear_loop(void *NPY_UNUSED(traverse_context),
193190
{
194191
while (size--) {
195192
if (data != NULL) {
196-
free(*(ss **)data);
197-
*(ss **)data = NULL;
193+
ssfree((ss *)data);
194+
memset(data, 0, sizeof(ss));
198195
}
199196
data += stride;
200197
}

stringdtype/stringdtype/src/main.c

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -50,16 +50,15 @@ _memory_usage(PyObject *NPY_UNUSED(self), PyObject *obj)
5050

5151
// initialize with the size of the internal buffer
5252
size_t memory_usage = PyArray_NBYTES(arr);
53-
size_t struct_size = sizeof(ss);
5453

5554
do {
56-
ss **in = (ss **)*dataptr;
57-
npy_intp stride = *strideptr / descr->elsize;
55+
char *in = dataptr[0];
56+
npy_intp stride = *strideptr;
5857
npy_intp count = *innersizeptr;
5958

6059
while (count--) {
6160
// +1 byte for the null terminator
62-
memory_usage += (*in)->len + struct_size + 1;
61+
memory_usage += ((ss *)in)->len + 1;
6362
in += stride;
6463
}
6564

Lines changed: 48 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,58 +1,76 @@
11
#include "static_string.h"
22

3-
// allocates a new ss string of length len, filling with the contents of init
4-
ss *
5-
ssnewlen(const char *init, size_t len)
3+
int
4+
ssnewlen(const char *init, size_t len, ss *to_init)
65
{
6+
if ((to_init->buf != NULL) || (to_init->len != 0)) {
7+
return -1;
8+
}
9+
710
// one extra byte for null terminator
8-
ss *ret = (ss *)malloc(sizeof(ss) + sizeof(char) * (len + 1));
11+
char *ret_buf = (char *)malloc(sizeof(char) * (len + 1));
912

10-
if (ret == NULL) {
11-
return NULL;
13+
if (ret_buf == NULL) {
14+
return -1;
1215
}
1316

14-
ret->len = len;
17+
to_init->len = len;
1518

1619
if (len > 0) {
17-
memcpy(ret->buf, init, len);
20+
memcpy(ret_buf, init, len);
1821
}
1922

20-
ret->buf[len] = '\0';
23+
ret_buf[len] = '\0';
24+
25+
to_init->buf = ret_buf;
2126

22-
return ret;
27+
return 0;
2328
}
2429

25-
// returns a new heap-allocated copy of input string *s*
26-
ss *
27-
ssdup(const ss *s)
30+
void
31+
ssfree(ss *str)
2832
{
29-
return ssnewlen(s->buf, s->len);
33+
if (str->buf != NULL) {
34+
free(str->buf);
35+
str->buf = NULL;
36+
}
37+
str->len = 0;
3038
}
3139

32-
// returns a new, empty string of length len
33-
// does not do any initialization, the caller must
34-
// initialize and null-terminate the string
35-
ss *
36-
ssnewemptylen(size_t len)
40+
int
41+
ssdup(ss *in, ss *out)
3742
{
38-
ss *ret = (ss *)malloc(sizeof(ss) + sizeof(char) * (len + 1));
39-
ret->len = len;
40-
return ret;
43+
return ssnewlen(in->buf, in->len, out);
4144
}
4245

43-
ss *
44-
ssempty(void)
46+
int
47+
ssnewemptylen(size_t num_bytes, ss *out)
4548
{
46-
return ssnewlen("", 0);
49+
if (out->len != 0 || out->buf != NULL) {
50+
return -1;
51+
}
52+
53+
char *buf = (char *)malloc(sizeof(char) * (num_bytes + 1));
54+
55+
if (buf == NULL) {
56+
return -1;
57+
}
58+
59+
out->buf = buf;
60+
out->len = num_bytes;
61+
62+
return 0;
4763
}
4864

49-
ss *
50-
empty_if_null(ss **data)
65+
static ss EMPTY = {0, "\0"};
66+
67+
void
68+
load_string(char *data, ss **out)
5169
{
52-
if (*data == NULL) {
53-
return ssempty();
70+
if (data == NULL) {
71+
*out = &EMPTY;
5472
}
5573
else {
56-
return *data;
74+
*out = (ss *)data;
5775
}
5876
}

stringdtype/stringdtype/src/static_string.h

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,40 @@
66

77
typedef struct ss {
88
size_t len;
9-
char buf[];
9+
char *buf;
1010
} ss;
1111

12-
// allocates a new ss string of length len, filling with the contents of init
13-
ss *
14-
ssnewlen(const char *init, size_t len);
15-
16-
// returns a new heap-allocated copy of input string *s*
17-
ss *
18-
ssdup(const ss *s);
19-
20-
// returns a new, empty string of length len
21-
// does not do any initialization, the caller must
22-
// initialize and null-terminate the string
23-
ss *
24-
ssnewemptylen(size_t len);
25-
26-
// returns an new heap-allocated empty string
27-
ss *
28-
ssnewempty(void);
29-
30-
ss *
31-
empty_if_null(ss **data);
12+
// Allocates a new buffer for *to_init*, filling with the copied contents of
13+
// *init* and sets *to_init->len* to *len*.
14+
int
15+
ssnewlen(const char *init, size_t len, ss *to_init);
16+
17+
// Frees the internal string buffer held by *str*. If str->buf is NULL, does
18+
// nothing.
19+
void
20+
ssfree(ss *str);
21+
22+
// copies the contents out *in* into *out*. Allocates a new string buffer for
23+
// *out*, checks that *out->buf* is NULL and *out->len* is zero and errors
24+
// otherwise. Also errors if malloc fails.
25+
int
26+
ssdup(ss *in, ss *out);
27+
28+
// Allocates a new string buffer for *out* with enough capacity to store
29+
// *num_bytes* of text. The actual allocation is num_bytes + 1 bytes, to
30+
// account for the null terminator. Does not do any initialization, the caller
31+
// must initialize and null-terminate the string buffer. Errors if *out*
32+
// already contains data or if malloc fails.
33+
int
34+
ssnewemptylen(size_t num_bytes, ss *out);
35+
36+
// Interpret the contents of buffer *data* as an ss struct and set *out* to
37+
// that struct. If *data* is NULL, set *out* to point to a statically
38+
// allocated, empty SS struct. Since this function may set *out* to point to
39+
// statically allocated data, do not ever free memory owned by an output of
40+
// this function. That means this function is most useful for read-only
41+
// applications.
42+
void
43+
load_string(char *data, ss **out);
3244

3345
#endif /*_NPY_STATIC_STRING_H */

0 commit comments

Comments
 (0)