2032 lines
55 KiB
D
2032 lines
55 KiB
D
// Minimal druntime for webassembly. Assumes your program has a main function.
|
|
module object;
|
|
|
|
static import arsd.webassembly;
|
|
|
|
version(CarelessAlocation)
|
|
{
|
|
version = inline_concat;
|
|
}
|
|
|
|
import core.arsd.memory_allocation;
|
|
|
|
alias noreturn = typeof(*null);
|
|
alias string = immutable(char)[];
|
|
alias wstring = immutable(wchar)[];
|
|
alias dstring = immutable(dchar)[];
|
|
alias size_t = uint;
|
|
alias ptrdiff_t = int;
|
|
|
|
|
|
// then the entry point just for convenience so main works.
|
|
extern(C) int _Dmain(string[] args);
|
|
export extern(C) void _start() { _Dmain(null); }
|
|
|
|
extern(C) bool _xopEquals(in void*, in void*) { return false; } // assert(0);
|
|
|
|
// basic array support {
|
|
|
|
template _arrayOp(Args...)
|
|
{
|
|
import core.internal.array.operations;
|
|
alias _arrayOp = arrayOp!Args;
|
|
}
|
|
|
|
extern(C) void _d_array_slice_copy(void* dst, size_t dstlen, void* src, size_t srclen, size_t elemsz) {
|
|
auto d = cast(ubyte*) dst;
|
|
auto s = cast(ubyte*) src;
|
|
auto len = dstlen * elemsz;
|
|
|
|
while(len) {
|
|
*d = *s;
|
|
d++;
|
|
s++;
|
|
len--;
|
|
}
|
|
|
|
}
|
|
|
|
void reserve(T)(ref T[] arr, size_t length) @trusted {
|
|
arr = (cast(T*) (malloc(length * T.sizeof).ptr))[0 .. 0];
|
|
}
|
|
|
|
|
|
extern(C) void _d_arraybounds(string file, size_t line) {
|
|
arsd.webassembly.eval(
|
|
q{ console.error("Range error: " + $0 + ":" + $1 )},
|
|
file, line);
|
|
arsd.webassembly.abort();
|
|
}
|
|
|
|
|
|
/// Called when an out of range slice of an array is created
|
|
extern(C) void _d_arraybounds_slice(string file, uint line, size_t lwr, size_t upr, size_t length)
|
|
{
|
|
arsd.webassembly.eval(
|
|
q{ console.error("Range error: " + $0 + ":" + $1 + " [" + $2 + ".." + $3 + "] <> " + $4)},
|
|
file, line, lwr, upr, length);
|
|
arsd.webassembly.abort();
|
|
}
|
|
|
|
/// Called when an out of range array index is accessed
|
|
extern(C) void _d_arraybounds_index(string file, uint line, size_t index, size_t length)
|
|
{
|
|
arsd.webassembly.eval(
|
|
q{ console.error("Array index " + $0 + " out of bounds '[0.."+$1+"]' " + $2 + ":" + $3)},
|
|
index, length, file, line);
|
|
arsd.webassembly.abort();
|
|
}
|
|
|
|
|
|
extern(C) void* memset(void* s, int c, size_t n) @nogc nothrow pure
|
|
{
|
|
auto d = cast(ubyte*) s;
|
|
while(n) {
|
|
*d = cast(ubyte) c;
|
|
d++;
|
|
n--;
|
|
}
|
|
return s;
|
|
}
|
|
|
|
pragma(LDC_intrinsic, "llvm.memcpy.p0i8.p0i8.i#")
|
|
void llvm_memcpy(T)(void* dst, const(void)* src, T len, bool volatile_ = false);
|
|
|
|
extern(C) void *memcpy(void* dest, const(void)* src, size_t n) pure @nogc nothrow
|
|
{
|
|
ubyte *d = cast(ubyte*) dest;
|
|
const (ubyte) *s = cast(const(ubyte)*)src;
|
|
for (; n; n--) *d++ = *s++;
|
|
return dest;
|
|
}
|
|
|
|
extern(C) int memcmp(const(void)* s1, const(void*) s2, size_t n) pure @nogc nothrow @trusted
|
|
{
|
|
auto b = cast(ubyte*) s1;
|
|
auto b2 = cast(ubyte*) s2;
|
|
foreach(i; 0 .. n) {
|
|
if(auto diff = *b - *b2)
|
|
return diff;
|
|
b++;
|
|
b2++;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
public import core.arsd.utf_decoding;
|
|
|
|
// }
|
|
|
|
extern(C) void _d_assert(string file, uint line) @trusted @nogc pure
|
|
{
|
|
arsd.webassembly.eval(q{ console.error("Assert failure: " + $0 + ":" + $1); /*, "[" + $2 + ".." + $3 + "] <> " + $4);*/ }, file, line);//, lwr, upr, length);
|
|
arsd.webassembly.abort();
|
|
}
|
|
void _d_assertp(immutable(char)* file, uint line)
|
|
{
|
|
// import core.stdc.string : strlen;
|
|
size_t sz = 0;
|
|
while(file[sz] != '\0') sz++;
|
|
arsd.webassembly.eval(q{ console.error("Assert failure: " + $0 + ":" + $1 + "(" + $2 + ")"); /*, "[" + $2 + ".." + $3 + "] <> " + $4);*/ }, file[0 .. sz], line);//, lwr, upr, length);
|
|
arsd.webassembly.abort();
|
|
}
|
|
|
|
|
|
extern(C) void _d_assert_msg(string msg, string file, uint line) @trusted @nogc pure
|
|
{
|
|
arsd.webassembly.eval(q{ console.error("Assert failure: " + $0 + ":" + $1 + "(" + $2 + ")"); /*, "[" + $2 + ".." + $3 + "] <> " + $4);*/ }, file, line, msg);//, lwr, upr, length);
|
|
arsd.webassembly.abort();
|
|
}
|
|
|
|
void __switch_error(string file, size_t line) @trusted @nogc pure
|
|
{
|
|
_d_assert_msg("final switch error",file, line);
|
|
}
|
|
|
|
bool __equals(T1, T2)(scope const T1[] lhs, scope const T2[] rhs) {
|
|
if (lhs.length != rhs.length) {
|
|
return false;
|
|
}
|
|
foreach(i; 0..lhs.length) {
|
|
if (lhs[i] != rhs[i]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// bare basics class support {
|
|
|
|
|
|
extern(C) Object _d_allocclass(TypeInfo_Class ti) {
|
|
auto ptr = malloc(ti.m_init.length);
|
|
ptr[] = ti.m_init[];
|
|
return cast(Object) ptr.ptr;
|
|
}
|
|
|
|
extern(C) void* _d_dynamic_cast(Object o, TypeInfo_Class c) {
|
|
void* res = null;
|
|
size_t offset = 0;
|
|
if (o && _d_isbaseof2(typeid(o), c, offset))
|
|
{
|
|
res = cast(void*) o + offset;
|
|
}
|
|
return res;
|
|
}
|
|
|
|
/*************************************
|
|
* Attempts to cast Object o to class c.
|
|
* Returns o if successful, null if not.
|
|
*/
|
|
extern(C) void* _d_interface_cast(void* p, TypeInfo_Class c)
|
|
{
|
|
if (!p)
|
|
return null;
|
|
|
|
Interface* pi = **cast(Interface***) p;
|
|
return _d_dynamic_cast(cast(Object)(p - pi.offset), c);
|
|
}
|
|
|
|
|
|
extern(C)
|
|
int _d_isbaseof2(scope TypeInfo_Class oc, scope const TypeInfo_Class c, scope ref size_t offset) @safe
|
|
|
|
{
|
|
if (oc is c)
|
|
return true;
|
|
|
|
do
|
|
{
|
|
if (oc.base is c)
|
|
return true;
|
|
|
|
// Bugzilla 2013: Use depth-first search to calculate offset
|
|
// from the derived (oc) to the base (c).
|
|
foreach (iface; oc.interfaces)
|
|
{
|
|
if (iface.classinfo is c || _d_isbaseof2(iface.classinfo, c, offset))
|
|
{
|
|
offset += iface.offset;
|
|
return true;
|
|
}
|
|
}
|
|
|
|
oc = oc.base;
|
|
} while (oc);
|
|
|
|
return false;
|
|
}
|
|
|
|
int __cmp(T)(scope const T[] lhs, scope const T[] rhs) @trusted pure @nogc nothrow
|
|
if (__traits(isScalar, T))
|
|
{
|
|
// Compute U as the implementation type for T
|
|
static if (is(T == ubyte) || is(T == void) || is(T == bool))
|
|
alias U = char;
|
|
else static if (is(T == wchar))
|
|
alias U = ushort;
|
|
else static if (is(T == dchar))
|
|
alias U = uint;
|
|
else static if (is(T == ifloat))
|
|
alias U = float;
|
|
else static if (is(T == idouble))
|
|
alias U = double;
|
|
else static if (is(T == ireal))
|
|
alias U = real;
|
|
else
|
|
alias U = T;
|
|
|
|
static if (is(U == char))
|
|
{
|
|
int dstrcmp(scope const char[] s1, scope const char[] s2 ) @trusted pure @nogc nothrow
|
|
{
|
|
immutable len = s1.length <= s2.length ? s1.length : s2.length;
|
|
if (__ctfe)
|
|
{
|
|
foreach (const u; 0 .. len)
|
|
{
|
|
if (s1[u] != s2[u])
|
|
return s1[u] > s2[u] ? 1 : -1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const ret = memcmp( s1.ptr, s2.ptr, len );
|
|
if ( ret )
|
|
return ret;
|
|
}
|
|
return (s1.length > s2.length) - (s1.length < s2.length);
|
|
}
|
|
return dstrcmp(cast(char[]) lhs, cast(char[]) rhs);
|
|
}
|
|
else static if (!is(U == T))
|
|
{
|
|
// Reuse another implementation
|
|
return __cmp(cast(U[]) lhs, cast(U[]) rhs);
|
|
}
|
|
else
|
|
{
|
|
version (BigEndian)
|
|
static if (__traits(isUnsigned, T) ? !is(T == __vector) : is(T : P*, P))
|
|
{
|
|
if (!__ctfe)
|
|
{
|
|
import core.stdc.string : memcmp;
|
|
int c = memcmp(lhs.ptr, rhs.ptr, (lhs.length <= rhs.length ? lhs.length : rhs.length) * T.sizeof);
|
|
if (c)
|
|
return c;
|
|
static if (size_t.sizeof <= uint.sizeof && T.sizeof >= 2)
|
|
return cast(int) lhs.length - cast(int) rhs.length;
|
|
else
|
|
return int(lhs.length > rhs.length) - int(lhs.length < rhs.length);
|
|
}
|
|
}
|
|
|
|
immutable len = lhs.length <= rhs.length ? lhs.length : rhs.length;
|
|
foreach (const u; 0 .. len)
|
|
{
|
|
auto a = lhs.ptr[u], b = rhs.ptr[u];
|
|
static if (is(T : creal))
|
|
{
|
|
// Use rt.cmath2._Ccmp instead ?
|
|
// Also: if NaN is present, numbers will appear equal.
|
|
auto r = (a.re > b.re) - (a.re < b.re);
|
|
if (!r) r = (a.im > b.im) - (a.im < b.im);
|
|
}
|
|
else
|
|
{
|
|
// This pattern for three-way comparison is better than conditional operators
|
|
// See e.g. https://godbolt.org/z/3j4vh1
|
|
const r = (a > b) - (a < b);
|
|
}
|
|
if (r) return r;
|
|
}
|
|
return (lhs.length > rhs.length) - (lhs.length < rhs.length);
|
|
}
|
|
}
|
|
|
|
// This function is called by the compiler when dealing with array
|
|
// comparisons in the semantic analysis phase of CmpExp. The ordering
|
|
// comparison is lowered to a call to this template.
|
|
int __cmp(T1, T2)(T1[] s1, T2[] s2)
|
|
if (!__traits(isScalar, T1) && !__traits(isScalar, T2))
|
|
{
|
|
import core.internal.traits : Unqual;
|
|
alias U1 = Unqual!T1;
|
|
alias U2 = Unqual!T2;
|
|
|
|
static if (is(U1 == void) && is(U2 == void))
|
|
static @trusted ref inout(ubyte) at(inout(void)[] r, size_t i) { return (cast(inout(ubyte)*) r.ptr)[i]; }
|
|
else
|
|
static @trusted ref R at(R)(R[] r, size_t i) { return r.ptr[i]; }
|
|
|
|
// All unsigned byte-wide types = > dstrcmp
|
|
immutable len = s1.length <= s2.length ? s1.length : s2.length;
|
|
|
|
foreach (const u; 0 .. len)
|
|
{
|
|
static if (__traits(compiles, __cmp(at(s1, u), at(s2, u))))
|
|
{
|
|
auto c = __cmp(at(s1, u), at(s2, u));
|
|
if (c != 0)
|
|
return c;
|
|
}
|
|
else static if (__traits(compiles, at(s1, u).opCmp(at(s2, u))))
|
|
{
|
|
auto c = at(s1, u).opCmp(at(s2, u));
|
|
if (c != 0)
|
|
return c;
|
|
}
|
|
else static if (__traits(compiles, at(s1, u) < at(s2, u)))
|
|
{
|
|
if (int result = (at(s1, u) > at(s2, u)) - (at(s1, u) < at(s2, u)))
|
|
return result;
|
|
}
|
|
else
|
|
{
|
|
// TODO: fix this legacy bad behavior, see
|
|
// https://issues.dlang.org/show_bug.cgi?id=17244
|
|
static assert(is(U1 == U2), "Internal error.");
|
|
import core.stdc.string : memcmp;
|
|
auto c = (() @trusted => memcmp(&at(s1, u), &at(s2, u), U1.sizeof))();
|
|
if (c != 0)
|
|
return c;
|
|
}
|
|
}
|
|
return (s1.length > s2.length) - (s1.length < s2.length);
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
Support for switch statements switching on strings.
|
|
Params:
|
|
caseLabels = sorted array of strings generated by compiler. Note the
|
|
strings are sorted by length first, and then lexicographically.
|
|
condition = string to look up in table
|
|
Returns:
|
|
index of match in caseLabels, a negative integer if not found
|
|
*/
|
|
int __switch(T, caseLabels...)(/*in*/ const scope T[] condition) pure nothrow @safe @nogc
|
|
{
|
|
// This closes recursion for other cases.
|
|
static if (caseLabels.length == 0)
|
|
{
|
|
return int.min;
|
|
}
|
|
else static if (caseLabels.length == 1)
|
|
{
|
|
return __cmp(condition, caseLabels[0]) == 0 ? 0 : int.min;
|
|
}
|
|
// To be adjusted after measurements
|
|
// Compile-time inlined binary search.
|
|
else static if (caseLabels.length < 7)
|
|
{
|
|
int r = void;
|
|
enum mid = cast(int)caseLabels.length / 2;
|
|
if (condition.length == caseLabels[mid].length)
|
|
{
|
|
r = __cmp(condition, caseLabels[mid]);
|
|
if (r == 0) return mid;
|
|
}
|
|
else
|
|
{
|
|
// Equivalent to (but faster than) condition.length > caseLabels[$ / 2].length ? 1 : -1
|
|
r = ((condition.length > caseLabels[mid].length) << 1) - 1;
|
|
}
|
|
|
|
if (r < 0)
|
|
{
|
|
// Search the left side
|
|
return __switch!(T, caseLabels[0 .. mid])(condition);
|
|
}
|
|
else
|
|
{
|
|
// Search the right side
|
|
return __switch!(T, caseLabels[mid + 1 .. $])(condition) + mid + 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Need immutable array to be accessible in pure code, but case labels are
|
|
// currently coerced to the switch condition type (e.g. const(char)[]).
|
|
pure @trusted nothrow @nogc asImmutable(scope const(T[])[] items)
|
|
{
|
|
assert(__ctfe); // only @safe for CTFE
|
|
immutable T[][caseLabels.length] result = cast(immutable)(items[]);
|
|
return result;
|
|
}
|
|
static immutable T[][caseLabels.length] cases = asImmutable([caseLabels]);
|
|
|
|
// Run-time binary search in a static array of labels.
|
|
return __switchSearch!T(cases[], condition);
|
|
}
|
|
}
|
|
|
|
// binary search in sorted string cases, also see `__switch`.
|
|
private int __switchSearch(T)(/*in*/ const scope T[][] cases, /*in*/ const scope T[] condition) pure nothrow @safe @nogc
|
|
{
|
|
size_t low = 0;
|
|
size_t high = cases.length;
|
|
|
|
do
|
|
{
|
|
auto mid = (low + high) / 2;
|
|
int r = void;
|
|
if (condition.length == cases[mid].length)
|
|
{
|
|
r = __cmp(condition, cases[mid]);
|
|
if (r == 0) return cast(int) mid;
|
|
}
|
|
else
|
|
{
|
|
// Generates better code than "expr ? 1 : -1" on dmd and gdc, same with ldc
|
|
r = ((condition.length > cases[mid].length) << 1) - 1;
|
|
}
|
|
|
|
if (r > 0) low = mid + 1;
|
|
else high = mid;
|
|
}
|
|
while (low < high);
|
|
|
|
// Not found
|
|
return -1;
|
|
}
|
|
|
|
//TODO: Support someday?
|
|
extern(C) void _d_throw_exception(Throwable o)
|
|
{
|
|
assert(false, "Exception throw");
|
|
}
|
|
|
|
|
|
// for closures
|
|
extern(C) void* _d_allocmemory(size_t sz) {
|
|
return malloc(sz).ptr;
|
|
}
|
|
|
|
///For POD structures
|
|
extern (C) void* _d_allocmemoryT(TypeInfo ti)
|
|
{
|
|
return malloc(ti.size).ptr;
|
|
}
|
|
|
|
|
|
class Object
|
|
{
|
|
/// Convert Object to human readable string
|
|
string toString() { return "Object"; }
|
|
/// Compute hash function for Object
|
|
size_t toHash() @trusted nothrow
|
|
{
|
|
auto addr = cast(size_t)cast(void*)this;
|
|
return addr ^ (addr >>> 4);
|
|
}
|
|
|
|
/// Compare against another object. NOT IMPLEMENTED!
|
|
int opCmp(Object o) { assert(false, "not implemented"); }
|
|
/// Check equivalence againt another object
|
|
bool opEquals(Object o) { return this is o; }
|
|
}
|
|
|
|
/// Compare to objects
|
|
bool opEquals(Object lhs, Object rhs)
|
|
{
|
|
// If aliased to the same object or both null => equal
|
|
if (lhs is rhs) return true;
|
|
|
|
// If either is null => non-equal
|
|
if (lhs is null || rhs is null) return false;
|
|
|
|
if (!lhs.opEquals(rhs)) return false;
|
|
|
|
// If same exact type => one call to method opEquals
|
|
if (typeid(lhs) is typeid(rhs) ||
|
|
!__ctfe && typeid(lhs).opEquals(typeid(rhs)))
|
|
/* CTFE doesn't like typeid much. 'is' works, but opEquals doesn't
|
|
(issue 7147). But CTFE also guarantees that equal TypeInfos are
|
|
always identical. So, no opEquals needed during CTFE. */
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// General case => symmetric calls to method opEquals
|
|
return rhs.opEquals(lhs);
|
|
}
|
|
/************************
|
|
* Returns true if lhs and rhs are equal.
|
|
*/
|
|
bool opEquals(const Object lhs, const Object rhs)
|
|
{
|
|
// A hack for the moment.
|
|
return opEquals(cast()lhs, cast()rhs);
|
|
}
|
|
|
|
class TypeInfo
|
|
{
|
|
override string toString() const @safe nothrow
|
|
{
|
|
return typeid(this).name;
|
|
}
|
|
|
|
const(TypeInfo) next()nothrow pure inout @nogc { return null; }
|
|
size_t size() nothrow pure const @safe @nogc { return 0; }
|
|
|
|
bool equals(in void* p1, in void* p2) const { return p1 == p2; }
|
|
|
|
override size_t toHash() @trusted const nothrow
|
|
{
|
|
return hashOf(this.toString());
|
|
}
|
|
|
|
|
|
size_t getHash(scope const void* p) @trusted nothrow const
|
|
{
|
|
return hashOf(p);
|
|
}
|
|
|
|
/**
|
|
* Return default initializer. If the type should be initialized to all
|
|
* zeros, an array with a null ptr and a length equal to the type size will
|
|
* be returned. For static arrays, this returns the default initializer for
|
|
* a single element of the array, use `tsize` to get the correct size.
|
|
*/
|
|
const(void)[] initializer() const @trusted nothrow pure
|
|
{
|
|
return (cast(const(void)*) null)[0 .. typeof(null).sizeof];
|
|
}
|
|
|
|
@property uint flags() nothrow pure const @safe @nogc { return 0; }
|
|
/// Run the destructor on the object and all its sub-objects
|
|
void destroy(void* p) const {}
|
|
/// Run the postblit on the object and all its sub-objects
|
|
void postblit(void* p) const {}
|
|
|
|
@property size_t talign() nothrow pure const { return size; }
|
|
}
|
|
|
|
class TypeInfo_Class : TypeInfo
|
|
{
|
|
ubyte[] m_init; /// class static initializer (length gives class size)
|
|
string name; /// name of class
|
|
void*[] vtbl; // virtual function pointer table
|
|
Interface[] interfaces;
|
|
TypeInfo_Class base;
|
|
void* destructor;
|
|
void function(Object) classInvariant;
|
|
uint flags;
|
|
void* deallocator;
|
|
void*[] offTi;
|
|
void function(Object) defaultConstructor;
|
|
immutable(void)* rtInfo;
|
|
|
|
override @property size_t size() nothrow pure const
|
|
{ return Object.sizeof; }
|
|
|
|
override size_t getHash(scope const void* p) @trusted const
|
|
{
|
|
auto o = *cast(Object*)p;
|
|
return o ? o.toHash() : 0;
|
|
}
|
|
|
|
override bool equals(in void* p1, in void* p2) const
|
|
{
|
|
Object o1 = *cast(Object*)p1;
|
|
Object o2 = *cast(Object*)p2;
|
|
|
|
return (o1 is o2) || (o1 && o1.opEquals(o2));
|
|
}
|
|
|
|
override const(void)[] initializer() nothrow pure const @safe
|
|
{
|
|
return m_init;
|
|
}
|
|
}
|
|
|
|
void destroy(bool initialize = true, T)(ref T obj) if (is(T == struct))
|
|
{
|
|
import core.internal.destruction : destructRecurse;
|
|
|
|
destructRecurse(obj);
|
|
|
|
static if (initialize)
|
|
{
|
|
import core.internal.lifetime : emplaceInitializer;
|
|
emplaceInitializer(obj); // emplace T.init
|
|
}
|
|
}
|
|
|
|
private extern (D) nothrow alias void function (Object) fp_t;
|
|
private extern (C) void rt_finalize2(void* p, bool det = true, bool resetMemory = true) nothrow
|
|
{
|
|
auto ppv = cast(void**) p;
|
|
if (!p || !*ppv)
|
|
return;
|
|
|
|
auto pc = cast(TypeInfo_Class*) *ppv;
|
|
if (det)
|
|
{
|
|
auto c = *pc;
|
|
do
|
|
{
|
|
if (c.destructor)
|
|
(cast(fp_t) c.destructor)(cast(Object) p); // call destructor
|
|
}
|
|
while ((c = c.base) !is null);
|
|
}
|
|
|
|
if (resetMemory)
|
|
{
|
|
auto w = (*pc).initializer;
|
|
p[0 .. w.length] = w[];
|
|
}
|
|
*ppv = null; // zero vptr even if `resetMemory` is false
|
|
}
|
|
extern(C) void _d_callfinalizer(void* p)
|
|
{
|
|
rt_finalize2(p);
|
|
}
|
|
|
|
void destroy(bool initialize = true, T)(T obj) if (is(T == class))
|
|
{
|
|
static if (__traits(getLinkage, T) == "C++")
|
|
{
|
|
static if (__traits(hasMember, T, "__xdtor"))
|
|
obj.__xdtor();
|
|
|
|
static if (initialize)
|
|
{
|
|
const initializer = __traits(initSymbol, T);
|
|
(cast(void*)obj)[0 .. initializer.length] = initializer[];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Bypass overloaded opCast
|
|
auto ptr = (() @trusted => *cast(void**) &obj)();
|
|
rt_finalize2(ptr, true, initialize);
|
|
}
|
|
}
|
|
void destroy(bool initialize = true, T)(T obj) if (is(T == interface))
|
|
{
|
|
static assert(__traits(getLinkage, T) == "D", "Invalid call to destroy() on extern(" ~ __traits(getLinkage, T) ~ ") interface");
|
|
|
|
destroy!initialize(cast(Object)obj);
|
|
}
|
|
void destroy(bool initialize = true, T)(ref T obj)
|
|
if (!is(T == struct) && !is(T == interface) && !is(T == class) && !__traits(isStaticArray, T))
|
|
{
|
|
static if (initialize)
|
|
obj = T.init;
|
|
}
|
|
|
|
|
|
class TypeInfo_Pointer : TypeInfo
|
|
{
|
|
TypeInfo m_next;
|
|
|
|
override bool equals(in void* p1, in void* p2) const { return *cast(void**)p1 == *cast(void**)p2; }
|
|
override size_t getHash(scope const void* p) @trusted const
|
|
{
|
|
size_t addr = cast(size_t) *cast(const void**)p;
|
|
return addr ^ (addr >> 4);
|
|
}
|
|
override @property size_t size() nothrow pure const { return (void*).sizeof; }
|
|
|
|
override const(void)[] initializer() const @trusted { return (cast(void *)null)[0 .. (void*).sizeof]; }
|
|
|
|
override const (TypeInfo) next() const { return m_next; }
|
|
}
|
|
|
|
class TypeInfo_Array : TypeInfo {
|
|
TypeInfo value;
|
|
override size_t size() const { return (void[]).sizeof; }
|
|
override const(TypeInfo) next() const { return value; }
|
|
|
|
override bool equals(in void* p1, in void* p2) const
|
|
{
|
|
void[] a1 = *cast(void[]*)p1;
|
|
void[] a2 = *cast(void[]*)p2;
|
|
if (a1.length != a2.length)
|
|
return false;
|
|
size_t sz = value.size;
|
|
for (size_t i = 0; i < a1.length; i++)
|
|
{
|
|
if (!value.equals(a1.ptr + i * sz, a2.ptr + i * sz))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
override @property size_t talign() nothrow pure const
|
|
{
|
|
return (void[]).alignof;
|
|
}
|
|
override const(void)[] initializer() const @trusted { return (cast(void *)null)[0 .. (void[]).sizeof]; }
|
|
}
|
|
|
|
class TypeInfo_StaticArray : TypeInfo {
|
|
TypeInfo value;
|
|
size_t len;
|
|
override size_t size() const { return value.size * len; }
|
|
override const(TypeInfo) next() const { return value; }
|
|
|
|
override bool equals(in void* p1, in void* p2) const {
|
|
size_t sz = value.size;
|
|
|
|
for (size_t u = 0; u < len; u++)
|
|
{
|
|
if (!value.equals(p1 + u * sz, p2 + u * sz))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
override @property size_t talign() nothrow pure const
|
|
{
|
|
return value.talign;
|
|
}
|
|
|
|
}
|
|
|
|
import core.arsd.aa;
|
|
alias AARange = core.arsd.aa.Range;
|
|
extern (C)
|
|
{
|
|
// from druntime/src/rt/aaA.d
|
|
/* The real type is (non-importable) `rt.aaA.Impl*`;
|
|
* the compiler uses `void*` for its prototypes.
|
|
*/
|
|
private alias AA = void*;
|
|
|
|
// size_t _aaLen(in AA aa) pure nothrow @nogc;
|
|
private void* _aaGetY(scope AA* paa, const TypeInfo_AssociativeArray ti, const size_t valsz, const scope void* pkey) pure nothrow;
|
|
private void* _aaGetX(scope AA* paa, const TypeInfo_AssociativeArray ti, const size_t valsz, const scope void* pkey, out bool found) ;
|
|
// inout(void)* _aaGetRvalueX(inout AA aa, in TypeInfo keyti, in size_t valsz, in void* pkey);
|
|
inout(void[]) _aaValues(inout AA aa, const size_t keysz, const size_t valsz, const TypeInfo tiValueArray) ;
|
|
inout(void[]) _aaKeys(inout AA aa, const size_t keysz, const TypeInfo tiKeyArray) ;
|
|
void* _aaRehash(AA* paa, const scope TypeInfo keyti) ;
|
|
void _aaClear(AA aa) ;
|
|
|
|
// alias _dg_t = extern(D) int delegate(void*);
|
|
// int _aaApply(AA aa, size_t keysize, _dg_t dg);
|
|
|
|
// alias _dg2_t = extern(D) int delegate(void*, void*);
|
|
// int _aaApply2(AA aa, size_t keysize, _dg2_t dg);
|
|
|
|
AARange _aaRange(AA aa) pure nothrow @nogc @safe;
|
|
bool _aaRangeEmpty(AARange r) pure @safe @nogc nothrow;
|
|
void* _aaRangeFrontKey(AARange r);
|
|
void* _aaRangeFrontValue(AARange r) pure @nogc nothrow;
|
|
void _aaRangePopFront(ref AARange r) pure @nogc nothrow @safe;
|
|
|
|
int _aaEqual(scope const TypeInfo tiRaw, scope const AA aa1, scope const AA aa2);
|
|
size_t _aaGetHash(scope const AA* aa, scope const TypeInfo tiRaw) nothrow;
|
|
|
|
/*
|
|
_d_assocarrayliteralTX marked as pure, because aaLiteral can be called from pure code.
|
|
This is a typesystem hole, however this is existing hole.
|
|
Early compiler didn't check purity of toHash or postblit functions, if key is a UDT thus
|
|
copiler allowed to create AA literal with keys, which have impure unsafe toHash methods.
|
|
*/
|
|
void* _d_assocarrayliteralTX(const TypeInfo_AssociativeArray ti, void[] keys, void[] values);
|
|
}
|
|
|
|
private AARange _aaToRange(T: V[K], K, V)(ref T aa) pure nothrow @nogc @safe
|
|
{
|
|
// ensure we are dealing with a genuine AA.
|
|
static if (is(const(V[K]) == const(T)))
|
|
alias realAA = aa;
|
|
else
|
|
const(V[K]) realAA = aa;
|
|
return _aaRange(() @trusted { return *cast(AA*)&realAA; } ());
|
|
}
|
|
|
|
auto byKey(T : V[K], K, V)(T aa) pure nothrow @nogc @safe
|
|
{
|
|
import core.internal.traits : substInout;
|
|
|
|
static struct Result
|
|
{
|
|
AARange r;
|
|
|
|
pure nothrow @nogc:
|
|
@property bool empty() @safe { return _aaRangeEmpty(r); }
|
|
@property ref front() @trusted
|
|
{
|
|
return *cast(substInout!K*) _aaRangeFrontKey(r);
|
|
}
|
|
void popFront() @safe { _aaRangePopFront(r); }
|
|
@property Result save() { return this; }
|
|
}
|
|
|
|
return Result(_aaToRange(aa));
|
|
}
|
|
|
|
/** ditto */
|
|
auto byKey(T : V[K], K, V)(T* aa) pure nothrow @nogc
|
|
{
|
|
return (*aa).byKey();
|
|
}
|
|
|
|
|
|
|
|
auto byValue(T : V[K], K, V)(T aa) pure nothrow @nogc @safe
|
|
{
|
|
import core.internal.traits : substInout;
|
|
|
|
static struct Result
|
|
{
|
|
AARange r;
|
|
|
|
pure nothrow @nogc:
|
|
@property bool empty() @safe { return _aaRangeEmpty(r); }
|
|
@property ref front() @trusted
|
|
{
|
|
return *cast(substInout!V*) _aaRangeFrontValue(r);
|
|
}
|
|
void popFront() @safe { _aaRangePopFront(r); }
|
|
@property Result save() { return this; }
|
|
}
|
|
|
|
return Result(_aaToRange(aa));
|
|
}
|
|
|
|
/** ditto */
|
|
auto byValue(T : V[K], K, V)(T* aa) pure nothrow @nogc
|
|
{
|
|
return (*aa).byValue();
|
|
}
|
|
|
|
Key[] keys(T : Value[Key], Value, Key)(T aa) @property
|
|
{
|
|
// ensure we are dealing with a genuine AA.
|
|
static if (is(const(Value[Key]) == const(T)))
|
|
alias realAA = aa;
|
|
else
|
|
const(Value[Key]) realAA = aa;
|
|
auto res = () @trusted {
|
|
auto a = cast(void[])_aaKeys(*cast(inout(AA)*)&realAA, Key.sizeof, typeid(Key[]));
|
|
return *cast(Key[]*)&a;
|
|
}();
|
|
static if (__traits(hasPostblit, Key))
|
|
_doPostblit(res);
|
|
return res;
|
|
}
|
|
|
|
/** ditto */
|
|
Key[] keys(T : Value[Key], Value, Key)(T *aa) @property
|
|
{
|
|
return (*aa).keys;
|
|
}
|
|
|
|
/***********************************
|
|
* Returns a newly allocated dynamic array containing a copy of the values from
|
|
* the associative array.
|
|
* Params:
|
|
* aa = The associative array.
|
|
* Returns:
|
|
* A dynamic array containing a copy of the values.
|
|
*/
|
|
Value[] values(T : Value[Key], Value, Key)(T aa) @property
|
|
{
|
|
// ensure we are dealing with a genuine AA.
|
|
static if (is(const(Value[Key]) == const(T)))
|
|
alias realAA = aa;
|
|
else
|
|
const(Value[Key]) realAA = aa;
|
|
auto res = () @trusted {
|
|
auto a = cast(void[])_aaValues(*cast(inout(AA)*)&realAA, Key.sizeof, Value.sizeof, typeid(Value[]));
|
|
return *cast(Value[]*)&a;
|
|
}();
|
|
static if (__traits(hasPostblit, Value))
|
|
_doPostblit(res);
|
|
return res;
|
|
}
|
|
|
|
/** ditto */
|
|
Value[] values(T : Value[Key], Value, Key)(T *aa) @property
|
|
{
|
|
return (*aa).values;
|
|
}
|
|
inout(V) get(K, V)(inout(V[K]) aa, K key, lazy inout(V) defaultValue)
|
|
{
|
|
auto p = key in aa;
|
|
return p ? *p : defaultValue;
|
|
}
|
|
|
|
/** ditto */
|
|
inout(V) get(K, V)(inout(V[K])* aa, K key, lazy inout(V) defaultValue)
|
|
{
|
|
return (*aa).get(key, defaultValue);
|
|
}
|
|
// Tests whether T can be @safe-ly copied. Use a union to exclude destructor from the test.
|
|
private enum bool isSafeCopyable(T) = is(typeof(() @safe { union U { T x; } T *x; auto u = U(*x); }));
|
|
|
|
/***********************************
|
|
* Looks up key; if it exists applies the update callable else evaluates the
|
|
* create callable and adds it to the associative array
|
|
* Params:
|
|
* aa = The associative array.
|
|
* key = The key.
|
|
* create = The callable to apply on create.
|
|
* update = The callable to apply on update.
|
|
*/
|
|
void update(K, V, C, U)(ref V[K] aa, K key, scope C create, scope U update)
|
|
if (is(typeof(create()) : V) && (is(typeof(update(aa[K.init])) : V) || is(typeof(update(aa[K.init])) == void)))
|
|
{
|
|
bool found;
|
|
// if key is @safe-ly copyable, `update` may infer @safe
|
|
static if (isSafeCopyable!K)
|
|
{
|
|
auto p = () @trusted
|
|
{
|
|
return cast(V*) _aaGetX(cast(AA*) &aa, typeid(V[K]), V.sizeof, &key, found);
|
|
} ();
|
|
}
|
|
else
|
|
{
|
|
auto p = cast(V*) _aaGetX(cast(AA*) &aa, typeid(V[K]), V.sizeof, &key, found);
|
|
}
|
|
if (!found)
|
|
*p = create();
|
|
else
|
|
{
|
|
static if (is(typeof(update(*p)) == void))
|
|
update(*p);
|
|
else
|
|
*p = update(*p);
|
|
}
|
|
}
|
|
|
|
ref V require(K, V)(ref V[K] aa, K key, lazy V value = V.init)
|
|
{
|
|
bool found;
|
|
// if key is @safe-ly copyable, `require` can infer @safe
|
|
static if (isSafeCopyable!K)
|
|
{
|
|
auto p = () @trusted
|
|
{
|
|
return cast(V*) _aaGetX(cast(AA*) &aa, typeid(V[K]), V.sizeof, &key, found);
|
|
} ();
|
|
}
|
|
else
|
|
{
|
|
auto p = cast(V*) _aaGetX(cast(AA*) &aa, typeid(V[K]), V.sizeof, &key, found);
|
|
}
|
|
if (found)
|
|
return *p;
|
|
else
|
|
{
|
|
*p = value; // Not `return (*p = value)` since if `=` is overloaded
|
|
return *p; // this might not return a ref to the left-hand side.
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/***********************************
|
|
* Removes all remaining keys and values from an associative array.
|
|
* Params:
|
|
* aa = The associative array.
|
|
*/
|
|
void clear(Value, Key)(Value[Key] aa)
|
|
{
|
|
_aaClear(*cast(AA *) &aa);
|
|
}
|
|
|
|
/** ditto */
|
|
void clear(Value, Key)(Value[Key]* aa)
|
|
{
|
|
_aaClear(*cast(AA *) aa);
|
|
}
|
|
void* aaLiteral(Key, Value)(Key[] keys, Value[] values) @trusted pure
|
|
{
|
|
return _d_assocarrayliteralTX(typeid(Value[Key]), *cast(void[]*)&keys, *cast(void[]*)&values);
|
|
}
|
|
|
|
alias AssociativeArray(Key, Value) = Value[Key];
|
|
|
|
class TypeInfo_AssociativeArray : TypeInfo
|
|
{
|
|
override string toString() const
|
|
{
|
|
return value.toString() ~ "[" ~ key.toString() ~ "]";
|
|
}
|
|
|
|
override bool opEquals(Object o)
|
|
{
|
|
if (this is o)
|
|
return true;
|
|
auto c = cast(const TypeInfo_AssociativeArray)o;
|
|
return c && this.key == c.key &&
|
|
this.value == c.value;
|
|
}
|
|
|
|
override bool equals(in void* p1, in void* p2) @trusted const
|
|
{
|
|
return !!_aaEqual(this, *cast(const AA*) p1, *cast(const AA*) p2);
|
|
}
|
|
|
|
override size_t getHash(scope const void* p) nothrow @trusted const
|
|
{
|
|
return _aaGetHash(cast(AA*)p, this);
|
|
}
|
|
|
|
// BUG: need to add the rest of the functions
|
|
|
|
override @property size_t size() nothrow pure const
|
|
{
|
|
return (char[int]).sizeof;
|
|
}
|
|
|
|
override const(void)[] initializer() const @trusted
|
|
{
|
|
return (cast(void *)null)[0 .. (char[int]).sizeof];
|
|
}
|
|
|
|
override @property inout(TypeInfo) next() nothrow pure inout { return value; }
|
|
override @property uint flags() nothrow pure const { return 1; }
|
|
|
|
|
|
TypeInfo value;
|
|
TypeInfo key;
|
|
|
|
override @property size_t talign() nothrow pure const
|
|
{
|
|
return (char[int]).alignof;
|
|
}
|
|
|
|
version (WithArgTypes) override int argTypes(out TypeInfo arg1, out TypeInfo arg2)
|
|
{
|
|
arg1 = typeid(void*);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
class TypeInfo_Enum : TypeInfo {
|
|
TypeInfo base;
|
|
string name;
|
|
void[] m_init;
|
|
|
|
override size_t size() const { return base.size; }
|
|
override const(TypeInfo) next() const { return base.next; }
|
|
override bool equals(in void* p1, in void* p2) const { return base.equals(p1, p2); }
|
|
override @property size_t talign() const { return base.talign; }
|
|
override void destroy(void* p) const { return base.destroy(p); }
|
|
override void postblit(void* p) const { return base.postblit(p); }
|
|
|
|
override const(void)[] initializer() const
|
|
{
|
|
return m_init.length ? m_init : base.initializer();
|
|
}
|
|
}
|
|
|
|
extern (C) void[] _d_newarrayU(const scope TypeInfo ti, size_t length)
|
|
{
|
|
return malloc(length * ti.next.size);
|
|
}
|
|
|
|
extern(C) void[] _d_newarrayT(const TypeInfo ti, size_t length)
|
|
{
|
|
auto arr = _d_newarrayU(ti, length);
|
|
(cast(byte[])arr)[] = 0;
|
|
return arr;
|
|
}
|
|
|
|
extern(C) void[] _d_newarrayiT(const TypeInfo ti, size_t length)
|
|
{
|
|
auto result = _d_newarrayU(ti, length);
|
|
auto tinext = ti.next;
|
|
auto size = tinext.size;
|
|
auto init = tinext.initializer();
|
|
switch (init.length)
|
|
{
|
|
foreach (T; AliasSeq!(ubyte, ushort, uint, ulong))
|
|
{
|
|
case T.sizeof:
|
|
if (tinext.talign % T.alignof == 0)
|
|
{
|
|
(cast(T*)result.ptr)[0 .. size * length / T.sizeof] = *cast(T*)init.ptr;
|
|
return result;
|
|
}
|
|
goto default;
|
|
}
|
|
|
|
default:
|
|
{
|
|
immutable sz = init.length;
|
|
for (size_t u = 0; u < size * length; u += sz)
|
|
{
|
|
memcpy(result.ptr + u, init.ptr, sz);
|
|
}
|
|
return result;
|
|
}
|
|
}
|
|
}
|
|
|
|
extern (C) void* _d_newitemU(scope const TypeInfo _ti)
|
|
{
|
|
import core.arsd.objectutils;
|
|
auto ti = cast()_ti;
|
|
immutable tiSize = structTypeInfoSize(ti);
|
|
immutable itemSize = ti.size;
|
|
immutable size = itemSize + tiSize;
|
|
auto p = malloc(size);
|
|
|
|
return p.ptr;
|
|
}
|
|
|
|
/// ditto
|
|
extern (C) void* _d_newitemT(in TypeInfo _ti)
|
|
{
|
|
auto p = _d_newitemU(_ti);
|
|
memset(p, 0, _ti.size);
|
|
return p;
|
|
}
|
|
|
|
/// Same as above, for item with non-zero initializer.
|
|
extern (C) void* _d_newitemiT(in TypeInfo _ti)
|
|
{
|
|
auto p = _d_newitemU(_ti);
|
|
auto init = _ti.initializer();
|
|
assert(init.length <= _ti.size);
|
|
memcpy(p, init.ptr, init.length);
|
|
return p;
|
|
}
|
|
|
|
|
|
|
|
private void[] _d_newarrayOpT(alias op)(const TypeInfo ti, size_t[] dimensions)
|
|
{
|
|
if (dimensions.length == 0)
|
|
return null;
|
|
|
|
void[] foo(const TypeInfo ti, size_t[] dimensions)
|
|
{
|
|
size_t count = dimensions[0];
|
|
|
|
if (dimensions.length == 1)
|
|
{
|
|
auto r = op(ti, count);
|
|
return (*cast(void[]*)(&r))[0..count];
|
|
}
|
|
void[] p = malloc((void[]).sizeof * count);
|
|
|
|
foreach (i; 0..count)
|
|
{
|
|
(cast(void[]*)p.ptr)[i] = foo(ti.next, dimensions[1..$]);
|
|
}
|
|
return p[0..count];
|
|
}
|
|
|
|
return foo(ti, dimensions);
|
|
}
|
|
|
|
|
|
extern (C) void[] _d_newarraymTX(const TypeInfo ti, size_t[] dims)
|
|
{
|
|
if (dims.length == 0)
|
|
return null;
|
|
else
|
|
return _d_newarrayOpT!(_d_newarrayT)(ti, dims);
|
|
}
|
|
|
|
/// ditto
|
|
extern (C) void[] _d_newarraymiTX(const TypeInfo ti, size_t[] dims)
|
|
{
|
|
if (dims.length == 0)
|
|
return null;
|
|
else
|
|
return _d_newarrayOpT!(_d_newarrayiT)(ti, dims);
|
|
}
|
|
|
|
|
|
|
|
|
|
AllocatedBlock* getAllocatedBlock(void* ptr) {
|
|
auto block = (cast(AllocatedBlock*) ptr) - 1;
|
|
if(!block.checkChecksum())
|
|
return null;
|
|
return block;
|
|
}
|
|
|
|
/++
|
|
Marks the memory block as OK to append in-place if possible.
|
|
+/
|
|
void assumeSafeAppend(T)(T[] arr) {
|
|
auto block = getAllocatedBlock(arr.ptr);
|
|
if(block is null) assert(0);
|
|
|
|
block.used = arr.length;
|
|
}
|
|
|
|
/++
|
|
Marks the memory block associated with this array as unique, meaning
|
|
the runtime is allowed to free the old block immediately instead of
|
|
keeping it around for other lingering slices.
|
|
|
|
In real D, the GC would take care of this but here I have to hack it.
|
|
|
|
arsd.webasm extension
|
|
+/
|
|
void assumeUniqueReference(T)(T[] arr) {
|
|
auto block = getAllocatedBlock(arr.ptr);
|
|
if(block is null) assert(0);
|
|
|
|
block.flags |= AllocatedBlock.Flags.unique;
|
|
}
|
|
|
|
template _d_arraysetlengthTImpl(Tarr : T[], T) {
|
|
size_t _d_arraysetlengthT(return scope ref Tarr arr, size_t newlength) @trusted {
|
|
auto orig = arr;
|
|
|
|
if(newlength <= arr.length) {
|
|
arr = arr[0 ..newlength];
|
|
} else {
|
|
auto ptr = cast(T*) realloc(cast(ubyte[])arr, newlength * T.sizeof);
|
|
arr = ptr[0 .. newlength];
|
|
if(orig !is null) {
|
|
arr[0 .. orig.length] = orig[];
|
|
}
|
|
}
|
|
|
|
return newlength;
|
|
}
|
|
}
|
|
|
|
extern (C) byte[] _d_arrayappendcTX(const TypeInfo ti, ref byte[] px, size_t n) @trusted {
|
|
auto elemSize = ti.next.size;
|
|
auto newLength = n + px.length;
|
|
auto newSize = newLength * elemSize;
|
|
//import std.stdio; writeln(newSize, " ", newLength);
|
|
ubyte* ptr;
|
|
if(px.ptr is null)
|
|
ptr = malloc(newSize).ptr;
|
|
else // FIXME: anti-stomping by checking length == used
|
|
ptr = realloc(cast(ubyte[])px, newSize).ptr;
|
|
auto ns = ptr[0 .. newSize];
|
|
auto op = px.ptr;
|
|
auto ol = px.length * elemSize;
|
|
|
|
foreach(i, b; op[0 .. ol])
|
|
ns[i] = b;
|
|
|
|
(cast(size_t *)(&px))[0] = newLength;
|
|
(cast(void **)(&px))[1] = ns.ptr;
|
|
return px;
|
|
}
|
|
|
|
|
|
version(inline_concat)
|
|
extern(C) void[] _d_arraycatnTX(const TypeInfo ti, scope byte[][] arrs) @trusted
|
|
{
|
|
auto elemSize = ti.next.size;
|
|
size_t length;
|
|
foreach (b; arrs)
|
|
length += b.length;
|
|
if(!length)
|
|
return null;
|
|
ubyte* ptr = cast(ubyte*)malloc(length * elemSize);
|
|
|
|
//Copy data
|
|
{
|
|
ubyte* nPtr = ptr;
|
|
foreach(b; arrs)
|
|
{
|
|
byte* bPtr = b.ptr;
|
|
size_t copySize = b.length*elemSize;
|
|
nPtr[0..copySize] = cast(ubyte[])bPtr[0..copySize];
|
|
nPtr+= copySize;
|
|
}
|
|
}
|
|
return cast(void[])ptr[0..length];
|
|
}
|
|
|
|
version(inline_concat)
|
|
extern (C) byte[] _d_arraycatT(const TypeInfo ti, byte[] x, byte[] y)
|
|
{
|
|
import core.arsd.objectutils;
|
|
auto sizeelem = ti.next.size; // array element size
|
|
size_t xlen = x.length * sizeelem;
|
|
size_t ylen = y.length * sizeelem;
|
|
size_t len = xlen + ylen;
|
|
|
|
if (!len)
|
|
return null;
|
|
|
|
byte[] p = cast(byte[])malloc(len);
|
|
memcpy(p.ptr, x.ptr, xlen);
|
|
memcpy(p.ptr + xlen, y.ptr, ylen);
|
|
// do postblit processing
|
|
__doPostblit(p.ptr, xlen + ylen, ti.next);
|
|
return p[0 .. x.length + y.length];
|
|
}
|
|
|
|
extern (C) void[] _d_arrayappendcd(ref byte[] x, dchar c)
|
|
{
|
|
// c could encode into from 1 to 4 characters
|
|
char[4] buf = void;
|
|
byte[] appendthis; // passed to appendT
|
|
if (c <= 0x7F)
|
|
{
|
|
buf.ptr[0] = cast(char)c;
|
|
appendthis = (cast(byte *)buf.ptr)[0..1];
|
|
}
|
|
else if (c <= 0x7FF)
|
|
{
|
|
buf.ptr[0] = cast(char)(0xC0 | (c >> 6));
|
|
buf.ptr[1] = cast(char)(0x80 | (c & 0x3F));
|
|
appendthis = (cast(byte *)buf.ptr)[0..2];
|
|
}
|
|
else if (c <= 0xFFFF)
|
|
{
|
|
buf.ptr[0] = cast(char)(0xE0 | (c >> 12));
|
|
buf.ptr[1] = cast(char)(0x80 | ((c >> 6) & 0x3F));
|
|
buf.ptr[2] = cast(char)(0x80 | (c & 0x3F));
|
|
appendthis = (cast(byte *)buf.ptr)[0..3];
|
|
}
|
|
else if (c <= 0x10FFFF)
|
|
{
|
|
buf.ptr[0] = cast(char)(0xF0 | (c >> 18));
|
|
buf.ptr[1] = cast(char)(0x80 | ((c >> 12) & 0x3F));
|
|
buf.ptr[2] = cast(char)(0x80 | ((c >> 6) & 0x3F));
|
|
buf.ptr[3] = cast(char)(0x80 | (c & 0x3F));
|
|
appendthis = (cast(byte *)buf.ptr)[0..4];
|
|
}
|
|
else
|
|
assert(false, "Could not append dchar"); // invalid utf character - should we throw an exception instead?
|
|
|
|
//
|
|
// TODO: This always assumes the array type is shared, because we do not
|
|
// get a typeinfo from the compiler. Assuming shared is the safest option.
|
|
// Once the compiler is fixed, the proper typeinfo should be forwarded.
|
|
//
|
|
return _d_arrayappendT(typeid(shared char[]), x, appendthis);
|
|
}
|
|
|
|
|
|
|
|
|
|
alias AliasSeq(T...) = T;
|
|
static foreach(type; AliasSeq!(byte, char, dchar, double, float, int, long, short, ubyte, uint, ulong, ushort, void, wchar)) {
|
|
mixin(q{
|
|
class TypeInfo_}~type.mangleof~q{ : TypeInfo {
|
|
override string toString() const pure nothrow @safe { return type.stringof; }
|
|
override size_t size() const { return type.sizeof; }
|
|
override @property size_t talign() const pure nothrow
|
|
{
|
|
return type.alignof;
|
|
}
|
|
|
|
override bool equals(in void* a, in void* b) const {
|
|
static if(is(type == void))
|
|
return false;
|
|
else
|
|
return (*(cast(type*) a) == (*(cast(type*) b)));
|
|
}
|
|
static if(!is(type == void))
|
|
override size_t getHash(scope const void* p) @trusted const nothrow
|
|
{
|
|
return hashOf(*cast(const type *)p);
|
|
}
|
|
override const(void)[] initializer() pure nothrow @trusted const
|
|
{
|
|
static if(__traits(isZeroInit, type))
|
|
return (cast(void*)null)[0 .. type.sizeof];
|
|
else
|
|
{
|
|
static immutable type[1] c;
|
|
return c;
|
|
}
|
|
}
|
|
}
|
|
class TypeInfo_A}~type.mangleof~q{ : TypeInfo_Array {
|
|
override string toString() const { return (type[]).stringof; }
|
|
override const(TypeInfo) next() const { return cast(inout)typeid(type); }
|
|
override size_t getHash(scope const void* p) @trusted const nothrow
|
|
{
|
|
return hashOf(*cast(const type[]*) p);
|
|
}
|
|
|
|
override bool equals(in void* av, in void* bv) const {
|
|
type[] a = *(cast(type[]*) av);
|
|
type[] b = *(cast(type[]*) bv);
|
|
|
|
static if(is(type == void))
|
|
return false;
|
|
else {
|
|
foreach(idx, item; a)
|
|
if(item != b[idx])
|
|
return false;
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
// typeof(null)
|
|
class TypeInfo_n : TypeInfo
|
|
{
|
|
const: pure: @nogc: nothrow: @safe:
|
|
override string toString() { return "typeof(null)"; }
|
|
override size_t getHash(scope const void*) { return 0; }
|
|
override bool equals(in void*, in void*) { return true; }
|
|
override @property size_t size() { return typeof(null).sizeof; }
|
|
override const(void)[] initializer() @trusted { return (cast(void *)null)[0 .. size_t.sizeof]; }
|
|
}
|
|
|
|
struct Interface {
|
|
TypeInfo_Class classinfo;
|
|
void*[] vtbl;
|
|
size_t offset;
|
|
}
|
|
|
|
/**
|
|
* Array of pairs giving the offset and type information for each
|
|
* member in an aggregate.
|
|
*/
|
|
struct OffsetTypeInfo
|
|
{
|
|
size_t offset; /// Offset of member from start of object
|
|
TypeInfo ti; /// TypeInfo for this member
|
|
}
|
|
|
|
class TypeInfo_Axa : TypeInfo_Aa {
|
|
|
|
}
|
|
class TypeInfo_Aya : TypeInfo_Aa {
|
|
|
|
}
|
|
|
|
class TypeInfo_Function : TypeInfo
|
|
{
|
|
override string toString() const pure @trusted{return deco;}
|
|
override bool opEquals(Object o)
|
|
{
|
|
if (this is o)
|
|
return true;
|
|
auto c = cast(const TypeInfo_Function)o;
|
|
return c && this.deco == c.deco;
|
|
}
|
|
|
|
// BUG: need to add the rest of the functions
|
|
|
|
override @property size_t size() nothrow pure const
|
|
{
|
|
return 0; // no size for functions
|
|
}
|
|
override const(void)[] initializer() const @safe{return null;}
|
|
TypeInfo _next;
|
|
override const(TypeInfo) next()nothrow pure inout @nogc { return _next; }
|
|
|
|
/**
|
|
* Mangled function type string
|
|
*/
|
|
string deco;
|
|
}
|
|
|
|
|
|
class TypeInfo_Delegate : TypeInfo {
|
|
TypeInfo next;
|
|
string deco;
|
|
override @property size_t size() nothrow pure const
|
|
{
|
|
alias dg = int delegate();
|
|
return dg.sizeof;
|
|
}
|
|
override bool equals(in void* p1, in void* p2) const
|
|
{
|
|
auto dg1 = *cast(void delegate()*)p1;
|
|
auto dg2 = *cast(void delegate()*)p2;
|
|
return dg1 == dg2;
|
|
}
|
|
override const(void)[] initializer() const @trusted
|
|
{
|
|
return (cast(void *)null)[0 .. (int delegate()).sizeof];
|
|
}
|
|
override size_t getHash(scope const void* p) @trusted const
|
|
{
|
|
return hashOf(*cast(const void delegate() *)p);
|
|
}
|
|
|
|
override @property size_t talign() nothrow pure const
|
|
{
|
|
alias dg = int delegate();
|
|
return dg.alignof;
|
|
}
|
|
}
|
|
|
|
|
|
//Directly copied from LWDR source.
|
|
class TypeInfo_Interface : TypeInfo
|
|
{
|
|
TypeInfo_Class info;
|
|
|
|
override bool equals(in void* p1, in void* p2) const
|
|
{
|
|
Interface* pi = **cast(Interface ***)*cast(void**)p1;
|
|
Object o1 = cast(Object)(*cast(void**)p1 - pi.offset);
|
|
pi = **cast(Interface ***)*cast(void**)p2;
|
|
Object o2 = cast(Object)(*cast(void**)p2 - pi.offset);
|
|
|
|
return o1 == o2 || (o1 && o1.opCmp(o2) == 0);
|
|
}
|
|
override size_t getHash(scope const void* p) @trusted const
|
|
{
|
|
if (!*cast(void**)p)
|
|
{
|
|
return 0;
|
|
}
|
|
Interface* pi = **cast(Interface ***)*cast(void**)p;
|
|
Object o = cast(Object)(*cast(void**)p - pi.offset);
|
|
assert(o);
|
|
return o.toHash();
|
|
}
|
|
|
|
override const(void)[] initializer() const @trusted
|
|
{
|
|
return (cast(void *)null)[0 .. Object.sizeof];
|
|
}
|
|
|
|
override @property size_t size() nothrow pure const
|
|
{
|
|
return Object.sizeof;
|
|
}
|
|
}
|
|
|
|
class TypeInfo_Const : TypeInfo {
|
|
override size_t getHash(scope const(void*) p) @trusted const nothrow { return base.getHash(p); }
|
|
TypeInfo base;
|
|
override size_t size() const { return base.size; }
|
|
override const(TypeInfo) next() const { return base.next; }
|
|
override const(void)[] initializer() nothrow pure const{return base.initializer();}
|
|
override @property size_t talign() nothrow pure const { return base.talign; }
|
|
override bool equals(in void* p1, in void* p2) const { return base.equals(p1, p2); }
|
|
}
|
|
|
|
|
|
///For some reason, getHash for interfaces wanted that
|
|
pragma(mangle, "_D9invariant12_d_invariantFC6ObjectZv")
|
|
extern(D) void _d_invariant(Object o)
|
|
{
|
|
TypeInfo_Class c;
|
|
|
|
//printf("__d_invariant(%p)\n", o);
|
|
|
|
// BUG: needs to be filename/line of caller, not library routine
|
|
assert(o !is null); // just do null check, not invariant check
|
|
|
|
c = typeid(o);
|
|
do
|
|
{
|
|
if (c.classInvariant)
|
|
{
|
|
(*c.classInvariant)(o);
|
|
}
|
|
c = c.base;
|
|
} while (c);
|
|
}
|
|
|
|
/+
|
|
class TypeInfo_Immutable : TypeInfo {
|
|
size_t getHash(in void*) nothrow { return 0; }
|
|
TypeInfo base;
|
|
}
|
|
+/
|
|
class TypeInfo_Invariant : TypeInfo {
|
|
TypeInfo base;
|
|
override size_t getHash(scope const (void*) p) @trusted const nothrow { return base.getHash(p); }
|
|
override size_t size() const { return base.size; }
|
|
override const(TypeInfo) next() const { return base; }
|
|
}
|
|
class TypeInfo_Shared : TypeInfo {
|
|
override size_t getHash(scope const (void*) p) @trusted const nothrow { return base.getHash(p); }
|
|
TypeInfo base;
|
|
override size_t size() const { return base.size; }
|
|
override const(TypeInfo) next() const { return base; }
|
|
}
|
|
class TypeInfo_Inout : TypeInfo {
|
|
override size_t getHash(scope const (void*) p) @trusted const nothrow { return base.getHash(p); }
|
|
TypeInfo base;
|
|
override size_t size() const { return base.size; }
|
|
override const(TypeInfo) next() const { return base; }
|
|
}
|
|
|
|
class TypeInfo_Struct : TypeInfo {
|
|
string name;
|
|
void[] m_init;
|
|
@safe pure nothrow
|
|
{
|
|
size_t function(in void*) xtoHash;
|
|
bool function(in void*, in void*) xopEquals;
|
|
int function(in void*, in void*) xopCmp;
|
|
string function(in void*) xtoString;
|
|
}
|
|
uint m_flags;
|
|
union {
|
|
void function(void*) xdtor;
|
|
void function(void*, const TypeInfo_Struct) xdtorti;
|
|
}
|
|
void function(void*) xpostblit;
|
|
uint align_;
|
|
immutable(void)* rtinfo;
|
|
// private struct _memberFunc //? Is it necessary
|
|
// {
|
|
// union
|
|
// {
|
|
// struct // delegate
|
|
// {
|
|
// const void* ptr;
|
|
// const void* funcptr;
|
|
// }
|
|
// @safe pure nothrow
|
|
// {
|
|
// bool delegate(in void*) xopEquals;
|
|
// int delegate(in void*) xopCmp;
|
|
// }
|
|
// }
|
|
// }
|
|
|
|
enum StructFlags : uint
|
|
{
|
|
hasPointers = 0x1,
|
|
isDynamicType = 0x2, // built at runtime, needs type info in xdtor
|
|
}
|
|
override size_t size() const { return m_init.length; }
|
|
override @property uint flags() nothrow pure const @safe @nogc { return m_flags; }
|
|
|
|
override size_t toHash() const
|
|
{
|
|
return hashOf(this.name);
|
|
}
|
|
override bool opEquals(Object o)
|
|
{
|
|
if (this is o)
|
|
return true;
|
|
auto s = cast(const TypeInfo_Struct)o;
|
|
return s && this.name == s.name;
|
|
}
|
|
override size_t getHash(scope const void* p) @trusted pure nothrow const
|
|
{
|
|
assert(p);
|
|
if (xtoHash)
|
|
{
|
|
return (*xtoHash)(p);
|
|
}
|
|
else
|
|
{
|
|
return hashOf(p[0 .. initializer().length]);
|
|
}
|
|
}
|
|
|
|
|
|
override bool equals(in void* p1, in void* p2) @trusted const
|
|
{
|
|
if (!p1 || !p2)
|
|
return false;
|
|
else if (xopEquals)
|
|
return (*xopEquals)(p1, p2);
|
|
else if (p1 == p2)
|
|
return true;
|
|
else
|
|
// BUG: relies on the GC not moving objects
|
|
return memcmp(p1, p2, m_init.length) == 0;
|
|
}
|
|
override @property size_t talign() nothrow pure const { return align_; }
|
|
final override void destroy(void* p) const
|
|
{
|
|
if (xdtor)
|
|
{
|
|
if (m_flags & StructFlags.isDynamicType)
|
|
(*xdtorti)(p, this);
|
|
else
|
|
(*xdtor)(p);
|
|
}
|
|
}
|
|
|
|
override void postblit(void* p) const
|
|
{
|
|
if (xpostblit)
|
|
(*xpostblit)(p);
|
|
}
|
|
|
|
override const(void)[] initializer() nothrow pure const @safe
|
|
{
|
|
return m_init;
|
|
}
|
|
|
|
}
|
|
|
|
extern(C) bool _xopCmp(in void*, in void*) { return false; }
|
|
|
|
// }
|
|
|
|
void __ArrayDtor(T)(scope T[] a)
|
|
{
|
|
foreach_reverse (ref T e; a)
|
|
e.__xdtor();
|
|
}
|
|
|
|
TTo[] __ArrayCast(TFrom, TTo)(return scope TFrom[] from) nothrow
|
|
{
|
|
const fromSize = from.length * TFrom.sizeof;
|
|
const toLength = fromSize / TTo.sizeof;
|
|
|
|
if ((fromSize % TTo.sizeof) != 0)
|
|
{
|
|
//onArrayCastError(TFrom.stringof, fromSize, TTo.stringof, toLength * TTo.sizeof);
|
|
import arsd.webassembly;
|
|
abort();
|
|
}
|
|
|
|
struct Array
|
|
{
|
|
size_t length;
|
|
void* ptr;
|
|
}
|
|
auto a = cast(Array*)&from;
|
|
a.length = toLength; // jam new length
|
|
return *cast(TTo[]*)a;
|
|
}
|
|
|
|
extern (C) void[] _d_arrayappendT(const TypeInfo ti, ref byte[] x, byte[] y)
|
|
{
|
|
auto length = x.length;
|
|
auto tinext = ti.next;
|
|
auto sizeelem = tinext./*t*/size; // array element size
|
|
_d_arrayappendcTX(ti, x, y.length);
|
|
memcpy(x.ptr + length * sizeelem, y.ptr, y.length * sizeelem);
|
|
|
|
// do postblit
|
|
//__doPostblit(x.ptr + length * sizeelem, y.length * sizeelem, tinext);
|
|
return x;
|
|
}
|
|
|
|
extern (C) int _adEq2(void[] a1, void[] a2, TypeInfo ti)
|
|
{
|
|
debug(adi) printf("_adEq2(a1.length = %d, a2.length = %d)\n", a1.length, a2. length);
|
|
if (a1.length != a2.length)
|
|
return 0; // not equal
|
|
if (!ti.equals(&a1, &a2))
|
|
return 0;
|
|
return 1;
|
|
}
|
|
|
|
V[K] dup(T : V[K], K, V)(T aa)
|
|
{
|
|
//pragma(msg, "K = ", K, ", V = ", V);
|
|
|
|
// Bug10720 - check whether V is copyable
|
|
static assert(is(typeof({ V v = aa[K.init]; })),
|
|
"cannot call " ~ T.stringof ~ ".dup because " ~ V.stringof ~ " is not copyable");
|
|
|
|
V[K] result;
|
|
|
|
//foreach (k, ref v; aa)
|
|
// result[k] = v; // Bug13701 - won't work if V is not mutable
|
|
|
|
ref V duplicateElem(ref K k, ref const V v) @trusted pure nothrow
|
|
{
|
|
void* pv = _aaGetY(cast(AA*)&result, typeid(V[K]), V.sizeof, &k);
|
|
memcpy(pv, &v, V.sizeof);
|
|
return *cast(V*)pv;
|
|
}
|
|
|
|
foreach (k, ref v; aa)
|
|
{
|
|
static if (!__traits(hasPostblit, V))
|
|
duplicateElem(k, v);
|
|
else static if (__traits(isStaticArray, V))
|
|
_doPostblit(duplicateElem(k, v)[]);
|
|
else static if (!is(typeof(v.__xpostblit())) && is(immutable V == immutable UV, UV))
|
|
(() @trusted => *cast(UV*) &duplicateElem(k, v))().__xpostblit();
|
|
else
|
|
duplicateElem(k, v).__xpostblit();
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
/** ditto */
|
|
V[K] dup(T : V[K], K, V)(T* aa)
|
|
{
|
|
return (*aa).dup;
|
|
}
|
|
|
|
T[] dup(T)(scope T[] array) pure nothrow @trusted if (__traits(isPOD, T) && !is(const(T) : T))
|
|
{
|
|
T[] result;
|
|
foreach(ref e; array) {
|
|
result ~= e;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
T[] dup(T)(scope const(T)[] array) pure nothrow @trusted if (__traits(isPOD, T))
|
|
{
|
|
T[] result;
|
|
foreach(ref e; array) {
|
|
result ~= e;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
immutable(T)[] idup(T)(scope const(T)[] array) pure nothrow @trusted
|
|
{
|
|
immutable(T)[] result;
|
|
foreach(ref e; array) {
|
|
result ~= e;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
class Error { this(string msg) {} }
|
|
class Throwable : Object
|
|
{
|
|
interface TraceInfo
|
|
{
|
|
int opApply(scope int delegate(ref const(char[]))) const;
|
|
int opApply(scope int delegate(ref size_t, ref const(char[]))) const;
|
|
string toString() const;
|
|
}
|
|
|
|
string msg; /// A message describing the error.
|
|
|
|
/**
|
|
* The _file name of the D source code corresponding with
|
|
* where the error was thrown from.
|
|
*/
|
|
string file;
|
|
/**
|
|
* The _line number of the D source code corresponding with
|
|
* where the error was thrown from.
|
|
*/
|
|
size_t line;
|
|
|
|
/**
|
|
* The stack trace of where the error happened. This is an opaque object
|
|
* that can either be converted to $(D string), or iterated over with $(D
|
|
* foreach) to extract the items in the stack trace (as strings).
|
|
*/
|
|
TraceInfo info;
|
|
|
|
/**
|
|
* A reference to the _next error in the list. This is used when a new
|
|
* $(D Throwable) is thrown from inside a $(D catch) block. The originally
|
|
* caught $(D Exception) will be chained to the new $(D Throwable) via this
|
|
* field.
|
|
*/
|
|
private Throwable nextInChain;
|
|
|
|
private uint _refcount; // 0 : allocated by GC
|
|
// 1 : allocated by _d_newThrowable()
|
|
// 2.. : reference count + 1
|
|
|
|
/**
|
|
* Returns:
|
|
* A reference to the _next error in the list. This is used when a new
|
|
* $(D Throwable) is thrown from inside a $(D catch) block. The originally
|
|
* caught $(D Exception) will be chained to the new $(D Throwable) via this
|
|
* field.
|
|
*/
|
|
@property inout(Throwable) next() @safe inout return scope pure nothrow @nogc { return nextInChain; }
|
|
|
|
/**
|
|
* Replace next in chain with `tail`.
|
|
* Use `chainTogether` instead if at all possible.
|
|
*/
|
|
@property void next(Throwable tail) @safe scope pure nothrow @nogc{}
|
|
|
|
/**
|
|
* Returns:
|
|
* mutable reference to the reference count, which is
|
|
* 0 - allocated by the GC, 1 - allocated by _d_newThrowable(),
|
|
* and >=2 which is the reference count + 1
|
|
* Note:
|
|
* Marked as `@system` to discourage casual use of it.
|
|
*/
|
|
@system @nogc final pure nothrow ref uint refcount() return { return _refcount; }
|
|
|
|
/**
|
|
* Loop over the chain of Throwables.
|
|
*/
|
|
int opApply(scope int delegate(Throwable) dg)
|
|
{
|
|
int result = 0;
|
|
for (Throwable t = this; t; t = t.nextInChain)
|
|
{
|
|
result = dg(t);
|
|
if (result)
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
/**
|
|
* Append `e2` to chain of exceptions that starts with `e1`.
|
|
* Params:
|
|
* e1 = start of chain (can be null)
|
|
* e2 = second part of chain (can be null)
|
|
* Returns:
|
|
* Throwable that is at the start of the chain; null if both `e1` and `e2` are null
|
|
*/
|
|
static @system @nogc pure nothrow Throwable chainTogether(return scope Throwable e1, return scope Throwable e2)
|
|
{
|
|
if (!e1)
|
|
return e2;
|
|
if (!e2)
|
|
return e1;
|
|
if (e2.refcount())
|
|
++e2.refcount();
|
|
|
|
for (auto e = e1; 1; e = e.nextInChain)
|
|
{
|
|
if (!e.nextInChain)
|
|
{
|
|
e.nextInChain = e2;
|
|
break;
|
|
}
|
|
}
|
|
return e1;
|
|
}
|
|
|
|
@nogc @safe pure nothrow this(string msg, Throwable nextInChain = null)
|
|
{
|
|
this.msg = msg;
|
|
this.nextInChain = nextInChain;
|
|
if (nextInChain && nextInChain._refcount)
|
|
++nextInChain._refcount;
|
|
//this.info = _d_traceContext();
|
|
}
|
|
|
|
@nogc @safe pure nothrow this(string msg, string file, size_t line, Throwable nextInChain = null)
|
|
{
|
|
this(msg, nextInChain);
|
|
this.file = file;
|
|
this.line = line;
|
|
//this.info = _d_traceContext();
|
|
}
|
|
|
|
@trusted nothrow ~this(){}
|
|
|
|
/**
|
|
* Overrides $(D Object.toString) and returns the error message.
|
|
* Internally this forwards to the $(D toString) overload that
|
|
* takes a $(D_PARAM sink) delegate.
|
|
*/
|
|
override string toString()
|
|
{
|
|
string s;
|
|
toString((in buf) { s ~= buf; });
|
|
return s;
|
|
}
|
|
|
|
/**
|
|
* The Throwable hierarchy uses a toString overload that takes a
|
|
* $(D_PARAM _sink) delegate to avoid GC allocations, which cannot be
|
|
* performed in certain error situations. Override this $(D
|
|
* toString) method to customize the error message.
|
|
*/
|
|
void toString(scope void delegate(in char[]) sink) const{}
|
|
|
|
/**
|
|
* Get the message describing the error.
|
|
* Base behavior is to return the `Throwable.msg` field.
|
|
* Override to return some other error message.
|
|
*
|
|
* Returns:
|
|
* Error message
|
|
*/
|
|
const(char)[] message() const
|
|
{
|
|
return this.msg;
|
|
}
|
|
}
|
|
class Exception : Throwable
|
|
{
|
|
|
|
/**
|
|
* Creates a new instance of Exception. The nextInChain parameter is used
|
|
* internally and should always be $(D null) when passed by user code.
|
|
* This constructor does not automatically throw the newly-created
|
|
* Exception; the $(D throw) statement should be used for that purpose.
|
|
*/
|
|
@nogc @safe pure nothrow this(string msg, string file = __FILE__, size_t line = __LINE__, Throwable nextInChain = null)
|
|
{
|
|
super(msg, file, line, nextInChain);
|
|
}
|
|
|
|
@nogc @safe pure nothrow this(string msg, Throwable nextInChain, string file = __FILE__, size_t line = __LINE__)
|
|
{
|
|
super(msg, file, line, nextInChain);
|
|
}
|
|
}
|
|
|
|
|
|
import core.internal.hash;
|