@@ -114,19 +114,19 @@ mod generic;
114
114
#[ cfg( target_arch = "x86_64" ) ]
115
115
mod x86;
116
116
117
- pub use generic:: escape_generic;
117
+ pub use generic:: { escape_generic, escape_into_generic } ;
118
118
119
119
/// Main entry point for JSON string escaping with SIMD acceleration
120
120
/// If the platform is supported, the SIMD path will be used. Otherwise, the generic fallback will be used.
121
121
pub fn escape < S : AsRef < str > > ( input : S ) -> String {
122
+ use generic:: escape_inner;
123
+
124
+ let mut result = Vec :: with_capacity ( input. as_ref ( ) . len ( ) + input. as_ref ( ) . len ( ) / 2 + 2 ) ;
125
+ result. push ( b'"' ) ;
126
+ let s = input. as_ref ( ) ;
127
+ let bytes = s. as_bytes ( ) ;
122
128
#[ cfg( target_arch = "x86_64" ) ]
123
129
{
124
- use generic:: escape_inner;
125
-
126
- let mut result = Vec :: with_capacity ( input. as_ref ( ) . len ( ) + input. as_ref ( ) . len ( ) / 2 + 2 ) ;
127
- result. push ( b'"' ) ;
128
- let s = input. as_ref ( ) ;
129
- let bytes = s. as_bytes ( ) ;
130
130
let len = bytes. len ( ) ;
131
131
// Runtime CPU feature detection for x86_64
132
132
if is_x86_feature_detected ! ( "avx512f" )
@@ -144,16 +144,71 @@ pub fn escape<S: AsRef<str>>(input: S) -> String {
144
144
} else {
145
145
escape_inner ( bytes, & mut result) ;
146
146
}
147
- result. push ( b'"' ) ;
148
- // SAFETY: We only pushed valid UTF-8 bytes (original string bytes and ASCII escape sequences)
149
- unsafe { String :: from_utf8_unchecked ( result) }
150
147
}
151
148
152
149
#[ cfg( target_arch = "aarch64" ) ]
153
150
{
154
151
#[ cfg( feature = "force_aarch64_neon" ) ]
155
152
{
156
- return aarch64:: escape_neon ( input) ;
153
+ aarch64:: escape_neon ( bytes, & mut result) ;
154
+ }
155
+ #[ cfg( not( feature = "force_aarch64_neon" ) ) ]
156
+ {
157
+ // on Apple M2 and later, the `bf16` feature is available
158
+ // it means they have more registers and can significantly benefit from the SIMD path
159
+ // TODO: add support for sve2 chips with wider registers
160
+ // github actions ubuntu-24.04-arm runner has 128 bits sve2 registers, it's not enough for the SIMD path
161
+ if cfg ! ( target_os = "macos" ) && std:: arch:: is_aarch64_feature_detected!( "bf16" ) {
162
+ aarch64:: escape_neon ( bytes, & mut result) ;
163
+ } else {
164
+ escape_inner ( bytes, & mut result) ;
165
+ }
166
+ }
167
+ }
168
+
169
+ #[ cfg( not( any( target_arch = "x86_64" , target_arch = "aarch64" ) ) ) ]
170
+ {
171
+ escape_inner ( bytes, & mut result) ;
172
+ }
173
+ result. push ( b'"' ) ;
174
+ // SAFETY: We only pushed valid UTF-8 bytes (original string bytes and ASCII escape sequences)
175
+ unsafe { String :: from_utf8_unchecked ( result) }
176
+ }
177
+
178
+ /// Main entry point for JSON string escaping with SIMD acceleration
179
+ /// If the platform is supported, the SIMD path will be used. Otherwise, the generic fallback will be used.
180
+ pub fn escape_into < S : AsRef < str > > ( input : S , output : & mut Vec < u8 > ) {
181
+ use generic:: escape_inner;
182
+
183
+ output. push ( b'"' ) ;
184
+ let s = input. as_ref ( ) ;
185
+ let bytes = s. as_bytes ( ) ;
186
+ #[ cfg( target_arch = "x86_64" ) ]
187
+ {
188
+ let len = bytes. len ( ) ;
189
+ // Runtime CPU feature detection for x86_64
190
+ if is_x86_feature_detected ! ( "avx512f" )
191
+ && is_x86_feature_detected ! ( "avx512bw" )
192
+ && len >= x86:: LOOP_SIZE_AVX512
193
+ {
194
+ unsafe { x86:: escape_avx512 ( bytes, output) }
195
+ } else if is_x86_feature_detected ! ( "avx2" ) && len >= x86:: LOOP_SIZE_AVX2 {
196
+ unsafe { x86:: escape_avx2 ( bytes, output) }
197
+ } else if is_x86_feature_detected ! ( "sse2" )
198
+ && /* if len < 128, no need to use simd */
199
+ len >= x86:: LOOP_SIZE_AVX2
200
+ {
201
+ unsafe { x86:: escape_sse2 ( bytes, output) }
202
+ } else {
203
+ escape_inner ( bytes, output) ;
204
+ }
205
+ }
206
+
207
+ #[ cfg( target_arch = "aarch64" ) ]
208
+ {
209
+ #[ cfg( feature = "force_aarch64_neon" ) ]
210
+ {
211
+ return aarch64:: escape_neon ( bytes, output) ;
157
212
}
158
213
#[ cfg( not( feature = "force_aarch64_neon" ) ) ]
159
214
{
@@ -162,15 +217,18 @@ pub fn escape<S: AsRef<str>>(input: S) -> String {
162
217
// TODO: add support for sve2 chips with wider registers
163
218
// github actions ubuntu-24.04-arm runner has 128 bits sve2 registers, it's not enough for the SIMD path
164
219
if cfg ! ( target_os = "macos" ) && std:: arch:: is_aarch64_feature_detected!( "bf16" ) {
165
- return aarch64:: escape_neon ( input ) ;
220
+ aarch64:: escape_neon ( bytes , output ) ;
166
221
} else {
167
- return escape_generic ( input ) ;
222
+ escape_inner ( bytes , output ) ;
168
223
}
169
224
}
170
225
}
171
226
172
227
#[ cfg( not( any( target_arch = "x86_64" , target_arch = "aarch64" ) ) ) ]
173
- escape_generic ( input)
228
+ {
229
+ escape_into_generic ( input, output) ;
230
+ }
231
+ output. push ( b'"' ) ;
174
232
}
175
233
176
234
#[ test]
@@ -377,6 +435,9 @@ fn test_rxjs() {
377
435
assert ! ( !sources. is_empty( ) ) ;
378
436
for source in sources {
379
437
assert_eq ! ( escape( & source) , serde_json:: to_string( & source) . unwrap( ) ) ;
438
+ let mut output = String :: new ( ) ;
439
+ escape_into ( & source, unsafe { output. as_mut_vec ( ) } ) ;
440
+ assert_eq ! ( output, serde_json:: to_string( & source) . unwrap( ) ) ;
380
441
}
381
442
}
382
443
@@ -402,5 +463,8 @@ fn test_sources() {
402
463
assert ! ( !sources. is_empty( ) ) ;
403
464
for source in sources {
404
465
assert_eq ! ( escape( & source) , serde_json:: to_string( & source) . unwrap( ) ) ;
466
+ let mut output = String :: new ( ) ;
467
+ escape_into ( & source, unsafe { output. as_mut_vec ( ) } ) ;
468
+ assert_eq ! ( output, serde_json:: to_string( & source) . unwrap( ) ) ;
405
469
}
406
470
}
0 commit comments