@@ -168,6 +168,42 @@ fn collect_and_sort_map_entries<'py>(
168168 Ok ( entries)
169169}
170170
171+ // `PyUnicode_DecodeUTF8` runs a state machine even on pure-ASCII input. Skip
172+ // it by allocating a compact-ASCII `PyUnicode` and memcpying into its inline
173+ // buffer; non-ASCII falls through to the standard decoder.
174+ #[ cfg( CPython ) ]
175+ #[ inline]
176+ fn pystring_from_bytes_fast < ' py > (
177+ py : Python < ' py > ,
178+ bytes : & [ u8 ] ,
179+ ) -> PyResult < Bound < ' py , PyString > > {
180+ if !bytes. is_ascii ( ) {
181+ return PyString :: from_bytes ( py, bytes) ;
182+ }
183+
184+ unsafe {
185+ let obj = ffi:: PyUnicode_New ( bytes. len ( ) as ffi:: Py_ssize_t , 127 ) ;
186+ if obj. is_null ( ) {
187+ return Err ( PyErr :: fetch ( py) ) ;
188+ }
189+
190+ let data = obj. cast :: < ffi:: PyASCIIObject > ( ) . offset ( 1 ) . cast :: < u8 > ( ) ;
191+ std:: ptr:: copy_nonoverlapping ( bytes. as_ptr ( ) , data, bytes. len ( ) ) ;
192+ * data. add ( bytes. len ( ) ) = 0 ;
193+
194+ Ok ( Bound :: from_owned_ptr ( py, obj) . cast_into_unchecked :: < PyString > ( ) )
195+ }
196+ }
197+
198+ #[ cfg( not( CPython ) ) ]
199+ #[ inline]
200+ fn pystring_from_bytes_fast < ' py > (
201+ py : Python < ' py > ,
202+ bytes : & [ u8 ] ,
203+ ) -> PyResult < Bound < ' py , PyString > > {
204+ PyString :: from_bytes ( py, bytes)
205+ }
206+
171207fn get_bytes_from_py_any < ' py > ( obj : & ' py Bound < ' py , PyAny > ) -> PyResult < & ' py [ u8 ] > {
172208 if let Ok ( b) = obj. cast :: < PyBytes > ( ) {
173209 Ok ( b. as_bytes ( ) )
@@ -222,8 +258,9 @@ where
222258 . into_pyobject ( py) ?
223259 . into ( ) ,
224260 major:: STRING => {
225- // The UTF-8 validation is done when it's converted into a Python string
226- PyString :: from_bytes (
261+ // ASCII fast path inside the helper; non-ASCII falls through to
262+ // `PyUnicode_DecodeUTF8`, which is where the spec validation lives.
263+ pystring_from_bytes_fast (
227264 py,
228265 <types:: UncheckedStr < & [ u8 ] > >:: decode ( r)
229266 . map_err ( |_| anyhow ! ( "Cannot decode as bytes" ) ) ?
@@ -277,7 +314,7 @@ where
277314 }
278315 }
279316
280- let key_py = PyString :: from_bytes ( py, key) ?;
317+ let key_py = pystring_from_bytes_fast ( py, key) ?;
281318 prev_key = Some ( key) ;
282319
283320 let value_py = decode_dag_cbor_to_pyobject ( py, r, depth + 1 ) ?;
0 commit comments