Skip to content

Commit 8c76fc6

Browse files
authored
Merge pull request #14 from Shimuuar/new-QQ-pyf
New quasiquoter pyf and number of memory-related bugs
2 parents 8b82224 + 4096ed7 commit 8c76fc6

File tree

6 files changed

+246
-133
lines changed

6 files changed

+246
-133
lines changed

include/inline-python.h

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,11 @@
55
#include <Rts.h>
66

77

8-
// Use new stable API from
8+
// Use new stable API from 3.13
99
#ifndef PyCFunctionFast
1010
typedef _PyCFunctionFast PyCFunctionFast;
1111
#endif
1212

13-
// ----------------------------------------------------------------
14-
// Standard status codes
15-
16-
#define IPY_OK 0
17-
#define IPY_ERR_PYTHON 1
18-
#define IPY_ERR_COMPILE 2
19-
2013

2114

2215
// ================================================================

src/Python/Inline/Literal.hs

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -109,12 +109,9 @@ toPy a = runPy $ basicToPy a >>= \case
109109
----------------------------------------------------------------
110110

111111
instance ToPy PyObject where
112-
basicToPy o = unsafeWithPyObject o $ \p ->
113-
p <$ Py [CU.exp| void { Py_INCREF($(PyObject* p)) } |]
112+
basicToPy o = unsafeWithPyObject o $ \p -> p <$ incref p
114113
instance FromPy PyObject where
115-
basicFromPy p = do
116-
Py [CU.exp| void { Py_INCREF($(PyObject* p)) } |]
117-
newPyObject p
114+
basicFromPy p = incref p >> newPyObject p
118115

119116
instance ToPy () where
120117
basicToPy () = Py [CU.exp| PyObject* { Py_None } |]

src/Python/Inline/QQ.hs

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module Python.Inline.QQ
77
( pymain
88
, py_
99
, pye
10+
, pyf
1011
) where
1112

1213
import Language.Haskell.TH.Quote
@@ -22,10 +23,7 @@ import Python.Internal.Eval
2223
-- This quote creates object of type @IO ()@
2324
pymain :: QuasiQuoter
2425
pymain = QuasiQuoter
25-
{ quoteExp = \txt -> [| runPy $ do p_main <- basicMainDict
26-
src <- $(expQQ "exec" (unindent txt)) p_main
27-
pyEvalInMain p_main p_main src
28-
|]
26+
{ quoteExp = \txt -> [| runPy $ evaluatorPymain $(expQQ Exec txt) |]
2927
, quotePat = error "quotePat"
3028
, quoteType = error "quoteType"
3129
, quoteDec = error "quoteDec"
@@ -38,13 +36,7 @@ pymain = QuasiQuoter
3836
-- This quote creates object of type @IO ()@
3937
py_ :: QuasiQuoter
4038
py_ = QuasiQuoter
41-
{ quoteExp = \txt -> [| runPy $ do p_globals <- basicMainDict
42-
p_locals <- basicNewDict
43-
src <- $(expQQ "exec" (unindent txt)) p_locals
44-
res <- pyEvalInMain p_globals p_locals src
45-
basicDecref p_locals
46-
return res
47-
|]
39+
{ quoteExp = \txt -> [| runPy $ evaluatorPy_ $(expQQ Exec txt) |]
4840
, quotePat = error "quotePat"
4941
, quoteType = error "quoteType"
5042
, quoteDec = error "quoteDec"
@@ -56,12 +48,19 @@ py_ = QuasiQuoter
5648
-- This quote creates object of type @IO PyObject@
5749
pye :: QuasiQuoter
5850
pye = QuasiQuoter
59-
{ quoteExp = \txt -> [| runPy $ do p_env <- basicNewDict
60-
src <- $(expQQ "eval" (unindent txt)) p_env
61-
res <- pyEvalExpr p_env src
62-
basicDecref p_env
63-
return res
64-
|]
51+
{ quoteExp = \txt -> [| runPy $ evaluatorPye $(expQQ Eval txt) |]
52+
, quotePat = error "quotePat"
53+
, quoteType = error "quoteType"
54+
, quoteDec = error "quoteDec"
55+
}
56+
57+
-- | Another quasiquoter which works around that sequence of python
58+
-- statements doesn't have any value associated with it. Content of
59+
-- quasiquote is function body. So to get value out of it one must
60+
-- call return
61+
pyf :: QuasiQuoter
62+
pyf = QuasiQuoter
63+
{ quoteExp = \txt -> [| runPy $ evaluatorPyf $(expQQ Fun txt) |]
6564
, quotePat = error "quotePat"
6665
, quoteType = error "quoteType"
6766
, quoteDec = error "quoteDec"

src/Python/Internal/Eval.hs

Lines changed: 38 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,18 @@ module Python.Internal.Eval
1515
-- * PyObject wrapper
1616
, newPyObject
1717
, decref
18+
, incref
1819
, takeOwnership
1920
, ensureGIL
2021
, dropGIL
2122
-- * Exceptions
2223
, convertHaskell2Py
2324
, convertPy2Haskell
2425
, throwPyError
26+
, mustThrowPyError
2527
, throwPyConvesionFailed
28+
-- * Debugging
29+
, debugPrintPy
2630
) where
2731

2832
import Control.Concurrent
@@ -91,6 +95,9 @@ C.include "<inline-python.h>"
9195
-- 2. Overhead of `runInBoundThread` is significant for GC (~1μs)
9296
-- will this cause problem or if there're only few object on
9397
-- haskell heap it would be fine?
98+
--
99+
-- In addition we must not do anything after interpreter shutdown.
100+
-- It already released memory. Most of it at least.
94101

95102

96103

@@ -241,6 +248,9 @@ doFinalizePython = [C.block| void {
241248
decref :: Ptr PyObject -> Py ()
242249
decref p = Py [CU.exp| void { Py_DECREF($(PyObject* p)) } |]
243250

251+
incref :: Ptr PyObject -> Py ()
252+
incref p = Py [CU.exp| void { Py_INCREF($(PyObject* p)) } |]
253+
244254
-- | Ensure that we hold GIL for duration of action
245255
ensureGIL :: Py a -> Py a
246256
ensureGIL action = do
@@ -266,19 +276,16 @@ takeOwnership p = ContT $ \c -> c p `finallyPy` decref p
266276

267277
-- | Wrap raw python object into
268278
newPyObject :: Ptr PyObject -> Py PyObject
269-
-- We need to use different implementation for different RTS
270279
-- See NOTE: [GC]
271-
newPyObject p
272-
| rtsSupportsBoundThreads = Py $ do
273-
fptr <- newForeignPtr_ p
274-
GHC.addForeignPtrFinalizer fptr $ runInBoundThread $ unPy $ decref p
275-
pure $ PyObject fptr
276-
| otherwise = Py $ do
277-
fptr <- newForeignPtr_ p
278-
PyObject fptr <$ addForeignPtrFinalizer py_XDECREF fptr
279-
280-
py_XDECREF :: FunPtr (Ptr PyObject -> IO ())
281-
py_XDECREF = [C.funPtr| void inline_py_XDECREF(PyObject* p) { Py_XDECREF(p); } |]
280+
newPyObject p = Py $ do
281+
fptr <- newForeignPtr_ p
282+
-- FIXME: We still have race between check and interpreter
283+
-- shutdown. At least it's narrow race
284+
GHC.addForeignPtrFinalizer fptr $ do
285+
[CU.exp| int { Py_IsInitialized() } |] >>= \case
286+
0 -> pure ()
287+
_ -> runPy $ decref p
288+
pure $ PyObject fptr
282289

283290

284291

@@ -348,6 +355,14 @@ throwPyError =
348355
NULL -> pure ()
349356
_ -> throwPy =<< convertPy2Haskell
350357

358+
-- | Throw python error as haskell exception if it's raised. If it's
359+
-- not that internal error. Another exception will be raised
360+
mustThrowPyError :: String -> Py a
361+
mustThrowPyError msg =
362+
Py [CU.exp| PyObject* { PyErr_Occurred() } |] >>= \case
363+
NULL -> error $ "mustThrowPyError: no python exception raised. " ++ msg
364+
_ -> throwPy =<< convertPy2Haskell
365+
351366
throwPyConvesionFailed :: Py ()
352367
throwPyConvesionFailed = do
353368
r <- Py [CU.block| int {
@@ -360,3 +375,14 @@ throwPyConvesionFailed = do
360375
case r of
361376
0 -> pure ()
362377
_ -> throwPy FromPyFailed
378+
379+
380+
----------------------------------------------------------------
381+
-- Debugging
382+
----------------------------------------------------------------
383+
384+
debugPrintPy :: Ptr PyObject -> Py ()
385+
debugPrintPy p = Py [CU.block| void {
386+
PyObject_Print($(PyObject *p), stdout, 0);
387+
printf(" [REF=%li]\n", Py_REFCNT($(PyObject *p)) );
388+
} |]

0 commit comments

Comments
 (0)