From 9d2edad34a1823bab012f5d0593d461a9ec396fb Mon Sep 17 00:00:00 2001
From: Masahiko Sawada <sawada.mshk@gmail.com>
Date: Thu, 25 Jun 2026 10:03:44 -0700
Subject: [PATCH v4 1/2] Optimize UUID parse using SIMD.

Previously, string_to_uuid() decoded one byte at a time, calling
isxdigit() twice and strtoul() once for every pair of hexadecimal
digits.  That loop dominated the cost of uuid_in().

This commit adds a fast path for the two common shapes: a bare string
of 32 hexadecimal digits, and the canonical 8x-4x-4x-4x-12x form
(where "nx" means n hexadecimal digits), each optionally wrapped in
braces.  Both are compacted into 32 contiguous hexadecimal digits and
decoded with hex_decode_safe().  Any other shape, or any decoding
error, is handed off to the original scalar parser, now
string_to_uuid_scalar(), so the accepted grammar and the error
messages are unchanged.

hex_decode_safe() silently skips whitespace while the UUID grammar
does not, so a decode can succeed and still write fewer than UUID_LEN
bytes.  The fast path therefore treats a short result as a failure,
just like an error, and lets the scalar parser reject the input and
report the syntax error.

The fast path is deliberately not conditional on SIMD support.
hex_decode_safe() selects a vectorized or scalar implementation
itself, and even its scalar implementation is an order of magnitude
faster than decoding a byte at a time, so gating this on USE_NO_SIMD
would only penalize platforms that have neither SSE2 nor NEON.

Reviewed-by: Bharath Rupireddy <bharath.rupireddyforpostgres@gmail.com>
Reviewed-by: Haibo Yan <tristan.yim@gmail.com>
Discussion: https://postgr.es/m/CAD21AoCqeR4UQU77Q_yOMNNzJ7AVeiO5QZT+4HnzPm4Wm-e02Q@mail.gmail.com
---
 src/backend/utils/adt/uuid.c       | 94 ++++++++++++++++++++++++++++--
 src/test/regress/expected/uuid.out | 55 +++++++++++++++++
 src/test/regress/sql/uuid.sql      | 16 +++++
 3 files changed, 160 insertions(+), 5 deletions(-)

diff --git a/src/backend/utils/adt/uuid.c b/src/backend/utils/adt/uuid.c
index 246225a8735..3ecca6d3748 100644
--- a/src/backend/utils/adt/uuid.c
+++ b/src/backend/utils/adt/uuid.c
@@ -19,7 +19,9 @@
 #include "common/hashfn.h"
 #include "lib/hyperloglog.h"
 #include "libpq/pqformat.h"
+#include "nodes/miscnodes.h"
 #include "port/pg_bswap.h"
+#include "utils/builtins.h"
 #include "utils/fmgrprotos.h"
 #include "utils/guc.h"
 #include "utils/skipsupport.h"
@@ -139,13 +141,13 @@ uuid_out(PG_FUNCTION_ARGS)
 }
 
 /*
- * We allow UUIDs as a series of 32 hexadecimal digits with an optional dash
- * after each group of 4 hexadecimal digits, and optionally surrounded by {}.
- * (The canonical format 8x-4x-4x-4x-12x, where "nx" means n hexadecimal
- * digits, is the only one used for output.)
+ * Reference implementation of the UUID grammar, parsing one character at a
+ * time.  string_to_uuid() recognizes the common shapes more cheaply and
+ * defers to this function for everything else, so this is also the only
+ * place that reports a syntax error.
  */
 static void
-string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext)
+string_to_uuid_scalar(const char *source, pg_uuid_t *uuid, Node *escontext)
 {
 	const char *src = source;
 	bool		braces = false;
@@ -194,6 +196,88 @@ syntax_error:
 					"uuid", source)));
 }
 
+/*
+ * We allow UUIDs as a series of 32 hexadecimal digits with an optional dash
+ * after each group of 4 hexadecimal digits, and optionally surrounded by {}.
+ * (The canonical format 8x-4x-4x-4x-12x, where "nx" means n hexadecimal
+ * digits, is the only one used for output.)
+ *
+ * The two common shapes -- a bare string of 32 hexadecimal digits and the
+ * canonical form, each optionally wrapped in braces -- are compacted into 32
+ * contiguous hex digits and decoded with hex_decode_safe(), which is much
+ * faster than the character-at-a-time loop.  Any other shape, or any decoding
+ * error, is handed off to string_to_uuid_scalar() so that the accepted
+ * grammar and the error messages are unchanged.
+ *
+ * Note that this fast path is not conditional on SIMD support:
+ * hex_decode_safe() picks a vectorized or scalar implementation itself, and
+ * even its scalar implementation is far faster than string_to_uuid_scalar().
+ */
+static void
+string_to_uuid(const char *source, pg_uuid_t *uuid, Node *escontext)
+{
+	const char *body = source;
+	size_t		len = strlen(source);
+	const char *hexsrc = NULL;
+	char		hexbuf[32];
+	uint64		written;
+	ErrorSaveContext esctx = {T_ErrorSaveContext};
+
+	/* Strip one optional surrounding brace pair */
+	if (len >= 2 && source[0] == '{' && source[len - 1] == '}')
+	{
+		body = source + 1;
+		len -= 2;
+	}
+
+	if (len == 32)
+	{
+		/*
+		 * Body is already 32 contiguous hex digits -- decode straight from
+		 * the input. hex_decode_safe() reads exactly body[0..31], so it never
+		 * touches the trailing NULL or '}'.
+		 */
+		hexsrc = body;
+	}
+	else if (len == 36 && body[8] == '-' && body[13] == '-' &&
+			 body[18] == '-' && body[23] == '-')
+	{
+		/*
+		 * Canonical 8x-4x-4x-4x-12x form; compact them into hexbuf with
+		 * fixed-offset copies, dropping the dashes.
+		 */
+		memcpy(&hexbuf[0], &body[0], 8);
+		memcpy(&hexbuf[8], &body[9], 4);
+		memcpy(&hexbuf[12], &body[14], 4);
+		memcpy(&hexbuf[16], &body[19], 4);
+		memcpy(&hexbuf[20], &body[24], 12);
+		hexsrc = hexbuf;
+	}
+
+	if (hexsrc == NULL)
+	{
+		/* Uncommon shape; let the general parse handle it */
+		string_to_uuid_scalar(source, uuid, escontext);
+		return;
+	}
+
+	/*
+	 * Decode the UUID hex data using our hex decoder that is SIMD-aware. We
+	 * give it a private error context so that a decode failure is swallowed
+	 * here and reported by the scalar path instead, keeping the error message
+	 * identical.
+	 */
+	written = hex_decode_safe(hexsrc, 32, (char *) uuid->data, (Node *) &esctx);
+
+	/*
+	 * Fall back to the scalar path on any error. We must also reject a short
+	 * result: hex_decode_safe() skips whitespace, so it can succeed yet write
+	 * fewer than UUID_LEN bytes, whereas the UUID grammar forbids whitespace.
+	 */
+	if (esctx.error_occurred || written != UUID_LEN)
+		string_to_uuid_scalar(source, uuid, escontext);
+}
+
 Datum
 uuid_recv(PG_FUNCTION_ARGS)
 {
diff --git a/src/test/regress/expected/uuid.out b/src/test/regress/expected/uuid.out
index d542eb14b26..b40f50ac8be 100644
--- a/src/test/regress/expected/uuid.out
+++ b/src/test/regress/expected/uuid.out
@@ -375,5 +375,60 @@ SELECT v = v::bytea::uuid as matched FROM gen_random_uuid() v;
  t
 (1 row)
 
+-- Test UUID shapes that the parser uses the SIMD path.
+SELECT '5b35380a-7143-4912-9b55-f322699c6770'::uuid;
+                 uuid                 
+--------------------------------------
+ 5b35380a-7143-4912-9b55-f322699c6770
+(1 row)
+
+SELECT '{5b35380a-7143-4912-9b55-f322699c6770}'::uuid;
+                 uuid                 
+--------------------------------------
+ 5b35380a-7143-4912-9b55-f322699c6770
+(1 row)
+
+SELECT '5b35380a714349129b55f322699c6770'::uuid;
+                 uuid                 
+--------------------------------------
+ 5b35380a-7143-4912-9b55-f322699c6770
+(1 row)
+
+SELECT '{5b35380a714349129b55f322699c6770}'::uuid;
+                 uuid                 
+--------------------------------------
+ 5b35380a-7143-4912-9b55-f322699c6770
+(1 row)
+
+-- Test if the UUID parser using SIMD optimization correctly rejects invalid UUID
+-- string format.
+SELECT '5b35380a714349129b55f32  99c6770'::uuid;
+ERROR:  invalid input syntax for type uuid: "5b35380a714349129b55f32  99c6770"
+LINE 1: SELECT '5b35380a714349129b55f32  99c6770'::uuid;
+               ^
+SELECT '5b35380a-7143-4912-9b55-f322699c67  '::uuid;
+ERROR:  invalid input syntax for type uuid: "5b35380a-7143-4912-9b55-f322699c67  "
+LINE 1: SELECT '5b35380a-7143-4912-9b55-f322699c67  '::uuid;
+               ^
+SELECT '  35380a-7143-4912-9b55-f322699c6770'::uuid;
+ERROR:  invalid input syntax for type uuid: "  35380a-7143-4912-9b55-f322699c6770"
+LINE 1: SELECT '  35380a-7143-4912-9b55-f322699c6770'::uuid;
+               ^
+SELECT 'AZ35380a-7143-4912-9b55-f322699c6770'::uuid;
+ERROR:  invalid input syntax for type uuid: "AZ35380a-7143-4912-9b55-f322699c6770"
+LINE 1: SELECT 'AZ35380a-7143-4912-9b55-f322699c6770'::uuid;
+               ^
+SELECT '{AZ35380a-7143-4912-9b55-f322699c6770}'::uuid;
+ERROR:  invalid input syntax for type uuid: "{AZ35380a-7143-4912-9b55-f322699c6770}"
+LINE 1: SELECT '{AZ35380a-7143-4912-9b55-f322699c6770}'::uuid;
+               ^
+SELECT '{AZ35380a714349129b55f322699c6770}'::uuid;
+ERROR:  invalid input syntax for type uuid: "{AZ35380a714349129b55f322699c6770}"
+LINE 1: SELECT '{AZ35380a714349129b55f322699c6770}'::uuid;
+               ^
+SELECT '{AZ35380a714349129b55f322699c67  }'::uuid;
+ERROR:  invalid input syntax for type uuid: "{AZ35380a714349129b55f322699c67  }"
+LINE 1: SELECT '{AZ35380a714349129b55f322699c67  }'::uuid;
+               ^
 -- clean up
 DROP TABLE guid1, guid2, guid3 CASCADE;
diff --git a/src/test/regress/sql/uuid.sql b/src/test/regress/sql/uuid.sql
index 54f0d8f8255..bdbeb91fe4c 100644
--- a/src/test/regress/sql/uuid.sql
+++ b/src/test/regress/sql/uuid.sql
@@ -178,5 +178,21 @@ SELECT '\x019a2f859ced7225b99d9c55044a2563'::bytea::uuid;
 SELECT '\x1234567890abcdef'::bytea::uuid; -- error
 SELECT v = v::bytea::uuid as matched FROM gen_random_uuid() v;
 
+-- Test UUID shapes that the parser uses the SIMD path.
+SELECT '5b35380a-7143-4912-9b55-f322699c6770'::uuid;
+SELECT '{5b35380a-7143-4912-9b55-f322699c6770}'::uuid;
+SELECT '5b35380a714349129b55f322699c6770'::uuid;
+SELECT '{5b35380a714349129b55f322699c6770}'::uuid;
+
+-- Test if the UUID parser using SIMD optimization correctly rejects invalid UUID
+-- string format.
+SELECT '5b35380a714349129b55f32  99c6770'::uuid;
+SELECT '5b35380a-7143-4912-9b55-f322699c67  '::uuid;
+SELECT '  35380a-7143-4912-9b55-f322699c6770'::uuid;
+SELECT 'AZ35380a-7143-4912-9b55-f322699c6770'::uuid;
+SELECT '{AZ35380a-7143-4912-9b55-f322699c6770}'::uuid;
+SELECT '{AZ35380a714349129b55f322699c6770}'::uuid;
+SELECT '{AZ35380a714349129b55f322699c67  }'::uuid;
+
 -- clean up
 DROP TABLE guid1, guid2, guid3 CASCADE;
-- 
2.55.0

