The OpenD Programming Language

1 /**
2  * This module implements the runtime-part of LDC exceptions
3  * on Windows, based on the MSVC++ runtime.
4  */
5 module ldc.eh_msvc;
6 
7 version (CRuntime_Microsoft):
8 
9 import core.sys.windows.windows;
10 import core.exception : onOutOfMemoryError, OutOfMemoryError;
11 import core.internal.container.common : xmalloc;
12 import core.stdc.stdlib : malloc, free, abort;
13 import core.stdc.string : memcpy;
14 import ldc.attributes;
15 import ldc.llvmasm;
16 
17 // pointers are image relative for Win64 versions
18 version (Win64)
19     struct ImgPtr(T) { uint offset; } // offset into image
20 else
21     alias ImgPtr(T) = T*;
22 
23 alias PMFN = ImgPtr!(void function(void*));
24 
25 struct TypeDescriptor
26 {
27     version (_RTTI)
28         const void * pVFTable;  // Field overloaded by RTTI
29     else
30         uint hash;  // Hash value computed from type's decorated name
31 
32     void * spare;   // reserved, possible for RTTI
33     char[1] name;   // variable size, zero terminated
34 }
35 
36 struct PMD
37 {
38     int mdisp;      // Offset of intended data within base
39     int pdisp;      // Displacement to virtual base pointer
40     int vdisp;      // Index within vbTable to offset of base
41 }
42 
43 struct CatchableType
44 {
45     uint  properties;       // Catchable Type properties (Bit field)
46     ImgPtr!TypeDescriptor pType;   // Pointer to TypeDescriptor
47     PMD   thisDisplacement; // Pointer to instance of catch type within thrown object.
48     int   sizeOrOffset;     // Size of simple-type object or offset into buffer of 'this' pointer for catch object
49     PMFN  copyFunction;     // Copy constructor or CC-closure
50 }
51 
52 enum CT_IsSimpleType    = 0x00000001;  // type is a simple type (includes pointers)
53 enum CT_ByReferenceOnly = 0x00000002;  // type must be caught by reference
54 enum CT_HasVirtualBase  = 0x00000004;  // type is a class with virtual bases
55 enum CT_IsWinRTHandle   = 0x00000008;  // type is a winrt handle
56 enum CT_IsStdBadAlloc   = 0x00000010;  // type is a a std::bad_alloc
57 
58 struct CatchableTypeArray
59 {
60     int	nCatchableTypes;
61     ImgPtr!CatchableType[1] arrayOfCatchableTypes; // variable size
62 }
63 
64 struct _ThrowInfo
65 {
66     uint    attributes;     // Throw Info attributes (Bit field)
67     PMFN    pmfnUnwind;     // Destructor to call when exception has been handled or aborted.
68     PMFN    pForwardCompat; // pointer to Forward compatibility frame handler
69     ImgPtr!CatchableTypeArray pCatchableTypeArray; // pointer to CatchableTypeArray
70 }
71 
72 enum TI_IsConst     = 0x00000001;   // thrown object has const qualifier
73 enum TI_IsVolatile  = 0x00000002;   // thrown object has volatile qualifier
74 enum TI_IsUnaligned = 0x00000004;   // thrown object has unaligned qualifier
75 enum TI_IsPure      = 0x00000008;   // object thrown from a pure module
76 enum TI_IsWinRT     = 0x00000010;   // object thrown is a WinRT Exception
77 
78 extern(Windows) void RaiseException(DWORD dwExceptionCode,
79                                     DWORD dwExceptionFlags,
80                                     DWORD nNumberOfArguments,
81                                     ULONG_PTR* lpArguments);
82 
83 enum int STATUS_MSC_EXCEPTION = 0xe0000000 | ('m' << 16) | ('s' << 8) | ('c' << 0);
84 
85 enum EXCEPTION_NONCONTINUABLE     = 0x01;
86 enum EXCEPTION_UNWINDING          = 0x02;
87 
88 enum EH_MAGIC_NUMBER1             = 0x19930520;
89 
90 struct CxxExceptionInfo
91 {
92     size_t Magic;
93     Throwable* pThrowable; // null for rethrow
94     _ThrowInfo* ThrowInfo;
95     version (Win64) void* ImgBase;
96 }
97 
98 // D runtime function
99 extern(C) int _d_isbaseof(ClassInfo oc, ClassInfo c);
100 
101 // error and exit
102 extern(C) void fatalerror(const(char)* format, ...) @system
103 {
104     import core.stdc.stdarg;
105     import core.stdc.stdio;
106 
107     va_list args;
108     va_start(args, format);
109     fprintf(stderr, "Fatal error in EH code: ");
110     vfprintf(stderr, format, args);
111     fprintf(stderr, "\n");
112     abort();
113 }
114 
115 extern(C) void _d_createTrace(Throwable t, void* context);
116 
117 extern(C) void _d_throw_exception(Throwable throwable)
118 {
119     if (throwable is null)
120         fatalerror("Cannot throw null exception");
121     auto ti = typeid(throwable);
122     if (ti is null)
123         fatalerror("Cannot throw corrupt exception object with null classinfo");
124 
125     /* Increment reference count if `o` is a refcounted Throwable
126      */
127     auto refcount = throwable.refcount();
128     if (refcount)       // non-zero means it's refcounted
129         throwable.refcount() = refcount + 1;
130 
131     if (exceptionStack.length > 0)
132     {
133         // we expect that the terminate handler will be called, so hook
134         // it to avoid it actually terminating
135         if (!old_terminate_handler)
136             old_terminate_handler = set_terminate(&msvc_eh_terminate);
137     }
138 
139     exceptionStack.push(throwable);
140 
141     _d_createTrace(throwable, null);
142 
143     CxxExceptionInfo info;
144     info.Magic = EH_MAGIC_NUMBER1;
145     info.pThrowable = &throwable;
146     info.ThrowInfo = getThrowInfo(ti).toPointer;
147     version (Win64) info.ImgBase = ehHeap.base;
148 
149     RaiseException(STATUS_MSC_EXCEPTION, EXCEPTION_NONCONTINUABLE,
150                    info.sizeof / size_t.sizeof, cast(ULONG_PTR*)&info);
151 }
152 
153 ///////////////////////////////////////////////////////////////
154 
155 import core.internal.container.hashtab;
156 import core.sync.mutex;
157 
158 __gshared HashTab!(TypeInfo_Class, ImgPtr!_ThrowInfo) throwInfoHashtab;
159 __gshared HashTab!(TypeInfo_Class, ImgPtr!CatchableType) catchableHashtab;
160 __gshared Mutex throwInfoMutex;
161 
162 // create and cache throwinfo for ti
163 ImgPtr!_ThrowInfo getThrowInfo(TypeInfo_Class ti) @system
164 {
165     throwInfoMutex.lock();
166     if (auto p = ti in throwInfoHashtab)
167     {
168         throwInfoMutex.unlock();
169         return *p;
170     }
171 
172     int classes = 0;
173     for (TypeInfo_Class tic = ti; tic; tic = tic.base)
174         classes++;
175 
176     size_t sz = int.sizeof + classes * ImgPtr!(CatchableType).sizeof;
177     ImgPtr!CatchableTypeArray cta = eh_malloc!CatchableTypeArray(sz);
178     toPointer(cta).nCatchableTypes = classes;
179 
180     size_t c = 0;
181     for (TypeInfo_Class tic = ti; tic; tic = tic.base)
182         cta.toPointer.arrayOfCatchableTypes.ptr[c++] = getCatchableType(tic);
183 
184     auto tinf = eh_malloc!_ThrowInfo();
185     *(tinf.toPointer) = _ThrowInfo(0, PMFN(), PMFN(), cta);
186     throwInfoHashtab[ti] = tinf;
187     throwInfoMutex.unlock();
188     return tinf;
189 }
190 
191 ImgPtr!CatchableType getCatchableType(TypeInfo_Class ti) @system
192 {
193     if (auto p = ti in catchableHashtab)
194         return *p;
195 
196     const sz = TypeDescriptor.sizeof + ti.name.length + 1;
197     auto td = eh_malloc!TypeDescriptor(sz);
198     auto ptd = td.toPointer;
199 
200     ptd.hash = 0;
201     ptd.spare = null;
202     ptd.name.ptr[0] = 'D';
203     memcpy(ptd.name.ptr + 1, ti.name.ptr, ti.name.length);
204     ptd.name.ptr[ti.name.length + 1] = 0;
205 
206     auto ct = eh_malloc!CatchableType();
207     ct.toPointer[0] = CatchableType(CT_IsSimpleType, td, PMD(0, -1, 0), size_t.sizeof, PMFN());
208     catchableHashtab[ti] = ct;
209     return ct;
210 }
211 
212 ///////////////////////////////////////////////////////////////
213 extern(C) Throwable _d_eh_enter_catch(void* ptr, ClassInfo catchType)
214 {
215     assert(ptr);
216 
217     // is this a thrown D exception?
218     auto e = *(cast(Throwable*) ptr);
219     size_t pos = exceptionStack.find(e);
220     if (pos >= exceptionStack.length())
221         return null;
222 
223     auto caught = e;
224     // append inner unhandled thrown exceptions
225     for (size_t p = pos + 1; p < exceptionStack.length(); p++)
226         e = chainExceptions(e, exceptionStack[p]);
227     exceptionStack.shrink(pos);
228 
229     // given the bad semantics of Errors, we are fine with passing
230     //  the test suite with slightly inaccurate behaviour by just
231     //  rethrowing a collateral Error here, though it might need to
232     //  be caught by a catch handler in an inner scope
233     if (e !is caught)
234     {
235         if (_d_isbaseof(typeid(e), catchType))
236             *cast(Throwable*) ptr = e; // the current catch can also catch this Error
237         else
238             _d_throw_exception(e);
239     }
240     return e;
241 }
242 
243 Throwable chainExceptions(Throwable e, Throwable t)
244 {
245     if (!cast(Error) e)
246         if (auto err = cast(Error) t)
247         {
248             err.bypassedException = e;
249             return err;
250         }
251 
252     return Throwable.chainTogether(e, t);
253 }
254 
255 ExceptionStack exceptionStack;
256 
257 static ~this()
258 {
259     // destructors not automatically run on globals
260     exceptionStack.destroy();
261 }
262 
263 struct ExceptionStack
264 {
265 nothrow:
266     ~this()
267     {
268         if (_p)
269             free(_p);
270     }
271 
272     void push(Throwable e) @system
273     {
274         if (_length == _cap)
275             grow();
276         _p[_length++] = e;
277     }
278 
279     Throwable pop() @system
280     {
281         return _p[--_length];
282     }
283 
284     void shrink(size_t sz) @system
285     {
286         while (_length > sz)
287             _p[--_length] = null;
288     }
289 
290     ref inout(Throwable) opIndex(size_t idx) inout @system
291     {
292         return _p[idx];
293     }
294 
295     size_t find(Throwable e)
296     {
297         for (size_t i = _length; i > 0; )
298             if (exceptionStack[--i] is e)
299                 return i;
300         return ~0;
301     }
302 
303     @property size_t length() const { return _length; }
304     @property bool empty() const { return !length; }
305 
306     void swap(ref ExceptionStack other)
307     {
308         static void swapField(T)(ref T a, ref T b) { T o = b; b = a; a = o; }
309         swapField(_length, other._length);
310         swapField(_p,      other._p);
311         swapField(_cap,    other._cap);
312     }
313 
314 private:
315     void grow()
316     {
317         // alloc from GC? add array as a GC range?
318         immutable ncap = _cap ? 2 * _cap : 64;
319         auto p = cast(Throwable*)xmalloc(ncap * Throwable.sizeof);
320         p[0 .. _length] = _p[0 .. _length];
321         free(_p);
322         _p = p;
323         _cap = ncap;
324     }
325 
326     size_t _length;
327     Throwable* _p;
328     size_t _cap;
329 }
330 
331 ///////////////////////////////////////////////////////////////
332 alias terminate_handler = void function();
333 extern(C) terminate_handler set_terminate(terminate_handler new_handler);
334 terminate_handler old_terminate_handler; // explicitely per thread
335 
336 // helper to access TLS from naked asm
337 size_t tlsUncaughtExceptions() nothrow @assumeUsed
338 {
339     return exceptionStack.length;
340 }
341 
342 auto tlsOldTerminateHandler() nothrow @assumeUsed
343 {
344     return old_terminate_handler;
345 }
346 
347 void msvc_eh_terminate() nothrow @naked
348 {
349     version (Win32)
350     {
351         __asm(
352            `call __D3ldc7eh_msvc21tlsUncaughtExceptionsFNbZk
353             cmp $$1, %eax
354             jle L_term
355 
356             // hacking into the call chain to return EXCEPTION_EXECUTE_HANDLER
357             //  as the return value of __FrameUnwindFilter so that
358             // __FrameUnwindToState continues with the next unwind block
359 
360             // undo one level of exception frames from terminate()
361             mov %fs:(0), %eax
362             mov (%eax), %eax
363             mov %eax, %fs:(0)
364 
365             // assume standard stack frames for callers
366             mov %ebp, %eax   // frame pointer of terminate()
367             mov (%eax), %eax // frame pointer of __FrameUnwindFilter
368             mov %eax, %esp   // restore stack
369             pop %ebp         // and frame pointer
370             mov $$1, %eax    // return EXCEPTION_EXECUTE_HANDLER
371             ret
372 
373         L_term:
374             call __D3ldc7eh_msvc22tlsOldTerminateHandlerFNbNiNfZPFZv
375             cmp $$0, %eax
376             je L_ret
377             jmp *%eax
378         L_ret:
379             ret`,
380             "~{memory},~{flags},~{ebp},~{esp},~{eax}"
381         );
382     }
383     else
384     {
385         __asm(
386            `push %rbx                      // align stack for better debuggability
387             call _D3ldc7eh_msvc21tlsUncaughtExceptionsFNbZm
388             cmp $$1, %rax
389             jle L_term
390 
391             // update stack and IP so we just continue in __FrameUnwindHandler
392             // NOTE: these checks can fail if you have breakpoints set at
393             //       the respective code locations
394             mov 8(%rsp), %rax              // get return address
395             cmpb $$0xEB, (%rax)            // jmp?
396             jne noJump
397             movsbq 1(%rax), %rdx           // follow jmp
398             lea 2(%rax,%rdx), %rax
399         noJump:
400             cmpb $$0xE8, (%rax)            // call abort?
401             jne L_term
402             add $$5, %rax
403             mov (%rax), %edx
404             mov $$0xFFFFFF, %rbx
405             and %rbx, %rdx
406             cmp $$0xC48348, %rdx           // add ESP,nn  (debug UCRT libs)
407             je L_addESP_found
408             cmp $$0x90, %dl                // nop; (release libs)
409             jne L_term
410 
411         L_release_ucrt:
412             mov 8(%rsp), %rdx
413             cmpw $$0xD3FF, -2(%rdx)        // call ebx?
414             sete %bl                       // if not, it's UCRT 10.0.14393.0
415             movzbq %bl, %rbx
416             mov $$0x28, %rdx               // release build of vcruntimelib
417             jmp L_retTerminate
418 
419         L_addESP_found:
420             xor %rbx, %rbx                 // debug version: RBX not pushed inside terminate()
421             movzbq 3(%rax), %rdx           // read nn
422 
423             cmpb $$0xC3, 4(%rax)           // ret?
424             jne L_term
425 
426         L_retTerminate:
427             lea 0x10(%rsp,%rdx), %rdx      // RSP before returning from terminate()
428 
429             mov (%rdx), %rax               // return address inside __FrameUnwindHandler
430 
431             or %rbx, %rdx                  // RDX aligned, save RBX == 0 for UCRT 10.0.14393.0, 1 otherwise
432 
433             cmpb $$0xEB, -19(%rax)         // skip back to default jump inside "switch" (libvcruntimed.lib)
434             je L_switchFound
435 
436             cmpb $$0xEB, -20(%rax)         // skip back to default jump inside "switch" (vcruntime140d.dll)
437             je L_switchFound2
438 
439             mov $$0xC48348C0333048FF, %rbx // dec [rax+30h]; xor eax,eax; add rsp,nn (libvcruntime.lib)
440             cmp -0x18(%rax), %rbx
441             je L_retFound
442 
443             cmp 0x29(%rax), %rbx           // dec [rax+30h]; xor eax,eax; add rsp,nn (vcruntime140.dll)
444             je L_retVC14_11
445 
446             cmp 0x11(%rax), %rbx           // dec [rax+30h]; xor eax,eax; add rsp,nn (vcruntime140.dll, 14.16.x.x)
447             je L_retVC14_16
448 
449             cmp 0x1B(%rax), %rbx           // dec [rax+30h]; xor eax,eax; add rsp,nn (vcruntime140.dll 14.14.x.y)
450             je L_retVC14_14
451             
452             mov $$0x30245C8B483048FF, %rbx // dec [rax+30h]; mov rbx,qword ptr [rsp+30h]
453             cmp -0x2b(%rax), %rbx          // (libcmt.lib, 14.23.x.x)
454             je L_retVC14_23_libcmt
455             cmp 0x11(%rax), %rbx           // (vcruntime140.lib, 14.23.x.x)
456             je L_retVC14_23_msvcrt
457 
458             mov $$0xccc348c48348c033, %rbx // xor eax,eax; add rsp,48h; ret; int 3
459             cmp 0x2d(%rax), %rbx           // (libcmtd.lib, 14.23.x.x)
460             je L_retVC14_23_libcmtd
461 
462             jmp L_term
463 
464         L_retVC14_23_msvcrt:               // vcruntime140.dll 14.23.28105
465             lea 0x1b(%rax), %rax
466             mov 0x38(%rdx), %rbx           // restore RBX from stack
467             jmp L_rbxRestored
468 
469         L_retVC14_23_libcmt:               // libcmt.lib 14.23.28105
470             lea -0x21(%rax), %rax
471             mov 0x38(%rdx), %rbx           // restore RBX from stack
472             jmp L_rbxRestored
473 
474         L_retVC14_23_libcmtd:              // libcmtd.lib/vcruntime140d.dll 14.23.28105
475             lea 0x2f(%rax), %rax
476             jmp L_rbxRestored              // rbx not saved
477 
478         L_retVC14_14:                      // (vcruntime140.dll 14.14.x.y)
479             lea 0x20(%rax), %rax
480             jmp L_retContinue
481         L_retVC14_16:                      // vcruntime140 14.16.27012.6
482             lea 0x16(%rax), %rax
483             jmp L_retContinue
484         L_retVC14_11:                      // vcruntime140 14.11.25415.0 or earlier
485             lea 0x2E(%rax), %rax
486         L_retContinue:                     // vcruntime140 14.00.23026.0 or later?
487             cmpw $$0x8348, (%rax)          // add rsp,nn?
488             je L_xorSkipped
489 
490             inc %rax                       // vcruntime140 earlier than 14.00.23026.0?
491             jmp L_xorSkipped
492 
493         L_retFound:
494             lea -19(%rax), %rax
495             jmp L_xorSkipped
496 
497         L_switchFound2:
498             dec %rax
499         L_switchFound:
500             movsbq -18(%rax), %rbx         // follow jump
501             lea -17(%rax,%rbx), %rax
502 
503             cmpw $$0xC033, (%rax)          // xor EAX,EAX?
504             jne L_term
505 
506             add $$2, %rax
507         L_xorSkipped:
508             mov %rdx, %rbx                 // extract UCRT marker from EDX
509             and $$~1, %rdx
510             and $$1, %rbx
511 
512             cmovnz -8(%rdx), %rbx          // restore RBX (pushed inside terminate())
513             cmovz (%rsp), %rbx             // RBX not changed in terminate inside UCRT 10.0.14393.0
514 
515         L_rbxRestored:
516             lea 8(%rdx), %rsp
517             push %rax                      // new return after setting return value in __frameUnwindHandler
518 
519             call __processing_throw
520             movq $$1, (%rax)
521 
522             //add $$0x68, %rsp             // TODO: needs to be verified for different CRT builds
523             mov $$1, %rax                  // return EXCEPTION_EXECUTE_HANDLER
524             ret
525 
526         L_term:
527             call _D3ldc7eh_msvc22tlsOldTerminateHandlerFNbNiNfZPFZv
528             pop %rbx
529             cmp $$0, %rax
530             je L_ret
531             jmp *%rax
532         L_ret:
533             ret`,
534             "~{memory},~{flags},~{rbp},~{rsp},~{rax},~{rbx},~{rdx}"
535         );
536     }
537 }
538 
539 ///////////////////////////////////////////////////////////////
540 extern(C) void** __current_exception() nothrow;
541 extern(C) void** __current_exception_context() nothrow;
542 extern(C) int* __processing_throw() nothrow;
543 
544 struct FiberContext
545 {
546     ExceptionStack exceptionStack;
547     void* currentException;
548     void* currentExceptionContext;
549     int processingContext;
550 }
551 
552 FiberContext* fiberContext;
553 
554 extern(C) void* _d_eh_swapContext(FiberContext* newContext) nothrow
555 {
556     import core.stdc.string : memset;
557     if (!fiberContext)
558     {
559         fiberContext = cast(FiberContext*) xmalloc(FiberContext.sizeof);
560         memset(fiberContext, 0, FiberContext.sizeof);
561     }
562     fiberContext.exceptionStack.swap(exceptionStack);
563     fiberContext.currentException = *__current_exception();
564     fiberContext.currentExceptionContext = *__current_exception_context();
565     fiberContext.processingContext = *__processing_throw();
566 
567     if (newContext)
568     {
569         exceptionStack.swap(newContext.exceptionStack);
570         *__current_exception() = newContext.currentException;
571         *__current_exception_context() = newContext.currentExceptionContext;
572         *__processing_throw() = newContext.processingContext;
573     }
574     else
575     {
576         exceptionStack = ExceptionStack();
577         *__current_exception() = null;
578         *__current_exception_context() = null;
579         *__processing_throw() = 0;
580     }
581 
582     FiberContext* old = fiberContext;
583     fiberContext = newContext;
584     return old;
585 }
586 
587 static ~this()
588 {
589     import core.stdc.stdlib : free;
590     if (fiberContext)
591     {
592         destroy(*fiberContext);
593         free(fiberContext);
594     }
595 }
596 
597 ///////////////////////////////////////////////////////////////
598 extern(C) bool _d_enter_cleanup(void* ptr)
599 {
600     // currently just used to avoid that a cleanup handler that can
601     // be inferred to not return, is removed by the LLVM optimizer
602     //
603     // TODO: setup an exception handler here (ptr passes the address
604     // of a 40 byte stack area in a parent fuction scope) to deal with
605     // unhandled exceptions during unwinding.
606     return true;
607 }
608 
609 extern(C) void _d_leave_cleanup(void* ptr)
610 {
611 }
612 
613 ///////////////////////////////////////////////////////////////
614 void msvc_eh_init()
615 {
616     throwInfoMutex = new Mutex;
617 
618     version (Win64) ehHeap.initialize(0x10000);
619 
620     // preallocate type descriptors likely to be needed
621     getThrowInfo(typeid(Exception));
622     // better not have to allocate when this is thrown:
623     getThrowInfo(typeid(OutOfMemoryError));
624 }
625 
626 ///////////////////////////////////////////////////////////////
627 version (Win32)
628 {
629     ImgPtr!T eh_malloc(T)(size_t size = T.sizeof)
630     {
631         return cast(T*) xmalloc(size);
632     }
633 
634     T* toPointer(T)(T* imgPtr)
635     {
636         return imgPtr;
637     }
638 }
639 else
640 {
641     /**
642     * Heap dedicated for CatchableTypeArray/CatchableType/TypeDescriptor
643     * structs of cached _ThrowInfos.
644     * The heap is used to keep these structs tightly together, as they are
645     * referenced via 32-bit offsets from a common base. We simply use the
646     * heap's start as base (instead of the actual image base), and malloc()
647     * returns an offset.
648     * The allocated structs are all cached and never released, so this heap
649     * can only grow. The offsets remain constant after a grow, so it's only
650     * the base which may change.
651     */
652     struct EHHeap
653     {
654         void* base;
655         size_t capacity;
656         size_t length;
657 
658         void initialize(size_t initialCapacity)
659         {
660             base = xmalloc(initialCapacity);
661             capacity = initialCapacity;
662             length = size_t.sizeof; // don't use offset 0, it has a special meaning
663         }
664 
665         size_t malloc(size_t size)
666         {
667             auto offset = length;
668             enum alignmentMask = size_t.sizeof - 1;
669             auto newLength = (length + size + alignmentMask) & ~alignmentMask;
670             auto newCapacity = capacity;
671             while (newLength > newCapacity)
672                 newCapacity *= 2;
673             if (newCapacity != capacity)
674             {
675                 auto newBase = xmalloc(newCapacity);
676                 newBase[0 .. length] = base[0 .. length];
677                 // old base just leaks, could be used by exceptions still in flight
678                 base = newBase;
679                 capacity = newCapacity;
680             }
681             length = newLength;
682             return offset;
683         }
684     }
685 
686     __gshared EHHeap ehHeap;
687 
688     ImgPtr!T eh_malloc(T)(size_t size = T.sizeof)
689     {
690         return ImgPtr!T(cast(uint) ehHeap.malloc(size));
691     }
692 
693     // NB: The returned pointer may be invalidated by a consequent grow of ehHeap!
694     T* toPointer(T)(ImgPtr!T imgPtr)
695     {
696         return cast(T*) (ehHeap.base + imgPtr.offset);
697     }
698 }