\section[Stable-Pointers]{Creation and use of Stable Pointers} \begin{code} #if !defined(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}