Skip to content

Commit 2858965

Browse files
alexbiehlclaude
andcommitted
Add rewrite rules to fuse consecutive checkBounds into a single check
When fixed-width parsers (word64le, int32le, etc.) are composed with <*>, each independently checks bounds. These rewrite rules merge adjacent checkBounds into one: e.g. checkBounds 8 <*> checkBounds 8 becomes checkBounds 16. The rules are keyed on named functions (parserAp, parserBind, parserFmap) rather than class methods to ensure they fire reliably across module boundaries. Class instances delegate to these named wrappers with INLINE, while the wrappers themselves are NOINLINE [1] to stay visible for rule matching in phase 1 before inlining in phase 0. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent ca0163b commit 2858965

1 file changed

Lines changed: 76 additions & 25 deletions

File tree

src/Database/ClickHouse/Internal/Parser.hs

Lines changed: 76 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ module Database.ClickHouse.Internal.Parser
2020
text,
2121
takeN,
2222
uLEB128,
23+
checkBounds,
24+
parserAp,
25+
parserBind,
26+
parserFmap,
2327
)
2428
where
2529

@@ -58,39 +62,63 @@ instance MonadIO Parser where
5862
pure (ParseSuccess pos result)
5963

6064
instance Functor Parser where
61-
fmap f (Parser p) = Parser $ \end pos -> do
62-
result <- p end pos
63-
case result of
64-
ParseSuccess pos' x -> return $ ParseSuccess pos' (f x)
65-
ParseFailure err -> return $ ParseFailure err
66-
UnexpectedEndOfInput -> return UnexpectedEndOfInput
65+
fmap = parserFmap
66+
{-# INLINE fmap #-}
6767

6868
instance Applicative Parser where
6969
pure x = Parser $ \_ pos -> return $ ParseSuccess pos x
7070

71-
Parser pf <*> Parser px = Parser $ \end pos -> do
72-
result <- pf end pos
73-
case result of
74-
ParseSuccess pos' f -> do
75-
result' <- px end pos'
76-
case result' of
77-
ParseSuccess pos'' x -> return $ ParseSuccess pos'' (f x)
78-
ParseFailure err -> return $ ParseFailure err
79-
UnexpectedEndOfInput -> return UnexpectedEndOfInput
80-
ParseFailure err -> return $ ParseFailure err
81-
UnexpectedEndOfInput -> return UnexpectedEndOfInput
71+
(<*>) = parserAp
72+
{-# INLINE (<*>) #-}
8273

8374
instance Monad Parser where
8475
return = pure
8576

86-
Parser px >>= f = Parser $ \end pos -> do
87-
result <- px end pos
88-
case result of
89-
ParseSuccess pos' x -> do
90-
let Parser py = f x
91-
py end pos'
92-
ParseFailure err -> return $ ParseFailure err
93-
UnexpectedEndOfInput -> return UnexpectedEndOfInput
77+
(>>=) = parserBind
78+
{-# INLINE (>>=) #-}
79+
80+
-- | Apply a function to the result of a parser.
81+
--
82+
-- Named wrapper used in rewrite rules to fuse 'checkBounds' through 'fmap'.
83+
parserFmap :: (a -> b) -> Parser a -> Parser b
84+
parserFmap f (Parser p) = Parser $ \end pos -> do
85+
result <- p end pos
86+
case result of
87+
ParseSuccess pos' x -> return $ ParseSuccess pos' (f x)
88+
ParseFailure err -> return $ ParseFailure err
89+
UnexpectedEndOfInput -> return UnexpectedEndOfInput
90+
{-# NOINLINE [1] parserFmap #-}
91+
92+
-- | Applicative sequencing for parsers.
93+
--
94+
-- Named wrapper used in rewrite rules to fuse consecutive 'checkBounds'.
95+
parserAp :: Parser (a -> b) -> Parser a -> Parser b
96+
parserAp (Parser pf) (Parser px) = Parser $ \end pos -> do
97+
result <- pf end pos
98+
case result of
99+
ParseSuccess pos' f -> do
100+
result' <- px end pos'
101+
case result' of
102+
ParseSuccess pos'' x -> return $ ParseSuccess pos'' (f x)
103+
ParseFailure err -> return $ ParseFailure err
104+
UnexpectedEndOfInput -> return UnexpectedEndOfInput
105+
ParseFailure err -> return $ ParseFailure err
106+
UnexpectedEndOfInput -> return UnexpectedEndOfInput
107+
{-# NOINLINE [1] parserAp #-}
108+
109+
-- | Monadic bind for parsers.
110+
--
111+
-- Named wrapper used in rewrite rules to fuse consecutive 'checkBounds'.
112+
parserBind :: Parser a -> (a -> Parser b) -> Parser b
113+
parserBind (Parser pa) f = Parser $ \end pos -> do
114+
result <- pa end pos
115+
case result of
116+
ParseSuccess pos' x -> do
117+
let Parser pb = f x
118+
pb end pos'
119+
ParseFailure err -> return $ ParseFailure err
120+
UnexpectedEndOfInput -> return UnexpectedEndOfInput
121+
{-# NOINLINE [1] parserBind #-}
94122

95123
runParser :: Parser a -> ByteString -> (ParseResult a, ByteString)
96124
runParser (Parser p) bs = unsafeDupablePerformIO $ do
@@ -135,6 +163,29 @@ checkBounds n (Parser k) = Parser $ \end pos ->
135163
if pos `plusPtr` n <= end
136164
then k end pos
137165
else return UnexpectedEndOfInput
166+
{-# NOINLINE [1] checkBounds #-}
167+
168+
-- Rewrite rules that fuse consecutive checkBounds into a single check.
169+
--
170+
-- When two parsers each guarded by checkBounds are sequenced via parserAp,
171+
-- the two bounds checks can be merged: if the first parser consumes exactly
172+
-- n bytes and the second needs m bytes, checking (n + m) bytes upfront
173+
-- suffices and eliminates the second check.
174+
--
175+
-- The rules are keyed on parserAp/parserBind/parserFmap (named functions
176+
-- we control) rather than on class methods, which ensures they fire
177+
-- reliably across module boundaries.
178+
{-# RULES
179+
"checkBounds/parserAp" forall n m p q.
180+
parserAp (checkBounds n p) (checkBounds m q) =
181+
checkBounds (n + m) (parserAp p q)
182+
"checkBounds/parserBind" forall n m p f.
183+
parserBind (checkBounds n p) (\x -> checkBounds m (f x)) =
184+
checkBounds (n + m) (parserBind p f)
185+
"checkBounds/parserFmap" forall f n p.
186+
parserFmap f (checkBounds n p) =
187+
checkBounds n (parserFmap f p)
188+
#-}
138189

139190
{-# INLINE word8 #-}
140191
word8 :: Parser Word8

0 commit comments

Comments
 (0)