Cache one match context per thread in the PCRE2 shim

ober

2d0a8d8d3f86b9c86114c15e83062acc0aa6e453

diff --git a/jerboa_pcre2_shim.c b/jerboa_pcre2_shim.c
index d2058ef..3547fb8 100644
--- a/jerboa_pcre2_shim.c
+++ b/jerboa_pcre2_shim.c
@@ -28,16 +28,31 @@ int jerboa_pcre2_set_match_limits(uint32_t match_limit, uint32_t depth_limit)
     return 0;
 }
 
-static pcre2_match_context_8* jerboa_pcre2_make_match_context(void)
+/* One cached match context per thread. A PCRE2 match context is reusable
+ * across matches, so creating and freeing one per call is wasted work; the
+ * context is (re)configured only when the thread-local limits change. It is
+ * intentionally retained until thread exit (a single context per thread). */
+static __thread pcre2_match_context_8 *_ffi_match_ctx = NULL;
+static __thread uint32_t _ffi_ctx_match_limit = 0;
+static __thread uint32_t _ffi_ctx_depth_limit = 0;
+
+static pcre2_match_context_8* jerboa_pcre2_get_match_context(void)
 {
-    pcre2_match_context_8 *ctx = pcre2_match_context_create_8(NULL);
-    if (!ctx) return NULL;
-    if (pcre2_set_match_limit_8(ctx, _ffi_match_limit) < 0 ||
-        pcre2_set_depth_limit_8(ctx, _ffi_depth_limit) < 0) {
-        pcre2_match_context_free_8(ctx);
-        return NULL;
+    if (!_ffi_match_ctx) {
+        _ffi_match_ctx = pcre2_match_context_create_8(NULL);
+        if (!_ffi_match_ctx) return NULL;
+        _ffi_ctx_match_limit = 0;
+        _ffi_ctx_depth_limit = 0;
+    }
+    if (_ffi_ctx_match_limit != _ffi_match_limit ||
+        _ffi_ctx_depth_limit != _ffi_depth_limit) {
+        if (pcre2_set_match_limit_8(_ffi_match_ctx, _ffi_match_limit) < 0 ||
+            pcre2_set_depth_limit_8(_ffi_match_ctx, _ffi_depth_limit) < 0)
+            return NULL;
+        _ffi_ctx_match_limit = _ffi_match_limit;
+        _ffi_ctx_depth_limit = _ffi_depth_limit;
     }
-    return ctx;
+    return _ffi_match_ctx;
 }
 
 pcre2_code_8* jerboa_pcre2_compile(
@@ -83,11 +98,10 @@ int jerboa_pcre2_match(
     if (!code || (!subject && subject_length > 0) || !match_data) return PCRE2_ERROR_NULL;
     if (startoffset > subject_length) return PCRE2_ERROR_BADOFFSET;
 
-    ctx = jerboa_pcre2_make_match_context();
+    ctx = jerboa_pcre2_get_match_context();
     if (!ctx) return PCRE2_ERROR_NOMEMORY;
     rc = pcre2_match_8(code, (PCRE2_SPTR8)subject, subject_length,
                        startoffset, options, match_data, ctx);
-    pcre2_match_context_free_8(ctx);
     return rc;
 }
 
@@ -193,7 +207,7 @@ jerboa_pcre2_substitute_result_t* jerboa_pcre2_substitute_create(
         return result;
     }
 
-    ctx = jerboa_pcre2_make_match_context();
+    ctx = jerboa_pcre2_get_match_context();
     if (!ctx) {
         result->code = PCRE2_ERROR_NOMEMORY;
         return result;
@@ -202,7 +216,6 @@ jerboa_pcre2_substitute_result_t* jerboa_pcre2_substitute_create(
     capacity = subject_length + replacement_length + 256;
     result->data = (char*)malloc(capacity + 1);
     if (!result->data) {
-        pcre2_match_context_free_8(ctx);
         result->code = PCRE2_ERROR_NOMEMORY;
         return result;
     }
@@ -240,7 +253,6 @@ jerboa_pcre2_substitute_result_t* jerboa_pcre2_substitute_create(
         }
     }
 
-    pcre2_match_context_free_8(ctx);
     result->code = rc;
     if (rc >= 0) {
         result->length = outlen;
@@ -312,11 +324,10 @@ int jerboa_pcre2_jit_match(
     if (!code || (!subject && subject_length > 0) || !match_data) return PCRE2_ERROR_NULL;
     if (startoffset > subject_length) return PCRE2_ERROR_BADOFFSET;
 
-    ctx = jerboa_pcre2_make_match_context();
+    ctx = jerboa_pcre2_get_match_context();
     if (!ctx) return PCRE2_ERROR_NOMEMORY;
     rc = pcre2_jit_match_8(code, (PCRE2_SPTR8)subject, subject_length,
                            startoffset, options, match_data, ctx);
-    pcre2_match_context_free_8(ctx);
     return rc;
 }