/src/cpython/Objects/stringlib/join.h
Line | Count | Source |
1 | | /* stringlib: bytes joining implementation */ |
2 | | |
3 | | #if STRINGLIB_IS_UNICODE |
4 | | #error join.h only compatible with byte-wise strings |
5 | | #endif |
6 | | |
7 | | Py_LOCAL_INLINE(PyObject *) |
8 | | STRINGLIB(bytes_join_lock_held)(PyObject *sep, PyObject *seq) |
9 | 172k | { |
10 | 172k | const char *sepstr = STRINGLIB_STR(sep); |
11 | 172k | Py_ssize_t seplen = STRINGLIB_LEN(sep); |
12 | 172k | PyObject *res = NULL; |
13 | 172k | char *p; |
14 | 172k | Py_ssize_t seqlen = 0; |
15 | 172k | Py_ssize_t sz = 0; |
16 | 172k | Py_ssize_t i, nbufs; |
17 | 172k | PyObject *item; |
18 | 172k | Py_buffer *buffers = NULL; |
19 | 172k | #define NB_STATIC_BUFFERS 10 |
20 | 172k | Py_buffer static_buffers[NB_STATIC_BUFFERS]; |
21 | 172k | #define GIL_THRESHOLD 1048576 |
22 | 172k | int drop_gil = 1; |
23 | 172k | PyThreadState *save = NULL; |
24 | | |
25 | 172k | seqlen = PySequence_Fast_GET_SIZE(seq); |
26 | 172k | if (seqlen == 0) { |
27 | 31 | return STRINGLIB_NEW(NULL, 0); |
28 | 31 | } |
29 | | #if !STRINGLIB_MUTABLE |
30 | 172k | if (seqlen == 1) { |
31 | 45.1k | item = PySequence_Fast_GET_ITEM(seq, 0); |
32 | 45.1k | if (STRINGLIB_CHECK_EXACT(item)) { |
33 | 45.1k | return Py_NewRef(item); |
34 | 45.1k | } |
35 | 45.1k | } |
36 | 126k | #endif |
37 | 126k | if (seqlen > NB_STATIC_BUFFERS) { |
38 | 6.87k | buffers = PyMem_NEW(Py_buffer, seqlen); |
39 | 6.87k | if (buffers == NULL) { |
40 | 0 | PyErr_NoMemory(); |
41 | 0 | return NULL; |
42 | 0 | } |
43 | 6.87k | } |
44 | 120k | else { |
45 | 120k | buffers = static_buffers; |
46 | 120k | } |
47 | | |
48 | | /* Here is the general case. Do a pre-pass to figure out the total |
49 | | * amount of space we'll need (sz), and see whether all arguments are |
50 | | * bytes-like. |
51 | | */ |
52 | 538k | for (i = 0, nbufs = 0; i < seqlen; i++) { |
53 | 411k | Py_ssize_t itemlen; |
54 | 411k | item = PySequence_Fast_GET_ITEM(seq, i); |
55 | 411k | if (PyBytes_CheckExact(item)) { |
56 | | /* Fast path. */ |
57 | 410k | buffers[i].obj = Py_NewRef(item); |
58 | 410k | buffers[i].buf = PyBytes_AS_STRING(item); |
59 | 410k | buffers[i].len = PyBytes_GET_SIZE(item); |
60 | 410k | } |
61 | 1.27k | else { |
62 | | /* item is only borrowed; its __buffer__() may run Python that |
63 | | drops the sequence's last reference to it. */ |
64 | 1.27k | Py_INCREF(item); |
65 | 1.27k | if (PyObject_GetBuffer(item, &buffers[i], PyBUF_SIMPLE) != 0) { |
66 | 0 | PyErr_Format(PyExc_TypeError, |
67 | 0 | "sequence item %zd: expected a bytes-like object, " |
68 | 0 | "%.80s found", |
69 | 0 | i, Py_TYPE(item)->tp_name); |
70 | 0 | Py_DECREF(item); |
71 | 0 | goto error; |
72 | 0 | } |
73 | 1.27k | Py_DECREF(item); |
74 | | /* If the backing objects are mutable, then dropping the GIL |
75 | | * opens up race conditions where another thread tries to modify |
76 | | * the object which we hold a buffer on it. Such code has data |
77 | | * races anyway, but this is a conservative approach that avoids |
78 | | * changing the behaviour of that data race. |
79 | | */ |
80 | 1.27k | drop_gil = 0; |
81 | 1.27k | } |
82 | 411k | nbufs = i + 1; /* for error cleanup */ |
83 | 411k | itemlen = buffers[i].len; |
84 | 411k | if (itemlen > PY_SSIZE_T_MAX - sz) { |
85 | 0 | PyErr_SetString(PyExc_OverflowError, |
86 | 0 | "join() result is too long"); |
87 | 0 | goto error; |
88 | 0 | } |
89 | 411k | sz += itemlen; |
90 | 411k | if (i != 0) { |
91 | 284k | if (seplen > PY_SSIZE_T_MAX - sz) { |
92 | 0 | PyErr_SetString(PyExc_OverflowError, |
93 | 0 | "join() result is too long"); |
94 | 0 | goto error; |
95 | 0 | } |
96 | 284k | sz += seplen; |
97 | 284k | } |
98 | 411k | if (seqlen != PySequence_Fast_GET_SIZE(seq)) { |
99 | 0 | PyErr_SetString(PyExc_RuntimeError, |
100 | 0 | "sequence changed size during iteration"); |
101 | 0 | goto error; |
102 | 0 | } |
103 | 411k | } |
104 | | |
105 | | /* Allocate result space. */ |
106 | 126k | res = STRINGLIB_NEW(NULL, sz); |
107 | 126k | if (res == NULL) |
108 | 0 | goto error; |
109 | | |
110 | | /* Catenate everything. */ |
111 | 126k | p = STRINGLIB_STR(res); |
112 | 126k | if (sz < GIL_THRESHOLD) { |
113 | 126k | drop_gil = 0; /* Benefits are likely outweighed by the overheads */ |
114 | 126k | } |
115 | 126k | if (drop_gil) { |
116 | 0 | save = PyEval_SaveThread(); |
117 | 0 | } |
118 | 126k | if (!seplen) { |
119 | | /* fast path */ |
120 | 538k | for (i = 0; i < nbufs; i++) { |
121 | 411k | Py_ssize_t n = buffers[i].len; |
122 | 411k | char *q = buffers[i].buf; |
123 | 411k | memcpy(p, q, n); |
124 | 411k | p += n; |
125 | 411k | } |
126 | 126k | } |
127 | 0 | else { |
128 | 0 | Py_ssize_t n = buffers[0].len; |
129 | 0 | char *q = buffers[0].buf; |
130 | 0 | memcpy(p, q, n); |
131 | 0 | p += n; |
132 | |
|
133 | 0 | for (i = 1; i < nbufs; i++) { |
134 | 0 | memcpy(p, sepstr, seplen); |
135 | 0 | p += seplen; |
136 | |
|
137 | 0 | n = buffers[i].len; |
138 | 0 | q = buffers[i].buf; |
139 | 0 | memcpy(p, q, n); |
140 | 0 | p += n; |
141 | 0 | } |
142 | 0 | } |
143 | 126k | if (drop_gil) { |
144 | 0 | PyEval_RestoreThread(save); |
145 | 0 | } |
146 | 126k | goto done; |
147 | | |
148 | 0 | error: |
149 | 0 | res = NULL; |
150 | 126k | done: |
151 | 538k | for (i = 0; i < nbufs; i++) |
152 | 411k | PyBuffer_Release(&buffers[i]); |
153 | 126k | if (buffers != static_buffers) |
154 | 6.87k | PyMem_Free(buffers); |
155 | 126k | return res; |
156 | 0 | } bytesobject.c:stringlib_bytes_join_lock_held Line | Count | Source | 9 | 172k | { | 10 | 172k | const char *sepstr = STRINGLIB_STR(sep); | 11 | 172k | Py_ssize_t seplen = STRINGLIB_LEN(sep); | 12 | 172k | PyObject *res = NULL; | 13 | 172k | char *p; | 14 | 172k | Py_ssize_t seqlen = 0; | 15 | 172k | Py_ssize_t sz = 0; | 16 | 172k | Py_ssize_t i, nbufs; | 17 | 172k | PyObject *item; | 18 | 172k | Py_buffer *buffers = NULL; | 19 | 172k | #define NB_STATIC_BUFFERS 10 | 20 | 172k | Py_buffer static_buffers[NB_STATIC_BUFFERS]; | 21 | 172k | #define GIL_THRESHOLD 1048576 | 22 | 172k | int drop_gil = 1; | 23 | 172k | PyThreadState *save = NULL; | 24 | | | 25 | 172k | seqlen = PySequence_Fast_GET_SIZE(seq); | 26 | 172k | if (seqlen == 0) { | 27 | 31 | return STRINGLIB_NEW(NULL, 0); | 28 | 31 | } | 29 | 172k | #if !STRINGLIB_MUTABLE | 30 | 172k | if (seqlen == 1) { | 31 | 45.1k | item = PySequence_Fast_GET_ITEM(seq, 0); | 32 | 45.1k | if (STRINGLIB_CHECK_EXACT(item)) { | 33 | 45.1k | return Py_NewRef(item); | 34 | 45.1k | } | 35 | 45.1k | } | 36 | 126k | #endif | 37 | 126k | if (seqlen > NB_STATIC_BUFFERS) { | 38 | 6.87k | buffers = PyMem_NEW(Py_buffer, seqlen); | 39 | 6.87k | if (buffers == NULL) { | 40 | 0 | PyErr_NoMemory(); | 41 | 0 | return NULL; | 42 | 0 | } | 43 | 6.87k | } | 44 | 120k | else { | 45 | 120k | buffers = static_buffers; | 46 | 120k | } | 47 | | | 48 | | /* Here is the general case. Do a pre-pass to figure out the total | 49 | | * amount of space we'll need (sz), and see whether all arguments are | 50 | | * bytes-like. | 51 | | */ | 52 | 538k | for (i = 0, nbufs = 0; i < seqlen; i++) { | 53 | 411k | Py_ssize_t itemlen; | 54 | 411k | item = PySequence_Fast_GET_ITEM(seq, i); | 55 | 411k | if (PyBytes_CheckExact(item)) { | 56 | | /* Fast path. */ | 57 | 410k | buffers[i].obj = Py_NewRef(item); | 58 | 410k | buffers[i].buf = PyBytes_AS_STRING(item); | 59 | 410k | buffers[i].len = PyBytes_GET_SIZE(item); | 60 | 410k | } | 61 | 1.27k | else { | 62 | | /* item is only borrowed; its __buffer__() may run Python that | 63 | | drops the sequence's last reference to it. */ | 64 | 1.27k | Py_INCREF(item); | 65 | 1.27k | if (PyObject_GetBuffer(item, &buffers[i], PyBUF_SIMPLE) != 0) { | 66 | 0 | PyErr_Format(PyExc_TypeError, | 67 | 0 | "sequence item %zd: expected a bytes-like object, " | 68 | 0 | "%.80s found", | 69 | 0 | i, Py_TYPE(item)->tp_name); | 70 | 0 | Py_DECREF(item); | 71 | 0 | goto error; | 72 | 0 | } | 73 | 1.27k | Py_DECREF(item); | 74 | | /* If the backing objects are mutable, then dropping the GIL | 75 | | * opens up race conditions where another thread tries to modify | 76 | | * the object which we hold a buffer on it. Such code has data | 77 | | * races anyway, but this is a conservative approach that avoids | 78 | | * changing the behaviour of that data race. | 79 | | */ | 80 | 1.27k | drop_gil = 0; | 81 | 1.27k | } | 82 | 411k | nbufs = i + 1; /* for error cleanup */ | 83 | 411k | itemlen = buffers[i].len; | 84 | 411k | if (itemlen > PY_SSIZE_T_MAX - sz) { | 85 | 0 | PyErr_SetString(PyExc_OverflowError, | 86 | 0 | "join() result is too long"); | 87 | 0 | goto error; | 88 | 0 | } | 89 | 411k | sz += itemlen; | 90 | 411k | if (i != 0) { | 91 | 284k | if (seplen > PY_SSIZE_T_MAX - sz) { | 92 | 0 | PyErr_SetString(PyExc_OverflowError, | 93 | 0 | "join() result is too long"); | 94 | 0 | goto error; | 95 | 0 | } | 96 | 284k | sz += seplen; | 97 | 284k | } | 98 | 411k | if (seqlen != PySequence_Fast_GET_SIZE(seq)) { | 99 | 0 | PyErr_SetString(PyExc_RuntimeError, | 100 | 0 | "sequence changed size during iteration"); | 101 | 0 | goto error; | 102 | 0 | } | 103 | 411k | } | 104 | | | 105 | | /* Allocate result space. */ | 106 | 126k | res = STRINGLIB_NEW(NULL, sz); | 107 | 126k | if (res == NULL) | 108 | 0 | goto error; | 109 | | | 110 | | /* Catenate everything. */ | 111 | 126k | p = STRINGLIB_STR(res); | 112 | 126k | if (sz < GIL_THRESHOLD) { | 113 | 126k | drop_gil = 0; /* Benefits are likely outweighed by the overheads */ | 114 | 126k | } | 115 | 126k | if (drop_gil) { | 116 | 0 | save = PyEval_SaveThread(); | 117 | 0 | } | 118 | 126k | if (!seplen) { | 119 | | /* fast path */ | 120 | 538k | for (i = 0; i < nbufs; i++) { | 121 | 411k | Py_ssize_t n = buffers[i].len; | 122 | 411k | char *q = buffers[i].buf; | 123 | 411k | memcpy(p, q, n); | 124 | 411k | p += n; | 125 | 411k | } | 126 | 126k | } | 127 | 0 | else { | 128 | 0 | Py_ssize_t n = buffers[0].len; | 129 | 0 | char *q = buffers[0].buf; | 130 | 0 | memcpy(p, q, n); | 131 | 0 | p += n; | 132 | |
| 133 | 0 | for (i = 1; i < nbufs; i++) { | 134 | 0 | memcpy(p, sepstr, seplen); | 135 | 0 | p += seplen; | 136 | |
| 137 | 0 | n = buffers[i].len; | 138 | 0 | q = buffers[i].buf; | 139 | 0 | memcpy(p, q, n); | 140 | 0 | p += n; | 141 | 0 | } | 142 | 0 | } | 143 | 126k | if (drop_gil) { | 144 | 0 | PyEval_RestoreThread(save); | 145 | 0 | } | 146 | 126k | goto done; | 147 | | | 148 | 0 | error: | 149 | 0 | res = NULL; | 150 | 126k | done: | 151 | 538k | for (i = 0; i < nbufs; i++) | 152 | 411k | PyBuffer_Release(&buffers[i]); | 153 | 126k | if (buffers != static_buffers) | 154 | 6.87k | PyMem_Free(buffers); | 155 | 126k | return res; | 156 | 0 | } |
Unexecuted instantiation: bytearrayobject.c:stringlib_bytes_join_lock_held |
157 | | |
158 | | Py_LOCAL_INLINE(PyObject *) |
159 | | STRINGLIB(bytes_join)(PyObject *sep, PyObject *iterable) |
160 | 172k | { |
161 | 172k | PyObject *seq, *res; |
162 | | |
163 | 172k | seq = PySequence_Fast(iterable, "can only join an iterable"); |
164 | 172k | if (seq == NULL) { |
165 | 0 | return NULL; |
166 | 0 | } |
167 | | |
168 | 172k | Py_BEGIN_CRITICAL_SECTION_SEQUENCE_FAST(iterable); |
169 | 172k | res = STRINGLIB(bytes_join_lock_held)(sep, seq); |
170 | 172k | Py_END_CRITICAL_SECTION_SEQUENCE_FAST(); |
171 | | |
172 | 172k | Py_DECREF(seq); |
173 | 172k | return res; |
174 | 172k | } bytesobject.c:stringlib_bytes_join Line | Count | Source | 160 | 172k | { | 161 | 172k | PyObject *seq, *res; | 162 | | | 163 | 172k | seq = PySequence_Fast(iterable, "can only join an iterable"); | 164 | 172k | if (seq == NULL) { | 165 | 0 | return NULL; | 166 | 0 | } | 167 | | | 168 | 172k | Py_BEGIN_CRITICAL_SECTION_SEQUENCE_FAST(iterable); | 169 | 172k | res = STRINGLIB(bytes_join_lock_held)(sep, seq); | 170 | 172k | Py_END_CRITICAL_SECTION_SEQUENCE_FAST(); | 171 | | | 172 | 172k | Py_DECREF(seq); | 173 | 172k | return res; | 174 | 172k | } |
Unexecuted instantiation: bytearrayobject.c:stringlib_bytes_join |
175 | | |
176 | | #undef NB_STATIC_BUFFERS |
177 | | #undef GIL_THRESHOLD |