diff --git a/object.di b/object.di
index cd8ed2c..f49166d 100644
--- a/object.di
+++ b/object.di
@@ -49,6 +49,29 @@ struct Interface
     ptrdiff_t   offset;
 }
 
+struct PointerMap
+{
+    size_t[] bits = [1, 1, 0];
+
+    size_t size();
+    bool mustScanWordAt(size_t offset);
+    bool isPointerAt(size_t offset);
+    bool canUpdatePointers();
+}
+
+struct PointerMapBuilder
+{
+    private size_t[] m_bits = null;
+    private size_t m_size = 0;
+
+    void size(size_t bytes);
+    void mustScanWordAt(size_t offset);
+    void inlineAt(size_t offset, PointerMap pm);
+    PointerMap convertToPointerMap();
+}
+
+//const PointerMap conservativeBitMask = PointerMap([1, 1, 0]);
+
 version (GNU){
 }else version (DigitalMars)
 {
@@ -83,6 +106,9 @@ class ClassInfo : Object
         /// TypeInfo information about this class
         TypeInfo typeinfo;
     }
+    version (D_HavePointerMap) {
+        PointerMap pointermap;
+    }
     /// finds the classinfo of the class with the given name
     static ClassInfo find(char[] classname);
     /// creates an instance of this class (works only if there is a constructor without arguments)
@@ -114,6 +140,7 @@ class TypeInfo
     void[]   init();
     /// flags, 1: has possible pointers into GC memory
     uint     flags();
+    PointerMap pointermap();
     /// offsets of the various elements
     OffsetTypeInfo[] offTi();
 }
@@ -138,6 +165,10 @@ class TypeInfo_Array : TypeInfo
 {
     /// typeinfo of the elements, might be null for basic arrays, it is safer to use next()
     TypeInfo value;
+
+    //ensure derived array TypeInfos use correct method
+    //if this declaration is forgotten, e.g. TypeInfo_Ai will have a wrong vtbl entry
+    PointerMap pointermap();
 }
 
 class TypeInfo_StaticArray : TypeInfo
@@ -183,6 +214,10 @@ class TypeInfo_Struct : TypeInfo
     char[] function()   xtoString;
 
     uint m_flags;
+
+    version (D_HavePointerMap) {
+        PointerMap m_pointermap;
+    }
 }
 
 class TypeInfo_Tuple : TypeInfo
diff --git a/pm.d b/pm.d
new file mode 100644
index 0000000..18c3437
--- /dev/null
+++ b/pm.d
@@ -0,0 +1,369 @@
+//this is a test program to check the pointermap generated by dmd
+//it also checks some TypeInfos, but not much and the tests aren't exhaustive
+
+import tango.io.Stdout;
+import tango.core.tools.StackTrace;
+
+template Foo(T) {
+    T z;
+    void* u;
+    static void* g;
+}
+
+//template mixins
+struct S1 {
+    const char[] MASK = "01010";
+
+    int a;
+    void* b;
+
+    mixin Foo!(short) x;
+
+    byte u;
+}
+
+//make sure no static members are written
+struct S2 {
+    const char[] MASK = "";
+    static void* x;
+}
+
+//dgs, arrays
+struct S3 {
+    const char[] MASK = "1001";
+    void delegate() x;
+    int[] a;
+}
+
+//static arrays
+struct S4 {
+    const char[] MASK = "100110011001";
+    S3[3] x;
+}
+
+//static arrays of size 0
+struct S5 {
+    const char[] MASK = "";
+    S3[0] x;
+}
+
+//long bitmasks
+struct S6 {
+    const char[] MASK = "010101010101";
+    int a1;
+    void* b1;
+    int a2;
+    void* b2;
+    int a3;
+    void* b3;
+    int a4;
+    void* b4;
+    int a5;
+    void* b5;
+
+    int a6;
+    void* b6;
+}
+
+//rounding up for alignment
+struct S7 {
+    const char[] MASK = "10";
+    void* f;
+    byte r;
+}
+
+//unaligned static sized void[] arrays
+struct S8 {
+    const char[] MASK = "1"; //only 1 word due to no alignment
+    const char[] M_MASK = "0"; //non-moveable
+    void[5] a = void;
+}
+
+//as S8, but word aligned
+struct S9 {
+    const char[] MASK = "11";
+    const char[] M_MASK = "00";
+    void[size_t.sizeof*2] a = void;
+}
+
+//tuples
+struct S10 {
+    const char[] MASK = "110";
+    void* x;
+    typeof(S7.tupleof) y;
+    ubyte z;
+}
+
+//unions
+union S11 {
+    const char[] MASK = "011";
+    //non-moveable where two members at the same offset disagree
+    const char[] M_MASK = "010";
+    struct {
+        int a1;
+        void* b1;
+        int c1;
+    }
+    struct {
+        int a2;
+        void* b2;
+        void* c2;
+    }
+}
+
+//structs
+struct S12 {
+    const char[] MASK = "110011";
+    const char[] M_MASK = "110010";
+    S10 a;
+    S11 b;
+}
+
+//unaligned size 2
+struct S13 {
+    const char[] MASK = "";
+    byte x;
+}
+
+//very large bitmasks (spanning multiple entries in the bitmask size_t array)
+struct S14 {
+    const char[] MASK = "0101001010010100101001010010100101001010010100101001010010100101001010";
+    struct X { int a; void* b; int c; void* d; int e; }
+    X[14] x;
+}
+
+
+typedef S1 TD1;
+
+struct S15 {
+    const char[] MASK = "1" ~ S1.MASK;
+    void* p;
+    TD1 x;
+}
+
+//unaligned pointers
+struct S16 {
+    const char[] MASK = "001";
+align(1):
+    ubyte b;
+    void* p;
+    ubyte[size_t.sizeof-1] pad;
+    void* p2;
+}
+
+//tricky stuff with unions + moveable pointer bitmask
+struct S17 {
+    const char[] MASK = "01";
+    const char[] M_MASK = "00";
+align(1):
+    union {
+        struct {
+            size_t x1;
+            void* x2;
+        }
+        struct {
+            ubyte[size_t.sizeof-1] y1;
+            //second byte makes x2 unmoveable
+            ushort y2;
+        }
+    }
+}
+struct S17b {
+    const char[] MASK = "0110";
+    const char[] M_MASK = "0000";
+align(1):
+    union {
+        struct {
+            int[] x2;
+            void delegate() x1;
+        }
+        struct {
+            ubyte y1;
+            static if (size_t.sizeof == 4) {
+                long y2;
+            } else {
+                size_t[2] y2;
+            }
+        }
+    }
+}
+
+//classes
+class C1 {
+    const char[] MASK = "00011";
+    int z;
+    Object x;
+    void* o;
+    static int zu;
+}
+
+//derived classes
+class C2 : C1 {
+    const char[] MASK = "0001101"; //class sizes apparently aren't aligned
+    byte r;
+    void* o;
+    byte o2;
+}
+
+interface I1 {
+}
+
+//classes with interfaces
+class C3 : C1, I1 {
+    const char[] MASK = "0001110"; //interface stuff appended to end
+
+    void* z;
+
+    //hidden pointer
+    class C3_1 {
+        const char[] MASK = "001";
+    }
+}
+
+//classes deriving from classes with interfaces
+class C4 : C3 {
+    const char[] MASK = "00011100";
+
+    int x;
+}
+
+//no pointers
+class C5 {
+    const char[] MASK = "000";
+
+    int z;
+}
+
+version (D_HavePointerMap) {
+} else {
+    static assert(false, "no compiler support");
+}
+
+char[] getpm(PointerMap px) {
+    char[] res, mres;
+    size_t max = px.bits[0];
+    size_t mo = (px.bits.length-1)/2;
+    size_t[] b = px.bits[1..1+mo];
+    size_t[] mb = px.bits[1+mo..$];
+    assert(b.length == mb.length);
+    const BITS = size_t.sizeof*8;
+    bool bitat(size_t[] arr, size_t i) {
+        return !!(arr[i/BITS] & (1<<(i%BITS)));
+    }
+    for (size_t i = 0; i < max; i++) {
+        auto offset = i * size_t.sizeof;
+        auto bit = bitat(b, i);
+        auto mbit = bitat(mb, i);
+        res ~= bit ? "1" : "0";
+        mres ~= mbit ? "1" : "0";
+        assert(bit == px.mustScanWordAt(offset));
+        assert(mbit == px.isPointerAt(offset));
+        if (!bit)
+            assert(!mbit, "moveable-pointer bit can't be set if not a pointer");
+    }
+    if (b.length) {
+        for (size_t i = max; i < (b.length-1)*BITS; i++) {
+            assert(!bitat(b, i), "stray bit!");
+            assert(!bitat(mb, i), "stray bit!");
+        }
+    }
+    assert((res == mres) == px.canUpdatePointers());
+    return res ~ "|" ~ mres;
+}
+
+void checkpm(char[] name, char[] expected_b, char[] expected_m, size_t ts, PointerMap pm) {
+    char[] m = getpm(pm);
+    Stdout.formatln("{}: {} {}", name, pm.bits, m);
+    auto expected = expected_b ~ "|" ~ expected_m;
+    if (m != expected) {
+        Stdout.formatln("  pointermap not matching, expected: {}", expected);
+        assert(false);
+    }
+    //ts is rounded down to next alginment boundary
+    assert(pm.bits[0] == ts / size_t.sizeof, "bitmap size != type size");
+}
+
+void dostruct(T)() {
+    char[] m_mask = T.MASK;
+    static if (is(typeof(T.M_MASK))) {
+        m_mask = T.M_MASK;
+    }
+    checkpm(T.stringof, T.MASK, m_mask, T.sizeof, typeid(T).pointermap);
+}
+
+void doclass(T)() {
+    checkpm(T.stringof, T.MASK, T.MASK, T.classinfo.init.length, T.classinfo.pointermap);
+}
+
+void dotype(T)(char[] expectedmask, char[] expectedmmask = "-") {
+    if (expectedmmask == "-")
+        expectedmmask = expectedmask;
+    checkpm(T.stringof, expectedmask, expectedmmask, T.sizeof, typeid(T).pointermap);
+}
+
+void main() {
+    dostruct!(S1)();
+    dostruct!(S3)();
+    dostruct!(S3)();
+    dostruct!(S4)();
+    dostruct!(S5)();
+    dostruct!(S6)();
+    dostruct!(S7)();
+    dostruct!(S8)();
+    dostruct!(S9)();
+    dostruct!(S10)();
+    dostruct!(S11)();
+    dostruct!(S12)();
+    dostruct!(S13)();
+    dostruct!(S14)();
+    dostruct!(S15)();
+    dostruct!(S16)();
+    dostruct!(S17)();
+    dostruct!(S17b)();
+
+    doclass!(C1)();
+    doclass!(C2)();
+    doclass!(C3)();
+    doclass!(C3.C3_1)();
+    doclass!(C4)();
+    doclass!(C5)();
+
+    dotype!(size_t)("0");
+    dotype!(void*)("1"); //yes it's a moveable pointer (asymmetric to void[])
+    dotype!(void[size_t.sizeof])("1", "0"); //but this is non-moveable
+    dotype!(C1)("1");
+    dotype!(C5)("1");
+    dotype!(I1)("1");
+    dotype!(int[])("01");
+    dotype!(void[])("01");
+    dotype!(void delegate())("10");
+    dotype!(int[int])("1");
+    //functions are never heap allocated
+    //borked, because typeid() will return a TypeInfo_Pointer
+    //dotype!(void function())("0");
+
+    typedef S1 TD;
+    dotype!(TD)(S1.MASK);
+
+    //static arrays
+    alias int[3] Ai3;
+    //borked *shrug*
+    //also see TypeInfo_StaticArray.pointermap()
+    //the runtime/GC will handle static arrays correctly anyway
+    //dotype!(Ai3)("000");
+
+    //not entirely sane
+
+    //void must be a pointer type to deal with void[]
+    //but how can a 1-byte sized type be a pointer
+    //dotype!(void)("1");
+
+    //basic types with size != size_t.sizeof would require hacking LOTS of
+    //  TypeInfos, which I don't want to do - furthermore, the GC won't use the
+    //  PointerMaps of no-scan types, so it's all fine
+    //dotype!(byte)("0");
+    //dotype!(long)("00");
+
+    Stdout.formatln("all passed!");
+}
+
diff --git a/tango/core/Memory.d b/tango/core/Memory.d
index 4772cc2..b6cc380 100644
--- a/tango/core/Memory.d
+++ b/tango/core/Memory.d
@@ -8,7 +8,6 @@
  */
 module tango.core.Memory;
 
-
 private
 {
     extern (C) void gc_init();
@@ -23,9 +22,9 @@ private
     extern (C) uint gc_setAttr( void* p, uint a );
     extern (C) uint gc_clrAttr( void* p, uint a );
 
-    extern (C) void*  gc_malloc( size_t sz, uint ba = 0 );
-    extern (C) void*  gc_calloc( size_t sz, uint ba = 0 );
-    extern (C) void*  gc_realloc( void* p, size_t sz, uint ba = 0 );
+    extern (C) void*  gc_malloc( size_t sz, uint ba = 0, PointerMap bitMask = PointerMap.init);
+    extern (C) void*  gc_calloc( size_t sz, uint ba = 0, PointerMap bitMask = PointerMap.init);
+    extern (C) void*  gc_realloc( void* p, size_t sz, uint ba = 0, PointerMap bitMask = PointerMap.init);
     extern (C) size_t gc_extend( void* p, size_t mx, size_t sz );
     extern (C) size_t gc_reserve( size_t sz );
     extern (C) void   gc_free( void* p );
@@ -62,6 +61,13 @@ private
 /**
  * This struct encapsulates all garbage collection functionality for the D
  * programming language.
+ *
+ * Documentation of runtime configuration:
+ *
+ * The environment variable D_PRECISE_HEAP can be used to control the behavior
+ * of the GC at runtime.
+ *  D_PRECISE_HEAP=1 enable precise scanning
+ *  D_PRECISE_HEAP=0 disable precise scanning (may save space because no bitmasks need to be stored)
  */
 struct GC
 {
@@ -201,6 +207,7 @@ struct GC
      * Params:
      *  sz = The desired allocation size in bytes.
      *  ba = A bitmask of the attributes to set on this block.
+     *  bitMask = The pointer offset information for precise heap scanning.
      *
      * Returns:
      *  A reference to the allocated memory or null if insufficient memory
@@ -209,9 +216,10 @@ struct GC
      * Throws:
      *  OutOfMemoryException on allocation failure.
      */
-    static void* malloc( size_t sz, uint ba = 0 )
+    static void* malloc( size_t sz, uint ba = 0,
+        PointerMap bitMask = PointerMap.init )
     {
-        return gc_malloc( sz, ba );
+        return gc_malloc( sz, ba, bitMask );
     }
 
 
@@ -226,6 +234,7 @@ struct GC
      * Params:
      *  sz = The desired allocation size in bytes.
      *  ba = A bitmask of the attributes to set on this block.
+     *  bitMask = The pointer offset information for precise heap scanning.
      *
      * Returns:
      *  A reference to the allocated memory or null if insufficient memory
@@ -234,9 +243,10 @@ struct GC
      * Throws:
      *  OutOfMemoryException on allocation failure.
      */
-    static void* calloc( size_t sz, uint ba = 0 )
+    static void* calloc( size_t sz, uint ba = 0,
+        PointerMap bitMask = PointerMap.init )
     {
-        return gc_calloc( sz, ba );
+        return gc_calloc( sz, ba, bitMask );
     }
 
 
@@ -258,12 +268,15 @@ struct GC
      * reallocation is required.  If ba is not zero and p references the head
      * of a valid, known memory block then the bits in ba will replace those on
      * the current memory block and will also be set on the new block if a
-     * reallocation is required.
+     * reallocation is required.  Similarly, if bitMask is non-null, then
+     * the bitmask for the current block will propagated to the new block.
+     * Otherwise, the bitmask provided will be propagated to the old block.
      *
      * Params:
      *  p  = A pointer to the root of a valid memory block or to null.
      *  sz = The desired allocation size in bytes.
      *  ba = A bitmask of the attributes to set on this block.
+     *  bitMask = The pointer offset information for precise heap scanning.
      *
      * Returns:
      *  A reference to the allocated memory on success or null if sz is
@@ -272,9 +285,10 @@ struct GC
      * Throws:
      *  OutOfMemoryException on allocation failure.
      */
-    static void* realloc( void* p, size_t sz, uint ba = 0 )
+    static void* realloc( void* p, size_t sz, uint ba = 0,
+        PointerMap bitMask = PointerMap.init )
     {
-        return gc_realloc( p, sz, ba );
+        return gc_realloc( p, sz, ba, bitMask );
     }
 
 
diff --git a/tango/core/rt/compiler/dmd/object_.d b/tango/core/rt/compiler/dmd/object_.d
index b6991ee..f2227fb 100644
--- a/tango/core/rt/compiler/dmd/object_.d
+++ b/tango/core/rt/compiler/dmd/object_.d
@@ -157,6 +157,140 @@ struct Interface
 }
 
 /**
+ * Pointer map for precise heap scanning.
+ * Format:
+ *  PointerMap pm = typeid(T).pointermap;
+ *  pm.bits = [header] ~ scan_bits ~ pointer_bits
+ *  size_t header is the number of pointer sized units in T (T.sizeof/size_t.sizeof)
+ *  size_t[] scan_bits is the bitmap; each bit covers size_t bytes of T, meaning:
+ *      0: guaranteed not to be a pointer, don't scan
+ *      1: possibly a pointer, must scan
+ *  size_t[] pointer_bits is a second bitmap similar to scan_bits. If the
+ *  corrsponding bit in scan_bits is 0, the bit is 0; otherwise its meaning is:
+ *      0: pointer can not be moved, because it's possibly an integer
+ *      1: pointer can be moved, the corresponding word is always a pointer
+ *  Note that not the bit-arrays are concatenated, but the size_t arrays.
+ * This implies all GC-aware pointers must be aligned on size_t boundaries.
+ * The compiler won't set any bits for unaligned pointer fields.
+ * The least significant bit of a size_t item is considered the first bit.
+ * PointerMap.init is a conservative scanning mask equivelant to void*[]
+ */
+struct PointerMap
+{
+    size_t[] bits = [1, 1, 0];
+
+    private const size_t BITS = size_t.sizeof * 8;
+
+    /// return size in bytes (aligned)
+    size_t size()
+    {
+        return bits[0] * size_t.sizeof;
+    }
+
+    private bool getbit(size_t offset, bool pointer_bit)
+    {
+        assert(offset < size);
+
+        if ((offset & (size_t.sizeof - 1)) != 0)
+            return false;
+
+        size_t elem = offset / size_t.sizeof;
+        size_t start = 1; //scan_bits offset
+        if (pointer_bit)
+            start += (bits[0] + BITS - 1) / BITS; //pointer_bits offset
+        return !!(bits[start + elem / BITS] & (1 << (elem % BITS)));
+    }
+
+    /// return if the (aligned) field starting at byte offset is a pointer
+    /// Warning: the GC may access the internal data structure directly instead
+    /// of using this method to make scanning faster
+    bool mustScanWordAt(size_t offset)
+    {
+        return getbit(offset, false);
+    }
+
+    /// return if the (aligned) field starting at byte offset is a moveable pointer
+    /// "moveable pointer" means that the memory block referenced by the pointer can
+    /// be moved by the GC (the pointer field will be updated with the new address)
+    bool isPointerAt(size_t offset)
+    {
+        return getbit(offset, true);
+    }
+
+    /// return true if and only if there are integer fields overlapping with pointer
+    /// fields in this type
+    bool canUpdatePointers()
+    {
+        auto len = (bits.length - 1) / 2;
+        return bits[1 .. 1 + len] == bits[1 + len .. $];
+    }
+
+}
+
+/// code for manually building PointerMaps
+/// separate struct from PointerMap because for some representations, it may be
+/// hard to handle arbitrary pointerAt() calls to update the internal data structure
+/// (think of pointer maps encoded as lists of runs etc.)
+/// xxx untested
+struct PointerMapBuilder
+{
+    private size_t[] m_bits = null;
+    private size_t m_size = 0;
+
+    private const size_t BITS = size_t.sizeof * 8;
+
+    /// set the underlying type's size in bytes
+    void size(size_t bytes)
+    {
+        size_t nelem = bytes / size_t.sizeof;
+        m_bits.length = 1 + ((nelem + BITS - 1) / BITS) * 2;
+        m_bits[] = 0;
+        m_bits[0] = nelem;
+        m_size = bytes;
+    }
+
+    /// mark the pointer sized field at byte offset as pointer
+    /// if the offset is unaligned, it does nothing
+    void mustScanWordAt(size_t offset)
+    {
+        assert(offset < m_size);
+
+        if ((offset & (size_t.sizeof - 1)) != 0)
+            return;
+
+        size_t elem = offset / size_t.sizeof;
+        m_bits[1 + elem / BITS] |= 1 << (elem % BITS);
+    }
+
+    /// starting at the given byte offset, call pointerAt() for each pointer in pm
+    void inlineAt(size_t offset, PointerMap pm)
+    {
+        assert(offset + pm.size <= m_size);
+
+        for (size_t n = 0; n < pm.size; n += size_t.sizeof)
+        {
+            if (pm.mustScanWordAt(n))
+                mustScanWordAt(offset + n);
+        }
+    }
+
+    /// create a PointerMap instance
+    /// accessing this PointerMapBuilder after calling this method is not allowed
+    PointerMap convertToPointerMap() {
+        //no un-moveable pointer stuff supported => imply all pointers are moveable
+        size_t len = (m_bits[0] + BITS - 1) / BITS;
+        assert(len == (m_bits.length - 1) / 2);
+        m_bits[1 + len .. $] = m_bits[1 .. 1 + len];
+
+        auto res = PointerMap(m_bits);
+        *this = PointerMapBuilder.init; //invalidate this instance
+        return res;
+    }
+}
+
+//static const PointerMap cPointerMapNoScan = PointerMap([1, 0, 0]);
+
+/**
  * Runtime type information about a class. Can be retrieved for any class type
  * or instance by using the .classinfo property.
  * A pointer to this appears as the first entry in the class's vtbl[].
@@ -184,7 +318,11 @@ class ClassInfo : Object
     static if (__VERSION__ >= 1045) {
         TypeInfo typeinfo;
     }
-    
+
+    version (D_HavePointerMap) {
+        PointerMap pointermap;
+    }
+
     /**
      * Search all modules for ClassInfo corresponding to classname.
      * Returns: null if not found
@@ -304,6 +442,35 @@ class TypeInfo
     /// Get flags for type: 1 means GC should scan for pointers
     uint flags() { return 0; }
 
+    /// Get a pointer to PointerMap; used for GC scanning
+    PointerMap pointermap() {
+        if (flags() & 1) {
+            return PointerMap.init;
+        } else {
+            //return cPointerMapNoScan;
+            //work around for dmd bug #4397 (triggers infinite recursion)
+            static size_t[3] g_arr;
+            static PointerMap pm;
+            pm.bits = g_arr;
+            pm.bits[0] = 1;
+            pm.bits[1] = 0;
+            pm.bits[2] = 0;
+            return pm;
+        }
+    }
+
+    //return PointerMap for a single, moveable pointer
+    //also just a workaround for dmd bug #4397; should be a const variable
+    private PointerMap exactpointer() {
+        static size_t[3] g_arr;
+        static PointerMap pm;
+        pm.bits = g_arr;
+        pm.bits[0] = 1;
+        pm.bits[1] = 1;
+        pm.bits[2] = 1;
+        return pm;
+    }
+
     /// Get type information on the contents of the type; null if not available
     OffsetTypeInfo[] offTi() { return null; }
 }
@@ -329,6 +496,7 @@ class TypeInfo_Typedef : TypeInfo
 
     override TypeInfo next() { return base; }
     override uint flags() { return base.flags(); }
+    override PointerMap pointermap() { return base.pointermap(); }
     override void[] init() { return m_init.length ? m_init : base.init(); }
 
     TypeInfo base;
@@ -387,6 +555,7 @@ class TypeInfo_Pointer : TypeInfo
 
     override TypeInfo next() { return m_next; }
     override uint flags() { return 1; }
+    override PointerMap pointermap() { return exactpointer(); }
 
     TypeInfo m_next;
 }
@@ -466,6 +635,21 @@ class TypeInfo_Array : TypeInfo
     }
 
     override uint flags() { return 1; }
+
+    override PointerMap pointermap()
+    {
+        //return static mask for arrays
+        //  word 0: length
+        //  word 1: pointer
+        //work around for dmd bug #4397 (triggers infinite recursion)
+        static size_t[3] g_arr;
+        static PointerMap pm;
+        pm.bits = g_arr;
+        pm.bits[0] = 2;
+        pm.bits[1] = 0b10;
+        pm.bits[2] = 0b10; //moveable
+        return pm;
+    }
 }
 
 class TypeInfo_StaticArray : TypeInfo
@@ -550,6 +734,20 @@ class TypeInfo_StaticArray : TypeInfo
     override TypeInfo next() { return value; }
     override uint flags() { return value.flags(); }
 
+    override PointerMap pointermap()
+    {
+        //assert(0);
+        //this is kind of a hack to make arrays of static arrays work
+        //e.g. T[2][] (typeid(T[2]) would be this instance)
+        //because the GC repeats GC bitmasks shorter than the allocation size,
+        //  this should work well
+        //it's a hack because pointermap() is supposed to return a map that
+        //  covers the whole type (i.e. doesn't rely on repeat)
+        //this also might prevent subtle bugs, when a static array is resized
+        //  as dynamic array, and the bitmask is reused (can that happen at all?)
+        return value.pointermap();
+    }
+
     TypeInfo value;
     size_t   len;
 }
@@ -616,6 +814,7 @@ class TypeInfo_AssociativeArray : TypeInfo
 
     override TypeInfo next() { return value; }
     override uint flags() { return 1; }
+    override PointerMap pointermap() { return exactpointer(); }
 
     TypeInfo value;
     TypeInfo key;
@@ -687,6 +886,21 @@ class TypeInfo_Delegate : TypeInfo
 
     override uint flags() { return 1; }
 
+    override PointerMap pointermap()
+    {
+        //return static mask for delegates
+        //  word 0: context pointer
+        //  word 1: function pointer (not scanned)
+        //work around for dmd bug #4397 (triggers infinite recursion)
+        static size_t[3] g_arr;
+        static PointerMap pm;
+        pm.bits = g_arr;
+        pm.bits[0] = 2;
+        pm.bits[1] = 0b01;
+        pm.bits[2] = 0b01; //moveable
+        return pm;
+    }
+
     TypeInfo next;
 }
 
@@ -744,6 +958,7 @@ class TypeInfo_Class : TypeInfo
     }
 
     override uint flags() { return 1; }
+    override PointerMap pointermap() { return exactpointer(); }
 
     override OffsetTypeInfo[] offTi()
     {
@@ -813,6 +1028,7 @@ class TypeInfo_Interface : TypeInfo
     }
 
     override uint flags() { return 1; }
+    override PointerMap pointermap() { return exactpointer(); }
 
     ClassInfo info;
 }
@@ -908,6 +1124,12 @@ class TypeInfo_Struct : TypeInfo
     char[] function()   xtoString;
 
     uint m_flags;
+
+    version (D_HavePointerMap) {
+        PointerMap m_pointermap;
+
+        override PointerMap pointermap() { return m_pointermap; }
+    }
 }
 
 class TypeInfo_Tuple : TypeInfo
@@ -970,6 +1192,11 @@ class TypeInfo_Tuple : TypeInfo
     {
         assert(0);
     }
+
+    override PointerMap pointermap()
+    {
+        assert(0);
+    }
 }
 
 
diff --git a/tango/core/rt/compiler/dmd/rt/aaA.d b/tango/core/rt/compiler/dmd/rt/aaA.d
index e043524..9c0e04c 100644
--- a/tango/core/rt/compiler/dmd/rt/aaA.d
+++ b/tango/core/rt/compiler/dmd/rt/aaA.d
@@ -46,8 +46,8 @@ private
         ALL_BITS = 0b1111_1111
     }
 
-    extern (C) void* gc_malloc( size_t sz, uint ba = 0 );
-    extern (C) void* gc_calloc( size_t sz, uint ba = 0 );
+    extern (C) void*  gc_malloc( size_t sz, uint ba = 0, PointerMap bitMask = PointerMap.init);
+    extern (C) void*  gc_calloc( size_t sz, uint ba = 0, PointerMap bitMask = PointerMap.init);
     extern (C) void  gc_free( void* p );
 }
 
diff --git a/tango/core/rt/compiler/dmd/rt/adi.d b/tango/core/rt/compiler/dmd/rt/adi.d
index 0ed0ac9..d294f84 100644
--- a/tango/core/rt/compiler/dmd/rt/adi.d
+++ b/tango/core/rt/compiler/dmd/rt/adi.d
@@ -50,8 +50,8 @@ private
         ALL_BITS = 0b1111_1111
     }
 
-    extern (C) void* gc_malloc( size_t sz, uint ba = 0 );
-    extern (C) void* gc_calloc( size_t sz, uint ba = 0 );
+    extern (C) void*  gc_malloc( size_t sz, uint ba = 0, PointerMap bitMask = PointerMap.init);
+    extern (C) void*  gc_calloc( size_t sz, uint ba = 0, PointerMap bitMask = PointerMap.init);
     extern (C) void  gc_free( void* p );
 }
 
diff --git a/tango/core/rt/compiler/dmd/rt/lifetime.d b/tango/core/rt/compiler/dmd/rt/lifetime.d
index fd89ce7..022e452 100644
--- a/tango/core/rt/compiler/dmd/rt/lifetime.d
+++ b/tango/core/rt/compiler/dmd/rt/lifetime.d
@@ -57,8 +57,8 @@ private
     extern (C) uint gc_setAttr( void* p, uint a );
     extern (C) uint gc_clrAttr( void* p, uint a );
 
-    extern (C) void*  gc_malloc( size_t sz, uint ba = 0 );
-    extern (C) void*  gc_calloc( size_t sz, uint ba = 0 );
+    extern (C) void*  gc_malloc( size_t sz, uint ba = 0, PointerMap bitMask = PointerMap.init);
+    extern (C) void*  gc_calloc( size_t sz, uint ba = 0, PointerMap bitMask = PointerMap.init);
     extern (C) size_t gc_extend( void* p, size_t mx, size_t sz );
     extern (C) void   gc_free( void* p );
 
@@ -101,8 +101,13 @@ extern (C) Object _d_newclass(ClassInfo ci)
     }
     else
     {
+        PointerMap pm;
+        version (D_HavePointerMap) {
+            pm = ci.pointermap;
+        }
         p = gc_malloc(ci.init.length,
-                      BlkAttr.FINALIZE | (ci.flags & 2 ? BlkAttr.NO_SCAN : 0));
+                      BlkAttr.FINALIZE | (ci.flags & 2 ? BlkAttr.NO_SCAN : 0),
+                      pm);
         debug(PRINTF) printf(" p = %p\n", p);
     }
 
@@ -210,7 +215,11 @@ extern (C) ulong _d_newarrayT(TypeInfo ti, size_t length)
         }
         else
             size *= length;
-        p = gc_malloc(size + 1, !(ti.next.flags() & 1) ? BlkAttr.NO_SCAN : 0);
+        PointerMap pm;
+        version (D_HavePointerMap) {
+            pm = ti.next.pointermap();
+        }
+        p = gc_malloc(size + 1, !(ti.next.flags() & 1) ? BlkAttr.NO_SCAN : 0, pm);
         debug(PRINTF) printf(" p = %p\n", p);
         memset(p, 0, size);
         result = cast(ulong)length + (cast(ulong)cast(uint)p << 32);
@@ -250,7 +259,11 @@ extern (C) ulong _d_newarrayiT(TypeInfo ti, size_t length)
         }
         else
             size *= length;
-        auto p = gc_malloc(size + 1, !(ti.next.flags() & 1) ? BlkAttr.NO_SCAN : 0);
+        PointerMap pm;
+        version (D_HavePointerMap) {
+            pm = ti.next.pointermap();
+        }
+        auto p = gc_malloc(size + 1, !(ti.next.flags() & 1) ? BlkAttr.NO_SCAN : 0, pm);
         debug(PRINTF) printf(" p = %p\n", p);
         if (isize == 1)
             memset(p, *cast(ubyte*)q, size);
@@ -580,7 +593,11 @@ body
                             goto L1;
                         }
                     }
-                    newdata = cast(byte *)gc_malloc(newsize + 1, info.attr);
+                    PointerMap pm;
+                    version (D_HavePointerMap) {
+                        pm = ti.next.pointermap();
+                    }
+                    newdata = cast(byte *)gc_malloc(newsize + 1, info.attr, pm);
                     newdata[0 .. size] = p.data[0 .. size];
                 }
              L1:
@@ -589,7 +606,11 @@ body
         }
         else
         {
-            newdata = cast(byte *)gc_calloc(newsize + 1, !(ti.next.flags() & 1) ? BlkAttr.NO_SCAN : 0);
+            PointerMap pm;
+            version (D_HavePointerMap) {
+                pm = ti.next.pointermap();
+            }
+            newdata = cast(byte *)gc_calloc(newsize + 1, !(ti.next.flags() & 1) ? BlkAttr.NO_SCAN : 0, pm);
         }
     }
     else
@@ -680,7 +701,11 @@ body
                             goto L1;
                         }
                     }
-                    newdata = cast(byte *)gc_malloc(newsize + 1, info.attr);
+                    PointerMap pm;
+                    version (D_HavePointerMap) {
+                        pm = ti.next.pointermap();
+                    }
+                    newdata = cast(byte *)gc_malloc(newsize + 1, info.attr, pm);
                     newdata[0 .. size] = p.data[0 .. size];
                 L1: ;
                 }
@@ -688,7 +713,11 @@ body
         }
         else
         {
-            newdata = cast(byte *)gc_malloc(newsize + 1, !(ti.next.flags() & 1) ? BlkAttr.NO_SCAN : 0);
+            PointerMap pm;
+            version (D_HavePointerMap) {
+                pm = ti.next.pointermap();
+            }
+            newdata = cast(byte *)gc_malloc(newsize + 1, !(ti.next.flags() & 1) ? BlkAttr.NO_SCAN : 0, pm);
         }
 
         auto q = initializer.ptr; // pointer to initializer
@@ -746,7 +775,11 @@ extern (C) long _d_arrayappendT(TypeInfo ti, Array *px, byte[] y)
                 goto L1;
             }
         }
-        newdata = cast(byte *)gc_malloc(newCapacity(newlength, sizeelem) + 1, info.attr);
+        PointerMap pm;
+        version (D_HavePointerMap) {
+            pm = ti.next.pointermap();
+        }
+        newdata = cast(byte *)gc_malloc(newCapacity(newlength, sizeelem) + 1, info.attr, pm);
         memcpy(newdata, px.data, length * sizeelem);
         px.data = newdata;
     }
@@ -844,10 +877,14 @@ extern (C) byte[] _d_arrayappendcT(TypeInfo ti, ref byte[] x, ...)
                 goto L1;
             }
         }
-        debug(PRINTF) printf("_d_arrayappendcT(length = %d, newlength = %d, cap = %d)\n", length, newlength, info.size);
+        debug(PRINTF) printf("_d_arrayappendcT(length = %d, newlength = %d, cap = %d, ti=%.*s)\n", length, newlength, info.size, ti.toString());
         auto newcap = newCapacity(newlength, sizeelem);
         assert(newcap >= newlength * sizeelem);
-        newdata = cast(byte *)gc_malloc(newcap + 1, info.attr);
+        PointerMap pm;
+        version (D_HavePointerMap) {
+            pm = ti.next.pointermap();
+        }
+        newdata = cast(byte *)gc_malloc(newcap + 1, info.attr, pm);
         memcpy(newdata, x.ptr, length * sizeelem);
         (cast(void**)(&x))[1] = newdata;
     }
@@ -1049,7 +1086,11 @@ body
     if (!len)
         return null;
 
-    byte* p = cast(byte*)gc_malloc(len + 1, !(ti.next.flags() & 1) ? BlkAttr.NO_SCAN : 0);
+    PointerMap pm;
+    version (D_HavePointerMap) {
+        pm = ti.next.pointermap();
+    }
+    byte* p = cast(byte*)gc_malloc(len + 1, !(ti.next.flags() & 1) ? BlkAttr.NO_SCAN : 0, pm);
     memcpy(p, x.ptr, xlen);
     memcpy(p + xlen, y.ptr, ylen);
     p[len] = 0;
@@ -1078,7 +1119,11 @@ extern (C) byte[] _d_arraycatnT(TypeInfo ti, uint n, ...)
     if (!length)
         return null;
 
-    a = gc_malloc(length * size, !(ti.next.flags() & 1) ? BlkAttr.NO_SCAN : 0);
+    PointerMap pm;
+    version (D_HavePointerMap) {
+        pm = ti.next.pointermap();
+    }
+    a = gc_malloc(length * size, !(ti.next.flags() & 1) ? BlkAttr.NO_SCAN : 0, pm);
     p = cast(byte[]*)(&n + 1);
 
     uint j = 0;
@@ -1112,7 +1157,11 @@ extern (C) void* _d_arrayliteralT(TypeInfo ti, size_t length, ...)
         result = null;
     else
     {
-        result = gc_malloc(length * sizeelem, !(ti.next.flags() & 1) ? BlkAttr.NO_SCAN : 0);
+        PointerMap pm;
+        version (D_HavePointerMap) {
+            pm = ti.next.pointermap();
+        }
+        result = gc_malloc(length * sizeelem, !(ti.next.flags() & 1) ? BlkAttr.NO_SCAN : 0, pm);
 
         va_list q;
         va_start!(size_t)(q, length);
@@ -1165,7 +1214,11 @@ body
     {
         auto sizeelem = ti.next.tsize();                // array element size
         auto size = a.length * sizeelem;
-        r.ptr = gc_malloc(size, !(ti.next.flags() & 1) ? BlkAttr.NO_SCAN : 0);
+        PointerMap pm;
+        version (D_HavePointerMap) {
+            pm = ti.next.pointermap();
+        }
+        r.ptr = gc_malloc(size, !(ti.next.flags() & 1) ? BlkAttr.NO_SCAN : 0, pm);
         r.length = a.length;
         memcpy(r.ptr, a.ptr, size);
     }
diff --git a/tango/core/rt/gc/basic/gc.d b/tango/core/rt/gc/basic/gc.d
index 9c53497..58f160f 100644
--- a/tango/core/rt/gc/basic/gc.d
+++ b/tango/core/rt/gc/basic/gc.d
@@ -144,19 +144,19 @@ extern (C) uint gc_clrAttr( void* p, uint a )
     return _gc.clrAttr( p, a );
 }
 
-extern (C) void* gc_malloc( size_t sz, uint ba = 0 )
+extern (C) void* gc_malloc( size_t sz, uint ba = 0, PointerMap bitMask = PointerMap.init)
 {
-    return _gc.malloc( sz, ba );
+    return _gc.malloc( sz, ba, bitMask );
 }
 
-extern (C) void* gc_calloc( size_t sz, uint ba = 0 )
+extern (C) void* gc_calloc( size_t sz, uint ba = 0, PointerMap bitMask = PointerMap.init)
 {
-    return _gc.calloc( sz, ba );
+    return _gc.calloc( sz, ba, bitMask );
 }
 
-extern (C) void* gc_realloc( void* p, size_t sz, uint ba = 0 )
+extern (C) void* gc_realloc( void* p, size_t sz, uint ba = 0, PointerMap bitMask = PointerMap.init)
 {
-    return _gc.realloc( p, sz, ba );
+    return _gc.realloc( p, sz, ba, bitMask );
 }
 
 extern (C) size_t gc_extend( void* p, size_t mx, size_t sz )
@@ -233,6 +233,13 @@ extern (C) Object gc_weakpointerGet( void* wp )
     return _gc.weakpointerGet(wp);
 }
 
+//controlls whether the GC makes use of the stored bitmasks when scanning
+//if false is passed, everything will be scanned conservatively
+extern (C) void gc_set_precise( bool precise )
+{
+    _gc.setPreciseScanning(precise);
+}
+
 private alias extern(D) void delegate() ddel;
 private alias extern(D) void delegate(int, int) dint;
 
diff --git a/tango/core/rt/gc/basic/gcx.d b/tango/core/rt/gc/basic/gcx.d
index 74d22ae..0dbc01f 100644
--- a/tango/core/rt/gc/basic/gcx.d
+++ b/tango/core/rt/gc/basic/gcx.d
@@ -44,13 +44,15 @@ version = STACKGROWSDOWN;       // growing the stack means subtracting from the
                                 // else growing the stack means adding to the stack pointer
 version = MULTI_THREADED;       // produce multithreaded version
 
+version = ENABLE_PRECISE;       // precise heap scanning by default (if bitmasks available)
+
 /***************************************************/
 
 private import rt.gc.basic.gcbits;
 private import rt.gc.basic.gcstats;
 private import rt.gc.basic.gcalloc;
 
-private import cstdlib = tango.stdc.stdlib : calloc, free, malloc, realloc;
+private import cstdlib = tango.stdc.stdlib : calloc, free, malloc, realloc, getenv;
 private import cstring = tango.stdc.string : memcpy, memmove, memset;
 debug(THREADINVARIANT) private import tango.stdc.posix.pthread : pthread_self, pthread_t;
 debug(PRINTF) private import tango.stdc.stdio : printf;
@@ -72,6 +74,76 @@ struct BlkInfo
     uint   attr;
 }
 
+private struct BlkInfoIntern
+{
+    Pool*   pool;       //pool for base ptr
+    void*   base;       //start of the allocation
+    size_t  size;       //size of the allocation, INCLUDING bitmask (unlike BlkInfo.size)
+    uint    attr;       //like BlkInfo.attr
+
+    //pointer to first user-useable byte (needed for SENTINEL_PRE stuff)
+    void* userBase()
+    {
+        return base + SENTINEL_PRE;
+    }
+
+    //user useable size of the allocation (excluding bitmask)
+    size_t userSize()
+    {
+        //don't underflow (mostly for the size==0 case)
+        size_t extra = blockExtraSize(attr);
+        return size >= extra ? size - extra : 0;
+    }
+
+    //offset of the allocation into the pool
+    size_t offset()
+    {
+        return base - pool.baseAddr;
+    }
+
+    BlkInfo blkinfo()
+    {
+        return BlkInfo(userBase(), userSize(), attr);
+    }
+}
+
+//use bitmasks; controlled by ENABLE_PRECISE version and D_PRECISE_HEAP environment variable
+private bool g_enable_precise;
+//can be used to force conservative scanning (changeable at runtime via gc_set_precise())
+private bool g_scan_precise = true;
+
+private bool use_bitmask(uint bits)
+{
+    return !(bits & BlkAttr.NO_SCAN) && g_enable_precise;
+}
+
+//return size of extra data per block that isn't available for the user
+//bits = same as in BlkInfo.attr
+private size_t blockExtraSize(uint bits)
+{
+    return SENTINEL_EXTRA + (use_bitmask(bits) ? (size_t*).sizeof : 0);
+}
+
+//base,size refer to the full block (including extra-size)
+//bits = same as in BlkInfo.attr
+private void blockWriteBitMask(void* base, size_t size, uint bits, size_t* bitMask)
+{
+    if (!use_bitmask(bits))
+        return;
+    // Store the bit mask AFTER SENTINEL_POST.
+    *cast(size_t**)(base + size - (size_t*).sizeof) = bitMask;
+}
+
+//read bitmask from memory, or null if no bitmask attached to the allocation
+//parameters same as blockWriteBitMask()
+private size_t* blockReadBitMask(void* base, size_t size, uint bits)
+{
+    if (!use_bitmask(bits))
+        return null;
+    return *cast(size_t**)(base + size - (size_t*).sizeof);
+}
+
+
 private
 {
     const USE_CACHE = true;
@@ -425,7 +497,7 @@ class GC
     /**
      *
      */
-    final void *malloc(size_t size, uint bits = 0)
+    final void *malloc(size_t size, uint bits, PointerMap bitMask)
     {
         if (!size)
         {
@@ -437,7 +509,7 @@ class GC
         // when allocating.
         synchronized (gcLock)
         {
-            return mallocNoSync(size, bits);
+            return mallocNoSync(size, bits, bitMask.bits.ptr);
         }
     }
 
@@ -445,18 +517,20 @@ class GC
     //
     //
     //
-    private void *mallocNoSync(size_t size, uint bits = 0)
+    private void *mallocNoSync(size_t size, uint bits, size_t* bitMask)
     {
         assert(size != 0);
 
         void *p = null;
         Bins bin;
 
-        //debug(PRINTF) printf("GC::malloc(size = %d, gcx = %p)\n", size, gcx);
+        debug(PRINTF) printf("GC::malloc(size = %d, gcx = %p, bitMask = %p)\n", size, gcx, bitMask);
         assert(gcx);
         //debug(PRINTF) printf("gcx.self = %x, pthread_self() = %x\n", gcx.self, pthread_self());
 
-        size += SENTINEL_EXTRA;
+        size_t request_size = size;
+        size_t extra = blockExtraSize(bits);
+        size += extra;
 
         // Compute size bin
         // Cache previous binsize lookup - Dave Fladebo.
@@ -471,6 +545,7 @@ class GC
             lastbin = bin;
         }
 
+        size_t capacity;  // To figure out where to store the bitmask.
         if (bin < B_PAGE)
         {
             int  state     = gcx.disabled ? 1 : 0;
@@ -499,6 +574,7 @@ class GC
                 }
             }
             p = gcx.bucket[bin];
+            capacity = binsize[bin];
 
             // Return next item from free list
             gcx.bucket[bin] = (cast(List*)p).next;
@@ -512,11 +588,16 @@ class GC
             p = gcx.bigAlloc(size);
             if (!p)
                 onOutOfMemoryError();
+            size_t npages = (size + PAGESIZE - 1) / PAGESIZE;
+            capacity = npages * PAGESIZE;
         }
-        size -= SENTINEL_EXTRA;
-        p = sentinel_add(p);
-        sentinel_init(p, size);
-        gcx.log_malloc(p, size);
+
+        blockWriteBitMask(p, capacity, bits, bitMask);
+
+        p += SENTINEL_PRE;
+
+        sentinel_init(p, request_size);
+        gcx.log_malloc(p, request_size);
 
         if (bits)
         {
@@ -525,6 +606,7 @@ class GC
 
             gcx.setBits(pool, cast(size_t)(p - pool.baseAddr) / 16, bits);
         }
+        debug(PRINTF) printf("Done malloc.  Ptr:  %p  Bitmask:  %p\n", p, bitMask);
         return p;
     }
 
@@ -532,7 +614,7 @@ class GC
     /**
      *
      */
-    final void *calloc(size_t size, uint bits = 0)
+    final void *calloc(size_t size, uint bits, PointerMap bitMask)
     {
         if (!size)
         {
@@ -544,7 +626,7 @@ class GC
         // when allocating.
         synchronized (gcLock)
         {
-            return callocNoSync(size, bits);
+            return callocNoSync(size, bits, bitMask.bits.ptr);
         }
     }
 
@@ -552,12 +634,12 @@ class GC
     //
     //
     //
-    private void *callocNoSync(size_t size, uint bits = 0)
+    private void *callocNoSync(size_t size, uint bits, size_t* bitMask)
     {
         assert(size != 0);
 
         //debug(PRINTF) printf("calloc: %x len %d\n", p, len);
-        void *p = mallocNoSync(size, bits);
+        void *p = mallocNoSync(size, bits, bitMask);
         cstring.memset(p, 0, size);
         return p;
     }
@@ -566,153 +648,53 @@ class GC
     /**
      *
      */
-    final void *realloc(void *p, size_t size, uint bits = 0)
+    void *realloc(void *p, size_t size, uint bits, PointerMap bitMask)
     {
         // Since a finalizer could launch a new thread, we always need to lock
         // when collecting.  The safest way to do this is to simply always lock
         // when allocating.
         synchronized (gcLock)
         {
-            return reallocNoSync(p, size, bits);
+            return reallocNoSync(p, size, bits, bitMask.bits.ptr);
         }
     }
 
-
     //
     //
     //
-    private void *reallocNoSync(void *p, size_t size, uint bits = 0)
+    private void *reallocNoSync(void *p, size_t size, uint bits,
+        size_t* bitMask)
     {
         if (!size)
         {   if (p)
             {   freeNoSync(p);
-                p = null;
+                return null;
             }
         }
         else if (!p)
         {
-            p = mallocNoSync(size, bits);
+            return mallocNoSync(size, bits, bitMask);
         }
-        else
-        {   void *p2;
-            size_t psize;
-
-            //debug(PRINTF) printf("GC::realloc(p = %x, size = %u)\n", p, size);
-            version (SENTINEL)
-            {
-                sentinel_Invariant(p);
-                psize = *sentinel_size(p);
-                if (psize != size)
-                {
-                    if (psize)
-                    {
-                        Pool *pool = gcx.findPool(p);
-
-                        if (pool)
-                        {
-                            auto biti = cast(size_t)(p - pool.baseAddr) / 16;
 
-                            if (bits)
-                            {
-                                gcx.clrBits(pool, biti, BlkAttr.ALL_BITS);
-                                gcx.setBits(pool, biti, bits);
-                            }
-                            else
-                            {
-                                bits = gcx.getBits(pool, biti);
-                            }
-                        }
-                    }
-                    p2 = mallocNoSync(size, bits);
-                    if (psize < size)
-                        size = psize;
-                    //debug(PRINTF) printf("\tcopying %d bytes\n",size);
-                    cstring.memcpy(p2, p, size);
-                    p = p2;
-                }
-            }
-            else
-            {
-                psize = gcx.findSize(p);        // find allocated size
-                if (psize >= PAGESIZE && size >= PAGESIZE)
-                {
-                    auto psz = psize / PAGESIZE;
-                    auto newsz = (size + PAGESIZE - 1) / PAGESIZE;
-                    if (newsz == psz)
-                        return p;
+        BlkInfoIntern info = gcx.getInfoIntern(p);
 
-                    auto pool = gcx.findPool(p);
-                    auto pagenum = (p - pool.baseAddr) / PAGESIZE;
+        // reject interior pointers
+        if (p !is info.userBase())
+            return p; //return original pointer on failure
 
-                    if (newsz < psz)
-                    {   // Shrink in place
-                        synchronized (gcLock)
-                        {
-                            debug (MEMSTOMP) cstring.memset(p + size, 0xF2, psize - size);
-                            pool.freePages(pagenum + newsz, psz - newsz);
-                        }
-                        return p;
-                    }
-                    else if (pagenum + newsz <= pool.npages)
-                    {
-                        // Attempt to expand in place
-                        synchronized (gcLock)
-                        {
-                            for (size_t i = pagenum + psz; 1;)
-                            {
-                                if (i == pagenum + newsz)
-                                {
-                                    debug (MEMSTOMP) cstring.memset(p + psize, 0xF0, size - psize);
-                                    cstring.memset(&pool.pagetable[pagenum + psz], B_PAGEPLUS, newsz - psz);
-                                    return p;
-                                }
-                                if (i == pool.ncommitted)
-                                {
-                                    auto u = pool.extendPages(pagenum + newsz - pool.ncommitted);
-                                    if (u == OPFAIL)
-                                        break;
-                                    i = pagenum + newsz;
-                                    continue;
-                                }
-                                if (pool.pagetable[i] != B_FREE)
-                                    break;
-                                i++;
-                            }
-                        }
-                    }
-                }
-                if (psize < size ||             // if new size is bigger
-                    psize > size * 2)           // or less than half
-                {
-                    if (psize)
-                    {
-                        Pool *pool = gcx.findPool(p);
+        // block already big enough (no support for shrinking)
+        if (info.userSize() >= size)
+            return p;
 
-                        if (pool)
-                        {
-                            auto biti = cast(size_t)(p - pool.baseAddr) / 16;
+        // try to enlarge in-place
+        size_t add = size - info.userSize();
+        if (extendNoSync(p, add, add) != 0)
+            return p;
 
-                            if (bits)
-                            {
-                                gcx.clrBits(pool, biti, BlkAttr.ALL_BITS);
-                                gcx.setBits(pool, biti, bits);
-                            }
-                            else
-                            {
-                                bits = gcx.getBits(pool, biti);
-                            }
-                        }
-                    }
-                    p2 = mallocNoSync(size, bits);
-                    if (psize < size)
-                        size = psize;
-                    //debug(PRINTF) printf("\tcopying %d bytes\n",size);
-                    cstring.memcpy(p2, p, size);
-                    p = p2;
-                }
-            }
-        }
-        return p;
+        // allocate new block and copy old data
+        void* nptr = mallocNoSync(size, bits, bitMask);
+        memcpy(nptr, p, info.userSize());
+        return nptr;
     }
 
 
@@ -753,16 +735,28 @@ class GC
         {
             return 0;
         }
-        auto psize = gcx.findSize(p);   // find allocated size
-        if (psize < PAGESIZE)
-            return 0;                   // cannot extend buckets
+
+        BlkInfoIntern info = gcx.getInfoIntern(p);
+        Pool* pool = info.pool;
+        size_t psize = info.size;
+
+        // no GC pointer / interior pointer / cannot extend buckets
+        if (!pool || info.userBase() !is p || psize < PAGESIZE)
+            return 0;
+
+        //no SENTINEL
+        assert(info.userBase() == info.base);
+
+        size_t extra = blockExtraSize(info.attr);
+
+        minsize += extra;
+        maxsize += extra;
 
         auto psz = psize / PAGESIZE;
         auto minsz = (minsize + PAGESIZE - 1) / PAGESIZE;
         auto maxsz = (maxsize + PAGESIZE - 1) / PAGESIZE;
 
-        auto pool = gcx.findPool(p);
-        auto pagenum = (p - pool.baseAddr) / PAGESIZE;
+        auto pagenum = info.offset() / PAGESIZE;
 
         size_t sz;
         for (sz = 0; sz < maxsz; sz++)
@@ -788,13 +782,20 @@ class GC
         }
         else
             return 0;
-        debug (MEMSTOMP) memset(p + psize, 0xF0, (psz + sz) * PAGESIZE - psize);
+
+        size_t* bitMask = blockReadBitMask(info.base, info.size, info.attr);
+        size_t newSize = (psz + sz) * PAGESIZE;
+
+        debug (MEMSTOMP) memset(p + psize, 0xF0, newSize - psize);
         memset(pool.pagetable + pagenum + psz, B_PAGEPLUS, sz);
-        if (p == gcx.cached_size_key)
-            gcx.cached_size_val = (psz + sz) * PAGESIZE;
         if (p == gcx.cached_info_key)
-            gcx.cached_info_val.size = (psz + sz) * PAGESIZE;
-        return (psz + sz) * PAGESIZE;
+            gcx.cached_info_val.size = newSize;
+
+        blockWriteBitMask(p, newSize, info.attr, bitMask);
+
+        newSize -= extra;
+
+        return newSize;
     }
 
 
@@ -927,12 +928,7 @@ class GC
     //
     final void* addrOfNoSync(void *p)
     {
-        if (!p)
-        {
-            return null;
-        }
-
-        return gcx.findBase(p);
+        return gcx.getInfo(p).base;
     }
 
 
@@ -963,33 +959,13 @@ class GC
     //
     private size_t sizeOfNoSync(void *p)
     {
-        assert (p);
-
-        version (SENTINEL)
-        {
-            p = sentinel_sub(p);
-            size_t size = gcx.findSize(p);
+        BlkInfo info = gcx.getInfo(p);
 
-            // Check for interior pointer
-            // This depends on:
-            // 1) size is a power of 2 for less than PAGESIZE values
-            // 2) base of memory pool is aligned on PAGESIZE boundary
-            if (cast(size_t)p & (size - 1) & (PAGESIZE - 1))
-                size = 0;
-            return size ? size - SENTINEL_EXTRA : 0;
-        }
-        else
-        {
-            size_t size = gcx.findSize(p);
+        // Check for interior pointer
+        if (info.base !is p)
+            return 0;
 
-            // Check for interior pointer
-            // This depends on:
-            // 1) size is a power of 2 for less than PAGESIZE values
-            // 2) base of memory pool is aligned on PAGESIZE boundary
-            if (cast(size_t)p & (size - 1) & (PAGESIZE - 1))
-                return 0;
-            return size;
-        }
+        return info.size;
     }
 
 
@@ -1211,11 +1187,9 @@ class GC
     {
         debug(PRINTF) printf("GC.fullCollect()\n");
 
-        if (!thread_needLock())
-        {
-            gcx.fullcollectshell();
-        }
-        else synchronized (gcLock)
+        // Since a finalizer could launch a new thread, we always need to lock
+        // when collecting.
+        synchronized (gcLock)
         {
             gcx.fullcollectshell();
         }
@@ -1417,6 +1391,11 @@ class GC
            }
         return null;
     }
+
+    final void setPreciseScanning(bool precise)
+    {
+        g_scan_precise = precise;
+    }
 }
 
 
@@ -1486,12 +1465,9 @@ struct Gcx
     {
         void thread_Invariant() { }
     }
-    
-    void *cached_size_key;
-    size_t cached_size_val;
-    
+
     void *cached_info_key;
-    BlkInfo cached_info_val;
+    BlkInfoIntern cached_info_val;
 
     size_t nroots;
     size_t rootdim;
@@ -1523,6 +1499,18 @@ struct Gcx
     void initialize()
     {   int dummy;
 
+        //use bitmasks; controlled by ENABLE_PRECISE version and  environment variable
+        version (ENABLE_PRECISE) {
+            g_enable_precise = true;
+            char* v = getenv("D_PRECISE_HEAP");
+            if (v)
+            {
+                g_enable_precise = (v[0] == '1' && v[1] == '\0');
+            }
+        } else {
+            g_enable_precise = false;
+        }
+
         (cast(byte*)this)[0 .. Gcx.sizeof] = 0;
         stackBottom = cast(char*)&dummy;
         log_init();
@@ -1738,98 +1726,27 @@ struct Gcx
         return null;
     }
 
-
     /**
-     * Find base address of block containing pointer p.
-     * Returns null if not a gc'd pointer
-     */
-    void* findBase(void *p)
-    {
-        Pool *pool;
-
-        pool = findPool(p);
-        if (pool)
-        {
-            size_t offset = cast(size_t)(p - pool.baseAddr);
-            size_t pn = offset / PAGESIZE;
-            Bins   bin = cast(Bins)pool.pagetable[pn];
-
-            // Adjust bit to be at start of allocated memory block
-            if (bin <= B_PAGE)
-            {
-                return pool.baseAddr + (offset & notbinsize[bin]);
-            }
-            else if (bin == B_PAGEPLUS)
-            {
-                do
-                {   --pn, offset -= PAGESIZE;
-                } while (cast(Bins)pool.pagetable[pn] == B_PAGEPLUS);
-
-                return pool.baseAddr + (offset & (offset.max ^ (PAGESIZE-1)));
-            }
-            else
-            {
-                // we are in a B_FREE or B_UNCOMMITTED page
-                return null;
-            }
-        }
-        return null;
-    }
-
-
-    /**
-     * Find size of pointer p.
-     * Returns 0 if not a gc'd pointer
+     *
      */
-    size_t findSize(void *p)
+    BlkInfo getInfo(void* p)
     {
-        Pool*  pool;
-        size_t size = 0;
-
-        if (USE_CACHE && p == cached_size_key)
-            return cached_size_val;
-            
-        pool = findPool(p);
-        if (pool)
-        {
-            size_t pagenum;
-            Bins   bin;
-
-            pagenum = cast(size_t)(p - pool.baseAddr) / PAGESIZE;
-            bin = cast(Bins)pool.pagetable[pagenum];
-            size = binsize[bin];
-            if (bin == B_PAGE)
-            {   size_t npages = pool.ncommitted;
-                ubyte* pt;
-                size_t i;
-
-                pt = &pool.pagetable[0];
-                for (i = pagenum + 1; i < npages; i++)
-                {
-                    if (pt[i] != B_PAGEPLUS)
-                        break;
-                }
-                size = (i - pagenum) * PAGESIZE;
-            }
-            cached_size_key = p;
-            cached_size_val = size;
-        }
-        return size;
+        return getInfoIntern(p).blkinfo();
     }
 
-
     /**
-     *
+     * Internal version of getInfo(), which provides additional information.
      */
-    BlkInfo getInfo(void* p)
+    BlkInfoIntern getInfoIntern(void* p)
     {
         Pool*   pool;
-        BlkInfo info;
+        BlkInfoIntern info;
         
         if (USE_CACHE && p == cached_info_key)
             return cached_info_val;
 
         pool = findPool(p);
+        info.pool = pool;
         if (pool)
         {
             size_t offset = cast(size_t)(p - pool.baseAddr);
@@ -1880,7 +1797,7 @@ struct Gcx
             ////////////////////////////////////////////////////////////////////
 
             info.attr = getBits(pool, cast(size_t)(offset / 16));
-            
+
             cached_info_key = p;
             cached_info_val = info;
         }
@@ -2190,32 +2107,53 @@ struct Gcx
         return 1;
     }
 
+    /**Marks a range of memory using the conservative bit mask.  Used for
+     * the stack, for the data segment, and additional memory ranges.
+     */
+    void markConservative(void* p1, void* p2) {
+        mark(p1, p2, null);
+    }
+
 
     /**
      * Search a range of memory values and mark any pointers into the GC pool.
      */
-    void mark(void *pbot, void *ptop)
+    void mark(void *pbot, void *ptop, size_t* bitMask)
     {
+        const BITS = size_t.sizeof * 8;
+
         void **p1 = cast(void **)pbot;
         void **p2 = cast(void **)ptop;
         size_t pcache = 0;
         uint changes = 0;
 
+        if (bitMask is null || !g_scan_precise)
+            bitMask = PointerMap.init.bits.ptr;
+
+        size_t tSize = bitMask[0];
+        size_t* bits = bitMask + 1;
+
         //printf("marking range: %p -> %p\n", pbot, ptop);
-        for (; p1 < p2; p1++)
-        {
-            Pool *pool;
-            byte *p = cast(byte *)(*p1);
 
-            //if (log) debug(PRINTF) printf("\tmark %x\n", p);
-            if (p >= minAddr && p < maxAddr)
+        while(p1 + tSize <= p2)
+        {
+            for (size_t n = 0; n < tSize; n++)
             {
-                if ((cast(size_t)p & ~(PAGESIZE-1)) == pcache)
- 	            continue;
+                if ((bits[n / BITS] & (1 << (n % BITS)))) {
+                    debug(COLLECT_LOOP_PRINTF) printf("Marking %p\n", p1 + n);
+                    void* p = *(p1 + n);
+
+                    //if (log) debug(PRINTF) printf("\tmark %x\n", p);
+                    if (!(p >= minAddr && p < maxAddr))
+                        goto noscan;
+
+                    if ((cast(size_t)p & ~(PAGESIZE-1)) == pcache)
+                        goto noscan;
+
+                    Pool* pool = findPool(p);
+                    if (!pool)
+                        goto noscan;
 
-                pool = findPool(p);
-                if (pool)
-                {
                     size_t offset = cast(size_t)(p - pool.baseAddr);
                     size_t biti;
                     size_t pn = offset / PAGESIZE;
@@ -2239,7 +2177,7 @@ struct Gcx
                     else
                     {
                         // Don't mark bits in B_FREE or B_UNCOMMITTED pages
-                        continue;
+                        goto noscan;
                     }
 
                     if (bin >= B_PAGE) // Cache B_PAGE and B_PAGEPLUS lookups
@@ -2248,7 +2186,7 @@ struct Gcx
                     //debug(PRINTF) printf("\t\tmark(x%x) = %d\n", biti, pool.mark.test(biti));
                     if (!pool.mark.test(biti))
                     {
-                        //if (log) debug(PRINTF) printf("\t\tmarking %x\n", p);
+                        debug(PRINTF) printf("\t\tmarking %x\n", p);
                         pool.mark.set(biti);
                         if (!pool.noscan.test(biti))
                         {
@@ -2258,8 +2196,13 @@ struct Gcx
                         log_parent(sentinel_add(pool.baseAddr + biti * 16), sentinel_add(pbot));
                     }
                 }
+
+                //jump target if the pointer isn't scanned
+                noscan: ;
             }
+            p1 += tSize;
         }
+
         anychanges |= changes;
     }
 
@@ -2365,8 +2308,6 @@ struct Gcx
 
         thread_suspendAll();
 
-        cached_size_key = cached_size_key.init;
-        cached_size_val = cached_size_val.init;
         cached_info_key = cached_info_key.init;
         cached_info_val = cached_info_val.init;
 
@@ -2396,14 +2337,16 @@ struct Gcx
             pool.mark.copy(&pool.freebits);
         }
 
-        rt_scanStaticData( &mark );
+        rt_scanStaticData( &markConservative );
 
         version (MULTI_THREADED)
         {
             if (!noStack)
             {
+                debug(PRINTF) printf(" scan stack bot = %x, top = %x\n", stackTop, stackBottom);
+
                 // Scan stacks and registers for each paused thread
-                thread_scanAll( &mark, stackTop );
+                thread_scanAll( &markConservative, stackTop );
             }
         }
         else
@@ -2413,15 +2356,15 @@ struct Gcx
                 // Scan stack for main thread
                 debug(PRINTF) printf(" scan stack bot = %x, top = %x\n", stackTop, stackBottom);
                 version (STACKGROWSDOWN)
-                    mark(stackTop, stackBottom);
+                    markConservative(stackTop, stackBottom);
                 else
-                    mark(stackBottom, stackTop);
+                    markConservative(stackBottom, stackTop);
             }
         }
 
         // Scan roots[]
         debug(COLLECT_PRINTF) printf("scan roots[]\n");
-        mark(roots, roots + nroots);
+        markConservative(roots, roots + nroots);
 
         // Scan ranges[]
         debug(COLLECT_PRINTF) printf("scan ranges[]\n");
@@ -2429,7 +2372,7 @@ struct Gcx
         for (n = 0; n < nranges; n++)
         {
             debug(COLLECT_PRINTF) printf("\t%x .. %x\n", ranges[n].pbot, ranges[n].ptop);
-            mark(ranges[n].pbot, ranges[n].ptop);
+            markConservative(ranges[n].pbot, ranges[n].ptop);
         }
         //log--;
 
@@ -2474,21 +2417,34 @@ struct Gcx
 
                         pn = cast(size_t)(o - pool.baseAddr) / PAGESIZE;
                         bin = cast(Bins)pool.pagetable[pn];
-                        if (bin < B_PAGE)
+                        assert(bin != B_PAGEPLUS);
+                        assert(bin <= B_PAGE);
+                        if (bin <= B_PAGE)
                         {
-                            mark(o, o + binsize[bin]);
-                        }
-                        else if (bin == B_PAGE || bin == B_PAGEPLUS)
-                        {
-                            if (bin == B_PAGEPLUS)
+                            size_t blockSize = void;
+                            if (bin == B_PAGE)
                             {
-                                while (pool.pagetable[pn - 1] != B_PAGE)
-                                    pn--;
+                                u = 1;
+                                while (pn + u < pool.ncommitted && pool.pagetable[pn + u] == B_PAGEPLUS)
+                                    u++;
+                                blockSize = u * PAGESIZE;
                             }
-                            u = 1;
-                            while (pn + u < pool.ncommitted && pool.pagetable[pn + u] == B_PAGEPLUS)
-                                u++;
-                            mark(o, o + u * PAGESIZE);
+                            else
+                            {
+                                blockSize = binsize[bin];
+                            }
+
+                            size_t bitMaskSize = 0;
+                            size_t* bitMask = null;
+                            if (g_enable_precise)
+                            {
+                                bitMaskSize = (size_t*).sizeof;
+                                bitMask = *(cast(size_t**) (o + blockSize - bitMaskSize));
+                                blockSize -= bitMaskSize;
+                            }
+
+                            debug(PRINTF) printf("Bitmask:  %p\n", bitMask);
+                            mark(o + SENTINEL_PRE, o + blockSize - SENTINEL_POST, bitMask);
                         }
                     }
                 }
@@ -3073,9 +3029,12 @@ struct Pool
 
 version (SENTINEL)
 {
-    const size_t SENTINEL_PRE = cast(size_t) 0xF4F4F4F4F4F4F4F4UL; // 32 or 64 bits
-    const ubyte SENTINEL_POST = 0xF5;           // 8 bits
-    const uint SENTINEL_EXTRA = 2 * size_t.sizeof + 1;
+    const size_t SENTINEL_PRE_DATA = cast(size_t) 0xF4F4F4F4F4F4F4F4UL; // 32 or 64 bits
+    const ubyte SENTINEL_POST_DATA = 0xF5;           // 8 bits
+
+    const size_t SENTINEL_PRE = size_t.sizeof * 2;
+    const size_t SENTINEL_POST = 1;
+    const size_t SENTINEL_EXTRA = SENTINEL_PRE + SENTINEL_POST;
 
 
     size_t* sentinel_size(void *p)  { return &(cast(size_t *)p)[-2]; }
@@ -3086,15 +3045,15 @@ version (SENTINEL)
     void sentinel_init(void *p, size_t size)
     {
         *sentinel_size(p) = size;
-        *sentinel_pre(p) = SENTINEL_PRE;
-        *sentinel_post(p) = SENTINEL_POST;
+        *sentinel_pre(p) = SENTINEL_PRE_DATA;
+        *sentinel_post(p) = SENTINEL_POST_DATA;
     }
 
 
     void sentinel_Invariant(void *p)
     {
-        assert(*sentinel_pre(p) == SENTINEL_PRE);
-        assert(*sentinel_post(p) == SENTINEL_POST);
+        assert(*sentinel_pre(p) == SENTINEL_PRE_DATA);
+        assert(*sentinel_post(p) == SENTINEL_POST_DATA);
     }
 
 
@@ -3111,7 +3070,9 @@ version (SENTINEL)
 }
 else
 {
-    const uint SENTINEL_EXTRA = 0;
+    const size_t SENTINEL_PRE = 0;
+    const size_t SENTINEL_POST = 0;
+    const size_t SENTINEL_EXTRA = SENTINEL_PRE + SENTINEL_POST;
 
 
     void sentinel_init(void *p, size_t size)
