oj 3.16.11 → 3.17.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/ext/oj/dump.c CHANGED
@@ -40,7 +40,7 @@ static size_t ascii_friendly_size(const uint8_t *str, size_t len);
40
40
  static const char hex_chars[17] = "0123456789abcdef";
41
41
 
42
42
  // JSON standard except newlines are no escaped
43
- static char newline_friendly_chars[256] = "\
43
+ static char newline_friendly_chars[257] = "\
44
44
  66666666221622666666666666666666\
45
45
  11211111111111111111111111111111\
46
46
  11111111111111111111111111112111\
@@ -51,7 +51,7 @@ static char newline_friendly_chars[256] = "\
51
51
  11111111111111111111111111111111";
52
52
 
53
53
  // JSON standard
54
- static char hibit_friendly_chars[256] = "\
54
+ static char hibit_friendly_chars[257] = "\
55
55
  66666666222622666666666666666666\
56
56
  11211111111111111111111111111111\
57
57
  11111111111111111111111111112111\
@@ -62,7 +62,7 @@ static char hibit_friendly_chars[256] = "\
62
62
  11111111111111111111111111111111";
63
63
 
64
64
  // JSON standard but escape forward slashes `/`
65
- static char slash_friendly_chars[256] = "\
65
+ static char slash_friendly_chars[257] = "\
66
66
  66666666222622666666666666666666\
67
67
  11211111111111121111111111111111\
68
68
  11111111111111111111111111112111\
@@ -74,7 +74,7 @@ static char slash_friendly_chars[256] = "\
74
74
 
75
75
  // High bit set characters are always encoded as unicode. Worse case is 3
76
76
  // bytes per character in the output. That makes this conservative.
77
- static char ascii_friendly_chars[256] = "\
77
+ static char ascii_friendly_chars[257] = "\
78
78
  66666666222622666666666666666666\
79
79
  11211111111111111111111111111111\
80
80
  11111111111111111111111111112111\
@@ -85,7 +85,7 @@ static char ascii_friendly_chars[256] = "\
85
85
  33333333333333333333333333333333";
86
86
 
87
87
  // XSS safe mode
88
- static char xss_friendly_chars[256] = "\
88
+ static char xss_friendly_chars[257] = "\
89
89
  66666666222622666666666666666666\
90
90
  11211161111111121111111111116161\
91
91
  11111111111111111111111111112111\
@@ -96,7 +96,7 @@ static char xss_friendly_chars[256] = "\
96
96
  33333333333333333333333333333333";
97
97
 
98
98
  // JSON XSS combo
99
- static char hixss_friendly_chars[256] = "\
99
+ static char hixss_friendly_chars[257] = "\
100
100
  66666666222622666666666666666666\
101
101
  11211111111111111111111111111111\
102
102
  11111111111111111111111111112111\
@@ -107,7 +107,7 @@ static char hixss_friendly_chars[256] = "\
107
107
  11611111111111111111111111111111";
108
108
 
109
109
  // Rails XSS combo
110
- static char rails_xss_friendly_chars[256] = "\
110
+ static char rails_xss_friendly_chars[257] = "\
111
111
  66666666222622666666666666666666\
112
112
  11211161111111111111111111116161\
113
113
  11111111111111111111111111112111\
@@ -118,7 +118,7 @@ static char rails_xss_friendly_chars[256] = "\
118
118
  11611111111111111111111111111111";
119
119
 
120
120
  // Rails HTML non-escape
121
- static char rails_friendly_chars[256] = "\
121
+ static char rails_friendly_chars[257] = "\
122
122
  66666666222622666666666666666666\
123
123
  11211111111111111111111111111111\
124
124
  11111111111111111111111111112111\
@@ -201,6 +201,45 @@ void initialize_neon(void) {
201
201
  }
202
202
  #endif
203
203
 
204
+ #ifdef HAVE_SIMD_SSE4_2
205
+
206
+ static __m128i hibit_friendly_chars_sse42[8];
207
+
208
+ // From: https://stackoverflow.com/questions/36998538/fastest-way-to-horizontally-sum-sse-unsigned-byte-vector
209
+ inline static OJ_TARGET_SSE42 uint32_t _mm_sum_epu8(const __m128i v) {
210
+ __m128i vsum = _mm_sad_epu8(v, _mm_setzero_si128());
211
+ return _mm_cvtsi128_si32(vsum) + _mm_extract_epi16(vsum, 4);
212
+ }
213
+
214
+ inline static OJ_TARGET_SSE42 size_t hibit_friendly_size_sse42(const uint8_t *str, size_t len) {
215
+ size_t size = 0;
216
+ size_t i = 0;
217
+
218
+ for (; i + sizeof(__m128i) <= len; i += sizeof(__m128i), str += sizeof(__m128i)) {
219
+ size += sizeof(__m128i);
220
+
221
+ __m128i chunk = _mm_loadu_si128((__m128i *)str);
222
+ __m128i tmp = vector_lookup_sse42(chunk, hibit_friendly_chars_sse42, 8);
223
+ size += _mm_sum_epu8(tmp);
224
+ }
225
+ size_t total = size + calculate_string_size(str, len - i, hibit_friendly_chars);
226
+ return total;
227
+ }
228
+
229
+ void OJ_TARGET_SSE42 initialize_sse42(void) {
230
+ for (int i = 0; i < 8; i++) {
231
+ hibit_friendly_chars_sse42[i] = _mm_sub_epi8(
232
+ _mm_loadu_si128((__m128i *)(hibit_friendly_chars + i * sizeof(__m128i))),
233
+ _mm_set1_epi8('1'));
234
+ }
235
+ }
236
+
237
+ #else
238
+
239
+ #define SIMD_TARGET
240
+
241
+ #endif /* HAVE_SIMD_SSE4_2 */
242
+
204
243
  inline static size_t hibit_friendly_size(const uint8_t *str, size_t len) {
205
244
  #ifdef HAVE_SIMD_NEON
206
245
  size_t size = 0;
@@ -218,8 +257,38 @@ inline static size_t hibit_friendly_size(const uint8_t *str, size_t len) {
218
257
  size += tmp;
219
258
  }
220
259
 
260
+ #ifdef HAVE_FAST_MEMCPY
261
+ size_t total = 0;
262
+ if (len - i > 4) {
263
+ size += (len - i);
264
+ unsigned char buf[sizeof(uint8x16_t)];
265
+ memset(buf, ' ', sizeof(buf));
266
+ fast_memcpy16(buf, str, len - i);
267
+
268
+ uint8x16_t chunk = vld1q_u8((const unsigned char *)buf);
269
+ uint8x16_t tmp1 = vqtbl4q_u8(hibit_friendly_chars_neon[0], chunk);
270
+ uint8x16_t tmp2 = vqtbl4q_u8(hibit_friendly_chars_neon[1], veorq_u8(chunk, vdupq_n_u8(0x40)));
271
+ uint8x16_t result = vorrq_u8(tmp1, tmp2);
272
+ uint8_t tmp = vaddvq_u8(result);
273
+
274
+ size += tmp;
275
+
276
+ total = size;
277
+ } else {
278
+ total = size + calculate_string_size(str, len - i, hibit_friendly_chars);
279
+ }
280
+ #else
221
281
  size_t total = size + calculate_string_size(str, len - i, hibit_friendly_chars);
282
+ #endif
283
+
222
284
  return total;
285
+ #elif defined(HAVE_SIMD_SSE4_2)
286
+ if (SIMD_Impl == SIMD_SSE42) {
287
+ if (len >= sizeof(__m128i)) {
288
+ return hibit_friendly_size_sse42(str, len);
289
+ }
290
+ }
291
+ return calculate_string_size(str, len, hibit_friendly_chars);
223
292
  #else
224
293
  return calculate_string_size(str, len, hibit_friendly_chars);
225
294
  #endif
@@ -252,38 +321,46 @@ inline static size_t hixss_friendly_size(const uint8_t *str, size_t len) {
252
321
  }
253
322
 
254
323
  inline static long rails_xss_friendly_size(const uint8_t *str, size_t len) {
255
- long size = 0;
256
- uint8_t hi = 0;
324
+ long size = 0;
325
+ uint32_t hi = 0;
257
326
 
258
327
  #ifdef HAVE_SIMD_NEON
259
328
  size_t i = 0;
260
329
 
261
- uint8x16_t has_some_hibit = vdupq_n_u8(0);
262
- uint8x16_t hibit = vdupq_n_u8(0x80);
263
- for (; i + sizeof(uint8x16_t) <= len; i += sizeof(uint8x16_t), str += sizeof(uint8x16_t)) {
264
- size += sizeof(uint8x16_t);
330
+ if (len >= sizeof(uint8x16_t)) {
331
+ uint8x16_t has_some_hibit = vdupq_n_u8(0);
332
+ uint8x16_t hibit = vdupq_n_u8(0x80);
265
333
 
266
- uint8x16_t chunk = vld1q_u8(str);
334
+ for (; i + sizeof(uint8x16_t) <= len; i += sizeof(uint8x16_t), str += sizeof(uint8x16_t)) {
335
+ size += sizeof(uint8x16_t);
267
336
 
268
- // Check to see if any of these bytes have the high bit set.
269
- has_some_hibit = vorrq_u8(has_some_hibit, vandq_u8(chunk, hibit));
337
+ uint8x16_t chunk = vld1q_u8(str);
270
338
 
271
- uint8x16_t tmp1 = vqtbl4q_u8(rails_xss_friendly_chars_neon[0], chunk);
272
- uint8x16_t tmp2 = vqtbl4q_u8(rails_xss_friendly_chars_neon[1], veorq_u8(chunk, vdupq_n_u8(0x40)));
273
- uint8x16_t tmp3 = vqtbl4q_u8(rails_xss_friendly_chars_neon[2], veorq_u8(chunk, vdupq_n_u8(0x80)));
274
- uint8x16_t tmp4 = vqtbl4q_u8(rails_xss_friendly_chars_neon[3], veorq_u8(chunk, vdupq_n_u8(0xc0)));
275
- uint8x16_t result = vorrq_u8(tmp4, vorrq_u8(tmp3, vorrq_u8(tmp1, tmp2)));
276
- uint8_t tmp = vaddvq_u8(result);
277
- size += tmp;
339
+ // Check to see if any of these bytes have the high bit set.
340
+ has_some_hibit = vorrq_u8(has_some_hibit, vandq_u8(chunk, hibit));
341
+
342
+ uint8x16_t tmp1 = vqtbl4q_u8(rails_xss_friendly_chars_neon[0], chunk);
343
+ uint8x16_t tmp2 = vqtbl4q_u8(rails_xss_friendly_chars_neon[1], veorq_u8(chunk, vdupq_n_u8(0x40)));
344
+ uint8x16_t tmp3 = vqtbl4q_u8(rails_xss_friendly_chars_neon[2], veorq_u8(chunk, vdupq_n_u8(0x80)));
345
+ uint8x16_t tmp4 = vqtbl4q_u8(rails_xss_friendly_chars_neon[3], veorq_u8(chunk, vdupq_n_u8(0xc0)));
346
+ uint8x16_t result = vorrq_u8(tmp4, vorrq_u8(tmp3, vorrq_u8(tmp1, tmp2)));
347
+ uint8_t tmp = vaddvq_u8(result);
348
+ size += tmp;
349
+ }
350
+
351
+ // 'hi' should be set if any of the bytes we processed have the high bit set. It doesn't matter which ones.
352
+ hi = vmaxvq_u8(has_some_hibit) != 0;
278
353
  }
279
354
 
280
- // 'hi' should be set if any of the bytes we processed have the high bit set. It doesn't matter which ones.
281
- hi = vmaxvq_u8(has_some_hibit) != 0;
355
+ size_t len_remaining = len - i;
282
356
 
283
357
  for (; i < len; str++, i++) {
284
- size += rails_xss_friendly_chars[*str] - '0';
358
+ size += rails_xss_friendly_chars[*str];
285
359
  hi |= *str & 0x80;
286
360
  }
361
+
362
+ size -= (len_remaining * ((size_t)'0'));
363
+
287
364
  if (0 == hi) {
288
365
  return size;
289
366
  }
@@ -302,37 +379,43 @@ inline static long rails_xss_friendly_size(const uint8_t *str, size_t len) {
302
379
  }
303
380
 
304
381
  inline static size_t rails_friendly_size(const uint8_t *str, size_t len) {
305
- long size = 0;
306
- uint8_t hi = 0;
382
+ long size = 0;
383
+ uint32_t hi = 0;
307
384
  #ifdef HAVE_SIMD_NEON
308
- size_t i = 0;
385
+ size_t i = 0;
386
+ long extra = 0;
309
387
 
310
- uint8x16_t has_some_hibit = vdupq_n_u8(0);
311
- uint8x16_t hibit = vdupq_n_u8(0x80);
388
+ if (len >= sizeof(uint8x16_t)) {
389
+ uint8x16_t has_some_hibit = vdupq_n_u8(0);
390
+ uint8x16_t hibit = vdupq_n_u8(0x80);
312
391
 
313
- for (; i + sizeof(uint8x16_t) <= len; i += sizeof(uint8x16_t), str += sizeof(uint8x16_t)) {
314
- size += sizeof(uint8x16_t);
392
+ for (; i + sizeof(uint8x16_t) <= len; i += sizeof(uint8x16_t), str += sizeof(uint8x16_t)) {
393
+ size += sizeof(uint8x16_t);
315
394
 
316
- // See https://lemire.me/blog/2019/07/23/arbitrary-byte-to-byte-maps-using-arm-neon/
317
- uint8x16_t chunk = vld1q_u8(str);
395
+ // See https://lemire.me/blog/2019/07/23/arbitrary-byte-to-byte-maps-using-arm-neon/
396
+ uint8x16_t chunk = vld1q_u8(str);
318
397
 
319
- // Check to see if any of these bytes have the high bit set.
320
- has_some_hibit = vorrq_u8(has_some_hibit, vandq_u8(chunk, hibit));
398
+ // Check to see if any of these bytes have the high bit set.
399
+ has_some_hibit = vorrq_u8(has_some_hibit, vandq_u8(chunk, hibit));
321
400
 
322
- uint8x16_t tmp1 = vqtbl4q_u8(rails_friendly_chars_neon[0], chunk);
323
- uint8x16_t tmp2 = vqtbl4q_u8(rails_friendly_chars_neon[1], veorq_u8(chunk, vdupq_n_u8(0x40)));
324
- uint8x16_t result = vorrq_u8(tmp1, tmp2);
325
- uint8_t tmp = vaddvq_u8(result);
326
- size += tmp;
327
- }
401
+ uint8x16_t tmp1 = vqtbl4q_u8(rails_friendly_chars_neon[0], chunk);
402
+ uint8x16_t tmp2 = vqtbl4q_u8(rails_friendly_chars_neon[1], veorq_u8(chunk, vdupq_n_u8(0x40)));
403
+ uint8x16_t result = vorrq_u8(tmp1, tmp2);
404
+ uint8_t tmp = vaddvq_u8(result);
405
+ size += tmp;
406
+ }
328
407
 
329
- // 'hi' should be set if any of the bytes we processed have the high bit set. It doesn't matter which ones.
330
- hi = vmaxvq_u8(has_some_hibit) != 0;
408
+ // 'hi' should be set if any of the bytes we processed have the high bit set. It doesn't matter which ones.
409
+ hi = vmaxvq_u8(has_some_hibit) != 0;
410
+ }
331
411
 
332
- for (; i < len; str++, i++) {
333
- size += rails_friendly_chars[*str] - '0';
412
+ for (; i < len; str++, i++, extra++) {
413
+ size += rails_friendly_chars[*str];
334
414
  hi |= *str & 0x80;
335
415
  }
416
+
417
+ size -= (extra * ((size_t)'0'));
418
+
336
419
  if (0 == hi) {
337
420
  return size;
338
421
  }
@@ -896,22 +979,23 @@ void oj_dump_raw_json(VALUE obj, int depth, Out out) {
896
979
  }
897
980
  }
898
981
 
982
+ #if defined(__clang__) || defined(__GNUC__)
983
+ #define FORCE_INLINE __attribute__((always_inline))
984
+ #else
985
+ #define FORCE_INLINE
986
+ #endif
987
+
899
988
  #ifdef HAVE_SIMD_NEON
900
989
  typedef struct _neon_match_result {
901
990
  uint8x16_t needs_escape;
902
991
  bool has_some_hibit;
903
992
  bool do_unicode_validation;
993
+ uint64_t escape_mask;
904
994
  } neon_match_result;
905
995
 
906
- #if defined(__clang__) || defined(__GNUC__)
907
- #define FORCE_INLINE __attribute__((always_inline))
908
- #else
909
- #define FORCE_INLINE
910
- #endif
911
-
912
996
  static inline FORCE_INLINE neon_match_result
913
997
  neon_update(const char *str, uint8x16x4_t *cmap_neon, int neon_table_size, bool do_unicode_validation, bool has_hi) {
914
- neon_match_result result = {.has_some_hibit = false, .do_unicode_validation = false};
998
+ neon_match_result result = {.has_some_hibit = false, .do_unicode_validation = false, .escape_mask = 0};
915
999
 
916
1000
  uint8x16_t chunk = vld1q_u8((const unsigned char *)str);
917
1001
  uint8x16_t tmp1 = vqtbl4q_u8(cmap_neon[0], chunk);
@@ -927,17 +1011,122 @@ neon_update(const char *str, uint8x16x4_t *cmap_neon, int neon_table_size, bool
927
1011
  result.has_some_hibit = vmaxvq_u8(has_some_hibit) != 0;
928
1012
  result.do_unicode_validation = has_hi && do_unicode_validation && result.has_some_hibit;
929
1013
  }
1014
+ const uint8x8_t res = vshrn_n_u16(vreinterpretq_u16_u8(vmvnq_u8(vceqq_u8(result.needs_escape, vdupq_n_u8(0)))), 4);
1015
+ const uint64_t mask = vget_lane_u64(vreinterpret_u64_u8(res), 0);
1016
+ result.escape_mask = mask & 0x8888888888888888ull;
1017
+ return result;
1018
+ }
1019
+
1020
+ #elif defined(HAVE_SIMD_SSE4_2)
1021
+ typedef struct _sse42_match_result {
1022
+ __m128i actions;
1023
+ bool needs_escape;
1024
+ int escape_mask;
1025
+ bool has_some_hibit;
1026
+ bool do_unicode_validation;
1027
+ } sse42_match_result;
1028
+
1029
+ static inline OJ_TARGET_SSE42 sse42_match_result
1030
+ sse42_update(const char *str, __m128i *cmap_sse42, int sse42_tab_size, bool do_unicode_validation, bool has_hi) {
1031
+ sse42_match_result result = {.has_some_hibit = false, .do_unicode_validation = false};
1032
+
1033
+ __m128i chunk = _mm_loadu_si128((__m128i *)str);
1034
+ __m128i actions = vector_lookup_sse42(chunk, cmap_sse42, sse42_tab_size);
1035
+ __m128i needs_escape = _mm_xor_si128(_mm_cmpeq_epi8(actions, _mm_setzero_si128()), _mm_set1_epi8(0xFF));
1036
+ result.actions = _mm_add_epi8(actions, _mm_set1_epi8('1'));
1037
+
1038
+ result.escape_mask = _mm_movemask_epi8(needs_escape);
1039
+ result.needs_escape = result.escape_mask != 0;
1040
+ if (has_hi && do_unicode_validation) {
1041
+ __m128i has_some_hibit = _mm_and_si128(chunk, _mm_set1_epi8(0x80));
1042
+ result.has_some_hibit = _mm_movemask_epi8(has_some_hibit) != 0;
1043
+ result.do_unicode_validation = has_hi && do_unicode_validation && result.has_some_hibit;
1044
+ }
930
1045
  return result;
931
1046
  }
932
1047
 
933
1048
  #endif /* HAVE_SIMD_NEON */
934
1049
 
1050
+ static inline FORCE_INLINE const char *process_character(char action,
1051
+ const char *str,
1052
+ const char *end,
1053
+ Out out,
1054
+ const char *orig,
1055
+ bool do_unicode_validation,
1056
+ const char **check_start_) {
1057
+ const char *check_start = *check_start_;
1058
+ switch (action) {
1059
+ case '1':
1060
+ if (do_unicode_validation && check_start <= str) {
1061
+ if (0 != (0x80 & (uint8_t)*str)) {
1062
+ if (0xC0 == (0xC0 & (uint8_t)*str)) {
1063
+ *check_start_ = check_unicode(str, end, orig);
1064
+ } else {
1065
+ raise_invalid_unicode(orig, (int)(end - orig), (int)(str - orig));
1066
+ }
1067
+ }
1068
+ }
1069
+ *out->cur++ = *str;
1070
+ break;
1071
+ case '2':
1072
+ *out->cur++ = '\\';
1073
+ switch (*str) {
1074
+ case '\\': *out->cur++ = '\\'; break;
1075
+ case '\b': *out->cur++ = 'b'; break;
1076
+ case '\t': *out->cur++ = 't'; break;
1077
+ case '\n': *out->cur++ = 'n'; break;
1078
+ case '\f': *out->cur++ = 'f'; break;
1079
+ case '\r': *out->cur++ = 'r'; break;
1080
+ default: *out->cur++ = *str; break;
1081
+ }
1082
+ break;
1083
+ case '3': // Unicode
1084
+ if (0xe2 == (uint8_t)*str && do_unicode_validation && 2 <= end - str) {
1085
+ if (0x80 == (uint8_t)str[1] && (0xa8 == (uint8_t)str[2] || 0xa9 == (uint8_t)str[2])) {
1086
+ str = dump_unicode(str, end, out, orig);
1087
+ } else {
1088
+ *check_start_ = check_unicode(str, end, orig);
1089
+ *out->cur++ = *str;
1090
+ }
1091
+ break;
1092
+ }
1093
+ str = dump_unicode(str, end, out, orig);
1094
+ break;
1095
+ case '6': // control characters
1096
+ if (*(uint8_t *)str < 0x80) {
1097
+ if (0 == (uint8_t)*str && out->opts->dump_opts.omit_null_byte) {
1098
+ break;
1099
+ }
1100
+ APPEND_CHARS(out->cur, "\\u00", 4);
1101
+ dump_hex((uint8_t)*str, out);
1102
+ } else {
1103
+ if (0xe2 == (uint8_t)*str && do_unicode_validation && 2 <= end - str) {
1104
+ if (0x80 == (uint8_t)str[1] && (0xa8 == (uint8_t)str[2] || 0xa9 == (uint8_t)str[2])) {
1105
+ str = dump_unicode(str, end, out, orig);
1106
+ } else {
1107
+ *check_start_ = check_unicode(str, end, orig);
1108
+ *out->cur++ = *str;
1109
+ }
1110
+ break;
1111
+ }
1112
+ str = dump_unicode(str, end, out, orig);
1113
+ }
1114
+ break;
1115
+ default: break; // ignore, should never happen if the table is correct
1116
+ }
1117
+
1118
+ return str;
1119
+ }
1120
+
935
1121
  void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out out) {
936
1122
  size_t size;
937
1123
  char *cmap;
938
1124
  #ifdef HAVE_SIMD_NEON
939
- uint8x16x4_t *cmap_neon = NULL;
940
- int neon_table_size;
1125
+ uint8x16x4_t *cmap_neon = NULL;
1126
+ int neon_table_size = 0;
1127
+ #elif defined(HAVE_SIMD_SSE4_2)
1128
+ __m128i *cmap_sse42 = NULL;
1129
+ int sse42_tab_size;
941
1130
  #endif /* HAVE_SIMD_NEON */
942
1131
  const char *orig = str;
943
1132
  bool has_hi = false;
@@ -1006,6 +1195,9 @@ void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out ou
1006
1195
  #ifdef HAVE_SIMD_NEON
1007
1196
  cmap_neon = hibit_friendly_chars_neon;
1008
1197
  neon_table_size = 2;
1198
+ #elif defined(HAVE_SIMD_SSE4_2)
1199
+ cmap_sse42 = hibit_friendly_chars_sse42;
1200
+ sse42_tab_size = 8;
1009
1201
  #endif /* HAVE_NEON_SIMD */
1010
1202
  size = hibit_friendly_size((uint8_t *)str, cnt);
1011
1203
  }
@@ -1024,7 +1216,16 @@ void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out ou
1024
1216
  if (is_sym) {
1025
1217
  *out->cur++ = ':';
1026
1218
  }
1219
+ #ifdef HAVE_FAST_MEMCPY
1220
+ if (cnt <= 16) {
1221
+ fast_memcpy16(out->cur, str, cnt);
1222
+ out->cur += size;
1223
+ } else {
1224
+ APPEND_CHARS(out->cur, str, cnt);
1225
+ }
1226
+ #else
1027
1227
  APPEND_CHARS(out->cur, str, cnt);
1228
+ #endif
1028
1229
  *out->cur++ = '"';
1029
1230
  } else {
1030
1231
  const char *end = str + cnt;
@@ -1033,175 +1234,164 @@ void oj_dump_cstr(const char *str, size_t cnt, bool is_sym, bool escape1, Out ou
1033
1234
  if (is_sym) {
1034
1235
  *out->cur++ = ':';
1035
1236
  }
1036
- #ifdef HAVE_SIMD_NEON
1037
- const char *chunk_start;
1038
- const char *chunk_end;
1039
- const char *cursor = str;
1040
- int neon_state = (cmap_neon != NULL) ? 1 : 4;
1041
- char matches[16];
1042
- bool do_hi_validation = false;
1043
- // uint64_t neon_match_mask = 0;
1237
+
1238
+ #if defined(HAVE_SIMD_NEON) || defined(HAVE_SIMD_SSE4_2)
1239
+
1044
1240
  #define SEARCH_FLUSH \
1045
1241
  if (str > cursor) { \
1046
1242
  APPEND_CHARS(out->cur, cursor, str - cursor); \
1047
1243
  cursor = str; \
1048
1244
  }
1049
1245
 
1050
- loop:
1051
- #endif /* HAVE_SIMD_NEON */
1052
- for (; str < end; str++) {
1053
- char action = 0;
1246
+ const char *chunk_start;
1247
+ const char *chunk_end;
1248
+ const char *cursor = str;
1249
+ char matches[16];
1250
+ #endif /* HAVE_SIMD_NEON || HAVE_SIMD_SSE4_2 */
1251
+
1252
+ #if defined(HAVE_SIMD_NEON)
1253
+ bool use_simd = (cmap_neon != NULL && cnt >= (sizeof(uint8x16_t))) ? true : false;
1254
+ #elif defined(HAVE_SIMD_SSE4_2)
1255
+ bool use_simd = false;
1256
+ if (SIMD_Impl == SIMD_SSE42) {
1257
+ use_simd = (cmap_sse42 != NULL && cnt >= (sizeof(__m128i))) ? true : false;
1258
+ }
1259
+ #endif
1260
+
1054
1261
  #ifdef HAVE_SIMD_NEON
1055
- /* neon_state:
1056
- * 1: Scanning for matches. There must be at least
1057
- sizeof(uint8x16_t) bytes of input data to use SIMD and
1058
- cmap_neon must be non-null.
1059
- * 2: Matches have been found. Will set str to the position of the
1060
- * next match and set the state to 3.
1061
- * If there are no more matches it will transition to state 1.
1062
- * 4: Fallback to the scalar algorithm. Not enough data to use
1063
- * SIMD.
1064
- */
1065
- #define NEON_SET_STATE(state) \
1066
- neon_state = state; \
1067
- goto loop;
1068
- #define NEON_RETURN_TO_STATE(state) neon_state = state;
1069
- switch (neon_state) {
1070
- case 1: {
1071
- while (true) {
1262
+
1263
+ #ifdef HAVE_FAST_MEMCPY
1264
+ #define APPEND_CHARS_SMALL(dst, src, length) \
1265
+ fast_memcpy16((dst), (src), (length)); \
1266
+ (dst) += (length);
1267
+ #define MEMCPY16 fast_memcpy16
1268
+ #else
1269
+ #define APPEND_CHARS_SMALL(dst, src, length) APPEND_CHARS(dst, str, length);
1270
+ #define MEMCPY16 memcpy
1271
+ #endif
1272
+
1273
+ if (use_simd) {
1274
+ while (str < end) {
1275
+ const char *chunk_ptr = NULL;
1276
+ if (str + sizeof(uint8x16_t) <= end) {
1277
+ chunk_ptr = str;
1278
+ chunk_start = str;
1279
+ chunk_end = str + sizeof(uint8x16_t);
1280
+ } else if ((end - str) >= SIMD_MINIMUM_THRESHOLD) {
1281
+ memset(out->cur, ' ', sizeof(uint8x16_t));
1282
+ MEMCPY16(out->cur, str, (end - str));
1283
+ chunk_ptr = out->cur;
1284
+ chunk_start = str;
1285
+ chunk_end = end;
1286
+ } else {
1287
+ break;
1288
+ }
1289
+ neon_match_result result = neon_update(chunk_ptr,
1290
+ cmap_neon,
1291
+ neon_table_size,
1292
+ do_unicode_validation,
1293
+ has_hi);
1294
+ if ((result.do_unicode_validation) || result.escape_mask != 0) {
1295
+ SEARCH_FLUSH;
1296
+ bool process_each = result.do_unicode_validation;
1297
+ uint8x16_t actions = vaddq_u8(result.needs_escape, vdupq_n_u8('1'));
1298
+ vst1q_u8((unsigned char *)matches, actions);
1299
+ // If no byte in this chunk had the high bit set then we can skip
1300
+ // all of the '1' bytes by directly copying them to the output.
1301
+ if (!process_each) {
1302
+ while (result.escape_mask) {
1303
+ int esc_pos = OJ_CTZ64(result.escape_mask) >> 2;
1304
+ long run_len = esc_pos - (str - chunk_start);
1305
+ if (run_len > 0) {
1306
+ APPEND_CHARS_SMALL(out->cur, str, run_len);
1307
+ str += run_len;
1308
+ }
1309
+ str = process_character(matches[esc_pos],
1310
+ str,
1311
+ end,
1312
+ out,
1313
+ orig,
1314
+ do_unicode_validation,
1315
+ &check_start);
1316
+ str++;
1317
+ result.escape_mask &= result.escape_mask - 1;
1318
+ }
1319
+ if (str < chunk_end) {
1320
+ APPEND_CHARS_SMALL(out->cur, str, chunk_end - str);
1321
+ str = chunk_end;
1322
+ }
1323
+ } else {
1324
+ while (str < chunk_end) {
1325
+ long match_index = str - chunk_start;
1326
+ str = process_character(matches[match_index],
1327
+ str,
1328
+ end,
1329
+ out,
1330
+ orig,
1331
+ do_unicode_validation,
1332
+ &check_start);
1333
+ str++;
1334
+ }
1335
+ }
1336
+ cursor = str;
1337
+ continue;
1338
+ }
1339
+ str = chunk_end;
1340
+ }
1341
+ SEARCH_FLUSH;
1342
+ }
1343
+ #endif
1344
+
1345
+ #ifdef HAVE_SIMD_SSE4_2
1346
+ if (SIMD_Impl == SIMD_SSE42) {
1347
+ if (use_simd) {
1348
+ while (str < end) {
1072
1349
  const char *chunk_ptr = NULL;
1073
- if (str + sizeof(uint8x16_t) <= end) {
1350
+ if (str + sizeof(__m128i) <= end) {
1074
1351
  chunk_ptr = str;
1075
1352
  chunk_start = str;
1076
- chunk_end = str + sizeof(uint8x16_t);
1353
+ chunk_end = str + sizeof(__m128i);
1077
1354
  } else if ((end - str) >= SIMD_MINIMUM_THRESHOLD) {
1078
- memset(out->cur, 'A', sizeof(uint8x16_t));
1355
+ memset(out->cur, 'A', sizeof(__m128i));
1079
1356
  memcpy(out->cur, str, (end - str));
1080
1357
  chunk_ptr = out->cur;
1081
1358
  chunk_start = str;
1082
1359
  chunk_end = end;
1083
1360
  } else {
1084
- SEARCH_FLUSH;
1085
- NEON_SET_STATE(4);
1086
- break; /* Unreachable */
1361
+ break;
1087
1362
  }
1088
- neon_match_result result = neon_update(chunk_ptr,
1089
- cmap_neon,
1090
- neon_table_size,
1091
- do_unicode_validation,
1092
- has_hi);
1093
- if ((result.do_unicode_validation) || vmaxvq_u8(result.needs_escape) != 0) {
1363
+ sse42_match_result result = sse42_update(chunk_ptr,
1364
+ cmap_sse42,
1365
+ sse42_tab_size,
1366
+ do_unicode_validation,
1367
+ has_hi);
1368
+ if ((result.do_unicode_validation) || result.needs_escape) {
1094
1369
  SEARCH_FLUSH;
1095
- uint8x16_t actions = vaddq_u8(result.needs_escape, vdupq_n_u8('1'));
1096
- do_hi_validation = result.do_unicode_validation;
1097
- vst1q_u8((unsigned char *)matches, actions);
1098
- NEON_SET_STATE(2);
1099
- break; /* Unreachable */
1370
+ _mm_storeu_si128((__m128i *)matches, result.actions);
1371
+ while (str < chunk_end) {
1372
+ long match_index = str - chunk_start;
1373
+ str = process_character(matches[match_index],
1374
+ str,
1375
+ end,
1376
+ out,
1377
+ orig,
1378
+ do_unicode_validation,
1379
+ &check_start);
1380
+ str++;
1381
+ }
1382
+ cursor = str;
1383
+ continue;
1100
1384
  }
1101
1385
  str = chunk_end;
1102
1386
  }
1103
- // We must have run out of data to use SIMD. Go to state 4.
1104
1387
  SEARCH_FLUSH;
1105
- NEON_SET_STATE(4);
1106
- } break;
1107
- case 3:
1108
- cursor = str;
1109
- // This fall through is intentional. We return to state 3 after we process
1110
- // a byte (or multiple). We return to this state to ensure the cursor is
1111
- // pointing to the correct location. We then resume looking for matches
1112
- // within the previously processed chunk.
1113
- case 2:
1114
- if (str >= chunk_end) {
1115
- NEON_SET_STATE(1);
1116
- }
1117
- if (!do_hi_validation) {
1118
- long i = str - chunk_start;
1119
- for (; str < chunk_end; i++) {
1120
- if ((action = matches[i]) != '1') {
1121
- break;
1122
- }
1123
- *out->cur++ = *str++;
1124
- }
1125
- // The loop above may have advanced str and directly output them to out->cur.
1126
- // Ensure cursor is set appropriately.
1127
- cursor = str;
1128
- if (str >= chunk_end) {
1129
- // We must have advanced past the end... we are done.
1130
- NEON_SET_STATE(1);
1131
- }
1132
- } else {
1133
- long match_index = str - chunk_start;
1134
- action = matches[match_index];
1135
- }
1136
- NEON_RETURN_TO_STATE(3);
1137
- break;
1138
- case 4: action = cmap[(uint8_t)*str];
1139
- }
1140
- #undef NEON_SET_STATE
1141
- #undef NEON_RETURN_TO_STATE
1142
- #else
1143
- action = cmap[(uint8_t)*str];
1144
- #endif /* HAVE_SIMD_NEON */
1145
- switch (action) {
1146
- case '1':
1147
- if (do_unicode_validation && check_start <= str) {
1148
- if (0 != (0x80 & (uint8_t)*str)) {
1149
- if (0xC0 == (0xC0 & (uint8_t)*str)) {
1150
- check_start = check_unicode(str, end, orig);
1151
- } else {
1152
- raise_invalid_unicode(orig, (int)(end - orig), (int)(str - orig));
1153
- }
1154
- }
1155
- }
1156
- *out->cur++ = *str;
1157
- break;
1158
- case '2':
1159
- *out->cur++ = '\\';
1160
- switch (*str) {
1161
- case '\\': *out->cur++ = '\\'; break;
1162
- case '\b': *out->cur++ = 'b'; break;
1163
- case '\t': *out->cur++ = 't'; break;
1164
- case '\n': *out->cur++ = 'n'; break;
1165
- case '\f': *out->cur++ = 'f'; break;
1166
- case '\r': *out->cur++ = 'r'; break;
1167
- default: *out->cur++ = *str; break;
1168
- }
1169
- break;
1170
- case '3': // Unicode
1171
- if (0xe2 == (uint8_t)*str && do_unicode_validation && 2 <= end - str) {
1172
- if (0x80 == (uint8_t)str[1] && (0xa8 == (uint8_t)str[2] || 0xa9 == (uint8_t)str[2])) {
1173
- str = dump_unicode(str, end, out, orig);
1174
- } else {
1175
- check_start = check_unicode(str, end, orig);
1176
- *out->cur++ = *str;
1177
- }
1178
- break;
1179
- }
1180
- str = dump_unicode(str, end, out, orig);
1181
- break;
1182
- case '6': // control characters
1183
- if (*(uint8_t *)str < 0x80) {
1184
- if (0 == (uint8_t)*str && out->opts->dump_opts.omit_null_byte) {
1185
- break;
1186
- }
1187
- APPEND_CHARS(out->cur, "\\u00", 4);
1188
- dump_hex((uint8_t)*str, out);
1189
- } else {
1190
- if (0xe2 == (uint8_t)*str && do_unicode_validation && 2 <= end - str) {
1191
- if (0x80 == (uint8_t)str[1] && (0xa8 == (uint8_t)str[2] || 0xa9 == (uint8_t)str[2])) {
1192
- str = dump_unicode(str, end, out, orig);
1193
- } else {
1194
- check_start = check_unicode(str, end, orig);
1195
- *out->cur++ = *str;
1196
- }
1197
- break;
1198
- }
1199
- str = dump_unicode(str, end, out, orig);
1200
- }
1201
- break;
1202
- default: break; // ignore, should never happen if the table is correct
1203
1388
  }
1204
1389
  }
1390
+ #endif /* HAVE_SIMD_SSE4_2 */
1391
+
1392
+ for (; str < end; str++) {
1393
+ str = process_character(cmap[(uint8_t)*str], str, end, out, orig, do_unicode_validation, &check_start);
1394
+ }
1205
1395
  *out->cur++ = '"';
1206
1396
  }
1207
1397
  if (do_unicode_validation && 0 < str - orig && 0 != (0x80 & *(str - 1))) {
@@ -1266,10 +1456,11 @@ void oj_dump_raw(const char *str, size_t cnt, Out out) {
1266
1456
  }
1267
1457
 
1268
1458
  void oj_out_init(Out out) {
1269
- out->buf = out->stack_buffer;
1270
- out->cur = out->buf;
1271
- out->end = out->buf + sizeof(out->stack_buffer) - BUFFER_EXTRA;
1272
- out->allocated = false;
1459
+ out->buf = out->stack_buffer;
1460
+ out->cur = out->buf;
1461
+ out->end = out->buf + sizeof(out->stack_buffer) - BUFFER_EXTRA;
1462
+ out->allocated = false;
1463
+ out->key_filter_off = false;
1273
1464
  }
1274
1465
 
1275
1466
  void oj_out_free(Out out) {
@@ -1541,6 +1732,11 @@ void oj_dump_float(VALUE obj, int depth, Out out, bool as_ok) {
1541
1732
  size_t oj_dump_float_printf(char *buf, size_t blen, VALUE obj, double d, const char *format) {
1542
1733
  size_t cnt = snprintf(buf, blen, format, d);
1543
1734
 
1735
+ // snprintf() returns the length the output would have needed, which is
1736
+ // more than it wrote when the format asks for more room than buf has.
1737
+ if (blen <= cnt) {
1738
+ cnt = blen - 1;
1739
+ }
1544
1740
  // Round off issues at 16 significant digits so check for obvious ones of
1545
1741
  // 0001 and 9999.
1546
1742
  if (17 <= cnt && (0 == strcmp("0001", buf + cnt - 4) || 0 == strcmp("9999", buf + cnt - 4))) {
@@ -1551,3 +1747,34 @@ size_t oj_dump_float_printf(char *buf, size_t blen, VALUE obj, double d, const c
1551
1747
  }
1552
1748
  return cnt;
1553
1749
  }
1750
+
1751
+ // The only and except lists are the keys joined with a ':' and wrapped in one
1752
+ // at each end, so a key is in a list when ":key:" is in it. Matching in place
1753
+ // finds what searching the list for a copy of the key with the colons on it
1754
+ // found, without a buffer sized by the key.
1755
+ static bool key_listed(const char *list, const char *key, size_t klen) {
1756
+ const char *s;
1757
+
1758
+ for (s = list; NULL != (s = strchr(s, ':')); s++) {
1759
+ if (0 == strncmp(s + 1, key, klen) && ':' == s[klen + 1]) {
1760
+ return true;
1761
+ }
1762
+ }
1763
+ return false;
1764
+ }
1765
+
1766
+ bool oj_key_skip(VALUE key, const char *only, const char *except) {
1767
+ const char *skey;
1768
+
1769
+ switch (rb_type(key)) {
1770
+ case RUBY_T_STRING: skey = StringValueCStr(key); break;
1771
+ case RUBY_T_SYMBOL: skey = rb_id2name(rb_sym2id(key)); break;
1772
+ default: skey = NULL; break;
1773
+ }
1774
+ if (NULL != skey && '\0' != *skey) {
1775
+ size_t klen = strlen(skey);
1776
+
1777
+ return ((NULL != only && !key_listed(only, skey, klen)) || (NULL != except && key_listed(except, skey, klen)));
1778
+ }
1779
+ return NULL != only;
1780
+ }