summaryrefslogtreecommitdiff
path: root/ghc/runtime/c-as-asm/StablePtr.lc
diff options
context:
space:
mode:
Diffstat (limited to 'ghc/runtime/c-as-asm/StablePtr.lc')
-rw-r--r--ghc/runtime/c-as-asm/StablePtr.lc234
1 files changed, 234 insertions, 0 deletions
diff --git a/ghc/runtime/c-as-asm/StablePtr.lc b/ghc/runtime/c-as-asm/StablePtr.lc
new file mode 100644
index 0000000000..21de425866
--- /dev/null
+++ b/ghc/runtime/c-as-asm/StablePtr.lc
@@ -0,0 +1,234 @@
+\section[Stable-Pointers]{Creation and use of Stable Pointers}
+
+\begin{code}
+#ifndef PAR
+
+#include "rtsdefs.h"
+\end{code}
+
+This files (together with @ghc/runtime/storage/PerformIO.lhc@ and a
+small change in @HpOverflow.lc@) consists of the changes in the
+runtime system required to implement "Stable Pointers". But we're
+getting a bit ahead of ourselves --- what is a stable pointer and what
+is it used for?
+
+When Haskell calls C, it normally just passes over primitive integers,
+floats, bools, strings, etc. This doesn't cause any problems at all
+for garbage collection because the act of passing them makes a copy
+from the heap, stack or wherever they are onto the C-world stack.
+However, if we were to pass a heap object such as a (Haskell) @String@
+and a garbage collection occured before we finished using it, we'd run
+into problems since the heap object might have been moved or even
+deleted.
+
+So, if a C call is able to cause a garbage collection or we want to
+store a pointer to a heap object between C calls, we must be careful
+when passing heap objects. Our solution is to keep a table of all
+objects we've given to the C-world and to make sure that the garbage
+collector collects these objects --- updating the table as required to
+make sure we can still find the object.
+
+
+Of course, all this rather begs the question: why would we want to
+pass a boxed value?
+
+One very good reason is to preserve laziness across the language
+interface. Rather than evaluating an integer or a string because it
+{\em might\/} be required by the C function, we can wait until the C
+function actually wants the value and then force an evaluation.
+
+Another very good reason (the motivating reason!) is that the C code
+might want to execute an object of sort $IO ()$ for the side-effects
+it will produce. For example, this is used when interfacing to an X
+widgets library to allow a direct implementation of callbacks.
+
+
+The @makeStablePointer# :: a -> PrimIO (StablePtr a)@ function
+converts a value into a stable pointer. It is part of the @PrimIO@
+monad, because we want to be sure we don't allocate one twice by
+accident, and then only free one of the copies.
+
+\begin{verbatim}
+makeStablePointer# :: a -> State# _RealWorld -> StateAndStablePtr# _RealWorld a
+freeStablePointer# :: StablePtr# a -> State# _RealWorld -> State# _RealWorld
+deRefStablePointer# :: StablePtr# a -> State# _RealWorld -> StateAndPtr _RealWorld a
+\end{verbatim}
+There is also a C procedure @FreeStablePtr@ which frees a stable pointer.
+
+There may be additional functions on the C side to allow evaluation,
+application, etc of a stable pointer.
+
+\begin{code}
+EXTDATA(UnusedSP_closure);
+EXTDATA(EmptySPTable_closure);
+
+void
+enlargeSPTable( newTable, oldTable )
+ StgPtr newTable;
+ StgPtr oldTable;
+{
+ StgInt OldNoPtrs = SPT_NoPTRS(oldTable);
+ StgInt NewNoPtrs = SPT_NoPTRS(newTable);
+ StgInt i, NewTop;
+
+ ASSERT( NewNoPtrs > OldNoPtrs );
+ ASSERT( SPT_TOP(oldTable) == 0 );
+
+ CHECK_SPT_CLOSURE(oldTable);
+
+ /* Copy old stable pointers over */
+ for( i = 0; i < OldNoPtrs; ++i ) {
+ SPT_SPTR(newTable, i) = SPT_SPTR(oldTable,i);
+ }
+ /* Top up with unused stable pointers */
+ for( i = OldNoPtrs; i < NewNoPtrs; ++i ) {
+ SPT_SPTR(newTable, i) = UnusedSP_closure;
+ }
+
+ /* Setup free stack with indices of new stable pointers*/
+ NewTop = 0;
+ for( i = OldNoPtrs; i < NewNoPtrs; ++i ) {
+ SPT_FREE(newTable, NewTop++) = i;
+ }
+ SPT_TOP(newTable) = NewTop;
+
+ StorageMgrInfo.StablePointerTable = newTable;
+
+#if defined(DEBUG)
+ /* Now trash the old table to encourage bugs to show themselves */
+ if ( oldTable != EmptySPTable_closure ) {
+ I_ size = SPT_SIZE(oldTable) + _FHS;
+
+ for( i = 0; i != size; ++i ) {
+ oldTable[ i ] = DEALLOCATED_TRASH;
+ }
+ }
+#endif
+
+ CHECK_SPT_CLOSURE(newTable);
+}
+\end{code}
+
+
+There are a lot of checks in here. However, they are not to catch
+bugs in the compiler - they are to catch bugs in the users program.
+
+ToDo: maybe have a compiler switch to be less paranoid? [ADR]
+
+\begin{code}
+EXTDATA(UnusedSP_closure);
+
+void
+freeStablePointer(stablePtr)
+ I_ stablePtr;
+{
+ P_ SPTable = StorageMgrInfo.StablePointerTable;
+
+ /* Check what we can of tables integrity - can't check infotable
+ since we may be in a GC and (compacting) GC may have mangled it. */
+ CHECK_SPT_Size(SPTable);
+ CHECK_SPT_Contents(SPTable);
+
+ if (! (0 <= stablePtr && stablePtr < SPT_NoPTRS(SPTable)) ) { /* bogus index */
+ /* This can only happen if the Haskell/C programmer has really messed up. */
+
+ fprintf(stderr, "Panic (freeStablePointer): stable pointer %ld not in range 0..%ld.\n",
+ stablePtr, SPT_NoPTRS(SPTable)-1);
+ abort();
+ }
+
+ if (SPT_SPTR(SPTable,stablePtr) == UnusedSP_closure) { /* freeing an unused stable pointer */
+ /* This can only happen if the Haskell/C programmer has already
+ returned the same stable pointer or never allocated it. */
+
+ fprintf(stderr, "Panic: stable pointer %ld freed multiple times (or never allocated)\nby the Haskell/C programmer.\n", stablePtr);
+ EXIT(EXIT_FAILURE); /* ToDo: abort()? */
+ }
+
+ if (SPT_FULL(SPTable)) { /* free stack full! */
+ /* This can only happen if the Haskell/C programmer has returned
+ the same stable pointer several times.
+ */
+
+ fprintf(stderr, "Panic: stable pointer free stack overflowed.\nThis is probably due to the same stable pointer being freed multiple times\nby the Haskell/C programmer.\n");
+ EXIT(EXIT_FAILURE); /* ToDo: abort()? */
+ }
+
+ SPT_SPTR(SPTable,stablePtr) = UnusedSP_closure; /* erase old entry */
+ SPT_PUSH(SPTable,stablePtr); /* Add it to free stack */
+
+ CHECK_SPT_Size(SPTable);
+ CHECK_SPT_Contents(SPTable);
+}
+\end{code}
+
+\begin{code}
+StgPtr
+_deRefStablePointer(stablePtr,SPTable)
+ StgInt stablePtr;
+ StgPtr SPTable;
+{
+ CHECK_SPT_CLOSURE(SPTable);
+
+ if (! (0 <= stablePtr && stablePtr < SPT_NoPTRS(SPTable)) ) { /* bogus index */
+ /* This can only happen if the Haskell/C programmer has really messed up. */
+
+ fprintf(stderr, "Panic (deRefStablePointer): stable pointer %ld not in range 0..%ld.\n",
+ stablePtr, SPT_NoPTRS(SPTable)-1);
+ EXIT(EXIT_FAILURE); /* ToDo: abort()? */
+ }
+
+ if (SPT_SPTR(SPTable,stablePtr) == UnusedSP_closure) { /* dereferencing an unused stable pointer */
+ /* This can only happen if the Haskell/C programmer has already
+ returned this stable pointer. */
+
+ fprintf(stderr, "Panic: stable pointer %ld not allocated by the Haskell/C programmer.\n", stablePtr);
+ EXIT(EXIT_FAILURE); /* ToDo: abort()? */
+ }
+
+ return SPT_SPTR(SPTable,stablePtr);
+}
+\end{code}
+
+For error detecting in the debug version, we have a check that all
+free pointers are really free and all non-free pointers are really not
+free.
+
+\begin{code}
+#ifdef DEBUG
+int ValidateSPTable( P_ SPTable )
+{
+ I_ i, j;
+ I_ NoPtrs = SPT_NoPTRS( SPTable );
+ I_ Top = SPT_TOP( SPTable );
+
+ for( i = 0; i != Top; ++i ) {
+ /* Check the free indexes are in range */
+ if (!( (0 <= SPT_FREE( SPTable, i )) && (SPT_FREE( SPTable, i ) < NoPtrs) ) ) return 1;
+ /* Check the free indexes are unused */
+ if ( SPT_SPTR( SPTable, SPT_FREE( SPTable, i ) ) != UnusedSP_closure ) return 2;
+ }
+
+ /* Check each unused stable pointer is in free list (and vice-versa) */
+ for( i = 0; i != NoPtrs; i++ ) {
+ if ( SPT_SPTR( SPTable, i ) == UnusedSP_closure ) {
+ j = 0;
+ while (j != Top && SPT_FREE( SPTable, j ) != i) {
+ j++;
+ }
+ if (j == Top) return 3; /* Space leak - losing free SPs */
+ } else {
+ j = Top;
+ while (j != NoPtrs && SPT_FREE( SPTable, j ) != i) {
+ j++;
+ }
+ }
+ }
+
+ /* If all that worked, we've got a good structure here */
+ return 0;
+}
+#endif /* DEBUG */
+
+#endif /* ! PAR */
+\end{code}