diff --git a/.ignore b/.ignore index 08aa99c..79fd401 100644 --- a/.ignore +++ b/.ignore @@ -1,2 +1,5 @@ .git -external +build +test +external/cglm +external/xxhash diff --git a/Test_Runner b/Test_Runner new file mode 100755 index 0000000..12893f9 Binary files /dev/null and b/Test_Runner differ diff --git a/aliases.d b/aliases.d index 72ec3d2..b054ca0 100644 --- a/aliases.d +++ b/aliases.d @@ -1,9 +1,23 @@ module dlib.aliases; -import core.memory; -import std.stdint; +// import core.memory; +//import std.stdint; import dlib.math; +enum string NO_IMPL = "Not yet implemented."; + +public import std.traits; +public import std.meta; + +version(WebAssembly) +{ + enum bool NativeTarget = false; +} +else +{ + enum bool NativeTarget = true; +} + debug { const BUILD_DEBUG = true; @@ -13,41 +27,41 @@ else const BUILD_DEBUG = false; } -alias i8 = byte; -alias i16 = short; -alias i32 = int; -alias i64 = long; +alias i8 = byte; +alias i16 = short; +alias i32 = int; +alias i64 = long; -alias u8 = ubyte; -alias u16 = ushort; -alias u32 = uint; -alias u64 = ulong; +alias u8 = ubyte; +alias u16 = ushort; +alias u32 = uint; +alias u64 = ulong; -alias f32 = float; -alias f64 = double; +alias f32 = float; +alias f64 = double; -alias b32 = uint; +alias b32 = uint; -alias intptr = i64; +alias intptr = i64; alias uintptr = u64; -alias usize = size_t; +alias usize = size_t; -alias Vec2 = Vector!(f32, 2); -alias Vec3 = Vector!(f32, 3); -alias Vec4 = Vector!(f32, 4); +alias Vec2 = Vector!(f32, 2); +alias Vec3 = Vector!(f32, 3); +alias Vec4 = Vector!(f32, 4); -alias DVec2 = Vector!(f64, 2); -alias DVec3 = Vector!(f64, 3); -alias DVec4 = Vector!(f64, 4); +alias DVec2 = Vector!(f64, 2); +alias DVec3 = Vector!(f64, 3); +alias DVec4 = Vector!(f64, 4); -alias IVec2 = Vector!(i32, 2); -alias IVec3 = Vector!(i32, 3); -alias IVec4 = Vector!(i32, 4); +alias IVec2 = Vector!(i32, 2); +alias IVec3 = Vector!(i32, 3); +alias IVec4 = Vector!(i32, 4); -alias I8Vec2 = Vector!(i8, 2); -alias I8Vec3 = Vector!(i8, 3); -alias I8Vec4 = Vector!(i8, 4); +alias I8Vec2 = Vector!(i8, 2); +alias I8Vec3 = Vector!(i8, 3); +alias I8Vec4 = Vector!(i8, 4); alias I16Vec2 = Vector!(i16, 2); alias I16Vec3 = Vector!(i16, 3); @@ -61,13 +75,13 @@ alias I64Vec2 = Vector!(i64, 2); alias I64Vec3 = Vector!(i64, 3); alias I64Vec4 = Vector!(i64, 4); -alias UVec2 = Vector!(u32, 2); -alias UVec3 = Vector!(u32, 3); -alias UVec4 = Vector!(u32, 4); +alias UVec2 = Vector!(u32, 2); +alias UVec3 = Vector!(u32, 3); +alias UVec4 = Vector!(u32, 4); -alias U8Vec2 = Vector!(u8, 2); -alias U8Vec3 = Vector!(u8, 3); -alias U8Vec4 = Vector!(u8, 4); +alias U8Vec2 = Vector!(u8, 2); +alias U8Vec3 = Vector!(u8, 3); +alias U8Vec4 = Vector!(u8, 4); alias U16Vec2 = Vector!(u16, 2); alias U16Vec3 = Vector!(u16, 3); @@ -81,10 +95,345 @@ alias U64Vec2 = Vector!(u64, 2); alias U64Vec3 = Vector!(u64, 3); alias U64Vec4 = Vector!(u64, 4); -alias Mat2 = Matrix!(f32, 2); -alias Mat3 = Matrix!(f32, 3); -alias Mat4 = Matrix!(f32, 4); +alias Mat2 = Matrix!(f32, 2); +alias Mat3 = Matrix!(f32, 3); +alias Mat4 = Matrix!(f32, 4); -alias DMat2 = Matrix!(f64, 2); -alias DMat3 = Matrix!(f64, 3); -alias DMat4 = Matrix!(f64, 4); +alias DMat2 = Matrix!(f64, 2); +alias DMat3 = Matrix!(f64, 3); +alias DMat4 = Matrix!(f64, 4); + +/* + +enum bool isFloatingPoint(T) = __traits(isFloating, T) && is(T: real); +enum bool isSigned(T) = __traits(isArithmetic, T) && !__traits(isUnsigned, T) && is(T: real); + +template isIntegral(T) +{ + static if(!__traits(isIntegral, T)) + { + enum isIntegral = false; + } + else static if(is(T U == enum)) + { + enum isIntegral = isIntegral!U; + } + else + { + enum isIntegral = __traits(isZeroInit, T) && !is(immutable T == immutable bool) && !is(T == __vector); + } +} + +template isNumeric(T) +{ + static if (!__traits(isArithmetic, T)) + { + enum isNumeric = false; + } + else static if (__traits(isFloating, T)) + { + enum isNumeric = is(T : real); // Not __vector, imaginary, or complex. + } + else static if (is(T U == enum)) + { + enum isNumeric = isNumeric!U; + } + else + { + enum isNumeric = __traits(isZeroInit, T) // Not char, wchar, or dchar. + && !is(immutable T == immutable bool) && !is(T == __vector); + } +} + +enum RealFormat +{ + ieeeHalf, + ieeeSingle, + ieeeDouble, + ieeeExtended, // x87 80-bit real + ieeeExtended53, // x87 real rounded to precision of double. + ibmExtended, // IBM 128-bit extended + ieeeQuadruple, +} + +template floatTraits(T) +{ + // EXPMASK is a ushort mask to select the exponent portion (without sign) + // EXPSHIFT is the number of bits the exponent is left-shifted by in its ushort + // EXPBIAS is the exponent bias - 1 (exp == EXPBIAS yields ×2^-1). + // EXPPOS_SHORT is the index of the exponent when represented as a ushort array. + // SIGNPOS_BYTE is the index of the sign when represented as a ubyte array. + // RECIP_EPSILON is the value such that (smallest_subnormal) * RECIP_EPSILON == T.min_normal + enum Unqual!T RECIP_EPSILON = (1/T.epsilon); + static if (T.mant_dig == 24) + { + // Single precision float + enum ushort EXPMASK = 0x7F80; + enum ushort EXPSHIFT = 7; + enum ushort EXPBIAS = 0x3F00; + enum uint EXPMASK_INT = 0x7F80_0000; + enum uint MANTISSAMASK_INT = 0x007F_FFFF; + enum realFormat = RealFormat.ieeeSingle; + version (LittleEndian) + { + enum EXPPOS_SHORT = 1; + enum SIGNPOS_BYTE = 3; + } + else + { + enum EXPPOS_SHORT = 0; + enum SIGNPOS_BYTE = 0; + } + } + else static if (T.mant_dig == 53) + { + static if (T.sizeof == 8) + { + // Double precision float, or real == double + enum ushort EXPMASK = 0x7FF0; + enum ushort EXPSHIFT = 4; + enum ushort EXPBIAS = 0x3FE0; + enum uint EXPMASK_INT = 0x7FF0_0000; + enum uint MANTISSAMASK_INT = 0x000F_FFFF; // for the MSB only + enum ulong MANTISSAMASK_LONG = 0x000F_FFFF_FFFF_FFFF; + enum realFormat = RealFormat.ieeeDouble; + version (LittleEndian) + { + enum EXPPOS_SHORT = 3; + enum SIGNPOS_BYTE = 7; + } + else + { + enum EXPPOS_SHORT = 0; + enum SIGNPOS_BYTE = 0; + } + } + else static if (T.sizeof == 12) + { + // Intel extended real80 rounded to double + enum ushort EXPMASK = 0x7FFF; + enum ushort EXPSHIFT = 0; + enum ushort EXPBIAS = 0x3FFE; + enum realFormat = RealFormat.ieeeExtended53; + version (LittleEndian) + { + enum EXPPOS_SHORT = 4; + enum SIGNPOS_BYTE = 9; + } + else + { + enum EXPPOS_SHORT = 0; + enum SIGNPOS_BYTE = 0; + } + } + else + static assert(false, "No traits support for " ~ T.stringof); + } + else static if (T.mant_dig == 64) + { + // Intel extended real80 + enum ushort EXPMASK = 0x7FFF; + enum ushort EXPSHIFT = 0; + enum ushort EXPBIAS = 0x3FFE; + enum realFormat = RealFormat.ieeeExtended; + version (LittleEndian) + { + enum EXPPOS_SHORT = 4; + enum SIGNPOS_BYTE = 9; + } + else + { + enum EXPPOS_SHORT = 0; + enum SIGNPOS_BYTE = 0; + } + } + else static if (T.mant_dig == 113) + { + // Quadruple precision float + enum ushort EXPMASK = 0x7FFF; + enum ushort EXPSHIFT = 0; + enum ushort EXPBIAS = 0x3FFE; + enum realFormat = RealFormat.ieeeQuadruple; + version (LittleEndian) + { + enum EXPPOS_SHORT = 7; + enum SIGNPOS_BYTE = 15; + } + else + { + enum EXPPOS_SHORT = 0; + enum SIGNPOS_BYTE = 0; + } + } + else static if (T.mant_dig == 106) + { + // IBM Extended doubledouble + enum ushort EXPMASK = 0x7FF0; + enum ushort EXPSHIFT = 4; + enum realFormat = RealFormat.ibmExtended; + + // For IBM doubledouble the larger magnitude double comes first. + // It's really a double[2] and arrays don't index differently + // between little and big-endian targets. + enum DOUBLEPAIR_MSB = 0; + enum DOUBLEPAIR_LSB = 1; + + // The exponent/sign byte is for most significant part. + version (LittleEndian) + { + enum EXPPOS_SHORT = 3; + enum SIGNPOS_BYTE = 7; + } + else + { + enum EXPPOS_SHORT = 0; + enum SIGNPOS_BYTE = 0; + } + } + else + static assert(false, "No traits support for " ~ T.stringof); +} + +version (StdDdoc) +{ + template Unqual(T) + { + import core.internal.traits : CoreUnqual = Unqual; + alias Unqual = CoreUnqual!(T); + } +} +else +{ + import core.internal.traits : CoreUnqual = Unqual; + alias Unqual = CoreUnqual; +} + +// These apply to all floating-point types +version (LittleEndian) +{ + enum MANTISSA_LSB = 0; + enum MANTISSA_MSB = 1; +} +else +{ + enum MANTISSA_LSB = 1; + enum MANTISSA_MSB = 0; +} + + +bool isInfinity(X)(X x) @nogc @trusted pure nothrow +if (isFloatingPoint!(X)) +{ + alias F = floatTraits!(X); + static if (F.realFormat == RealFormat.ieeeSingle) + { + return ((*cast(uint *)&x) & 0x7FFF_FFFF) == 0x7F80_0000; + } + else static if (F.realFormat == RealFormat.ieeeDouble) + { + return ((*cast(ulong *)&x) & 0x7FFF_FFFF_FFFF_FFFF) + == 0x7FF0_0000_0000_0000; + } + else static if (F.realFormat == RealFormat.ieeeExtended || + F.realFormat == RealFormat.ieeeExtended53) + { + const ushort e = cast(ushort)(F.EXPMASK & (cast(ushort *)&x)[F.EXPPOS_SHORT]); + const ulong ps = *cast(ulong *)&x; + + // On Motorola 68K, infinity can have hidden bit = 1 or 0. On x86, it is always 1. + return e == F.EXPMASK && (ps & 0x7FFF_FFFF_FFFF_FFFF) == 0; + } + else static if (F.realFormat == RealFormat.ieeeQuadruple) + { + const long psLsb = (cast(long *)&x)[MANTISSA_LSB]; + const long psMsb = (cast(long *)&x)[MANTISSA_MSB]; + return (psLsb == 0) + && (psMsb & 0x7FFF_FFFF_FFFF_FFFF) == 0x7FFF_0000_0000_0000; + } + else + { + return (x < -X.max) || (X.max < x); + } +} + +bool isNaN(X)(X x) @nogc @trusted pure nothrow +if (isFloatingPoint!(X)) +{ + version (all) + { + return x != x; + } + else + { + alias F = floatTraits!(X); + static if (F.realFormat == RealFormat.ieeeSingle) + { + const uint p = *cast(uint *)&x; + // Sign bit (MSB) is irrelevant so mask it out. + // Next 8 bits should be all set. + // At least one bit among the least significant 23 bits should be set. + return (p & 0x7FFF_FFFF) > 0x7F80_0000; + } + else static if (F.realFormat == RealFormat.ieeeDouble) + { + const ulong p = *cast(ulong *)&x; + // Sign bit (MSB) is irrelevant so mask it out. + // Next 11 bits should be all set. + // At least one bit among the least significant 52 bits should be set. + return (p & 0x7FFF_FFFF_FFFF_FFFF) > 0x7FF0_0000_0000_0000; + } + else static if (F.realFormat == RealFormat.ieeeExtended || + F.realFormat == RealFormat.ieeeExtended53) + { + const ushort e = F.EXPMASK & (cast(ushort *)&x)[F.EXPPOS_SHORT]; + const ulong ps = *cast(ulong *)&x; + return e == F.EXPMASK && + ps & 0x7FFF_FFFF_FFFF_FFFF; // not infinity + } + else static if (F.realFormat == RealFormat.ieeeQuadruple) + { + const ushort e = F.EXPMASK & (cast(ushort *)&x)[F.EXPPOS_SHORT]; + const ulong psLsb = (cast(ulong *)&x)[MANTISSA_LSB]; + const ulong psMsb = (cast(ulong *)&x)[MANTISSA_MSB]; + return e == F.EXPMASK && + (psLsb | (psMsb& 0x0000_FFFF_FFFF_FFFF)) != 0; + } + else + { + return x != x; + } + } +} + +enum bool isInstanceOf(alias S, T) = is(T == S!Args, Args...); + +template isInstanceOf(alias S, alias T) +{ + enum impl(alias T : S!Args, Args...) = true; + enum impl(alias T) = false; + enum isInstanceOf = impl!T; +} + +enum isAssignable(Lhs, Rhs = Lhs) = isRvalueAssignable!(Lhs, Rhs) && isLvalueAssignable!(Lhs, Rhs); +enum isRvalueAssignable(Lhs, Rhs = Lhs) = __traits(compiles, { lvalueOf!Lhs = rvalueOf!Rhs; }); +enum isLvalueAssignable(Lhs, Rhs = Lhs) = __traits(compiles, { lvalueOf!Lhs = lvalueOf!Rhs; }); + +template isDynamicArray(T) +{ + static if (is(T == U[], U)) + enum bool isDynamicArray = true; + else static if (is(T U == enum)) + // BUG: isDynamicArray / isStaticArray considers enums + // with appropriate base types as dynamic/static arrays + // Retain old behaviour for now, see + // https://github.com/dlang/phobos/pull/7574 + enum bool isDynamicArray = isDynamicArray!U; + else + enum bool isDynamicArray = false; +} + +enum isStaticArray(T) = __traits(isStaticArray, T); + +enum isArray(T) = isStaticArray!T || isDynamicArray!T; + +*/ diff --git a/alloc.d b/alloc.d index b9c8df9..7665334 100644 --- a/alloc.d +++ b/alloc.d @@ -5,9 +5,14 @@ import dlib.math; import dlib.platform; import dlib.util; -import std.stdio; -import core.stdc.string : memset; -import core.memory; +version(WebAssembly) +{ + +} +else +{ + import core.memory : malloc = pureMalloc, realloc = pureRealloc, free = pureFree; +} static Scratch g_scratch; static u64 g_scratch_index; @@ -76,16 +81,16 @@ MFree(T)(T[] slice) T* Alloc(T)() { - void* mem = pureMalloc(T.sizeof); - memset(mem, 0, T.sizeof); + void* mem = malloc(T.sizeof); + MemSet(mem, 0, T.sizeof); return (cast(T*)mem); } T[] -Alloc(T)(u64 count) +Alloc(T)(usize count) { - void* mem = pureMalloc(T.sizeof * count); - memset(mem, 0, T.sizeof * count); + void* mem = malloc(T.sizeof * count); + MemSet(mem, 0, T.sizeof * count); return (cast(T*)mem)[0 .. count]; } @@ -93,7 +98,7 @@ T[] Alloc(T)(T[] target) { T[] arr = Alloc!(T)(target.length); - arr[] = target[]; + arr[] = target[]; return arr; } @@ -132,20 +137,20 @@ Alloc(T)(u64 count, T set) T[] Realloc(T)(T[] arr, u64 count) { - void* mem = pureRealloc(arr.ptr, T.sizeof * count); + void* mem = realloc(arr.ptr, T.sizeof * count); return (cast(T*)mem)[0 .. count]; } void Free(T)(T[] arr) { - pureFree(arr.ptr); + free(arr.ptr); } void Free(T)(T* ptr) { - pureFree(ptr); + free(ptr); } Arena @@ -257,8 +262,8 @@ T[] Alloc(T)(Arena* arena, u64 count) { void* mem = AllocAlign(arena, T.sizeof * count, DEFAULT_ALIGNMENT); - memset(mem, 0, T.sizeof * count); - return (cast(T*)mem)[0 .. count]; + MemSet(mem, 0, T.sizeof * count); + return (cast(T*)mem)[0 .. cast(usize)count]; } T[] @@ -305,7 +310,7 @@ T* Alloc(T)(Arena* arena) { void* mem = AllocAlign(arena, T.sizeof, DEFAULT_ALIGNMENT); - memset(mem, 0, T.sizeof); + MemSet(mem, 0, T.sizeof); return cast(T*)mem; }; @@ -413,7 +418,7 @@ ScratchAlloc(string target) } T[] -ScratchAlloc(T)(T[] target, u64 start, u64 len) +ScratchAlloc(T)(T[] target, usize start, usize len) { T[] arr = ScratchAlloc!(T)(len); arr[0 .. $] = target[start .. start+len]; @@ -421,7 +426,7 @@ ScratchAlloc(T)(T[] target, u64 start, u64 len) } string -ScratchAlloc(string target, u64 start, u64 len) +ScratchAlloc(string target, usize start, usize len) { u8[] str = ScratchAlloc!(u8)(len); str[0 .. $] = cast(u8[])target[start .. start+len]; diff --git a/assets.d b/assets.d index 12c3d43..5bf7164 100644 --- a/assets.d +++ b/assets.d @@ -1,46 +1,50 @@ module dlib.assets; -import dlibincludes : cgltf_result, - cgltf_memory_options, - cgltf_file_options, - cgltf_size, - cgltf_image, - cgltf_accessor, - cgltf_result_success, - cgltf_result_io_error, - cgltf_free, - cgltf_options, - cgltf_node, - cgltf_load_buffers, - cgltf_data, - cgltf_load_buffer_base64, - stbi_image_free, - stbi_load_from_memory, - cgltf_primitive_type_triangles, - cgltf_node_transform_world, - cgltf_float, - cgltf_mesh, - cgltf_parse, - cgltf_attribute_type_position, - cgltf_type_vec3, - cgltf_component_type_r_32f, - cgltf_attribute_type_normal, - cgltf_attribute_type_tangent, - cgltf_type_vec4, - cgltf_attribute_type_texcoord, - cgltf_type_vec2, - cgltf_component_type_r_8u, - cgltf_component_type_r_16u, - cgltf_attribute_type_color, - cgltf_component_type_r_32u; import dlib.aliases; + +static if(NativeTarget) +{ + +import dlibincludes : cgltf_result, + cgltf_memory_options, + cgltf_file_options, + cgltf_size, + cgltf_image, + cgltf_accessor, + cgltf_result_success, + cgltf_result_io_error, + cgltf_free, + cgltf_options, + cgltf_node, + cgltf_load_buffers, + cgltf_data, + cgltf_load_buffer_base64, + stbi_image_free, + stbi_load_from_memory, + cgltf_primitive_type_triangles, + cgltf_node_transform_world, + cgltf_float, + cgltf_mesh, + cgltf_parse, + cgltf_attribute_type_position, + cgltf_type_vec3, + cgltf_component_type_r_32f, + cgltf_attribute_type_normal, + cgltf_attribute_type_tangent, + cgltf_type_vec4, + cgltf_attribute_type_texcoord, + cgltf_type_vec2, + cgltf_component_type_r_8u, + cgltf_component_type_r_16u, + cgltf_attribute_type_color, + cgltf_component_type_r_32u; import dlib.util; import dlib.alloc; import dlib.math; +import std.format; import std.file; import std.path; -import std.format; import std.stdio; import core.stdc.string : strlen; import core.stdc.stdio : File = FILE, FOpen = fopen, FSeek = fseek, FTell = ftell, FClose = fclose, FRead = fread, SeekSet = SEEK_SET, SeekEnd = SEEK_END; @@ -144,14 +148,14 @@ struct Mesh __gshared string g_BASE_ASSETS_DIR; -static u32 + static u32 MagicValue(string str) { assert(str.length == 4, "Magic value must 4 characters"); return cast(u32)(cast(u32)(str[0] << 24) | cast(u32)(str[1] << 16) | cast(u32)(str[2] << 8) | cast(u32)(str[3] << 0)); } -void + void SetBaseAssetsDir(string dir) { if(!isDir(dir)) @@ -162,7 +166,7 @@ SetBaseAssetsDir(string dir) g_BASE_ASSETS_DIR = dir; } -void + void U32ColToVec4(u32 col, Vec4* vec) { if(!col) @@ -222,7 +226,7 @@ GLTFFreeCallback(cgltf_memory_options* memory_opts, cgltf_file_options* file_opt Free(data); } -ImageData + ImageData LoadImage(void* data, i32 size) { ImageData image; @@ -251,7 +255,7 @@ LoadImage(void* data, i32 size) return image; } -void + void UnloadImage(ImageData* image) { version(NO_STBI) {} @@ -261,7 +265,7 @@ UnloadImage(ImageData* image) } } -ImageData + ImageData LoadImage(cgltf_image* asset_image, string tex_path) { ImageData image; @@ -335,7 +339,7 @@ LoadImage(cgltf_image* asset_image, string tex_path) } } - PostTypeCheck: +PostTypeCheck: u8[] image_data; if(accepted) { @@ -359,7 +363,7 @@ LoadImage(cgltf_image* asset_image, string tex_path) return image; } -ModelData + ModelData LoadGLTF(Arena* arena, string file_name) { ModelData model; @@ -616,11 +620,11 @@ LoadGLTF(Arena* arena, string file_name) for(u64 k = 0; k < model.meshes[mesh_index].vtx.length; k += 1) { model.meshes[mesh_index].vtx[k].color = Vec4( - cast(f32)(buffer[k*3+0])/255.0, - cast(f32)(buffer[k*3+1])/255.0, - cast(f32)(buffer[k*3+2])/255.0, - 1.0 - ); + cast(f32)(buffer[k*3+0])/255.0, + cast(f32)(buffer[k*3+1])/255.0, + cast(f32)(buffer[k*3+2])/255.0, + 1.0 + ); } } else if(attr.component_type == cgltf_component_type_r_16u) @@ -629,11 +633,11 @@ LoadGLTF(Arena* arena, string file_name) for(u64 k = 0; k < model.meshes[mesh_index].vtx.length; k += 1) { model.meshes[mesh_index].vtx[k].color = Vec4( - cast(f32)(buffer[k*3+0])/65535.0, - cast(f32)(buffer[k*3+1])/65535.0, - cast(f32)(buffer[k*3+2])/65535.0, - 1.0 - ); + cast(f32)(buffer[k*3+0])/65535.0, + cast(f32)(buffer[k*3+1])/65535.0, + cast(f32)(buffer[k*3+2])/65535.0, + 1.0 + ); } } else if(attr.component_type == cgltf_component_type_r_32f) @@ -657,11 +661,11 @@ LoadGLTF(Arena* arena, string file_name) for(u64 k = 0; k < model.meshes[mesh_index].vtx.length; k += 1) { model.meshes[mesh_index].vtx[k].color = Vec4( - cast(f32)(buffer[k*4+0])/255.0, - cast(f32)(buffer[k*4+1])/255.0, - cast(f32)(buffer[k*4+2])/255.0, - cast(f32)(buffer[k*4+3])/255.0 - ); + cast(f32)(buffer[k*4+0])/255.0, + cast(f32)(buffer[k*4+1])/255.0, + cast(f32)(buffer[k*4+2])/255.0, + cast(f32)(buffer[k*4+3])/255.0 + ); } } else if(attr.component_type == cgltf_component_type_r_16u) @@ -670,11 +674,11 @@ LoadGLTF(Arena* arena, string file_name) for(u64 k = 0; k < model.meshes[mesh_index].vtx.length; k += 1) { model.meshes[mesh_index].vtx[k].color = Vec4( - cast(f32)(buffer[k*4+0])/65535.0, - cast(f32)(buffer[k*4+1])/65535.0, - cast(f32)(buffer[k*4+2])/65535.0, - cast(f32)(buffer[k*4+3])/65535.0 - ); + cast(f32)(buffer[k*4+0])/65535.0, + cast(f32)(buffer[k*4+1])/65535.0, + cast(f32)(buffer[k*4+2])/65535.0, + cast(f32)(buffer[k*4+3])/65535.0 + ); } } else if(attr.component_type == cgltf_component_type_r_32f) @@ -689,7 +693,7 @@ LoadGLTF(Arena* arena, string file_name) { Logf("Color component type [%s] not supported", attr.component_type); } - + } } } break; @@ -705,7 +709,7 @@ LoadGLTF(Arena* arena, string file_name) model.meshes[mesh_index].idx = model.idx_buf[idx_index .. idx_index+attr.count]; idx_index += attr.count; - + if(attr.component_type == cgltf_component_type_r_16u) { u16* buffer = GetGLTFBuffer!(u16)(attr); @@ -754,7 +758,7 @@ LoadGLTF(Arena* arena, string file_name) return model; } -void + void AddMeshVertices(cgltf_accessor* accessor, ModelData* model, u64 mesh_index, u64* vtx_count) { if(model.meshes[mesh_index].vtx == null) @@ -766,17 +770,17 @@ AddMeshVertices(cgltf_accessor* accessor, ModelData* model, u64 mesh_index, u64* } } -T* + T* GetGLTFBuffer(T)(cgltf_accessor* accessor) { return cast(T*)(accessor.buffer_view.buffer.data) + (accessor.buffer_view.offset/T.sizeof) + (accessor.offset/T.sizeof); } -static ImageData + static ImageData GenerateDefaultTexture(u32 x, u32 y) { ImageData data = { - w: x, + w: x, h: y, ch: 4, }; @@ -802,7 +806,7 @@ GenerateDefaultTexture(u32 x, u32 y) return data; } -static Material + static Material GenerateDefaultMaterial() { Material mat; @@ -813,4 +817,4 @@ GenerateDefaultMaterial() return mat; } -import std.exception; +} diff --git a/build.sh b/build.sh index b1d3b04..3d3bc5c 100755 --- a/build.sh +++ b/build.sh @@ -70,3 +70,16 @@ if ! [ -f "${build}/libcgltf.a" ]; then ar rcs $lib $obj rm $obj fi + + +# XXHASH +src="${script_dir}/external/xxhash/xxhash.c" +flags="-std=c99 -Wno-everything -Iexternal/xxhash -c -static" +obj="${build}/xxhash.o" +lib="${build}/libxxhash.a" + +if ! [ -f $lib ]; then + $c_compiler $flags $src $out $obj + ar rcs $lib $obj + rm $obj +fi diff --git a/dlibincludes.c b/dlibincludes.c index 5cbc29c..cbe9173 100644 --- a/dlibincludes.c +++ b/dlibincludes.c @@ -17,9 +17,17 @@ # include FT_GLYPH_H #endif -#ifndef NO_STBI +#if !defined(NO_STBI) && !defined(BUILD_WASM) # include "external/stb/stb_image.h" # include "external/stb/stb_image_write.h" +#endif + +#ifndef BUILD_WASM +# define CGLM_FORCE_DEPTH_ZERO_TO_ONE +# include "external/cglm/cglm.h" +#endif + +#ifndef NO_STBI # include "external/stb/stb_truetype.h" #endif @@ -27,8 +35,12 @@ # include "../VulkanRenderer/vulkan_includes.c" #endif -#define CGLM_FORCE_DEPTH_ZERO_TO_ONE -#include "external/cglm/cglm.h" #include "external/cgltf/cgltf.h" +#define PRINTF_SUPPORT_FLOAT +#include "external/printf/printf.c" + +#define XXH_NO_STDLIB 1 +#define XXH_STATIC_LINKING_ONLY 1 +#include "external/xxhash/xxhash.h" diff --git a/external/arsd-webassembly/arsd/color.d b/external/arsd-webassembly/arsd/color.d new file mode 100644 index 0000000..b453f72 --- /dev/null +++ b/external/arsd-webassembly/arsd/color.d @@ -0,0 +1,224 @@ +module arsd.color; + +char toHex(int a) { + if(a < 10) + return cast(char) (a + '0'); + else + return cast(char) (a - 10 + 'a'); +} + +struct Color { + int r, g, b, a; + this(int r, int g, int b, int a = 255) { + this.r = r; + this.g = g; + this.b = b; + this.a = a; + } + + void toTempString(char[] data) { + data[0] = '#'; + data[1] = toHex(r >> 4); + data[2] = toHex(r & 0x0f); + data[3] = toHex(g >> 4); + data[4] = toHex(g & 0x0f); + data[5] = toHex(b >> 4); + data[6] = toHex(b & 0x0f); + } + + static Color fromHsl(double h, double s, double l, double a = 255) { + h = h % 360; + + double C = (1 - absInternal(2 * l - 1)) * s; + + double hPrime = h / 60; + + double X = C * (1 - absInternal(hPrime % 2 - 1)); + + double r, g, b; + + if(h is double.nan) + r = g = b = 0; + else if (hPrime >= 0 && hPrime < 1) { + r = C; + g = X; + b = 0; + } else if (hPrime >= 1 && hPrime < 2) { + r = X; + g = C; + b = 0; + } else if (hPrime >= 2 && hPrime < 3) { + r = 0; + g = C; + b = X; + } else if (hPrime >= 3 && hPrime < 4) { + r = 0; + g = X; + b = C; + } else if (hPrime >= 4 && hPrime < 5) { + r = X; + g = 0; + b = C; + } else if (hPrime >= 5 && hPrime < 6) { + r = C; + g = 0; + b = X; + } + + double m = l - C / 2; + + r += m; + g += m; + b += m; + + return Color( + cast(int)(r * 255), + cast(int)(g * 255), + cast(int)(b * 255), + cast(int)(a)); + } + + static immutable Color white = Color(255, 255, 255, 255); + static immutable Color black = Color(0, 0, 0, 255); + static immutable Color red = Color(255, 0, 0, 255); + static immutable Color blue = Color(0, 0, 255, 255); + static immutable Color green = Color(0, 255, 0, 255); + static immutable Color yellow = Color(255, 255, 0, 255); + static immutable Color teal = Color(0, 255, 255, 255); + static immutable Color purple = Color(255, 0, 255, 255); + static immutable Color gray = Color(127, 127, 127, 255); + static immutable Color transparent = Color(0, 0, 0, 0); +} + +struct Point { + int x; + int y; + + pure const nothrow @safe: + + Point opBinary(string op)(in Point rhs) @nogc { + return Point(mixin("x" ~ op ~ "rhs.x"), mixin("y" ~ op ~ "rhs.y")); + } + + Point opBinary(string op)(int rhs) @nogc { + return Point(mixin("x" ~ op ~ "rhs"), mixin("y" ~ op ~ "rhs")); + } + +} + +struct Size { + int width; + int height; +} + + +nothrow @safe @nogc pure +double absInternal(double a) { return a < 0 ? -a : a; } + +struct Rectangle { + int left; /// + int top; /// + int right; /// + int bottom; /// + + pure const nothrow @safe @nogc: + + /// + this(int left, int top, int right, int bottom) { + this.left = left; + this.top = top; + this.right = right; + this.bottom = bottom; + } + + /// + this(in Point upperLeft, in Point lowerRight) { + this(upperLeft.x, upperLeft.y, lowerRight.x, lowerRight.y); + } + + /// + this(in Point upperLeft, in Size size) { + this(upperLeft.x, upperLeft.y, upperLeft.x + size.width, upperLeft.y + size.height); + } + + /// + @property Point upperLeft() { + return Point(left, top); + } + + /// + @property Point upperRight() { + return Point(right, top); + } + + /// + @property Point lowerLeft() { + return Point(left, bottom); + } + + /// + @property Point lowerRight() { + return Point(right, bottom); + } + + /// + @property Point center() { + return Point((right + left) / 2, (bottom + top) / 2); + } + + /// + @property Size size() { + return Size(width, height); + } + + /// + @property int width() { + return right - left; + } + + /// + @property int height() { + return bottom - top; + } + + /// Returns true if this rectangle entirely contains the other + bool contains(in Rectangle r) { + return contains(r.upperLeft) && contains(r.lowerRight); + } + + /// ditto + bool contains(in Point p) { + return (p.x >= left && p.x < right && p.y >= top && p.y < bottom); + } + + /// Returns true of the two rectangles at any point overlap + bool overlaps(in Rectangle r) { + // the -1 in here are because right and top are exclusive + return !((right-1) < r.left || (r.right-1) < left || (bottom-1) < r.top || (r.bottom-1) < top); + } + + /++ + Returns a Rectangle representing the intersection of this and the other given one. + + History: + Added July 1, 2021 + +/ + Rectangle intersectionOf(in Rectangle r) { + auto tmp = Rectangle(max(left, r.left), max(top, r.top), min(right, r.right), min(bottom, r.bottom)); + if(tmp.left >= tmp.right || tmp.top >= tmp.bottom) + tmp = Rectangle.init; + + return tmp; + } +} + +private int max(int a, int b) @nogc nothrow pure @safe { + return a >= b ? a : b; +} +private int min(int a, int b) @nogc nothrow pure @safe { + return a <= b ? a : b; +} + + +enum arsd_jsvar_compatible = "arsd_jsvar_compatible"; +class MemoryImage {} diff --git a/external/arsd-webassembly/arsd/simpleaudio.d b/external/arsd-webassembly/arsd/simpleaudio.d new file mode 100644 index 0000000..47099cc --- /dev/null +++ b/external/arsd-webassembly/arsd/simpleaudio.d @@ -0,0 +1,8 @@ +module arsd.simpleaudio; + +struct AudioOutputThread { + this(int) {} + void start() {} + void beep(int = 0) {} + void boop(int = 0) {} +} diff --git a/external/arsd-webassembly/arsd/simpledisplay.d b/external/arsd-webassembly/arsd/simpledisplay.d new file mode 100644 index 0000000..de2a941 --- /dev/null +++ b/external/arsd-webassembly/arsd/simpledisplay.d @@ -0,0 +1,315 @@ +module arsd.simpledisplay; + +public import arsd.color; + +import arsd.webassembly; + +//shared static this() { eval("hi there"); } + +// the js bridge is SO EXPENSIVE we have to minimize using it. + +class SimpleWindow { + this(int width, int height, string title = "D Application") { + this.width = width; + this.height = height; + + element = eval!NativeHandle(q{ + var s = document.getElementById("screen"); + var canvas = document.createElement("canvas"); + canvas.addEventListener("contextmenu", function(event) { event.preventDefault(); }); + canvas.setAttribute("width", $0); + canvas.setAttribute("height", $1); + canvas.setAttribute("title", $2); + s.appendChild(canvas); + return canvas; + }, width, height, title); + + canvasContext = eval!NativeHandle(q{ + return $0.getContext("2d"); + }, element); + } + + NativeHandle element; + NativeHandle canvasContext; + int width; + int height; + + void close() { + eval(q{ clearInterval($0); }, intervalId); + intervalId = 0; + } + + void delegate() onClosing; + + ScreenPainter draw() { + return ScreenPainter(this); + } + + int intervalId; + + void eventLoop(T...)(int timeout, T t) { + foreach(arg; t) { + static if(is(typeof(arg) : void delegate())) { + sdpy_timer = arg; + } else static if(is(typeof(arg) : void delegate(KeyEvent))) { + sdpy_key = arg; + } else static if(is(typeof(arg) : void delegate(MouseEvent))) { + sdpy_mouse = arg; + } else static assert(0, typeof(arg).stringof); + } + + if(timeout) + intervalId = eval!int(q{ + return setInterval(function(a) { exports.sdpy_timer_trigger(); }, $0); + }, timeout); + + eval(q{ + function translate(key) { + var k = 0; + switch(key) { + case "[": k = 1; break; + case "]": k = 2; break; + case "Left": case "ArrowLeft": k = 3; break; + case "Right": case "ArrowRight": k = 4; break; + case "Down": case "ArrowDown": k = 5; break; + case "Up": case "ArrowUp": k = 6; break; + case " ": k = 7; break; + // "Enter", "Esc" / "Escape" + default: k = 0; + } + return k; + } + document.body.addEventListener("keydown", function(event) { + exports.sdpy_key_trigger(1, translate(event.key)); + event.preventDefault(); + }, true); + document.body.addEventListener("keyup", function(event) { + exports.sdpy_key_trigger(0, translate(event.key)); + event.preventDefault(); + }, true); + $0.addEventListener("mousedown", function(event) { + exports.sdpy_mouse_trigger(1, event.button, event.offsetX, event.offsetY); + }, true); + $0.addEventListener("mouseup", function(event) { + exports.sdpy_mouse_trigger(0, event.button); + }, true); + }, element); + + } +} + +void delegate() sdpy_timer; +void delegate(KeyEvent) sdpy_key; +void delegate(MouseEvent) sdpy_mouse; + +export extern(C) void sdpy_timer_trigger() { + sdpy_timer(); +} +export extern(C) void sdpy_key_trigger(int pressed, int key) { + KeyEvent ke; + ke.pressed = pressed ? true : false; + ke.key = key; + if(sdpy_key) + sdpy_key(ke); +} +export extern(C) void sdpy_mouse_trigger(int pressed, int button, int x, int y) { + MouseEvent me; + me.type = pressed ? MouseEventType.buttonPressed : MouseEventType.buttonReleased; + switch(button) { + case 0: + me.button = MouseButton.left; + break; + case 1: + me.button = MouseButton.middle; + break; + case 2: + me.button = MouseButton.right; + break; + default: + } + me.x = x; + me.y = y; + if(sdpy_mouse) + sdpy_mouse(me); + +} + + +// push arguments in reverse order then push the command +enum canvasRender = q{ +}; + +struct ScreenPainter { + this(SimpleWindow window) { + // no need to arc here tbh + this.w = window.width; + this.h = window.height; + + this.element = NativeHandle(window.element.handle, false); + this.context = NativeHandle(window.canvasContext.handle, false); + } + @disable this(this); // for now... + NativeHandle element; + NativeHandle context; + + private int w, h; + + void clear() { + addCommand(1); + } + + void outlineColor(Color c) { + char[7] data; + c.toTempString(data[]); + addCommand(2, 7, data[0], data[1], data[2], data[3], data[4], data[5], data[6]); + return; + //context.properties.strokeStyle!string = cast(immutable)(data[]); + } + void fillColor(Color c) { + char[7] data; + c.toTempString(data[]); + addCommand(3, 7, data[0], data[1], data[2], data[3], data[4], data[5], data[6]); + return; + //context.properties.fillStyle!string = cast(immutable)(data[]); + } + + void drawPolygon(Point[] points) { + addCommand(8); + addCommand(cast(double) points.length); + foreach(point; points) { + push(cast(double) point.x); + push(cast(double) point.y); + } + } + + void drawRectangle(Point p, int w, int h) { + addCommand(4, p.x, p.y, w, h); + } + void drawRectangle(Point p, Size s) { + drawRectangle(p, s.width, s.height); + } + void drawText(Point p, in char[] txt, Point lowerRight = Point(0, 0), uint alignment = 0) { + // FIXME use the new system + addCommand(5, p.x, p.y + 16, txt.length); + foreach(c; txt) + push(cast(double) c); + return; + eval(q{ + var context = $0; + context.font = "18px sans-serif"; + context.strokeText($1, $2, $3 + 16); + }, context, txt, p.x, p.y); + } + + void drawCircle(Point upperLeft, int diameter) { + addCommand(6, upperLeft.x + diameter / 2, upperLeft.y + diameter / 2, diameter / 2); + } + + void drawLine(Point p1, Point p2) { + drawLine(p1.x, p1.y, p2.x, p2.y); + } + + void drawLine(int x1, int y1, int x2, int y2) { + addCommand(7, x1, y1, x2, y2); + } + + private: + void addCommand(T...)(int cmd, T args) { + push(cmd); + foreach(arg; args) { + push(arg); + } + } + + // 50ish % on ronaroids total cpu without this + // with it, we at like 16% + static __gshared double[] commandStack; + size_t commandStackPosition; + + void push(T)(T t) { + if(commandStackPosition == commandStack.length) { + commandStack.length = commandStack.length + 1024; + commandStack.assumeUniqueReference(); + } + + commandStack[commandStackPosition++] = t; + } + + ~this() { + executeCanvasCommands(this.context.handle, this.commandStack.ptr, commandStackPosition); + } +} + +extern(C) void executeCanvasCommands(int handle, double* start, size_t len); + +struct KeyEvent { + int key; + bool pressed; +} + +enum MouseEventType : int { + motion = 0, /// The mouse moved inside the window + buttonPressed = 1, /// A mouse button was pressed or the wheel was spun + buttonReleased = 2, /// A mouse button was released +} + +struct MouseEvent { + MouseEventType type; + int x; + int y; + int dx; + int dy; + + MouseButton button; + int modifierState; +} + +enum MouseButton : int { + none = 0, + left = 1, /// + right = 2, /// + middle = 4, /// + wheelUp = 8, /// + wheelDown = 16, /// + backButton = 32, /// often found on the thumb and used for back in browsers + forwardButton = 64, /// often found on the thumb and used for forward in browsers +} + +enum TextAlignment : uint { + Left = 0, /// + Center = 1, /// + Right = 2, /// + + VerticalTop = 0, /// + VerticalCenter = 4, /// + VerticalBottom = 8, /// +} + +enum Key { + LeftBracket = 1, + RightBracket, + Left, + Right, + Down, + Up, + Space +} + +enum MouseCursor { cross } + +class OperatingSystemFont {} +enum UsingSimpledisplayX11 = false; +enum SimpledisplayTimerAvailable = false; + + +class Sprite{} + +enum bool OpenGlEnabled = false; + +alias ScreenPainterImplementation = ScreenPainter; + +mixin template ExperimentalTextComponent() { + class TextLayout { + + } +} diff --git a/external/arsd-webassembly/arsd/webassembly.d b/external/arsd-webassembly/arsd/webassembly.d new file mode 100644 index 0000000..6bbfb41 --- /dev/null +++ b/external/arsd-webassembly/arsd/webassembly.d @@ -0,0 +1,178 @@ +/+ + This is the D interface to my webassembly javascript bridge. ++/ +module arsd.webassembly; + +struct AcquireArgument { + int type; + const(void)* ptr; + int length; +} + +// the basic bridge functions defined in webassembly-core.js { + +@trusted @nogc pure nothrow +{ + extern(C) void retain(int); + extern(C) void release(int); + extern(C) int acquire(int returnType, string callingModuleName, string code, AcquireArgument[] arguments); + extern(C) void abort(); + extern(C) int monotimeNow(); +} + + +// } + +export extern(C) int invoke_d_array_delegate(size_t ptr, size_t funcptr, ubyte[] arg) { + void delegate(in ubyte[] arr) dg; + + dg.ptr = cast(void*) ptr; + dg.funcptr = cast(typeof(dg.funcptr)) funcptr; + + dg(arg); + return 0; + +}; + +/++ + Evaluates the given code in Javascript. The arguments are available in JS as $0, $1, $2, .... + The `this` object in the evaluated code is set to an object representing the D module that + you can store some stuff in across calls without having to hit the global namespace. + + Note that if you want to return a value from javascript, you MUST use the return keyword + in the script string. + + Wrong: `eval!NativeHandle("document");` + + Right: `eval!NativeHandle("return document");` ++/ +template eval(T = void) { + T eval(Args...)(string code, Args args, string callingModuleName = __MODULE__) @trusted @nogc pure { + AcquireArgument[Args.length] aa; + foreach(idx, ref arg; args) { + // FIXME: some other type for unsigned.... + static if(is(typeof(arg) : const int)) { + aa[idx].type = 0; + aa[idx].ptr = cast(void*) arg; + aa[idx].length = arg.sizeof; + } else static if(is(immutable typeof(arg) == immutable string)) { + aa[idx].type = 1; + aa[idx].ptr = arg.ptr; + aa[idx].length = arg.length; + } else static if(is(immutable typeof(arg) == immutable NativeHandle)) { + aa[idx].type = 2; + aa[idx].ptr = cast(void*) arg.handle; + aa[idx].length = NativeHandle.sizeof; + } else static if(is(typeof(arg) : const float)) { + aa[idx].type = 3; + aa[idx].ptr = cast(void*) &arg; + aa[idx].length = arg.sizeof; + } else static if(is(immutable typeof(arg) == immutable ubyte[])) { + aa[idx].type = 4; + aa[idx].ptr = arg.ptr; + aa[idx].length = arg.length; + /* + } else static if(is(typeof(arg) == delegate)) { + aa[idx].type = 5; + aa[idx].ptr = cast(void*) &arg; + aa[idx].length = arg.sizeof; + */ + } else { + static assert(0); + } + } + static if(is(T == void)) + acquire(0, callingModuleName, code, aa[]); + else static if(is(T == int)) + return acquire(1, callingModuleName, code, aa[]); + else static if(is(T == float)) + return *cast(float*) cast(void*) acquire(2, callingModuleName, code, aa[]); + else static if(is(T == NativeHandle)) + return NativeHandle(acquire(3, callingModuleName, code, aa[])); + else static if(is(T == string)) { + auto ptr = cast(int*) acquire(7, callingModuleName, code, aa[]); + auto len = *ptr; + ptr++; + return (cast(immutable(char)*) ptr)[0 .. len]; + } + else static assert(0); + } +} + +// and do some opDispatch on the native things to call their methods and it should look p cool + +struct NativeHandle { + @trusted @nogc pure: + + int handle; + bool arc; + this(int handle, bool arc = true) { + this.handle = handle; + this.arc = arc; + } + + this(this) { + if(arc) retain(handle); + } + + ~this() { + if(arc) release(handle); + } + + // never store these, they don't affect the refcount + PropertiesHelper properties() { + return PropertiesHelper(handle); + } + + // never store these, they don't affect the refcount + MethodsHelper methods() { + return MethodsHelper(handle); + + } +} + +struct MethodsHelper { + @trusted @nogc pure: + @disable this(); + @disable this(this); + + int handle; + private this(int handle) { this.handle = handle; } + + template opDispatch(string name) { + template opDispatch(T = NativeHandle) + { + T opDispatch(Args...)(Args args, string callingModuleName = __MODULE__) @trusted @nogc pure + { + return eval!T(q{ + return $0[$1].apply($0, Array.prototype.slice.call(arguments, 2)); + }, NativeHandle(this.handle, false), name, args, callingModuleName); + } + } + } + +} +struct PropertiesHelper { + @trusted @nogc pure: + @disable this(); + @disable this(this); + + int handle; + private this(int handle) { this.handle = handle; } + + template opDispatch(string name) { + template opDispatch(T = NativeHandle) { + T opDispatch() { + return eval!T(q{ + return $0[$1]; + }, NativeHandle(this.handle, false), name); + } + + void opDispatch(T value) { + return eval!void(q{ + return $0[$1] = $2; + }, NativeHandle(this.handle, false), name, value); + } + } + } +} diff --git a/external/arsd-webassembly/core/arsd/aa.d b/external/arsd-webassembly/core/arsd/aa.d new file mode 100644 index 0000000..aa7cff3 --- /dev/null +++ b/external/arsd-webassembly/core/arsd/aa.d @@ -0,0 +1,778 @@ +/** + * Implementation of associative arrays. + * + * Copyright: Copyright Digital Mars 2000 - 2015. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Martin Nowak + * Source: $(DRUNTIMESRC rt/_aaA.d) + */ +module core.arsd.aa; + +/// AA version for debuggers, bump whenever changing the layout +extern (C) immutable int _aaVersion = 1; + +import core.internal.hash; +import core.arsd.memory_allocation; + +uint min(uint a, uint b) { return a < b ? a : b; } +uint max(uint a, uint b) { return a > b ? a : b; } + +// grow threshold +private enum GROW_NUM = 4; +private enum GROW_DEN = 5; +// shrink threshold +private enum SHRINK_NUM = 1; +private enum SHRINK_DEN = 8; +// grow factor +private enum GROW_FAC = 4; +// growing the AA doubles it's size, so the shrink threshold must be +// smaller than half the grow threshold to have a hysteresis +static assert(GROW_FAC * SHRINK_NUM * GROW_DEN < GROW_NUM * SHRINK_DEN); +// initial load factor (for literals), mean of both thresholds +private enum INIT_NUM = (GROW_DEN * SHRINK_NUM + GROW_NUM * SHRINK_DEN) / 2; +private enum INIT_DEN = SHRINK_DEN * GROW_DEN; + +private enum INIT_NUM_BUCKETS = 8; +// magic hash constants to distinguish empty, deleted, and filled buckets +private enum HASH_EMPTY = 0; +private enum HASH_DELETED = 0x1; +private enum HASH_FILLED_MARK = size_t(1) << 8 * size_t.sizeof - 1; + +// The compiler uses `void*` for its prototypes. +// Don't wrap in a struct to maintain ABI compatibility. +alias AA = Impl*; + +private bool empty(scope const AA impl) pure nothrow @nogc +{ + return impl is null || !impl.length; +} + +private struct Impl +{ +private: + this(scope const TypeInfo_AssociativeArray ti, size_t sz = INIT_NUM_BUCKETS) + { + keysz = cast(uint) ti.key.size; + valsz = cast(uint) ti.value.size; + buckets = allocBuckets(sz); + firstUsed = cast(uint) buckets.length; + valoff = cast(uint) talign(keysz, ti.value.talign); + + import core.arsd.objectutils : hasPostblit; + + if (hasPostblit(cast()ti.key)) + flags |= Flags.keyHasPostblit; + if ((ti.key.flags | ti.value.flags) & 1) + flags |= Flags.hasPointers; + + entryTI = fakeEntryTI(this, ti.key, ti.value); + } + + Bucket[] buckets; + uint used; + uint deleted; + TypeInfo_Struct entryTI; + uint firstUsed; + immutable uint keysz; + immutable uint valsz; + immutable uint valoff; + Flags flags; + + enum Flags : ubyte + { + none = 0x0, + keyHasPostblit = 0x1, + hasPointers = 0x2, + } + + @property size_t length() const pure nothrow @nogc + { + assert(used >= deleted); + return used - deleted; + } + + @property size_t dim() const pure nothrow @nogc @safe + { + return buckets.length; + } + + @property size_t mask() const pure nothrow @nogc + { + return dim - 1; + } + + // find the first slot to insert a value with hash + inout(Bucket)* findSlotInsert(size_t hash) inout pure nothrow @nogc + { + for (size_t i = hash & mask, j = 1;; ++j) + { + if (!buckets[i].filled) + return &buckets[i]; + i = (i + j) & mask; + } + } + + // lookup a key + inout(Bucket)* findSlotLookup(size_t hash, scope const void* pkey, scope const TypeInfo keyti) inout + { + for (size_t i = hash & mask, j = 1;; ++j) + { + if (buckets[i].hash == hash && keyti.equals(pkey, buckets[i].entry)) + return &buckets[i]; + else if (buckets[i].empty) + return null; + i = (i + j) & mask; + } + } + + void grow(scope const TypeInfo keyti) + { + // If there are so many deleted entries, that growing would push us + // below the shrink threshold, we just purge deleted entries instead. + if (length * SHRINK_DEN < GROW_FAC * dim * SHRINK_NUM) + resize(dim); + else + resize(GROW_FAC * dim); + } + + void shrink(scope const TypeInfo keyti) + { + if (dim > INIT_NUM_BUCKETS) + resize(dim / GROW_FAC); + } + + void resize(size_t ndim) + { + auto obuckets = buckets; + buckets = allocBuckets(ndim); + + foreach (ref b; obuckets[firstUsed .. $]) + if (b.filled) + *findSlotInsert(b.hash) = b; + + firstUsed = 0; + used -= deleted; + deleted = 0; + free(cast(ubyte*)(obuckets.ptr)); // safe to free b/c impossible to reference + } + + void clear() pure nothrow + { + import core.stdc.string : memset; + // clear all data, but don't change bucket array length + memset(&buckets[firstUsed], 0, (buckets.length - firstUsed) * Bucket.sizeof); + deleted = used = 0; + firstUsed = cast(uint) dim; + } +} + +//============================================================================== +// Bucket +//------------------------------------------------------------------------------ + +private struct Bucket +{ +private pure nothrow @nogc: + size_t hash; + void* entry; + + @property bool empty() const + { + return hash == HASH_EMPTY; + } + + @property bool deleted() const + { + return hash == HASH_DELETED; + } + + @property bool filled() const @safe + { + return cast(ptrdiff_t) hash < 0; + } +} + +Bucket[] allocBuckets(size_t dim) @trusted +{ + enum attr = 0b0001_0000; //enum attr = GC.BlkAttr.NO_INTERIOR; + immutable sz = dim * Bucket.sizeof; + return (cast(Bucket*) calloc(sz, attr))[0 .. dim]; +} + +//============================================================================== +// Entry +//------------------------------------------------------------------------------ + +private void* allocEntry(scope const Impl* aa, scope const void* pkey) +{ + immutable akeysz = aa.valoff; + void* res = void; + if(aa.entryTI) + res = _d_newitemU(aa.entryTI); + else + res = malloc(akeysz + aa.valsz).ptr; + + memcpy(res, pkey, aa.keysz); // copy key + memset(res + akeysz, 0, aa.valsz); // zero value + + return res; +} + +package void entryDtor(void* p, const TypeInfo_Struct sti) +{ + // key and value type info stored after the TypeInfo_Struct by tiEntry() + auto sizeti = __traits(classInstanceSize, TypeInfo_Struct); + auto extra = cast(const(TypeInfo)*)(cast(void*) sti + sizeti); + extra[0].destroy(p); + extra[1].destroy(p + talign(extra[0].size, extra[1].talign)); +} + +private bool hasDtor(const TypeInfo ti) pure nothrow +{ + + if (typeid(ti) is typeid(TypeInfo_Struct)) + if ((cast(TypeInfo_Struct) cast(void*) ti).xdtor) + return true; + if (typeid(ti) is typeid(TypeInfo_StaticArray)) + return hasDtor(cast()ti.next); + + return false; +} + +// build type info for Entry with additional key and value fields +TypeInfo_Struct fakeEntryTI(ref Impl aa, const TypeInfo keyti, const TypeInfo valti) +{ + import core.arsd.objectutils; + //Same as unqualify + auto kti = unqualify(keyti); + auto vti = unqualify(valti); + + + bool entryHasDtor = hasDtor(kti) || hasDtor(vti); + if (!entryHasDtor) + return null; + + // save kti and vti after type info for struct + enum sizeti = __traits(classInstanceSize, TypeInfo_Struct); + void* p = malloc(sizeti + (2) * (void*).sizeof).ptr; + + memcpy(p, __traits(initSymbol, TypeInfo_Struct).ptr, sizeti); + + auto ti = cast(TypeInfo_Struct) p; + auto extra = cast(TypeInfo*)(p + sizeti); + extra[0] = cast() kti; + extra[1] = cast() vti; + + static immutable tiMangledName = "S2rt3aaA__T5EntryZ"; + ti.name = tiMangledName; + + + // we don't expect the Entry objects to be used outside of this module, so we have control + // over the non-usage of the callback methods and other entries and can keep these null + // xtoHash, xopEquals, xopCmp, xtoString and xpostblit + immutable entrySize = aa.valoff + aa.valsz; + ti.m_init = (cast(ubyte*) null)[0 .. entrySize]; // init length, but not ptr + + if (entryHasDtor) + { + // xdtor needs to be built from the dtors of key and value for the GC + ti.xdtorti = &entryDtor; + ti.m_flags |= TypeInfo_Struct.StructFlags.isDynamicType; + } + + ti.align_ = cast(uint) max(kti.talign, vti.talign); + + return ti; +} + + +//============================================================================== +// Helper functions +//------------------------------------------------------------------------------ + +private size_t talign(size_t tsize, size_t algn) @safe pure nothrow @nogc +{ + immutable mask = algn - 1; + assert(!(mask & algn)); + return (tsize + mask) & ~mask; +} + +// mix hash to "fix" bad hash functions +private size_t mix(size_t h) @safe pure nothrow @nogc +{ + // final mix function of MurmurHash2 + enum m = 0x5bd1e995; + h ^= h >> 13; + h *= m; + h ^= h >> 15; + return h; +} + +private size_t calcHash(scope const void* pkey, scope const TypeInfo keyti) nothrow +{ + immutable hash = keyti.getHash(pkey); + // highest bit is set to distinguish empty/deleted from filled buckets + return mix(hash) | HASH_FILLED_MARK; +} + +private size_t nextpow2(const size_t n) pure nothrow @nogc +{ + import core.bitop : bsr; + + if (!n) + return 1; + + const isPowerOf2 = !((n - 1) & n); + return 1 << (bsr(n) + !isPowerOf2); +} + + +//============================================================================== +// API Implementation +//------------------------------------------------------------------------------ + +/** Allocate associative array data. + * Called for `new SomeAA` expression. + * Params: + * ti = TypeInfo for the associative array + * Returns: + * A new associative array. + */ +extern (C) Impl* _aaNew(const TypeInfo_AssociativeArray ti) +{ + return new Impl(ti); +} + +/// Determine number of entries in associative array. +extern (C) size_t _aaLen(scope const AA aa) pure nothrow @nogc +{ + return aa ? aa.length : 0; +} + +/****************************** + * Lookup *pkey in aa. + * Called only from implementation of (aa[key]) expressions when value is mutable. + * Params: + * paa = associative array opaque pointer + * ti = TypeInfo for the associative array + * valsz = ignored + * pkey = pointer to the key value + * Returns: + * if key was in the aa, a mutable pointer to the existing value. + * If key was not in the aa, a mutable pointer to newly inserted value which + * is set to all zeros + */ +extern (C) void* _aaGetY(scope ubyte** paa, const TypeInfo_AssociativeArray ti, + const size_t valsz, scope const void* pkey) +{ + bool found; + return _aaGetX(paa, ti, valsz, pkey, found); +} + +/****************************** + * Lookup *pkey in aa. + * Called only from implementation of require + * Params: + * paa = associative array opaque pointer + * ti = TypeInfo for the associative array + * valsz = ignored + * pkey = pointer to the key value + * found = true if the value was found + * Returns: + * if key was in the aa, a mutable pointer to the existing value. + * If key was not in the aa, a mutable pointer to newly inserted value which + * is set to all zeros + */ +extern (C) void* _aaGetX(scope ubyte** paa, const TypeInfo_AssociativeArray ti, + const size_t valsz, scope const void* pkey, out bool found) +{ + + // lazily alloc implementation + AA aa = *cast(AA*)paa; + if (aa is null) + { + aa = new Impl(ti); + *cast(AA*)paa = aa; + } + + // get hash and bucket for key + immutable hash = calcHash(pkey, ti.key); + + // found a value => return it + if (auto p = aa.findSlotLookup(hash, pkey, ti.key)) + { + found = true; + return p.entry + aa.valoff; + } + + auto p = aa.findSlotInsert(hash); + if (p.deleted) + --aa.deleted; + // check load factor and possibly grow + else if (++aa.used * GROW_DEN > aa.dim * GROW_NUM) + { + aa.grow(ti.key); + p = aa.findSlotInsert(hash); + assert(p.empty); + } + + // update search cache and allocate entry + aa.firstUsed = min(aa.firstUsed, cast(uint)(p - aa.buckets.ptr)); + p.hash = hash; + p.entry = allocEntry(aa, pkey); + // postblit for key + if (aa.flags & Impl.Flags.keyHasPostblit) + { + import core.arsd.objectutils; + __doPostblit(p.entry, aa.keysz, unqualify(ti.key)); + } + // return pointer to value + return p.entry + aa.valoff; +} + +/****************************** + * Lookup *pkey in aa. + * Called only from implementation of (aa[key]) expressions when value is not mutable. + * Params: + * aa = associative array opaque pointer + * keyti = TypeInfo for the key + * valsz = ignored + * pkey = pointer to the key value + * Returns: + * pointer to value if present, null otherwise + */ +extern (C) inout(void)* _aaGetRvalueX(inout ubyte** aa, scope const TypeInfo keyti, const size_t valsz, + scope const void* pkey) +{ + return _aaInX(aa, keyti, pkey); +} + +/****************************** + * Lookup *pkey in aa. + * Called only from implementation of (key in aa) expressions. + * Params: + * aa = associative array opaque pointer + * keyti = TypeInfo for the key + * pkey = pointer to the key value + * Returns: + * pointer to value if present, null otherwise + */ +extern (C) inout(void)* _aaInX(inout ubyte** _aa, scope const TypeInfo keyti, scope const void* pkey) +{ + import std.stdio; + AA aa = cast(AA)_aa; + if (aa.empty) + return null; + + immutable hash = calcHash(pkey, keyti); + if (auto p = aa.findSlotLookup(hash, pkey, keyti)) + return cast(inout)(p.entry + aa.valoff); + return null; +} + +/// Delete entry scope const AA, return true if it was present +extern (C) bool _aaDelX(ubyte* _aa, scope const TypeInfo keyti, scope const void* pkey) +{ + AA aa = cast(AA)_aa; + if (aa.empty) + return false; + immutable hash = calcHash(pkey, keyti); + if (auto p = aa.findSlotLookup(hash, pkey, keyti)) + { + // clear entry + p.hash = HASH_DELETED; + p.entry = null; + + ++aa.deleted; + // `shrink` reallocates, and allocating from a finalizer leads to + // InvalidMemoryError: https://issues.dlang.org/show_bug.cgi?id=21442 + if (aa.length * SHRINK_DEN < aa.dim * SHRINK_NUM) // && !GC.inFinalizer() no GC so never in finalizer + aa.shrink(keyti); + + return true; + } + return false; +} + +/// Remove all elements from AA. +extern (C) void _aaClear(ubyte* _aa) pure nothrow +{ + AA aa = cast(AA)_aa; + if (!aa.empty) + { + aa.clear(); + } +} + +/// Rehash AA +extern (C) void* _aaRehash(ubyte** _paa, scope const TypeInfo keyti) +{ + AA* paa = cast(AA*)_paa; + AA aa = *paa; + if (!aa.empty) + aa.resize(nextpow2(INIT_DEN * aa.length / INIT_NUM)); + return aa; +} + +/// Return a GC allocated array of all values +extern (C) inout(void[]) _aaValues(inout ubyte* _aa, const size_t keysz, const size_t valsz, + const TypeInfo tiValueArray) +{ + AA aa = cast(AA)_aa; + if (aa.empty) + return null; + + auto res = _d_newarrayU(tiValueArray, aa.length).ptr; + auto pval = res; + + immutable off = aa.valoff; + foreach (b; aa.buckets[aa.firstUsed .. $]) + { + if (!b.filled) + continue; + pval[0 .. valsz] = b.entry[off .. valsz + off]; + pval += valsz; + } + // postblit is done in object.values + return (cast(inout(void)*) res)[0 .. aa.length]; // fake length, return number of elements +} + +/// Return a GC allocated array of all keys +extern (C) inout(void[]) _aaKeys(inout ubyte* _aa, const size_t keysz, const TypeInfo tiKeyArray) +{ + AA aa = cast(AA)_aa; + if (aa.empty) + return null; + + auto res = _d_newarrayU(tiKeyArray, aa.length).ptr; + auto pkey = res; + + foreach (b; aa.buckets[aa.firstUsed .. $]) + { + if (!b.filled) + continue; + pkey[0 .. keysz] = b.entry[0 .. keysz]; + pkey += keysz; + } + // postblit is done in object.keys + return (cast(inout(void)*) res)[0 .. aa.length]; // fake length, return number of elements +} + +// opApply callbacks are extern(D) +extern (D) alias dg_t = int delegate(void*); +extern (D) alias dg2_t = int delegate(void*, void*); + +/// foreach opApply over all values +extern (C) int _aaApply(ubyte* _aa, const size_t keysz, dg_t dg) +{ + AA aa = cast(AA)_aa; + if (aa.empty) + return 0; + + immutable off = aa.valoff; + foreach (b; aa.buckets) + { + if (!b.filled) + continue; + if (auto res = dg(b.entry + off)) + return res; + } + return 0; +} + +/// foreach opApply over all key/value pairs +extern (C) int _aaApply2(ubyte* _aa, const size_t keysz, dg2_t dg) +{ + AA aa = cast(AA)_aa; + if (aa.empty) + return 0; + + immutable off = aa.valoff; + foreach (b; aa.buckets) + { + if (!b.filled) + continue; + if (auto res = dg(b.entry, b.entry + off)) + return res; + } + return 0; +} + +/** Construct an associative array of type ti from corresponding keys and values. + * Called for an AA literal `[k1:v1, k2:v2]`. + * Params: + * ti = TypeInfo for the associative array + * keys = array of keys + * vals = array of values + * Returns: + * A new associative array opaque pointer, or null if `keys` is empty. + */ +extern (C) ubyte* _d_assocarrayliteralTX(const TypeInfo_AssociativeArray ti, void[] keys, + void[] vals) +{ + assert(keys.length == vals.length); + + immutable keysz = ti.key.size; + immutable valsz = ti.value.size; + immutable length = keys.length; + + if (!length) + return null; + + auto aa = new Impl(ti, nextpow2(INIT_DEN * length / INIT_NUM)); + + void* pkey = keys.ptr; + void* pval = vals.ptr; + immutable off = aa.valoff; + uint actualLength = 0; + foreach (_; 0 .. length) + { + immutable hash = calcHash(pkey, ti.key); + auto p = aa.findSlotLookup(hash, pkey, ti.key); + if (p is null) + { + p = aa.findSlotInsert(hash); + p.hash = hash; + p.entry = allocEntry(aa, pkey); // move key, no postblit + aa.firstUsed = min(aa.firstUsed, cast(uint)(p - aa.buckets.ptr)); + actualLength++; + } + else if (aa.entryTI && hasDtor(ti.value)) + { + // destroy existing value before overwriting it + ti.value.destroy(p.entry + off); + } + // set hash and blit value + auto pdst = p.entry + off; + pdst[0 .. valsz] = pval[0 .. valsz]; // move value, no postblit + + pkey += keysz; + pval += valsz; + } + aa.used = actualLength; + return cast(ubyte*)aa; +} + +/// compares 2 AAs for equality +extern (C) int _aaEqual(scope const TypeInfo tiRaw, scope const ubyte* _aa1, scope const ubyte* _aa2) +{ + AA aa1 = cast(AA)_aa1; + AA aa2 = cast(AA)_aa2; + if (aa1 is aa2) + return true; + + immutable len = _aaLen(aa1); + if (len != _aaLen(aa2)) + return false; + + if (!len) // both empty + return true; + + import core.arsd.objectutils; + + auto uti = unqualify(tiRaw); //unqualify + auto ti = *cast(TypeInfo_AssociativeArray*)&uti; + // compare the entries + immutable off = aa1.valoff; + foreach (b1; aa1.buckets) + { + if (!b1.filled) + continue; + auto pb2 = aa2.findSlotLookup(b1.hash, b1.entry, ti.key); + if (pb2 is null || !ti.value.equals(b1.entry + off, pb2.entry + off)) + return false; + } + return true; +} + +/// compute a hash +extern (C) size_t _aaGetHash(scope const ubyte** _paa, scope const TypeInfo tiRaw) nothrow +{ + AA* paa = cast(AA*)_paa; + const AA aa = *paa; + + if (aa.empty) + return 0; + + + import core.arsd.objectutils; + auto uti = unqualify(tiRaw); + auto ti = *cast(TypeInfo_AssociativeArray*)&uti; + immutable off = aa.valoff; + auto keyHash = &ti.key.getHash; + auto valHash = &ti.value.getHash; + + size_t h; + foreach (b; aa.buckets) + { + // use addition here, so that hash is independent of element order + if (b.filled) + h += hashOf(valHash(b.entry + off), keyHash(b.entry)); + } + + return h; +} + +/** + * _aaRange implements a ForwardRange + */ +struct Range +{ + ubyte* impl; + size_t idx; + alias impl this; +} + +extern (C) pure nothrow @nogc @trusted +{ + Range _aaRange(return scope ubyte* _aa) + { + AA aa = cast(AA)_aa; + if (!aa) + return Range(); + + foreach (i; aa.firstUsed .. aa.dim) + { + if (aa.buckets[i].filled) + return Range(cast(ubyte*)aa, i); + } + return Range(cast(ubyte*)aa, aa.dim); + } + + bool _aaRangeEmpty(Range r) + { + return r.impl is null || r.idx >= (cast(Impl*)r.impl).dim; + } + + void* _aaRangeFrontKey(Range r) + { + assert(!_aaRangeEmpty(r)); + if (r.idx >= (cast(Impl*)r.impl).dim) + return null; + return (cast(Impl*)r.impl).buckets[r.idx].entry; + } + + void* _aaRangeFrontValue(Range r) + { + Impl* ri = cast(Impl*)r.impl; + assert(!_aaRangeEmpty(r)); + if (r.idx >= ri.dim) + return null; + + auto entry = ri.buckets[r.idx].entry; + return entry is null ? + null : + (() @trusted { return entry + ri.valoff; } ()); + } + + void _aaRangePopFront(ref Range r) + { + Impl* ri = (cast(Impl*)r.impl); + if (r.idx >= ri.dim) return; + for (++r.idx; r.idx < ri.dim; ++r.idx) + { + if (ri.buckets[r.idx].filled) + break; + } + } +} diff --git a/external/arsd-webassembly/core/arsd/memory_allocation.d b/external/arsd-webassembly/core/arsd/memory_allocation.d new file mode 100644 index 0000000..dd6ac92 --- /dev/null +++ b/external/arsd-webassembly/core/arsd/memory_allocation.d @@ -0,0 +1,260 @@ +module core.arsd.memory_allocation; + + + +private __gshared ubyte* nextFree; +private __gshared size_t memorySize; // in units of 64 KB pages + +// ldc defines this, used to find where wasm memory begins +private extern extern(C) ubyte __heap_base; +// ---unused--- -- stack grows down -- -- heap here -- +// this is less than __heap_base. memory map 0 ... __data_end ... __heap_base ... end of memory +private extern extern(C) ubyte __data_end; + +// llvm intrinsics { + /+ + mem must be 0 (it is index of memory thing) + delta is in 64 KB pages + return OLD size in 64 KB pages, or size_t.max if it failed. + +/ + pragma(LDC_intrinsic, "llvm.wasm.memory.grow.i32") + private int llvm_wasm_memory_grow(int mem, int delta); + + + // in 64 KB pages + pragma(LDC_intrinsic, "llvm.wasm.memory.size.i32") + private int llvm_wasm_memory_size(int mem); +// } +// debug +void printBlockDebugInfo(AllocatedBlock* block) { + import std.stdio; + writeln(block.blockSize, " ", block.flags, " ", block.checkChecksum() ? "OK" : "X", " "); + if(block.checkChecksum()) + writeln(cast(size_t)((cast(ubyte*) (block + 2)) + block.blockSize), " ", block.file, " : ", block.line); +} + + +// debug +export extern(C) void printBlockDebugInfo(void* ptr) { + if(ptr is null) { + foreach(block; AllocatedBlock) { + printBlockDebugInfo(block); + } + return; + } + + // otherwise assume it is a pointer returned from malloc + + auto block = (cast(AllocatedBlock*) ptr) - 1; + if(ptr is null) + block = cast(AllocatedBlock*) &__heap_base; + + printBlockDebugInfo(block); +} + + +export extern(C) ubyte* bridge_malloc(size_t sz) { + return malloc(sz).ptr; +} + + + +align(16) +struct AllocatedBlock { + enum Magic = 0x731a_9bec; + enum Flags { + inUse = 1, + unique = 2, + } + + size_t blockSize; + size_t flags; + size_t magic; + size_t checksum; + + size_t used; // the amount actually requested out of the block; used for assumeSafeAppend + + /* debug */ + string file; + size_t line; + + // note this struct MUST align each alloc on an 8 byte boundary or JS is gonna throw bullshit + + void populateChecksum() { + checksum = blockSize ^ magic; + } + + bool checkChecksum() const @nogc { + return magic == Magic && checksum == (blockSize ^ magic); + } + + ubyte[] dataSlice() return { + return ((cast(ubyte*) &this) + typeof(this).sizeof)[0 .. blockSize]; + } + + static int opApply(scope int delegate(AllocatedBlock*) dg) { + if(nextFree is null) + return 0; + ubyte* next = &__heap_base; + AllocatedBlock* block = cast(AllocatedBlock*) next; + while(block.checkChecksum()) { + if(auto result = dg(block)) + return result; + next += AllocatedBlock.sizeof; + next += block.blockSize; + block = cast(AllocatedBlock*) next; + } + + return 0; + } +} + +static assert(AllocatedBlock.sizeof % 16 == 0); + + + +private bool growMemoryIfNeeded(size_t sz) @trusted { + if(cast(size_t) nextFree + AllocatedBlock.sizeof + sz >= memorySize * 64*1024) { + if(llvm_wasm_memory_grow(0, 4) == size_t.max) + assert(0, "Out of memory"); // out of memory + + memorySize = llvm_wasm_memory_size(0); + + return true; + } + + return false; +} + +void free(ubyte* ptr) @nogc @trusted { + auto block = (cast(AllocatedBlock*) ptr) - 1; + if(!block.checkChecksum()) + assert(false, "Could not check block on free"); + + block.used = 0; + block.flags = 0; + + // last one + if(ptr + block.blockSize == nextFree) { + nextFree = cast(ubyte*) block; + assert(cast(size_t)nextFree % 16 == 0); + } +} + + +ubyte[] malloc(size_t sz, string file = __FILE__, size_t line = __LINE__) @trusted { + // lol bumping that pointer + if(nextFree is null) { + nextFree = &__heap_base; // seems to be 75312 + assert(cast(size_t)nextFree % 16 == 0); + memorySize = llvm_wasm_memory_size(0); + } + + while(growMemoryIfNeeded(sz)) {} + + auto base = cast(AllocatedBlock*) nextFree; + + auto blockSize = sz; + if(auto val = blockSize % 16) + blockSize += 16 - val; // does NOT include this metadata section! + + // debug list allocations + //import std.stdio; writeln(file, ":", line, " / ", sz, " +", blockSize); + + base.blockSize = blockSize; + base.flags = AllocatedBlock.Flags.inUse; + // these are just to make it more reliable to detect this header by backtracking through the pointer from a random array. + // otherwise it'd prolly follow the linked list from the beginning every time or make a free list or something. idk tbh. + base.magic = AllocatedBlock.Magic; + base.populateChecksum(); + + base.used = sz; + + // debug + base.file = file; + base.line = line; + + nextFree += AllocatedBlock.sizeof; + + auto ret = nextFree; + + nextFree += blockSize; + + //writeln(cast(size_t) nextFree); + //import std.stdio; writeln(cast(size_t) ret, " of ", sz, " rounded to ", blockSize); + //writeln(file, ":", line); + assert(cast(size_t) ret % 8 == 0); + + return ret[0 .. sz]; +} + + +ubyte[] calloc(size_t count, size_t size, string file = __FILE__, size_t line = __LINE__) @trusted +{ + auto ret = malloc(count*size,file,line); + ret[0..$] = 0; + return ret; +} + + +ubyte[] realloc(ubyte* ptr, size_t newSize, string file = __FILE__, size_t line = __LINE__) @trusted { + if(ptr is null) + return malloc(newSize, file, line); + + auto block = (cast(AllocatedBlock*) ptr) - 1; + if(!block.checkChecksum()) + assert(false, "Could not check block while realloc"); + + // block.populateChecksum(); + if(newSize <= block.blockSize) { + block.used = newSize; + return ptr[0 .. newSize]; + } else { + // FIXME: see if we can extend teh block into following free space before resorting to malloc + + if(ptr + block.blockSize == nextFree) { + while(growMemoryIfNeeded(newSize)) {} + + size_t blockSize = newSize; + if(const over = blockSize % 16) + blockSize+= 16 - over; + + block.blockSize = blockSize; + block.used = newSize; + block.populateChecksum(); + nextFree = ptr + block.blockSize; + assert(cast(size_t)nextFree % 16 == 0); + return ptr[0 .. newSize]; + } + + auto newThing = malloc(newSize); + newThing[0 .. block.used] = ptr[0 .. block.used]; + + if(block.flags & AllocatedBlock.Flags.unique) { + // if we do malloc, this means we are allowed to free the existing block + free(ptr); + } + + assert(cast(size_t) newThing.ptr % 16 == 0); + + return newThing; + } +} + +/** +* If the ptr isn't owned by the runtime, it will completely malloc the data (instead of realloc) +* and copy its old content. +*/ +ubyte[] realloc(ubyte[] ptr, size_t newSize, string file = __FILE__, size_t line = __LINE__) @trusted +{ + if(ptr is null) + return malloc(newSize, file, line); + auto block = (cast(AllocatedBlock*) ptr) - 1; + if(!block.checkChecksum()) + { + auto ret = malloc(newSize, file, line); + ret[0..ptr.length] = ptr[]; //Don't clear ptr memory as it can't be clear. + return ret; + } + else return realloc(ptr.ptr, newSize, file, line); +} \ No newline at end of file diff --git a/external/arsd-webassembly/core/arsd/objectutils.d b/external/arsd-webassembly/core/arsd/objectutils.d new file mode 100644 index 0000000..5c41a94 --- /dev/null +++ b/external/arsd-webassembly/core/arsd/objectutils.d @@ -0,0 +1,68 @@ +module core.arsd.objectutils; + +///Provides only __doPostblit and hasPostblit for making the code simpler. +size_t structTypeInfoSize(const TypeInfo ti) pure nothrow @nogc +{ + if (ti && typeid(ti) is typeid(TypeInfo_Struct)) // avoid a complete dynamic type cast + { + auto sti = cast(TypeInfo_Struct)cast(void*)ti; + if (sti.xdtor) + return size_t.sizeof; + } + return 0; +} +// strip const/immutable/shared/inout from type info +inout(TypeInfo) unqualify(return scope inout(TypeInfo) cti) pure nothrow @nogc +{ + TypeInfo ti = cast() cti; + while (ti) + { + // avoid dynamic type casts + auto tti = typeid(ti); + if (tti is typeid(TypeInfo_Const)) + ti = (cast(TypeInfo_Const)cast(void*)ti).base; + else if (tti is typeid(TypeInfo_Invariant)) + ti = (cast(TypeInfo_Invariant)cast(void*)ti).base; + else if (tti is typeid(TypeInfo_Shared)) + ti = (cast(TypeInfo_Shared)cast(void*)ti).base; + else if (tti is typeid(TypeInfo_Inout)) + ti = (cast(TypeInfo_Inout)cast(void*)ti).base; + else + break; + } + return ti; +} + +bool hasPostblit(in TypeInfo ti) nothrow pure +{ + return (&ti.postblit).funcptr !is &TypeInfo.postblit; +} + +void __doPostblit(void *ptr, size_t len, const TypeInfo ti) +{ + if (!hasPostblit(ti)) + return; + + if (auto tis = cast(TypeInfo_Struct)ti) + { + // this is a struct, check the xpostblit member + auto pblit = tis.xpostblit; + if (!pblit) + // postblit not specified, no point in looping. + return; + + // optimized for struct, call xpostblit directly for each element + immutable size = ti.size; + const eptr = ptr + len; + for (;ptr < eptr;ptr += size) + pblit(ptr); + } + else + { + // generic case, call the typeinfo's postblit function + immutable size = ti.size; + const eptr = ptr + len; + for (;ptr < eptr;ptr += size) + ti.postblit(ptr); + } +} diff --git a/external/arsd-webassembly/core/arsd/utf_decoding.d b/external/arsd-webassembly/core/arsd/utf_decoding.d new file mode 100644 index 0000000..cf14a5d --- /dev/null +++ b/external/arsd-webassembly/core/arsd/utf_decoding.d @@ -0,0 +1,444 @@ +module core.arsd.utf_decoding; + + +import core.internal.utf : decode, toUTF8; + +/**********************************************/ +/* 1 argument versions */ + +/** +Delegate type corresponding to transformed loop body + +The parameter is a pointer to the current `char`, `wchar` or `dchar` + +Returns: non-zero when a `break` statement is hit +*/ +extern (D) alias dg_t = int delegate(void* c); + +// Note: dg is extern(D), but _aApplycd() is extern(C) + +/** +Loop over a string while changing the UTF encoding + +There are 6 combinations of conversions between `char`, `wchar`, and `dchar`, +and 2 of each of those. + +The naming convention is as follows: + +_aApply{c,d,w}{c,d,w}{1,2} + +The first letter corresponds to the input string encoding, and the second letter corresponds to the target character type. + +- c = `char` +- w = `wchar` +- d = `dchar` + +The `1` variant only produces the character, the `2` variant also produces a loop index. + +Examples: +--- +void main() +{ + string str; + wtring wstr; + dstring dstr; + + foreach (dchar c; str) {} + // _aApplycd1 + + foreach (wchar c; dstr) {} + // _aApplydw1 + + foreach (i, wchar c; str) {} + // _aApplycw2 + + foreach (wchar w; wstr) {} + // no conversion +} +--- + +Params: + aa = input string + dg = foreach body transformed into a delegate, similar to `opApply` + +Returns: + non-zero when the loop was exited through a `break` +*/ +extern (C) int _aApplycd1(in char[] aa, dg_t dg) +{ + int result; + size_t len = aa.length; + + debug(apply) printf("_aApplycd1(), len = %d\n", len); + for (size_t i = 0; i < len; ) + { + dchar d = aa[i]; + if (d & 0x80) + d = decode(aa, i); + else + ++i; + result = dg(cast(void *)&d); + if (result) + break; + } + return result; +} + + + +/// ditto +extern (C) int _aApplywd1(in wchar[] aa, dg_t dg) +{ + int result; + size_t len = aa.length; + + debug(apply) printf("_aApplywd1(), len = %d\n", len); + for (size_t i = 0; i < len; ) + { + dchar d = aa[i]; + if (d >= 0xD800) + d = decode(aa, i); + else + ++i; + result = dg(cast(void *)&d); + if (result) + break; + } + return result; +} + + +/// ditto +extern (C) int _aApplycw1(in char[] aa, dg_t dg) +{ + int result; + size_t len = aa.length; + + debug(apply) printf("_aApplycw1(), len = %d\n", len); + for (size_t i = 0; i < len; ) + { + wchar w = aa[i]; + if (w & 0x80) + { + dchar d = decode(aa, i); + if (d <= 0xFFFF) + w = cast(wchar) d; + else + { + w = cast(wchar)((((d - 0x10000) >> 10) & 0x3FF) + 0xD800); + result = dg(cast(void *)&w); + if (result) + break; + w = cast(wchar)(((d - 0x10000) & 0x3FF) + 0xDC00); + } + } + else + ++i; + result = dg(cast(void *)&w); + if (result) + break; + } + return result; +} + + +/// ditto +extern (C) int _aApplywc1(in wchar[] aa, dg_t dg) +{ + int result; + size_t len = aa.length; + + debug(apply) printf("_aApplywc1(), len = %d\n", len); + for (size_t i = 0; i < len; ) + { + wchar w = aa[i]; + if (w & ~0x7F) + { + char[4] buf = void; + + dchar d = decode(aa, i); + auto b = toUTF8(buf, d); + foreach (char c2; b) + { + result = dg(cast(void *)&c2); + if (result) + return result; + } + } + else + { + char c = cast(char)w; + ++i; + result = dg(cast(void *)&c); + if (result) + break; + } + } + return result; +} + + +/// ditto +extern (C) int _aApplydc1(in dchar[] aa, dg_t dg) +{ + int result; + + debug(apply) printf("_aApplydc1(), len = %d\n", aa.length); + foreach (dchar d; aa) + { + if (d & ~0x7F) + { + char[4] buf = void; + + auto b = toUTF8(buf, d); + foreach (char c2; b) + { + result = dg(cast(void *)&c2); + if (result) + return result; + } + } + else + { + char c = cast(char)d; + result = dg(cast(void *)&c); + if (result) + break; + } + } + return result; +} + + +/// ditto +extern (C) int _aApplydw1(in dchar[] aa, dg_t dg) +{ + int result; + + debug(apply) printf("_aApplydw1(), len = %d\n", aa.length); + foreach (dchar d; aa) + { + wchar w; + + if (d <= 0xFFFF) + w = cast(wchar) d; + else + { + w = cast(wchar)((((d - 0x10000) >> 10) & 0x3FF) + 0xD800); + result = dg(cast(void *)&w); + if (result) + break; + w = cast(wchar)(((d - 0x10000) & 0x3FF) + 0xDC00); + } + result = dg(cast(void *)&w); + if (result) + break; + } + return result; +} + + +/****************************************************************************/ +/* 2 argument versions */ + +/** +Delegate type corresponding to transformed loop body + +Parameters are pointers to a `size_t` loop index, and the current `char`, `wchar` or `dchar`. + +Returns: non-zero when a `break` statement is hit +*/ +extern (D) alias dg2_t = int delegate(void* i, void* c); + +// Note: dg is extern(D), but _aApplycd2() is extern(C) + +/** +Variants of _aApplyXXX that include a loop index. +*/ +extern (C) int _aApplycd2(in char[] aa, dg2_t dg) +{ + int result; + size_t len = aa.length; + + debug(apply) printf("_aApplycd2(), len = %d\n", len); + size_t n; + for (size_t i = 0; i < len; i += n) + { + dchar d = aa[i]; + if (d & 0x80) + { + n = i; + d = decode(aa, n); + n -= i; + } + else + n = 1; + result = dg(&i, cast(void *)&d); + if (result) + break; + } + return result; +} + +/// ditto +extern (C) int _aApplywd2(in wchar[] aa, dg2_t dg) +{ + int result; + size_t len = aa.length; + + debug(apply) printf("_aApplywd2(), len = %d\n", len); + size_t n; + for (size_t i = 0; i < len; i += n) + { + dchar d = aa[i]; + if (d & ~0x7F) + { + n = i; + d = decode(aa, n); + n -= i; + } + else + n = 1; + result = dg(&i, cast(void *)&d); + if (result) + break; + } + return result; +} + +/// ditto +extern (C) int _aApplycw2(in char[] aa, dg2_t dg) +{ + int result; + size_t len = aa.length; + + debug(apply) printf("_aApplycw2(), len = %d\n", len); + size_t n; + for (size_t i = 0; i < len; i += n) + { + wchar w = aa[i]; + if (w & 0x80) + { + n = i; + dchar d = decode(aa, n); + n -= i; + if (d <= 0xFFFF) + w = cast(wchar) d; + else + { + w = cast(wchar) ((((d - 0x10000) >> 10) & 0x3FF) + 0xD800); + result = dg(&i, cast(void *)&w); + if (result) + break; + w = cast(wchar) (((d - 0x10000) & 0x3FF) + 0xDC00); + } + } + else + n = 1; + result = dg(&i, cast(void *)&w); + if (result) + break; + } + return result; +} + + +/// ditto +extern (C) int _aApplywc2(in wchar[] aa, dg2_t dg) +{ + int result; + size_t len = aa.length; + + debug(apply) printf("_aApplywc2(), len = %d\n", len); + size_t n; + for (size_t i = 0; i < len; i += n) + { + wchar w = aa[i]; + if (w & ~0x7F) + { + char[4] buf = void; + + n = i; + dchar d = decode(aa, n); + n -= i; + auto b = toUTF8(buf, d); + foreach (char c2; b) + { + result = dg(&i, cast(void *)&c2); + if (result) + return result; + } + } + else + { + char c = cast(char)w; + n = 1; + result = dg(&i, cast(void *)&c); + if (result) + break; + } + } + return result; +} + + +/// ditto +extern (C) int _aApplydc2(in dchar[] aa, dg2_t dg) +{ + int result; + size_t len = aa.length; + + debug(apply) printf("_aApplydc2(), len = %d\n", len); + for (size_t i = 0; i < len; i++) + { + dchar d = aa[i]; + if (d & ~0x7F) + { + char[4] buf = void; + + auto b = toUTF8(buf, d); + foreach (char c2; b) + { + result = dg(&i, cast(void *)&c2); + if (result) + return result; + } + } + else + { + char c = cast(char)d; + result = dg(&i, cast(void *)&c); + if (result) + break; + } + } + return result; +} + + +/// ditto +extern (C) int _aApplydw2(in dchar[] aa, dg2_t dg) +{ int result; + + debug(apply) printf("_aApplydw2(), len = %d\n", aa.length); + foreach (size_t i, dchar d; aa) + { + wchar w; + auto j = i; + + if (d <= 0xFFFF) + w = cast(wchar) d; + else + { + w = cast(wchar) ((((d - 0x10000) >> 10) & 0x3FF) + 0xD800); + result = dg(&j, cast(void *)&w); + if (result) + break; + w = cast(wchar) (((d - 0x10000) & 0x3FF) + 0xDC00); + } + result = dg(&j, cast(void *)&w); + if (result) + break; + } + return result; +} diff --git a/external/arsd-webassembly/core/internal/utf.d b/external/arsd-webassembly/core/internal/utf.d new file mode 100644 index 0000000..43aab77 --- /dev/null +++ b/external/arsd-webassembly/core/internal/utf.d @@ -0,0 +1,892 @@ +/******************************************** + * Encode and decode UTF-8, UTF-16 and UTF-32 strings. + * + * For Win32 systems, the C wchar_t type is UTF-16 and corresponds to the D + * wchar type. + * For Posix systems, the C wchar_t type is UTF-32 and corresponds to + * the D utf.dchar type. + * + * UTF character support is restricted to (\u0000 <= character <= \U0010FFFF). + * + * See_Also: + * $(LINK2 http://en.wikipedia.org/wiki/Unicode, Wikipedia)
+ * $(LINK http://www.cl.cam.ac.uk/~mgk25/unicode.html#utf-8)
+ * $(LINK http://anubis.dkuug.dk/JTC1/SC2/WG2/docs/n1335) + * + * Copyright: Copyright Digital Mars 2003 - 2016. + * License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0). + * Authors: Walter Bright, Sean Kelly + * Source: $(DRUNTIMESRC core/internal/_utf.d) + */ + +module core.internal.utf; + +extern (C) void onUnicodeError( string msg, size_t idx, string file = __FILE__, size_t line = __LINE__ ) @trusted +{ + _d_assert_msg("onUnicodeError: "~msg, file, line); +} + +/******************************* + * Test if c is a valid UTF-32 character. + * + * \uFFFE and \uFFFF are considered valid by this function, + * as they are permitted for internal use by an application, + * but they are not allowed for interchange by the Unicode standard. + * + * Returns: true if it is, false if not. + */ + +@safe @nogc pure nothrow +bool isValidDchar(dchar c) +{ + /* Note: FFFE and FFFF are specifically permitted by the + * Unicode standard for application internal use, but are not + * allowed for interchange. + * (thanks to Arcane Jill) + */ + + return c < 0xD800 || + (c > 0xDFFF && c <= 0x10FFFF /*&& c != 0xFFFE && c != 0xFFFF*/); +} + +unittest +{ + debug(utf) printf("utf.isValidDchar.unittest\n"); + assert(isValidDchar(cast(dchar)'a') == true); + assert(isValidDchar(cast(dchar)0x1FFFFF) == false); +} + + + +static immutable UTF8stride = +[ + cast(ubyte) + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, + 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3, + 4,4,4,4,4,4,4,4,5,5,5,5,6,6,0xFF,0xFF, +]; + +/** + * stride() returns the length of a UTF-8 sequence starting at index i + * in string s. + * Returns: + * The number of bytes in the UTF-8 sequence or + * 0xFF meaning s[i] is not the start of of UTF-8 sequence. + */ +@safe @nogc pure nothrow +uint stride(const scope char[] s, size_t i) +{ + return UTF8stride[s[i]]; +} + +/** + * stride() returns the length of a UTF-16 sequence starting at index i + * in string s. + */ +@safe @nogc pure nothrow +uint stride(const scope wchar[] s, size_t i) +{ uint u = s[i]; + return 1 + (u >= 0xD800 && u <= 0xDBFF); +} + +/** + * stride() returns the length of a UTF-32 sequence starting at index i + * in string s. + * Returns: The return value will always be 1. + */ +@safe @nogc pure nothrow +uint stride(const scope dchar[] s, size_t i) +{ + return 1; +} + +/******************************************* + * Given an index i into an array of characters s[], + * and assuming that index i is at the start of a UTF character, + * determine the number of UCS characters up to that index i. + */ +@safe +size_t toUCSindex(const scope char[] s, size_t i) +{ + size_t n; + size_t j; + + for (j = 0; j < i; ) + { + j += stride(s, j); + n++; + } + if (j > i) + { + onUnicodeError("invalid UTF-8 sequence", j); + } + return n; +} + +/** ditto */ +@safe +size_t toUCSindex(const scope wchar[] s, size_t i) +{ + size_t n; + size_t j; + + for (j = 0; j < i; ) + { + j += stride(s, j); + n++; + } + if (j > i) + { + onUnicodeError("invalid UTF-16 sequence", j); + } + return n; +} + +/** ditto */ +@safe @nogc pure nothrow +size_t toUCSindex(const scope dchar[] s, size_t i) +{ + return i; +} + +/****************************************** + * Given a UCS index n into an array of characters s[], return the UTF index. + */ +@safe +size_t toUTFindex(const scope char[] s, size_t n) +{ + size_t i; + + while (n--) + { + uint j = UTF8stride[s[i]]; + if (j == 0xFF) + onUnicodeError("invalid UTF-8 sequence", i); + i += j; + } + return i; +} + +/** ditto */ +@safe @nogc pure nothrow +size_t toUTFindex(const scope wchar[] s, size_t n) +{ + size_t i; + + while (n--) + { wchar u = s[i]; + + i += 1 + (u >= 0xD800 && u <= 0xDBFF); + } + return i; +} + +/** ditto */ +@safe @nogc pure nothrow +size_t toUTFindex(const scope dchar[] s, size_t n) +{ + return n; +} + +/* =================== Decode ======================= */ + +/*************** + * Decodes and returns character starting at s[idx]. idx is advanced past the + * decoded character. If the character is not well formed, a UtfException is + * thrown and idx remains unchanged. + */ +@safe +dchar decode(const scope char[] s, ref size_t idx) + in + { + assert(idx >= 0 && idx < s.length); + } + out (result) + { + assert(isValidDchar(result)); + } + do + { + size_t len = s.length; + dchar V; + size_t i = idx; + char u = s[i]; + + if (u & 0x80) + { uint n; + char u2; + + /* The following encodings are valid, except for the 5 and 6 byte + * combinations: + * 0xxxxxxx + * 110xxxxx 10xxxxxx + * 1110xxxx 10xxxxxx 10xxxxxx + * 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + * 111110xx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + * 1111110x 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx + */ + for (n = 1; ; n++) + { + if (n > 4) + goto Lerr; // only do the first 4 of 6 encodings + if (((u << n) & 0x80) == 0) + { + if (n == 1) + goto Lerr; + break; + } + } + + // Pick off (7 - n) significant bits of B from first byte of octet + V = cast(dchar)(u & ((1 << (7 - n)) - 1)); + + if (i + (n - 1) >= len) + goto Lerr; // off end of string + + /* The following combinations are overlong, and illegal: + * 1100000x (10xxxxxx) + * 11100000 100xxxxx (10xxxxxx) + * 11110000 1000xxxx (10xxxxxx 10xxxxxx) + * 11111000 10000xxx (10xxxxxx 10xxxxxx 10xxxxxx) + * 11111100 100000xx (10xxxxxx 10xxxxxx 10xxxxxx 10xxxxxx) + */ + u2 = s[i + 1]; + if ((u & 0xFE) == 0xC0 || + (u == 0xE0 && (u2 & 0xE0) == 0x80) || + (u == 0xF0 && (u2 & 0xF0) == 0x80) || + (u == 0xF8 && (u2 & 0xF8) == 0x80) || + (u == 0xFC && (u2 & 0xFC) == 0x80)) + goto Lerr; // overlong combination + + for (uint j = 1; j != n; j++) + { + u = s[i + j]; + if ((u & 0xC0) != 0x80) + goto Lerr; // trailing bytes are 10xxxxxx + V = (V << 6) | (u & 0x3F); + } + if (!isValidDchar(V)) + goto Lerr; + i += n; + } + else + { + V = cast(dchar) u; + i++; + } + + idx = i; + return V; + + Lerr: + onUnicodeError("invalid UTF-8 sequence", i); + return V; // dummy return +} + + +/** ditto */ +@safe +dchar decode(const scope wchar[] s, ref size_t idx) + in + { + assert(idx >= 0 && idx < s.length); + } + out (result) + { + assert(isValidDchar(result)); + } + do + { + string msg; + dchar V; + size_t i = idx; + uint u = s[i]; + + if (u & ~0x7F) + { if (u >= 0xD800 && u <= 0xDBFF) + { uint u2; + + if (i + 1 == s.length) + { msg = "surrogate UTF-16 high value past end of string"; + goto Lerr; + } + u2 = s[i + 1]; + if (u2 < 0xDC00 || u2 > 0xDFFF) + { msg = "surrogate UTF-16 low value out of range"; + goto Lerr; + } + u = ((u - 0xD7C0) << 10) + (u2 - 0xDC00); + i += 2; + } + else if (u >= 0xDC00 && u <= 0xDFFF) + { msg = "unpaired surrogate UTF-16 value"; + goto Lerr; + } + else if (u == 0xFFFE || u == 0xFFFF) + { msg = "illegal UTF-16 value"; + goto Lerr; + } + else + i++; + } + else + { + i++; + } + + idx = i; + return cast(dchar)u; + + Lerr: + onUnicodeError(msg, i); + return cast(dchar)u; // dummy return + } + +/** ditto */ +@safe +dchar decode(const scope dchar[] s, ref size_t idx) + in + { + assert(idx >= 0 && idx < s.length); + } + do + { + size_t i = idx; + dchar c = s[i]; + + if (!isValidDchar(c)) + goto Lerr; + idx = i + 1; + return c; + + Lerr: + onUnicodeError("invalid UTF-32 value", i); + return c; // dummy return + } + + +/* =================== Encode ======================= */ + +/******************************* + * Encodes character c and appends it to array s[]. + */ +@safe pure nothrow +void encode(ref char[] s, dchar c) + in + { + assert(isValidDchar(c)); + } + do + { + char[] r = s; + + if (c <= 0x7F) + { + r ~= cast(char) c; + } + else + { + char[4] buf = void; + uint L; + + if (c <= 0x7FF) + { + buf[0] = cast(char)(0xC0 | (c >> 6)); + buf[1] = cast(char)(0x80 | (c & 0x3F)); + L = 2; + } + else if (c <= 0xFFFF) + { + buf[0] = cast(char)(0xE0 | (c >> 12)); + buf[1] = cast(char)(0x80 | ((c >> 6) & 0x3F)); + buf[2] = cast(char)(0x80 | (c & 0x3F)); + L = 3; + } + else if (c <= 0x10FFFF) + { + buf[0] = cast(char)(0xF0 | (c >> 18)); + buf[1] = cast(char)(0x80 | ((c >> 12) & 0x3F)); + buf[2] = cast(char)(0x80 | ((c >> 6) & 0x3F)); + buf[3] = cast(char)(0x80 | (c & 0x3F)); + L = 4; + } + else + { + assert(0); + } + r ~= buf[0 .. L]; + } + s = r; + } + +unittest +{ + debug(utf) printf("utf.encode.unittest\n"); + + char[] s = "abcd".dup; + encode(s, cast(dchar)'a'); + assert(s.length == 5); + assert(s == "abcda"); + + encode(s, cast(dchar)'\u00A9'); + assert(s.length == 7); + assert(s == "abcda\xC2\xA9"); + //assert(s == "abcda\u00A9"); // BUG: fix compiler + + encode(s, cast(dchar)'\u2260'); + assert(s.length == 10); + assert(s == "abcda\xC2\xA9\xE2\x89\xA0"); +} + +/** ditto */ +@safe pure nothrow +void encode(ref wchar[] s, dchar c) + in + { + assert(isValidDchar(c)); + } + do + { + wchar[] r = s; + + if (c <= 0xFFFF) + { + r ~= cast(wchar) c; + } + else + { + wchar[2] buf = void; + + buf[0] = cast(wchar) ((((c - 0x10000) >> 10) & 0x3FF) + 0xD800); + buf[1] = cast(wchar) (((c - 0x10000) & 0x3FF) + 0xDC00); + r ~= buf; + } + s = r; + } + +/** ditto */ +@safe pure nothrow +void encode(ref dchar[] s, dchar c) + in + { + assert(isValidDchar(c)); + } + do + { + s ~= c; + } + +/** +Returns the code length of $(D c) in the encoding using $(D C) as a +code point. The code is returned in character count, not in bytes. + */ +@safe pure nothrow @nogc +ubyte codeLength(C)(dchar c) +{ + static if (C.sizeof == 1) + { + if (c <= 0x7F) return 1; + if (c <= 0x7FF) return 2; + if (c <= 0xFFFF) return 3; + if (c <= 0x10FFFF) return 4; + assert(false); + } + else static if (C.sizeof == 2) + { + return c <= 0xFFFF ? 1 : 2; + } + else + { + static assert(C.sizeof == 4); + return 1; + } +} + +/* =================== Validation ======================= */ + +/*********************************** +Checks to see if string is well formed or not. $(D S) can be an array + of $(D char), $(D wchar), or $(D dchar). Returns $(D false) if it is not. + Use to check all untrusted input for correctness. + */ +@safe +bool isValidString(S)(const scope S s) +{ + auto len = s.length; + for (size_t i = 0; i < len; ) + { + decode(s, i); + } + + return true; +} + +/* =================== Conversion to UTF8 ======================= */ + +@safe nothrow @nogc +char[] toUTF8(return scope char[] buf, dchar c) + in + { + assert(isValidDchar(c)); + } + do + { + if (c <= 0x7F) + { + buf[0] = cast(char) c; + return buf[0 .. 1]; + } + else if (c <= 0x7FF) + { + buf[0] = cast(char)(0xC0 | (c >> 6)); + buf[1] = cast(char)(0x80 | (c & 0x3F)); + return buf[0 .. 2]; + } + else if (c <= 0xFFFF) + { + buf[0] = cast(char)(0xE0 | (c >> 12)); + buf[1] = cast(char)(0x80 | ((c >> 6) & 0x3F)); + buf[2] = cast(char)(0x80 | (c & 0x3F)); + return buf[0 .. 3]; + } + else if (c <= 0x10FFFF) + { + buf[0] = cast(char)(0xF0 | (c >> 18)); + buf[1] = cast(char)(0x80 | ((c >> 12) & 0x3F)); + buf[2] = cast(char)(0x80 | ((c >> 6) & 0x3F)); + buf[3] = cast(char)(0x80 | (c & 0x3F)); + return buf[0 .. 4]; + } + assert(0); + } + +/******************* + * Encodes string s into UTF-8 and returns the encoded string. + */ +@safe +string toUTF8(return scope string s) + in + { + assert(isValidString(s)); + } + do + { + return s; + } + +/** ditto */ +@trusted +string toUTF8(const scope wchar[] s) +{ + char[] r; + size_t i; + size_t slen = s.length; + + r.length = slen; + + for (i = 0; i < slen; i++) + { wchar c = s[i]; + + if (c <= 0x7F) + r[i] = cast(char)c; // fast path for ascii + else + { + r.length = i; + foreach (dchar ch; s[i .. slen]) + { + encode(r, ch); + } + break; + } + } + return cast(string)r; +} + +/** ditto */ +@trusted +string toUTF8(const scope dchar[] s) +{ + char[] r; + size_t i; + size_t slen = s.length; + + r.length = slen; + + for (i = 0; i < slen; i++) + { dchar c = s[i]; + + if (c <= 0x7F) + r[i] = cast(char)c; // fast path for ascii + else + { + r.length = i; + foreach (dchar d; s[i .. slen]) + { + encode(r, d); + } + break; + } + } + return cast(string)r; +} + +/* =================== Conversion to UTF16 ======================= */ + +@safe pure nothrow @nogc +wchar[] toUTF16(return scope wchar[] buf, dchar c) + in + { + assert(isValidDchar(c)); + } + do + { + if (c <= 0xFFFF) + { + buf[0] = cast(wchar) c; + return buf[0 .. 1]; + } + else + { + buf[0] = cast(wchar) ((((c - 0x10000) >> 10) & 0x3FF) + 0xD800); + buf[1] = cast(wchar) (((c - 0x10000) & 0x3FF) + 0xDC00); + return buf[0 .. 2]; + } + } + +/**************** + * Encodes string s into UTF-16 and returns the encoded string. + * toUTF16z() is suitable for calling the 'W' functions in the Win32 API that take + * an LPWSTR or LPCWSTR argument. + */ +@trusted +wstring toUTF16(const scope char[] s) +{ + wchar[] r; + size_t slen = s.length; + + if (!__ctfe) + { + // Reserve still does a lot if slen is zero. + // Return early for that case. + if (0 == slen) + return ""w; + r.reserve(slen); + } + for (size_t i = 0; i < slen; ) + { + dchar c = s[i]; + if (c <= 0x7F) + { + i++; + r ~= cast(wchar)c; + } + else + { + c = decode(s, i); + encode(r, c); + } + } + return cast(wstring)r; +} + +alias const(wchar)* wptr; +/** ditto */ +@trusted +wptr toUTF16z(const scope char[] s) +{ + wchar[] r; + size_t slen = s.length; + + if (!__ctfe) + { + // Reserve still does a lot if slen is zero. + // Return early for that case. + if (0 == slen) + return &"\0"w[0]; + r.reserve(slen + 1); + } + for (size_t i = 0; i < slen; ) + { + dchar c = s[i]; + if (c <= 0x7F) + { + i++; + r ~= cast(wchar)c; + } + else + { + c = decode(s, i); + encode(r, c); + } + } + r ~= '\000'; + return &r[0]; +} + +/** ditto */ +@safe +wstring toUTF16(return scope wstring s) + in + { + assert(isValidString(s)); + } + do + { + return s; + } + +/** ditto */ +@trusted +wstring toUTF16(const scope dchar[] s) +{ + wchar[] r; + size_t slen = s.length; + + if (!__ctfe) + { + // Reserve still does a lot if slen is zero. + // Return early for that case. + if (0 == slen) + return ""w; + r.reserve(slen); + } + for (size_t i = 0; i < slen; i++) + { + encode(r, s[i]); + } + return cast(wstring)r; +} + +/* =================== Conversion to UTF32 ======================= */ + +/***** + * Encodes string s into UTF-32 and returns the encoded string. + */ +@trusted +dstring toUTF32(const scope char[] s) +{ + dchar[] r; + size_t slen = s.length; + size_t j = 0; + + r.length = slen; // r[] will never be longer than s[] + for (size_t i = 0; i < slen; ) + { + dchar c = s[i]; + if (c >= 0x80) + c = decode(s, i); + else + i++; // c is ascii, no need for decode + r[j++] = c; + } + return cast(dstring)r[0 .. j]; +} + +/** ditto */ +@trusted +dstring toUTF32(const scope wchar[] s) +{ + dchar[] r; + size_t slen = s.length; + size_t j = 0; + + r.length = slen; // r[] will never be longer than s[] + for (size_t i = 0; i < slen; ) + { + dchar c = s[i]; + if (c >= 0x80) + c = decode(s, i); + else + i++; // c is ascii, no need for decode + r[j++] = c; + } + return cast(dstring)r[0 .. j]; +} + +/** ditto */ +@safe +dstring toUTF32(return scope dstring s) + in + { + assert(isValidString(s)); + } + do + { + return s; + } + +/* ================================ tests ================================== */ + +unittest +{ + debug(utf) printf("utf.toUTF.unittest\n"); + + auto c = "hello"c[]; + auto w = toUTF16(c); + assert(w == "hello"); + auto d = toUTF32(c); + assert(d == "hello"); + + c = toUTF8(w); + assert(c == "hello"); + d = toUTF32(w); + assert(d == "hello"); + + c = toUTF8(d); + assert(c == "hello"); + w = toUTF16(d); + assert(w == "hello"); + + + c = "hel\u1234o"; + w = toUTF16(c); + assert(w == "hel\u1234o"); + d = toUTF32(c); + assert(d == "hel\u1234o"); + + c = toUTF8(w); + assert(c == "hel\u1234o"); + d = toUTF32(w); + assert(d == "hel\u1234o"); + + c = toUTF8(d); + assert(c == "hel\u1234o"); + w = toUTF16(d); + assert(w == "hel\u1234o"); + + + c = "he\U000BAAAAllo"; + w = toUTF16(c); + //foreach (wchar c; w) printf("c = x%x\n", c); + //foreach (wchar c; cast(wstring)"he\U000BAAAAllo") printf("c = x%x\n", c); + assert(w == "he\U000BAAAAllo"); + d = toUTF32(c); + assert(d == "he\U000BAAAAllo"); + + c = toUTF8(w); + assert(c == "he\U000BAAAAllo"); + d = toUTF32(w); + assert(d == "he\U000BAAAAllo"); + + c = toUTF8(d); + assert(c == "he\U000BAAAAllo"); + w = toUTF16(d); + assert(w == "he\U000BAAAAllo"); + + wchar[2] buf; + auto ret = toUTF16(buf, '\U000BAAAA'); + assert(ret == "\U000BAAAA"); +} diff --git a/external/arsd-webassembly/object.d b/external/arsd-webassembly/object.d new file mode 100644 index 0000000..51e1739 --- /dev/null +++ b/external/arsd-webassembly/object.d @@ -0,0 +1,2031 @@ +// 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; diff --git a/external/arsd-webassembly/std/random.d b/external/arsd-webassembly/std/random.d new file mode 100644 index 0000000..aa56fd9 --- /dev/null +++ b/external/arsd-webassembly/std/random.d @@ -0,0 +1,8 @@ +module std.random; + +import arsd.webassembly; + +int uniform(int low, int high) { + int max = high - low; + return low + eval!int(q{ return Math.floor(Math.random() * $0); }, max); +} diff --git a/external/arsd-webassembly/std/stdio.d b/external/arsd-webassembly/std/stdio.d new file mode 100644 index 0000000..5a9275b --- /dev/null +++ b/external/arsd-webassembly/std/stdio.d @@ -0,0 +1,17 @@ +module std.stdio; + +import arsd.webassembly; + +void writeln(T...)(T t) { + eval(q{ + var str = ""; + for(var i = 0; i < arguments.length; i++) + str += arguments[i]; + + str += "\n"; + + var txt = document.createTextNode(str); + var fd = document.getElementById("stdout"); + fd.appendChild(txt); + }, t); +} \ No newline at end of file diff --git a/external/printf/printf.c b/external/printf/printf.c new file mode 100644 index 0000000..6398e6f --- /dev/null +++ b/external/printf/printf.c @@ -0,0 +1,840 @@ +/////////////////////////////////////////////////////////////////////////////// +// \author (c) Marco Paland (info@paland.com) +// 2014-2019, PALANDesign Hannover, Germany +// +// \license The MIT License (MIT) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// \brief Tiny printf, sprintf and (v)snprintf implementation, optimized for speed on +// embedded systems with a very limited resources. These routines are thread +// safe and reentrant! +// Use this instead of the bloated standard/newlib printf cause these use +// malloc for printf (and may not be thread safe). +// +/////////////////////////////////////////////////////////////////////////////// + +#include +#include + +#include "printf.h" + + +// define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the +// printf_config.h header file +// default: undefined +#ifdef PRINTF_INCLUDE_CONFIG_H +#include "printf_config.h" +#endif + + +// 'ntoa' conversion buffer size, this must be big enough to hold one converted +// numeric number including padded zeros (dynamically created on stack) +// default: 32 byte +#ifndef PRINTF_NTOA_BUFFER_SIZE +#define PRINTF_NTOA_BUFFER_SIZE 32U +#endif + +// 'ftoa' conversion buffer size, this must be big enough to hold one converted +// float number including padded zeros (dynamically created on stack) +// default: 32 byte +#ifndef PRINTF_FTOA_BUFFER_SIZE +#define PRINTF_FTOA_BUFFER_SIZE 32U +#endif + +// support for the floating point type (%f) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_FLOAT +#define PRINTF_SUPPORT_FLOAT +#endif + +// support for exponential floating point notation (%e/%g) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL +#define PRINTF_SUPPORT_EXPONENTIAL +#endif + +// define the default floating point precision +// default: 6 digits +#ifndef PRINTF_DEFAULT_FLOAT_PRECISION +#define PRINTF_DEFAULT_FLOAT_PRECISION 6U +#endif + +// define the largest float suitable to print with %f +// default: 1e9 +#ifndef PRINTF_MAX_FLOAT +#define PRINTF_MAX_FLOAT 1e9 +#endif + +// support for the long long types (%llu or %p) +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_LONG_LONG +#define PRINTF_SUPPORT_LONG_LONG +#endif + +// support for the ptrdiff_t type (%t) +// ptrdiff_t is normally defined in as long or long long type +// default: activated +#ifndef PRINTF_DISABLE_SUPPORT_PTRDIFF_T +#define PRINTF_SUPPORT_PTRDIFF_T +#endif + +/////////////////////////////////////////////////////////////////////////////// + +// internal flag definitions +#define FLAGS_ZEROPAD (1U << 0U) +#define FLAGS_LEFT (1U << 1U) +#define FLAGS_PLUS (1U << 2U) +#define FLAGS_SPACE (1U << 3U) +#define FLAGS_HASH (1U << 4U) +#define FLAGS_UPPERCASE (1U << 5U) +#define FLAGS_CHAR (1U << 6U) +#define FLAGS_SHORT (1U << 7U) +#define FLAGS_LONG (1U << 8U) +#define FLAGS_LONG_LONG (1U << 9U) +#define FLAGS_PRECISION (1U << 10U) +#define FLAGS_ADAPT_EXP (1U << 11U) + + +// import float.h for DBL_MAX +#if defined(PRINTF_SUPPORT_FLOAT) +#include +#endif + + +// output function type +typedef void (*out_fct_type)(char character, void* buffer, size_t idx, size_t maxlen); + +// internal buffer output +static inline void _out_buffer(char character, void* buffer, size_t idx, size_t maxlen) +{ + if (idx < maxlen) { + ((char*)buffer)[idx] = character; + } +} + +// internal null output +static inline void _out_null(char character, void* buffer, size_t idx, size_t maxlen) +{ + (void)character; (void)buffer; (void)idx; (void)maxlen; +} + +// internal secure strlen +// \return The length of the string (excluding the terminating 0) limited by 'maxsize' +static inline unsigned int _strnlen_s(const char* str, size_t maxsize) +{ + const char* s; + for (s = str; *s && maxsize--; ++s); + return (unsigned int)(s - str); +} + + +// internal test if char is a digit (0-9) +// \return true if char is a digit +static inline bool _is_digit(char ch) +{ + return (ch >= '0') && (ch <= '9'); +} + + +// internal ASCII string to unsigned int conversion +static unsigned int _atoi(const char** str) +{ + unsigned int i = 0U; + while (_is_digit(**str)) { + i = i * 10U + (unsigned int)(*((*str)++) - '0'); + } + return i; +} + + +// output the specified string in reverse, taking care of any zero-padding +static size_t _out_rev(out_fct_type out, char* buffer, size_t idx, size_t maxlen, const char* buf, size_t len, unsigned int width, unsigned int flags) +{ + const size_t start_idx = idx; + + // pad spaces up to given width + if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) { + for (size_t i = len; i < width; i++) { + out(' ', buffer, idx++, maxlen); + } + } + + // reverse string + while (len) { + out(buf[--len], buffer, idx++, maxlen); + } + + // append pad spaces up to given width + if (flags & FLAGS_LEFT) { + while (idx - start_idx < width) { + out(' ', buffer, idx++, maxlen); + } + } + + return idx; +} + + +// internal itoa format +static size_t _ntoa_format(out_fct_type out, char* buffer, size_t idx, size_t maxlen, char* buf, size_t len, bool negative, unsigned int base, unsigned int prec, unsigned int width, unsigned int flags) +{ + // pad leading zeros + if (!(flags & FLAGS_LEFT)) { + if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { + width--; + } + while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + } + + // handle hash + if (flags & FLAGS_HASH) { + if (!(flags & FLAGS_PRECISION) && len && ((len == prec) || (len == width))) { + len--; + if (len && (base == 16U)) { + len--; + } + } + if ((base == 16U) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'x'; + } + else if ((base == 16U) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'X'; + } + else if ((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE)) { + buf[len++] = 'b'; + } + if (len < PRINTF_NTOA_BUFFER_SIZE) { + buf[len++] = '0'; + } + } + + if (len < PRINTF_NTOA_BUFFER_SIZE) { + if (negative) { + buf[len++] = '-'; + } + else if (flags & FLAGS_PLUS) { + buf[len++] = '+'; // ignore the space if the '+' exists + } + else if (flags & FLAGS_SPACE) { + buf[len++] = ' '; + } + } + + return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags); +} + + +// internal itoa for 'long' type +static size_t _ntoa_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long value, bool negative, unsigned long base, unsigned int prec, unsigned int width, unsigned int flags) +{ + char buf[PRINTF_NTOA_BUFFER_SIZE]; + size_t len = 0U; + + // no hash for 0 values + if (!value) { + flags &= ~FLAGS_HASH; + } + + // write if precision != 0 and value is != 0 + if (!(flags & FLAGS_PRECISION) || value) { + do { + const char digit = (char)(value % base); + buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10; + value /= base; + } while (value && (len < PRINTF_NTOA_BUFFER_SIZE)); + } + + return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags); +} + + +// internal itoa for 'long long' type +#if defined(PRINTF_SUPPORT_LONG_LONG) +static size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long long value, bool negative, unsigned long long base, unsigned int prec, unsigned int width, unsigned int flags) +{ + char buf[PRINTF_NTOA_BUFFER_SIZE]; + size_t len = 0U; + + // no hash for 0 values + if (!value) { + flags &= ~FLAGS_HASH; + } + + // write if precision != 0 and value is != 0 + if (!(flags & FLAGS_PRECISION) || value) { + do { + const char digit = (char)(value % base); + buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10; + value /= base; + } while (value && (len < PRINTF_NTOA_BUFFER_SIZE)); + } + + return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags); +} +#endif // PRINTF_SUPPORT_LONG_LONG + + +#if defined(PRINTF_SUPPORT_FLOAT) + +#if defined(PRINTF_SUPPORT_EXPONENTIAL) +// forward declaration so that _ftoa can switch to exp notation for values > PRINTF_MAX_FLOAT +static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags); +#endif + + +// internal ftoa for fixed decimal floating point +static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags) +{ + char buf[PRINTF_FTOA_BUFFER_SIZE]; + size_t len = 0U; + double diff = 0.0; + + // powers of 10 + static const double pow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 }; + + // test for special values + if (value != value) + return _out_rev(out, buffer, idx, maxlen, "nan", 3, width, flags); + if (value < -DBL_MAX) + return _out_rev(out, buffer, idx, maxlen, "fni-", 4, width, flags); + if (value > DBL_MAX) + return _out_rev(out, buffer, idx, maxlen, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags); + + // test for very large values + // standard printf behavior is to print EVERY whole number digit -- which could be 100s of characters overflowing your buffers == bad + if ((value > PRINTF_MAX_FLOAT) || (value < -PRINTF_MAX_FLOAT)) { +#if defined(PRINTF_SUPPORT_EXPONENTIAL) + return _etoa(out, buffer, idx, maxlen, value, prec, width, flags); +#else + return 0U; +#endif + } + + // test for negative + bool negative = false; + if (value < 0) { + negative = true; + value = 0 - value; + } + + // set default precision, if not set explicitly + if (!(flags & FLAGS_PRECISION)) { + prec = PRINTF_DEFAULT_FLOAT_PRECISION; + } + // limit precision to 9, cause a prec >= 10 can lead to overflow errors + while ((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) { + buf[len++] = '0'; + prec--; + } + + int whole = (int)value; + double tmp = (value - whole) * pow10[prec]; + unsigned long frac = (unsigned long)tmp; + diff = tmp - frac; + + if (diff > 0.5) { + ++frac; + // handle rollover, e.g. case 0.99 with prec 1 is 1.0 + if (frac >= pow10[prec]) { + frac = 0; + ++whole; + } + } + else if (diff < 0.5) { + } + else if ((frac == 0U) || (frac & 1U)) { + // if halfway, round up if odd OR if last digit is 0 + ++frac; + } + + if (prec == 0U) { + diff = value - (double)whole; + if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) { + // exactly 0.5 and ODD, then round up + // 1.5 -> 2, but 2.5 -> 2 + ++whole; + } + } + else { + unsigned int count = prec; + // now do fractional part, as an unsigned number + while (len < PRINTF_FTOA_BUFFER_SIZE) { + --count; + buf[len++] = (char)(48U + (frac % 10U)); + if (!(frac /= 10U)) { + break; + } + } + // add extra 0s + while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) { + buf[len++] = '0'; + } + if (len < PRINTF_FTOA_BUFFER_SIZE) { + // add decimal + buf[len++] = '.'; + } + } + + // do whole part, number is reversed + while (len < PRINTF_FTOA_BUFFER_SIZE) { + buf[len++] = (char)(48 + (whole % 10)); + if (!(whole /= 10)) { + break; + } + } + + // pad leading zeros + if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) { + if (width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) { + width--; + } + while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) { + buf[len++] = '0'; + } + } + + if (len < PRINTF_FTOA_BUFFER_SIZE) { + if (negative) { + buf[len++] = '-'; + } + else if (flags & FLAGS_PLUS) { + buf[len++] = '+'; // ignore the space if the '+' exists + } + else if (flags & FLAGS_SPACE) { + buf[len++] = ' '; + } + } + + return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags); +} + + +#if defined(PRINTF_SUPPORT_EXPONENTIAL) +// internal ftoa variant for exponential floating-point type, contributed by Martijn Jasperse +static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags) +{ + // check for NaN and special values + if ((value != value) || (value > DBL_MAX) || (value < -DBL_MAX)) { + return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags); + } + + // determine the sign + const bool negative = value < 0; + if (negative) { + value = -value; + } + + // default precision + if (!(flags & FLAGS_PRECISION)) { + prec = PRINTF_DEFAULT_FLOAT_PRECISION; + } + + // determine the decimal exponent + // based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c) + union { + uint64_t U; + double F; + } conv; + + conv.F = value; + int exp2 = (int)((conv.U >> 52U) & 0x07FFU) - 1023; // effectively log2 + conv.U = (conv.U & ((1ULL << 52U) - 1U)) | (1023ULL << 52U); // drop the exponent so conv.F is now in [1,2) + // now approximate log10 from the log2 integer part and an expansion of ln around 1.5 + int expval = (int)(0.1760912590558 + exp2 * 0.301029995663981 + (conv.F - 1.5) * 0.289529654602168); + // now we want to compute 10^expval but we want to be sure it won't overflow + exp2 = (int)(expval * 3.321928094887362 + 0.5); + const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453; + const double z2 = z * z; + conv.U = (uint64_t)(exp2 + 1023) << 52U; + // compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex + conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14))))); + // correct for rounding errors + if (value < conv.F) { + expval--; + conv.F /= 10; + } + + // the exponent format is "%+03d" and largest value is "307", so set aside 4-5 characters + unsigned int minwidth = ((expval < 100) && (expval > -100)) ? 4U : 5U; + + // in "%g" mode, "prec" is the number of *significant figures* not decimals + if (flags & FLAGS_ADAPT_EXP) { + // do we want to fall-back to "%f" mode? + if ((value >= 1e-4) && (value < 1e6)) { + if ((int)prec > expval) { + prec = (unsigned)((int)prec - expval - 1); + } + else { + prec = 0; + } + flags |= FLAGS_PRECISION; // make sure _ftoa respects precision + // no characters in exponent + minwidth = 0U; + expval = 0; + } + else { + // we use one sigfig for the whole part + if ((prec > 0) && (flags & FLAGS_PRECISION)) { + --prec; + } + } + } + + // will everything fit? + unsigned int fwidth = width; + if (width > minwidth) { + // we didn't fall-back so subtract the characters required for the exponent + fwidth -= minwidth; + } else { + // not enough characters, so go back to default sizing + fwidth = 0U; + } + if ((flags & FLAGS_LEFT) && minwidth) { + // if we're padding on the right, DON'T pad the floating part + fwidth = 0U; + } + + // rescale the float value + if (expval) { + value /= conv.F; + } + + // output the floating part + const size_t start_idx = idx; + idx = _ftoa(out, buffer, idx, maxlen, negative ? -value : value, prec, fwidth, flags & ~FLAGS_ADAPT_EXP); + + // output the exponent part + if (minwidth) { + // output the exponential symbol + out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen); + // output the exponent value + idx = _ntoa_long(out, buffer, idx, maxlen, (expval < 0) ? -expval : expval, expval < 0, 10, 0, minwidth-1, FLAGS_ZEROPAD | FLAGS_PLUS); + // might need to right-pad spaces + if (flags & FLAGS_LEFT) { + while (idx - start_idx < width) out(' ', buffer, idx++, maxlen); + } + } + return idx; +} +#endif // PRINTF_SUPPORT_EXPONENTIAL +#endif // PRINTF_SUPPORT_FLOAT + + +// internal vsnprintf +static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format, va_list va) +{ + unsigned int flags, width, precision, n; + size_t idx = 0U; + + if (!buffer) { + // use null output function + out = _out_null; + } + + while (*format) + { + // format specifier? %[flags][width][.precision][length] + if (*format != '%') { + // no + out(*format, buffer, idx++, maxlen); + format++; + continue; + } + else { + // yes, evaluate it + format++; + } + + // evaluate flags + flags = 0U; + do { + switch (*format) { + case '0': flags |= FLAGS_ZEROPAD; format++; n = 1U; break; + case '-': flags |= FLAGS_LEFT; format++; n = 1U; break; + case '+': flags |= FLAGS_PLUS; format++; n = 1U; break; + case ' ': flags |= FLAGS_SPACE; format++; n = 1U; break; + case '#': flags |= FLAGS_HASH; format++; n = 1U; break; + default : n = 0U; break; + } + } while (n); + + // evaluate width field + width = 0U; + if (_is_digit(*format)) { + width = _atoi(&format); + } + else if (*format == '*') { + const int w = va_arg(va, int); + if (w < 0) { + flags |= FLAGS_LEFT; // reverse padding + width = (unsigned int)-w; + } + else { + width = (unsigned int)w; + } + format++; + } + + // evaluate precision field + precision = 0U; + if (*format == '.') { + flags |= FLAGS_PRECISION; + format++; + if (_is_digit(*format)) { + precision = _atoi(&format); + } + else if (*format == '*') { + const int prec = (int)va_arg(va, int); + precision = prec > 0 ? (unsigned int)prec : 0U; + format++; + } + } + + // evaluate length field + switch (*format) { + case 'l' : + flags |= FLAGS_LONG; + format++; + if (*format == 'l') { + flags |= FLAGS_LONG_LONG; + format++; + } + break; + case 'h' : + flags |= FLAGS_SHORT; + format++; + if (*format == 'h') { + flags |= FLAGS_CHAR; + format++; + } + break; +#if defined(PRINTF_SUPPORT_PTRDIFF_T) + case 't' : + flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; +#endif + case 'j' : + flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; + case 'z' : + flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG); + format++; + break; + default : + break; + } + + // evaluate specifier + switch (*format) { + case 'd' : + case 'i' : + case 'u' : + case 'x' : + case 'X' : + case 'o' : + case 'b' : { + // set the base + unsigned int base; + if (*format == 'x' || *format == 'X') { + base = 16U; + } + else if (*format == 'o') { + base = 8U; + } + else if (*format == 'b') { + base = 2U; + } + else { + base = 10U; + flags &= ~FLAGS_HASH; // no hash for dec format + } + // uppercase + if (*format == 'X') { + flags |= FLAGS_UPPERCASE; + } + + // no plus or space flag for u, x, X, o, b + if ((*format != 'i') && (*format != 'd')) { + flags &= ~(FLAGS_PLUS | FLAGS_SPACE); + } + + // ignore '0' flag when precision is given + if (flags & FLAGS_PRECISION) { + flags &= ~FLAGS_ZEROPAD; + } + + // convert the integer + if ((*format == 'i') || (*format == 'd')) { + // signed + if (flags & FLAGS_LONG_LONG) { +#if defined(PRINTF_SUPPORT_LONG_LONG) + const long long value = va_arg(va, long long); + idx = _ntoa_long_long(out, buffer, idx, maxlen, (unsigned long long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); +#endif + } + else if (flags & FLAGS_LONG) { + const long value = va_arg(va, long); + idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); + } + else { + const int value = (flags & FLAGS_CHAR) ? (char)va_arg(va, int) : (flags & FLAGS_SHORT) ? (short int)va_arg(va, int) : va_arg(va, int); + idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned int)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags); + } + } + else { + // unsigned + if (flags & FLAGS_LONG_LONG) { +#if defined(PRINTF_SUPPORT_LONG_LONG) + idx = _ntoa_long_long(out, buffer, idx, maxlen, va_arg(va, unsigned long long), false, base, precision, width, flags); +#endif + } + else if (flags & FLAGS_LONG) { + idx = _ntoa_long(out, buffer, idx, maxlen, va_arg(va, unsigned long), false, base, precision, width, flags); + } + else { + const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int) : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) : va_arg(va, unsigned int); + idx = _ntoa_long(out, buffer, idx, maxlen, value, false, base, precision, width, flags); + } + } + format++; + break; + } +#if defined(PRINTF_SUPPORT_FLOAT) + case 'f' : + case 'F' : + if (*format == 'F') flags |= FLAGS_UPPERCASE; + idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags); + format++; + break; +#if defined(PRINTF_SUPPORT_EXPONENTIAL) + case 'e': + case 'E': + case 'g': + case 'G': + if ((*format == 'g')||(*format == 'G')) flags |= FLAGS_ADAPT_EXP; + if ((*format == 'E')||(*format == 'G')) flags |= FLAGS_UPPERCASE; + idx = _etoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags); + format++; + break; +#endif // PRINTF_SUPPORT_EXPONENTIAL +#endif // PRINTF_SUPPORT_FLOAT + case 'c' : { + unsigned int l = 1U; + // pre padding + if (!(flags & FLAGS_LEFT)) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + // char output + out((char)va_arg(va, int), buffer, idx++, maxlen); + // post padding + if (flags & FLAGS_LEFT) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + format++; + break; + } + + case 's' : { + const char* p = va_arg(va, char*); + unsigned int l = _strnlen_s(p, precision ? precision : (size_t)-1); + // pre padding + if (flags & FLAGS_PRECISION) { + l = (l < precision ? l : precision); + } + if (!(flags & FLAGS_LEFT)) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + // string output + while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) { + out(*(p++), buffer, idx++, maxlen); + } + // post padding + if (flags & FLAGS_LEFT) { + while (l++ < width) { + out(' ', buffer, idx++, maxlen); + } + } + format++; + break; + } + + case 'p' : { + width = sizeof(void*) * 2U; + flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE; +#if defined(PRINTF_SUPPORT_LONG_LONG) + const bool is_ll = sizeof(uintptr_t) == sizeof(long long); + if (is_ll) { + idx = _ntoa_long_long(out, buffer, idx, maxlen, (uintptr_t)va_arg(va, void*), false, 16U, precision, width, flags); + } + else { +#endif + idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)((uintptr_t)va_arg(va, void*)), false, 16U, precision, width, flags); +#if defined(PRINTF_SUPPORT_LONG_LONG) + } +#endif + format++; + break; + } + + case '%' : + out('%', buffer, idx++, maxlen); + format++; + break; + + default : + out(*format, buffer, idx++, maxlen); + format++; + break; + } + } + + // termination + out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen); + + // return written chars without terminating \0 + return (int)idx; +} + + +/////////////////////////////////////////////////////////////////////////////// + + +int sprintf_(char* buffer, const char* format, ...) +{ + va_list va; + va_start(va, format); + const int ret = _vsnprintf(_out_buffer, buffer, (size_t)-1, format, va); + va_end(va); + return ret; +} + diff --git a/external/printf/printf.h b/external/printf/printf.h new file mode 100644 index 0000000..2c17739 --- /dev/null +++ b/external/printf/printf.h @@ -0,0 +1,97 @@ +/////////////////////////////////////////////////////////////////////////////// +// \author (c) Marco Paland (info@paland.com) +// 2014-2019, PALANDesign Hannover, Germany +// +// \license The MIT License (MIT) +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. +// +// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on +// embedded systems with a very limited resources. +// Use this instead of bloated standard/newlib printf. +// These routines are thread safe and reentrant. +// +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _PRINTF_H_ +#define _PRINTF_H_ + +#include +#include + + +#ifdef __cplusplus +extern "C" { +#endif + + +/** + * Tiny sprintf implementation + * Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING (V)SNPRINTF INSTEAD! + * \param buffer A pointer to the buffer where to store the formatted string. MUST be big enough to store the output! + * \param format A string that specifies the format of the output + * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character + */ +#define sprintf sprintf_ +int sprintf_(char* buffer, const char* format, ...); + + +/** + * Tiny snprintf/vsnprintf implementation + * \param buffer A pointer to the buffer where to store the formatted string + * \param count The maximum number of characters to store in the buffer, including a terminating null character + * \param format A string that specifies the format of the output + * \param va A value identifying a variable arguments list + * \return The number of characters that COULD have been written into the buffer, not counting the terminating + * null character. A value equal or larger than count indicates truncation. Only when the returned value + * is non-negative and less than count, the string has been completely written. + */ +#define snprintf snprintf_ +#define vsnprintf vsnprintf_ +int snprintf_(char* buffer, size_t count, const char* format, ...); +int vsnprintf_(char* buffer, size_t count, const char* format, va_list va); + + +/** + * Tiny vprintf implementation + * \param format A string that specifies the format of the output + * \param va A value identifying a variable arguments list + * \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character + */ +#define vprintf vprintf_ +int vprintf_(const char* format, va_list va); + + +/** + * printf with output function + * You may use this as dynamic alternative to printf() with its fixed _putchar() output + * \param out An output function which takes one character and an argument pointer + * \param arg An argument pointer for user data passed to output function + * \param format A string that specifies the format of the output + * \return The number of characters that are sent to the output function, not counting the terminating null character + */ +int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...); + + +#ifdef __cplusplus +} +#endif + + +#endif // _PRINTF_H_ diff --git a/external/tinyalloc/LICENCE b/external/tinyalloc/LICENCE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/external/tinyalloc/LICENCE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/external/tinyalloc/tinyalloc.c b/external/tinyalloc/tinyalloc.c new file mode 100644 index 0000000..eb9fa3f --- /dev/null +++ b/external/tinyalloc/tinyalloc.c @@ -0,0 +1,270 @@ +#include "tinyalloc.h" +#include + +#ifdef TA_DEBUG +extern void print_s(char *); +extern void print_i(size_t); +#else +#define print_s(X) +#define print_i(X) +#endif + +typedef struct Block Block; + +struct Block { + void *addr; + Block *next; + size_t size; +}; + +typedef struct { + Block *free; // first free block + Block *used; // first used block + Block *fresh; // first available blank block + size_t top; // top free addr +} Heap; + +static Heap *heap = NULL; +static const void *heap_limit = NULL; +static size_t heap_split_thresh; +static size_t heap_alignment; +static size_t heap_max_blocks; + +/** + * If compaction is enabled, inserts block + * into free list, sorted by addr. + * If disabled, add block has new head of + * the free list. + */ +static void insert_block(Block *block) { +#ifndef TA_DISABLE_COMPACT + Block *ptr = heap->free; + Block *prev = NULL; + while (ptr != NULL) { + if ((size_t)block->addr <= (size_t)ptr->addr) { + print_s("insert"); + print_i((size_t)ptr); + break; + } + prev = ptr; + ptr = ptr->next; + } + if (prev != NULL) { + if (ptr == NULL) { + print_s("new tail"); + } + prev->next = block; + } else { + print_s("new head"); + heap->free = block; + } + block->next = ptr; +#else + block->next = heap->free; + heap->free = block; +#endif +} + +#ifndef TA_DISABLE_COMPACT +static void release_blocks(Block *scan, Block *to) { + Block *scan_next; + while (scan != to) { + print_s("release"); + print_i((size_t)scan); + scan_next = scan->next; + scan->next = heap->fresh; + heap->fresh = scan; + scan->addr = 0; + scan->size = 0; + scan = scan_next; + } +} + +static void compact() { + Block *ptr = heap->free; + Block *prev; + Block *scan; + while (ptr != NULL) { + prev = ptr; + scan = ptr->next; + while (scan != NULL && + (size_t)prev->addr + prev->size == (size_t)scan->addr) { + print_s("merge"); + print_i((size_t)scan); + prev = scan; + scan = scan->next; + } + if (prev != ptr) { + size_t new_size = + (size_t)prev->addr - (size_t)ptr->addr + prev->size; + print_s("new size"); + print_i(new_size); + ptr->size = new_size; + Block *next = prev->next; + // make merged blocks available + release_blocks(ptr->next, prev->next); + // relink + ptr->next = next; + } + ptr = ptr->next; + } +} +#endif + +bool ta_init(const void *base, const void *limit, const size_t heap_blocks, const size_t split_thresh, const size_t alignment) { + heap = (Heap *)base; + heap_limit = limit; + heap_split_thresh = split_thresh; + heap_alignment = alignment; + heap_max_blocks = heap_blocks; + + heap->free = NULL; + heap->used = NULL; + heap->fresh = (Block *)(heap + 1); + heap->top = (size_t)(heap->fresh + heap_blocks); + + Block *block = heap->fresh; + size_t i = heap_max_blocks - 1; + while (i--) { + block->next = block + 1; + block++; + } + block->next = NULL; + return true; +} + +bool ta_free(void *free) { + Block *block = heap->used; + Block *prev = NULL; + while (block != NULL) { + if (free == block->addr) { + if (prev) { + prev->next = block->next; + } else { + heap->used = block->next; + } + insert_block(block); +#ifndef TA_DISABLE_COMPACT + compact(); +#endif + return true; + } + prev = block; + block = block->next; + } + return false; +} + +static Block *alloc_block(size_t num) { + Block *ptr = heap->free; + Block *prev = NULL; + size_t top = heap->top; + num = (num + heap_alignment - 1) & -heap_alignment; + while (ptr != NULL) { + const int is_top = ((size_t)ptr->addr + ptr->size >= top) && ((size_t)ptr->addr + num <= (size_t)heap_limit); + if (is_top || ptr->size >= num) { + if (prev != NULL) { + prev->next = ptr->next; + } else { + heap->free = ptr->next; + } + ptr->next = heap->used; + heap->used = ptr; + if (is_top) { + print_s("resize top block"); + ptr->size = num; + heap->top = (size_t)ptr->addr + num; +#ifndef TA_DISABLE_SPLIT + } else if (heap->fresh != NULL) { + size_t excess = ptr->size - num; + if (excess >= heap_split_thresh) { + ptr->size = num; + Block *split = heap->fresh; + heap->fresh = split->next; + split->addr = (void *)((size_t)ptr->addr + num); + print_s("split"); + print_i((size_t)split->addr); + split->size = excess; + insert_block(split); +#ifndef TA_DISABLE_COMPACT + compact(); +#endif + } +#endif + } + return ptr; + } + prev = ptr; + ptr = ptr->next; + } + // no matching free blocks + // see if any other blocks available + size_t new_top = top + num; + if (heap->fresh != NULL && new_top <= (size_t)heap_limit) { + ptr = heap->fresh; + heap->fresh = ptr->next; + ptr->addr = (void *)top; + ptr->next = heap->used; + ptr->size = num; + heap->used = ptr; + heap->top = new_top; + return ptr; + } + return NULL; +} + +void *ta_alloc(size_t num) { + Block *block = alloc_block(num); + if (block != NULL) { + return block->addr; + } + return NULL; +} + +static void memclear(void *ptr, size_t num) { + size_t *ptrw = (size_t *)ptr; + size_t numw = (num & -sizeof(size_t)) / sizeof(size_t); + while (numw--) { + *ptrw++ = 0; + } + num &= (sizeof(size_t) - 1); + uint8_t *ptrb = (uint8_t *)ptrw; + while (num--) { + *ptrb++ = 0; + } +} + +void *ta_calloc(size_t num, size_t size) { + num *= size; + Block *block = alloc_block(num); + if (block != NULL) { + memclear(block->addr, num); + return block->addr; + } + return NULL; +} + +static size_t count_blocks(Block *ptr) { + size_t num = 0; + while (ptr != NULL) { + num++; + ptr = ptr->next; + } + return num; +} + +size_t ta_num_free() { + return count_blocks(heap->free); +} + +size_t ta_num_used() { + return count_blocks(heap->used); +} + +size_t ta_num_fresh() { + return count_blocks(heap->fresh); +} + +bool ta_check() { + return heap_max_blocks == ta_num_free() + ta_num_used() + ta_num_fresh(); +} diff --git a/external/tinyalloc/tinyalloc.h b/external/tinyalloc/tinyalloc.h new file mode 100644 index 0000000..0d00596 --- /dev/null +++ b/external/tinyalloc/tinyalloc.h @@ -0,0 +1,23 @@ +#ifdef __cplusplus +extern "C" { +#endif + +#include +#include +#include + +uint32_t grow(uint32_t pages); + +bool ta_init(const void *base, const void *limit, const size_t heap_blocks, const size_t split_thresh, const size_t alignment); +void *ta_alloc(size_t num); +void *ta_calloc(size_t num, size_t size); +bool ta_free(void *ptr); + +size_t ta_num_free(); +size_t ta_num_used(); +size_t ta_num_fresh(); +bool ta_check(); + +#ifdef __cplusplus +} +#endif diff --git a/external/xxhash/xxhash.c b/external/xxhash/xxhash.c index e60cc37..a23b7d6 100644 --- a/external/xxhash/xxhash.c +++ b/external/xxhash/xxhash.c @@ -38,5 +38,6 @@ #define XXH_STATIC_LINKING_ONLY /* access advanced declarations */ #define XXH_IMPLEMENTATION /* access definitions */ +#define XXH_NO_STDLIB 1 #include "xxhash.h" diff --git a/external/xxhash/xxhash.h b/external/xxhash/xxhash.h index c4cb3ba..2ee7597 100644 --- a/external/xxhash/xxhash.h +++ b/external/xxhash/xxhash.h @@ -2394,35 +2394,57 @@ static void XXH_free(void* p) { free(p); } #endif /* XXH_NO_STDLIB */ -#ifndef XXH_memcpy -/*! - * @internal - * @brief XXH_memcpy() macro can be redirected at compile time - */ -# include -# define XXH_memcpy memcpy -#endif +void * +memcpy(void *dest, const void *src, size_t n) +{ + if(!dest || n == 0) return dest; -#ifndef XXH_memset -/*! - * @internal - * @brief XXH_memset() macro can be redirected at compile time - */ -# include -# define XXH_memset memset -#endif + uint8_t *dp = (uint8_t *)dest; + uint8_t *sp = (uint8_t *)src; -#ifndef XXH_memcmp -/*! - * @internal - * @brief XXH_memcmp() macro can be redirected at compile time - * Note: only needed by XXH128. - */ -# include -# define XXH_memcmp memcmp -#endif + for(size_t i = 0; i < n; i += 1) + { + dp[i] = sp[i]; + } + return dest; +} +void * +memset(void *dest, int ch, size_t n) +{ + if(!dest || n == 0) return dest; + + uint8_t *p = (uint8_t *)dest; + uint8_t v = (uint8_t )(ch & 0xFF); + + for(size_t i = 0; i < n; i += 1) + { + p[i] = v; + } + + return dest; +} + +int +memcmp(const void *s1, const void *s2, size_t n) +{ + int result = 0; + + uint8_t *p1 = (uint8_t *)s1; + uint8_t *p2 = (uint8_t *)s2; + + for(size_t i = 0; i < n; i += 1) + { + result += p1[i] - p2[i]; + } + + return result; +} + +#define XXH_memcpy memcpy +#define XXH_memset memset +#define XXH_memcmp memcmp #include /* ULLONG_MAX */ diff --git a/fonts.d b/fonts.d index 64f6d5e..2f253f4 100644 --- a/fonts.d +++ b/fonts.d @@ -5,10 +5,6 @@ import dlib.aliases; import dlib.util; import dlib.alloc; import dlib.math; -import std.math.traits : isNaN; -import std.math : Floor = floor; -import std.algorithm.comparison : clamp; -import std.algorithm.sorting; const u64 FONT_MAX_BANDS = 8; const u64 FONT_TEX_WIDTH = 4096; @@ -162,6 +158,9 @@ struct Glyph f32 atlas_top; } +static if(NativeTarget) +{ + __gshared FT_Library FT_LIB; alias FontFace = FT_Face; @@ -388,6 +387,8 @@ CreateAtlas(Arena* arena, FontFace font, u32 size, UVec2 atlas_dim) return atlas; } +} + void BuildFontGlyph(Arena* arena, u32 index, u32 glyph_index, SlugFontInfo* font_info, u32* curve_index, stbtt_fontinfo* stb_font_info) { @@ -421,14 +422,14 @@ BuildFontGlyph(Arena* arena, u32 index, u32 glyph_index, SlugFontInfo* font_info f32 dx = x-cx; f32 dy = y-cy; - if(fabs(dx) < 0.1 && fabs(dy) < 0.1) + if(Abs(dx) < 0.1 && Abs(dy) < 0.1) { cx = x; cy = y; break; } - f32 length = sqrt(dx*dx + dy*dy); + f32 length = Sqrt(dx*dx + dy*dy); f32 nx = -dy/length*0.05; f32 ny = -dx/length*0.05; @@ -554,7 +555,7 @@ LoadFontCurves(Arena* arena, SlugFontInfo* font_info, u8[] font_file_data) curve_texel_count += font_info.glyphs[i].curve_count*2; } - u32 curve_texel_height = clamp(cast(u32)((curve_texel_count+FONT_TEX_WIDTH-1) / FONT_TEX_WIDTH), 1, u32.max); + u32 curve_texel_height = Clamp(cast(u32)((curve_texel_count+FONT_TEX_WIDTH-1) / FONT_TEX_WIDTH), 1, u32.max); u32[] curve_start_indices = Alloc!(u32)(arena, font_info.glyphs.length); SlugTexel!(f32) curve_texel = { @@ -578,14 +579,14 @@ LoadFontCurves(Arena* arena, SlugFontInfo* font_info, u8[] font_file_data) UVec2 t0 = UVec2(curve_index%FONT_TEX_WIDTH, curve_index/FONT_TEX_WIDTH); u32 offset = cast(u32)((t0.y*FONT_TEX_WIDTH + t0.x) * 4); - curve_texel.data[offset .. offset+4] = [curve.p1.x, curve.p1.y, curve.p2.x, curve.p2.y]; + Assign(curve_texel.data[offset .. offset+4], curve.p1.x, curve.p1.y, curve.p2.x, curve.p2.y); curve_index += 1; UVec2 t1 = UVec2(curve_index%FONT_TEX_WIDTH, curve_index/FONT_TEX_WIDTH); offset = cast(u32)((t1.y*FONT_TEX_WIDTH + t1.x) * 4); - curve_texel.data[offset .. offset+4] = [curve.p3.x, curve.p3.y, 0.0, 0.0]; + Assign(curve_texel.data[offset .. offset+4], curve.p3.x, curve.p3.y, 0.0, 0.0); curve_index += 1; } @@ -617,7 +618,7 @@ LoadFontCurves(Arena* arena, SlugFontInfo* font_info, u8[] font_file_data) } } - u32 band_texel_height = clamp(cast(u32)((band_total_count+FONT_TEX_WIDTH-1) / FONT_TEX_WIDTH), 1U, u32.max); + u32 band_texel_height = Clamp(cast(u32)((band_total_count+FONT_TEX_WIDTH-1) / FONT_TEX_WIDTH), 1U, u32.max); SlugTexel!(u32) band_texel = { data: Alloc!(u32)(arena, FONT_TEX_WIDTH*band_texel_height*4), width: FONT_TEX_WIDTH, @@ -683,7 +684,7 @@ LoadFontCurves(Arena* arena, SlugFontInfo* font_info, u8[] font_file_data) count = band_data.bands[1][index].count; } - band_texel.data[di .. di+2] = [count, offsets[j]]; + Assign(band_texel.data[di .. di+2], count, offsets[j]); } foreach(j; 0 .. band_total) @@ -700,7 +701,7 @@ LoadFontCurves(Arena* arena, SlugFontInfo* font_info, u8[] font_file_data) u32 tl = ls+k; u32 di = cast(u32)((tl/FONT_TEX_WIDTH*FONT_TEX_WIDTH + tl%FONT_TEX_WIDTH) * 4); - band_texel.data[di .. di+2] = [ct%FONT_TEX_WIDTH, ct/FONT_TEX_WIDTH]; + Assign(band_texel.data[di .. di+2], ct%FONT_TEX_WIDTH, ct/FONT_TEX_WIDTH); } } @@ -729,7 +730,8 @@ SlugSortBand(Arena* arena, SlugBandEntry* entry, SlugCurve[] curves, u32 axis) refs[i] = SlugCurveRef(entry.indices[i], mx); } - sort!("a.sort_key < b.sort_key")(refs); + assert(false, "implement sorting!"); + // sort!("a.sort_key < b.sort_key")(refs); foreach(i; 0 .. entry.count) { @@ -802,8 +804,8 @@ SlugBuildBands(Arena* arena, u32 index, SlugFontInfo* font_info, u32 band_count) if(size.y > 0.0) { - u32 b0 = cast(u32)clamp(floor(curve_min.y * glyph.band_scale.y + glyph.band_offset.y), 0.0, cast(f32)(band_count-1)); - u32 b1 = cast(u32)clamp(floor(curve_max.y * glyph.band_scale.y + glyph.band_offset.y), 0.0, cast(f32)(band_count-1)); + u32 b0 = cast(u32)Clamp(Floor(curve_min.y * glyph.band_scale.y + glyph.band_offset.y), 0.0, cast(f32)(band_count-1)); + u32 b1 = cast(u32)Clamp(Floor(curve_max.y * glyph.band_scale.y + glyph.band_offset.y), 0.0, cast(f32)(band_count-1)); for(u32 j = b0; j <= b1; j += 1) { @@ -814,8 +816,8 @@ SlugBuildBands(Arena* arena, u32 index, SlugFontInfo* font_info, u32 band_count) if(size.x > 0.0) { - u32 b0 = cast(u32)clamp(Floor(curve_min.x * glyph.band_scale.x + glyph.band_offset.x), 0.0, cast(f32)(band_count-1)); - u32 b1 = cast(u32)clamp(Floor(curve_max.x * glyph.band_scale.x + glyph.band_offset.x), 0.0, cast(f32)(band_count-1)); + u32 b0 = cast(u32)Clamp(Floor(curve_min.x * glyph.band_scale.x + glyph.band_offset.x), 0.0, cast(f32)(band_count-1)); + u32 b1 = cast(u32)Clamp(Floor(curve_max.x * glyph.band_scale.x + glyph.band_offset.x), 0.0, cast(f32)(band_count-1)); for(u32 j = b0; j <= b1; j += 1) { @@ -883,7 +885,7 @@ SlugLoadFontVertex(u8 ch, IVec2 pos, SlugFont* font, SlugBuffer* buffer) vertices[3].em_space_pos = Vec2(glyph.rect.max.x, glyph.rect.min.y); u32 vidx = vertex_start; - indices[0 .. 6] = [vidx+0, vidx+2, vidx+3, vidx+3, vidx+1, vidx+0]; + Assign(indices[0 .. 6], vidx+0, vidx+2, vidx+3, vidx+3, vidx+1, vidx+0); buffer.quad_offset += 1; diff --git a/main.wasm b/main.wasm new file mode 100755 index 0000000..4b590d2 Binary files /dev/null and b/main.wasm differ diff --git a/math.d b/math.d index bbf9825..f121f51 100644 --- a/math.d +++ b/math.d @@ -5,15 +5,334 @@ import dlib.util; import dlibincludes; -import core.stdc.math : tanf, cosf, sinf, sqrtf, fabsf; -import std.algorithm.comparison : clamp; - -import std.math; -import std.math.algebraic; import std.traits; +import std.math.traits; import std.meta; -import std.format; -import std.stdio; + +static if(NativeTarget) +{ +} + +enum PI = 3.14159L; + +const u16[128] _RSQRT_TABLE = [ + 0xb451, 0xb2f0, 0xb196, 0xb044, 0xaef9, 0xadb6, 0xac79, 0xab43, + 0xaa14, 0xa8eb, 0xa7c8, 0xa6aa, 0xa592, 0xa480, 0xa373, 0xa26b, + 0xa168, 0xa06a, 0x9f70, 0x9e7b, 0x9d8a, 0x9c9d, 0x9bb5, 0x9ad1, + 0x99f0, 0x9913, 0x983a, 0x9765, 0x9693, 0x95c4, 0x94f8, 0x9430, + 0x936b, 0x92a9, 0x91ea, 0x912e, 0x9075, 0x8fbe, 0x8f0a, 0x8e59, + 0x8daa, 0x8cfe, 0x8c54, 0x8bac, 0x8b07, 0x8a64, 0x89c4, 0x8925, + 0x8889, 0x87ee, 0x8756, 0x86c0, 0x862b, 0x8599, 0x8508, 0x8479, + 0x83ec, 0x8361, 0x82d8, 0x8250, 0x81c9, 0x8145, 0x80c2, 0x8040, + 0xff02, 0xfd0e, 0xfb25, 0xf947, 0xf773, 0xf5aa, 0xf3ea, 0xf234, + 0xf087, 0xeee3, 0xed47, 0xebb3, 0xea27, 0xe8a3, 0xe727, 0xe5b2, + 0xe443, 0xe2dc, 0xe17a, 0xe020, 0xdecb, 0xdd7d, 0xdc34, 0xdaf1, + 0xd9b3, 0xd87b, 0xd748, 0xd61a, 0xd4f1, 0xd3cd, 0xd2ad, 0xd192, + 0xd07b, 0xcf69, 0xce5b, 0xcd51, 0xcc4a, 0xcb48, 0xca4a, 0xc94f, + 0xc858, 0xc764, 0xc674, 0xc587, 0xc49d, 0xc3b7, 0xc2d4, 0xc1f4, + 0xc116, 0xc03c, 0xbf65, 0xbe90, 0xbdbe, 0xbcef, 0xbc23, 0xbb59, + 0xba91, 0xb9cc, 0xb90a, 0xb84a, 0xb78c, 0xb6d0, 0xb617, 0xb560, +]; + +union Union64 +{ + f64 f; + u64 u; +} + +union Union32 +{ + f32 f; + u32 u; +} + +auto +AsUint(T)(T v) if(isFloatingPoint!(T)) +{ + static if(is(T == f32)) + { + Union32 u = { f: v }; + } + else + { + Union64 u = { f: v }; + } + + return u.u; +} + +auto +AsFloat(T)(T v) if(isIntegral!(T)) +{ + static if(T.sizeof == 4) + { + Union32 u = { u: v }; + } + else + { + Union64 u = { u: v }; + } + + return u.f; +} + +T +_MathInvalid(T)(T v) if(isFloatingPoint!(T)) +{ + return (v - v) / (v - v); +} + +u32 +Mul32(T)(T a, T b) if(isIntegral!(T)) +{ + return cast(u32)(cast(u64)(a*b) >> 32); +} + +u64 +Mul64(u64 a, u64 b) +{ + u64 ahi = a>>32; + u64 alo = a&0xffffffff; + u64 bhi = b>>32; + u64 blo = b&0xffffffff; + return ahi*bhi + (ahi*blo >> 32) + (alo*bhi >> 32); +} + +T +Sqrt(T)(T x) if(isFloatingPoint!(T)) +{ + u64 ix, top, m; + + ix = AsUint(x); + top = ix >> 52; + if (top - 0x001 >= 0x7ff - 0x001) { + if (ix * 2 == 0) + { + return x; + } + if (ix == 0x7ff0000000000000) + { + return x; + } + if (ix > 0x7ff0000000000000) + { + return _MathInvalid(x); + } + ix = AsUint(x * 0x1p52); + top = ix >> 52; + top -= 52; + } + + i32 even = top & 1; + m = (ix << 11) | 0x8000000000000000; + if (even) + { + m >>= 1; + } + + top = (top + 0x3ff) >> 1; + + static const u64 three = 0xc0000000; + u64 r, s, d, u, i; + + i = (ix >> 46) % 128; + r = cast(u32)(_RSQRT_TABLE[cast(usize)i] << 16); + s = Mul32(m>>32, r); + d = Mul32(s, r); + u = three - d; + r = Mul32(r, u) << 1; + s = Mul32(s, u) << 1; + d = Mul32(s, r); + u = three - d; + r = Mul32(r, u) << 1; + r = r << 32; + s = Mul64(m, r); + d = Mul64(s, r); + u = (three<<32) - d; + s = Mul64(s, u); + s = (s - 2) >> 9; + + u64 d0, d1, d2; + f64 y, t; + d0 = (m << 42) - s*s; + d1 = s - d0; + d2 = d1 + s + 1; + s += d1 >> 63; + s &= 0x000fffffffffffff; + s |= top << 52; + y = AsFloat(s); + + static if(true) + { + u64 tiny = d2==0 ? 0 : 0x0010000000000000; + tiny |= (d1^d2) & 0x8000000000000000; + t = AsFloat(tiny); + y = cast(f64)(y + t); + } + + return y; +} + +T +Abs(T)(T x) if(isSigned!(T)) +{ + static if(isFloatingPoint!(T)) + { + static if(is(T == f32)) + { + Union32 f = { f: x }; + } + else + { + Union64 f = { f: x }; + } + + f.u &= cast(typeof(f.u))-1 / 2; + + x = f.f; + } + else + { + x = x >= 0 ? x : -x; + } + + return x; +} + +T +Ceil(T)(T x) if(is(T == f32) || is(T == f64)) +{ + if(!isNaN(x) && !isInfinity(x) && x != 0.0) + { + x = _Floor(x+1.0); + } + + return x; +} + +T +Floor(T)(T x) if(is(T == f32) || is(T == f64)) +{ + if(!isNaN(x) && !isInfinity(x) && x != 0.0) + { + x = _Floor(x); + } + + return x; +} + +T +_Floor(T)(const T x) @trusted pure nothrow @nogc +{ + alias F = floatTraits!(T); + // Take care not to trigger library calls from the compiler, + // while ensuring that we don't get defeated by some optimizers. + union floatBits + { + T rv; + ushort[T.sizeof/2] vu; + + // Other kinds of extractors for real formats. + static if (F.realFormat == RealFormat.ieeeSingle) + uint vi; + else static if (F.realFormat == RealFormat.ieeeDouble) + ulong vi; + } + + floatBits y = void; + y.rv = x; + + // Find the exponent (power of 2) + // Do this by shifting the raw value so that the exponent lies in the low bits, + // then mask out the sign bit, and subtract the bias. + static if (F.realFormat == RealFormat.ieeeSingle) + { + int exp = ((y.vi >> (T.mant_dig - 1)) & 0xff) - 0x7f; + enum mantissa_mask = F.MANTISSAMASK_INT; + enum sign_shift = 31; + } + else static if (F.realFormat == RealFormat.ieeeDouble) + { + long exp = ((y.vi >> (T.mant_dig - 1)) & 0x7ff) - 0x3ff; + enum mantissa_mask = F.MANTISSAMASK_LONG; + enum sign_shift = 63; + } + else static if (F.realFormat == RealFormat.ieeeExtended || + F.realFormat == RealFormat.ieeeExtended53) + { + int exp = (y.vu[F.EXPPOS_SHORT] & 0x7fff) - 0x3fff; + + version (LittleEndian) + int pos = 0; + else + int pos = 4; + } + else static if (F.realFormat == RealFormat.ieeeQuadruple) + { + int exp = (y.vu[F.EXPPOS_SHORT] & 0x7fff) - 0x3fff; + + version (LittleEndian) + int pos = 0; + else + int pos = 7; + } + else + static assert(false, "Not implemented for this architecture"); + + if (exp < 0) + { + if (x < 0.0) + return -1.0; + else + return 0.0; + } + + static if (F.realFormat == RealFormat.ieeeSingle || + F.realFormat == RealFormat.ieeeDouble) + { + if (exp < (T.mant_dig - 1)) + { + // Clear all bits representing the fraction part. + // Note: the fraction mask represents the floating point number 0.999999... + // i.e: `2.0 ^^ (exp - T.mant_dig + 1) * (fraction_mask + 1) == 1.0` + const fraction_mask = mantissa_mask >> exp; + + if ((y.vi & fraction_mask) != 0) + { + // If 'x' is negative, then first substract (1.0 - T.epsilon) from the value. + if (y.vi >> sign_shift) + y.vi += fraction_mask; + y.vi &= ~fraction_mask; + } + } + } + else + { + static if (F.realFormat == RealFormat.ieeeExtended53) + exp = (T.mant_dig + 11 - 1) - exp; // mant_dig is really 64 + else + exp = (T.mant_dig - 1) - exp; + + // Zero 16 bits at a time. + while (exp >= 16) + { + version (LittleEndian) + y.vu[pos++] = 0; + else + y.vu[pos--] = 0; + exp -= 16; + } + + // Clear the remaining bits. + if (exp > 0) + y.vu[pos] &= 0xffff ^ ((1 << exp) - 1); + + if ((x < 0.0) && (x != y.rv)) + y.rv -= 1.0; + } + + return y.rv; +} T _MinOrMaxInner(T, bool max, Args...)(T first, Args args) @@ -169,7 +488,7 @@ struct Vector(T, int N) foreach(i; 0 .. N) { - if(fabsf(v[i] - other.v[i]) > 0.0000009) + if(Abs(v[i] - other.v[i]) > 0.0000009) { result = false; break; @@ -192,13 +511,19 @@ struct Vector(T, int N) Vector opUnary(string op)() if(op == "+" || op == "-" || op == "~" || op == "!") { Vector res; - mixin(GenerateLoop!("res.v[@] = " ~ op ~ " v[@];", N)()); + static foreach(i; 0 .. N) + { + mixin("res.v[", i, "] = " ~ op ~ "v[", i, "];\n"); + } return res; } ref Vector opOpAssign(string op, U)(U value) if(is(U: Vector)) { - mixin(GenerateLoop!("v[@] " ~ op ~ "= value.v[@];", N)()); + static foreach(i; 0 .. N) + { + mixin("v[", i, "] " ~op~ "= value.v[", i, "];"); + } return this; } @@ -233,7 +558,10 @@ struct Vector(T, int N) opBinaryRight(string op, U)(U operand) if(IsConvertible!(U) && (op == "*" || op == "+" || op == "-" || op == "/")) { Vector res; - mixin(GenerateLoop!("res.v[@] = v[@] "~op~" operand;", N)); + static foreach(i; 0 .. N) + { + mixin("res.v[", i, "] = v[", i, "] " ~op~ " operand;"); + } return res; } @@ -242,25 +570,36 @@ struct Vector(T, int N) Vector opBinary(string op, U)(U operand) if((is(U: Vector!(f32, 4)) && is(T: f32)) && (op == "*" || op == "+" || op == "-" || op == "/")) { Vector result; - f32* l = &x; - f32* r = &operand.x; - f32* res = &result.x; - - asm + + version(X86_64) { - mov R8, l; - mov R9, r; - mov R10, res; - movups XMM0, x.offsetof[R8]; - movups XMM1, operand.x.offsetof[R9]; + f32* l = &x; + f32* r = &operand.x; + f32* res = &result.x; + + asm + { + mov R8, l; + mov R9, r; + mov R10, res; + movups XMM0, x.offsetof[R8]; + movups XMM1, operand.x.offsetof[R9]; + } + static if(op == "*") asm { mulps XMM0, XMM1; } + else static if(op == "-") asm { subps XMM0, XMM1; } + else static if(op == "+") asm { addps XMM0, XMM1; } + else static if(op == "/") asm { divps XMM0, XMM1; } + asm + { + movups result.x.offsetof[R10], XMM0; + } } - static if(op == "*") asm { mulps XMM0, XMM1; } - else static if(op == "-") asm { subps XMM0, XMM1; } - else static if(op == "+") asm { addps XMM0, XMM1; } - else static if(op == "/") asm { divps XMM0, XMM1; } - asm + else { - movups result.x.offsetof[R10], XMM0; + static foreach(i; 0 .. N) + { + result.v[i] = mixin("this.v[", i, "] " ~op~ " operand.v[", i, "]"); + } } return result; @@ -269,26 +608,34 @@ struct Vector(T, int N) Vector opBinary(string op, U)(U operand) if(IsConvertible!(U) && (op == "*" || op == "+" || op == "-" || op == "/")) { Vector result; - Vector other = operand; - f32* l = &x; - f32* r = &other.x; - f32* res = &result.x; - - asm + + version(X86_64) { - mov R8, l; - mov R9, r; - mov R10, res; - movups XMM0, x.offsetof[R8]; - movups XMM1, other.x.offsetof[R9]; + Vector other = operand; + f32* l = &x; + f32* r = &other.x; + f32* res = &result.x; + + asm + { + mov R8, l; + mov R9, r; + mov R10, res; + movups XMM0, x.offsetof[R8]; + movups XMM1, other.x.offsetof[R9]; + } + static if(op == "*") asm { mulps XMM0, XMM1; } + else static if(op == "-") asm { subps XMM0, XMM1; } + else static if(op == "+") asm { addps XMM0, XMM1; } + else static if(op == "/") asm { divps XMM0, XMM1; } + asm + { + movups result.x.offsetof[R10], XMM0; + } } - static if(op == "*") asm { mulps XMM0, XMM1; } - else static if(op == "-") asm { subps XMM0, XMM1; } - else static if(op == "+") asm { addps XMM0, XMM1; } - else static if(op == "/") asm { divps XMM0, XMM1; } - asm + else { - movups result.x.offsetof[R10], XMM0; + static assert(false, "Currently not supported for platform"); } return result; @@ -299,7 +646,12 @@ struct Vector(T, int N) Vector opBinary(string op, U)(U operand) if(is(U: Vector) && U._N == N && (op == "*" || op == "+" || op == "-" || op == "/")) { Vector res; - mixin(GenerateLoop!("res.v[@] = v[@] " ~ op ~ " operand.v[@];", N)()); + + static foreach(i; 0 .. N) + { + mixin("res.v[", i, "] = v[", i, "] " ~ op ~ " operand.v[", i, "];"); + } + return res; } @@ -307,7 +659,12 @@ struct Vector(T, int N) { Vector res; Vector other = operand; - mixin(GenerateLoop!("res.v[@] = v[@] " ~ op ~ " other.v[@];", N)()); + + static foreach(i; 0 .. N) + { + mixin("res.v[", i, "] = v[", i, "] " ~ op ~ " other.v[", i, "];"); + } + return res; } } @@ -327,13 +684,18 @@ struct Vector(T, int N) U opCast(U)() if(IsVector!(U) && (U._N == _N)) { U result; - mixin(GenerateLoop!("res.v[@] = cast(U._T)v[@];", N)()); + + static foreach(i; 0 .. N) + { + rev.v[i] = cast(U._T)v[i]; + } + return result; } - template IsConvertible(T) + template IsConvertible(U) { - enum bool IsConvertible = (!is(T : Vector)) && is(typeof({ T x; Vector v = x; }())); + enum bool IsConvertible = !is(U == Vector) && __traits(compiles, { U u; T t = cast(T)u; } ); } template TypesAreNumeric(U, V) @@ -434,10 +796,10 @@ align(16) struct Matrix(T, int D) static assert(D > 0 && D <= 4); alias Vector!(T, D) MatrixVec; - alias T _T; - alias T[4] Row; + alias T _T; + alias T[4] Row; - enum N = D*D; + enum N = D*D; enum _D = D; union @@ -445,9 +807,12 @@ align(16) struct Matrix(T, int D) T[N] v = 0; Row[D] rows; MatrixVec[D] vec; - static if(D == 4) mat4 glm_mat; - static if(D == 3) mat3 glm_mat; - static if(D == 2) mat2 glm_mat; + static if(NativeTarget) + { + static if(D == 4) mat4 glm_mat; + static if(D == 3) mat3 glm_mat; + static if(D == 2) mat2 glm_mat; + } } // TODO: setup @@ -532,7 +897,7 @@ align(16) struct Matrix(T, int D) static foreach(i; 0 .. N) { - if(fabsf(this.v[i] - other.v[i]) > 0.0000009) + if(Abs(this.v[i] - other.v[i]) > 0.0000009) { result = false; } @@ -558,16 +923,22 @@ align(16) struct Matrix(T, int D) Vec4 opBinary(string op, U)(U x) if(is(U: Vec4) && is(T: f32) && (op == "*")) { Vec4 result = 0.0; - glm_mat4_mulv(glm_mat.ptr, x.v.ptr, result.v.ptr); + + static if(NativeTarget) + { + glm_mat4_mulv(glm_mat.ptr, x.v.ptr, result.v.ptr); + } + else assert(false, "Not implemented for platform"); + return result; } Matrix opBinary(string op, U)(U x) if(is(U: Matrix!(T, D)) && is(T: f32) && D == 4 && (op == "*")) { Matrix result; - MatZero(&result); - Mat4MulASM(&this, &x, &result); + MatZero(&result); + Mat4Mul(&this, &x, &result); return result; } @@ -612,7 +983,13 @@ struct Quat U opCast(U)() if(is(U: Mat4)) { Mat4 result; - glm_quat_mat4(vec.ptr, result.glm_mat.ptr); + + static if(NativeTarget) + { + glm_quat_mat4(vec.ptr, result.glm_mat.ptr); + } + else assert(false, "Not implemented for platform"); + return result; } @@ -630,106 +1007,109 @@ struct Quat } void -Mat4MulASM(Mat4* l, Mat4* r, Mat4* result) +Mat4Mul(Mat4* l, Mat4* r, Mat4* result) { - asm @trusted + version(X86_64) { - mov R8, l; - mov R9, r; - mov R10, result; + asm @trusted + { + mov R8, l; + mov R9, r; + mov R10, result; - movups XMM0, [R8]; - movups XMM1, [R9+00]; - movups XMM2, [R9+16]; - movups XMM3, [R9+32]; - movups XMM4, [R9+48]; + movups XMM0, [R8]; + movups XMM1, [R9+00]; + movups XMM2, [R9+16]; + movups XMM3, [R9+32]; + movups XMM4, [R9+48]; - movups XMM6, XMM1; - shufps XMM6, XMM6, 0; // XMM5 = vec.xxxx; - mulps XMM6, XMM0; // XMM6 = col1; + movups XMM6, XMM1; + shufps XMM6, XMM6, 0; // XMM5 = vec.xxxx; + mulps XMM6, XMM0; // XMM6 = col1; - movups XMM7, XMM2; - shufps XMM7, XMM7, 0; - mulps XMM7, XMM0; // XMM7 = col2; + movups XMM7, XMM2; + shufps XMM7, XMM7, 0; + mulps XMM7, XMM0; // XMM7 = col2; - movups XMM8, XMM3; - shufps XMM8, XMM8, 0; - mulps XMM8, XMM0; // XMM8 = col3; + movups XMM8, XMM3; + shufps XMM8, XMM8, 0; + mulps XMM8, XMM0; // XMM8 = col3; - movups XMM9, XMM4; - shufps XMM9, XMM9, 0; - mulps XMM9, XMM0; // XMM9 = col4; + movups XMM9, XMM4; + shufps XMM9, XMM9, 0; + mulps XMM9, XMM0; // XMM9 = col4; - movups XMM0, [R8+16]; + movups XMM0, [R8+16]; - movups XMM5, XMM1; - shufps XMM5, XMM5, 85; // XMM5 = vec.yyyy; - mulps XMM5, XMM0; - addps XMM6, XMM5; + movups XMM5, XMM1; + shufps XMM5, XMM5, 85; // XMM5 = vec.yyyy; + mulps XMM5, XMM0; + addps XMM6, XMM5; - movups XMM5, XMM2; - shufps XMM5, XMM5, 85; - mulps XMM5, XMM0; - addps XMM7, XMM5; + movups XMM5, XMM2; + shufps XMM5, XMM5, 85; + mulps XMM5, XMM0; + addps XMM7, XMM5; - movups XMM5, XMM3; - shufps XMM5, XMM5, 85; - mulps XMM5, XMM0; - addps XMM8, XMM5; + movups XMM5, XMM3; + shufps XMM5, XMM5, 85; + mulps XMM5, XMM0; + addps XMM8, XMM5; - movups XMM5, XMM4; - shufps XMM5, XMM5, 85; - mulps XMM5, XMM0; - addps XMM9, XMM5; + movups XMM5, XMM4; + shufps XMM5, XMM5, 85; + mulps XMM5, XMM0; + addps XMM9, XMM5; - movups XMM0, [R8+32]; + movups XMM0, [R8+32]; - movups XMM5, XMM1; - shufps XMM5, XMM5, 170; // XMM5 = vec.zzzz; - mulps XMM5, XMM0; - addps XMM6, XMM5; + movups XMM5, XMM1; + shufps XMM5, XMM5, 170; // XMM5 = vec.zzzz; + mulps XMM5, XMM0; + addps XMM6, XMM5; - movups XMM5, XMM2; - shufps XMM5, XMM5, 170; - mulps XMM5, XMM0; - addps XMM7, XMM5; + movups XMM5, XMM2; + shufps XMM5, XMM5, 170; + mulps XMM5, XMM0; + addps XMM7, XMM5; - movups XMM5, XMM3; - shufps XMM5, XMM5, 170; - mulps XMM5, XMM0; - addps XMM8, XMM5; - - movups XMM5, XMM4; - shufps XMM5, XMM5, 170; - mulps XMM5, XMM0; - addps XMM9, XMM5; + movups XMM5, XMM3; + shufps XMM5, XMM5, 170; + mulps XMM5, XMM0; + addps XMM8, XMM5; - movups XMM0, [R8+48]; + movups XMM5, XMM4; + shufps XMM5, XMM5, 170; + mulps XMM5, XMM0; + addps XMM9, XMM5; - movups XMM5, XMM1; - shufps XMM5, XMM5, 255; // XMM5 = vec.wwww; - mulps XMM5, XMM0; - addps XMM6, XMM5; + movups XMM0, [R8+48]; - movups XMM5, XMM2; - shufps XMM5, XMM5, 255; - mulps XMM5, XMM0; - addps XMM7, XMM5; + movups XMM5, XMM1; + shufps XMM5, XMM5, 255; // XMM5 = vec.wwww; + mulps XMM5, XMM0; + addps XMM6, XMM5; - movups XMM5, XMM3; - shufps XMM5, XMM5, 255; - mulps XMM5, XMM0; - addps XMM8, XMM5; + movups XMM5, XMM2; + shufps XMM5, XMM5, 255; + mulps XMM5, XMM0; + addps XMM7, XMM5; - movups XMM5, XMM4; - shufps XMM5, XMM5, 255; - mulps XMM5, XMM0; - addps XMM9, XMM5; + movups XMM5, XMM3; + shufps XMM5, XMM5, 255; + mulps XMM5, XMM0; + addps XMM8, XMM5; - movups [R10+00], XMM6; - movups [R10+16], XMM7; - movups [R10+32], XMM8; - movups [R10+48], XMM9; + movups XMM5, XMM4; + shufps XMM5, XMM5, 255; + mulps XMM5, XMM0; + addps XMM9, XMM5; + + movups [R10+00], XMM6; + movups [R10+16], XMM7; + movups [R10+32], XMM8; + movups [R10+48], XMM9; + } } } @@ -778,7 +1158,13 @@ pragma(inline) Quat QuatFromAxis(f32 angle, Vec3 axis) { Quat q; - glm_quatv(q.vec.ptr, angle, axis.v.ptr); + + static if(NativeTarget) + { + glm_quatv(q.vec.ptr, angle, axis.v.ptr); + } + else assert(false, "Not implemented for platform"); + return q; } @@ -804,14 +1190,14 @@ Dot(Vec4* l, Vec4* r) pragma(inline) f32 Norm(Vec3* v) { - return sqrtf(Dot(v, v)); + return Sqrt(Dot(v, v)); } pragma(inline) f32 Norm(Vec4* v) { // TODO: SIMD this - return sqrtf(Dot(v, v)); + return Sqrt(Dot(v, v)); } pragma(inline) void @@ -821,11 +1207,17 @@ Normalize(T)(T* vec) if(is(T: Vec2) || is(T: Vec3) || is(T: Vec4)) if(length < f32.epsilon) { - mixin(GenerateLoop!("vec.v[@] = 0.0;", vec._N)()); + static foreach(i; 0 .. vec._N) + { + vec.v[i] = 0.0; + } } else { - mixin(GenerateLoop!("vec.v[@] *= (1.0 / length);", vec._N)()); + static foreach(i; 0 .. vec._N) + { + vec.v[i] *= (1.0 / length); + } } } @@ -846,7 +1238,7 @@ Normalize(Quat q) q = Quat(1.0, 0.0, 0.0, 0.0); } - q.vec *= 1.0 / sqrtf(dot); + q.vec *= 1.0 / Sqrt(dot); return q; } @@ -856,7 +1248,13 @@ Perspective(f32 fov, f32 aspect, f32 near, f32 far) { Mat4 res; MatZero(&res); - glm_perspective(fov, aspect, near, far, res.glm_mat.ptr); + + static if(NativeTarget) + { + glm_perspective(fov, aspect, near, far, res.glm_mat.ptr); + } + else assert(false, "Not implemented for platform"); + res[1, 1] *= -1.0; return res; } @@ -865,7 +1263,12 @@ void Ortho(Mat4* mat, f32 left, f32 bottom, f32 right, f32 top, f32 near, f32 far) { MatZero(mat); - glm_ortho(left, right, bottom, top, near, far, mat.glm_mat.ptr); + + static if(NativeTarget) + { + glm_ortho(left, right, bottom, top, near, far, mat.glm_mat.ptr); + } + else assert(false, "Not implemented for platform"); } Mat4 @@ -873,7 +1276,13 @@ Ortho(f32 left, f32 bottom, f32 right, f32 top, f32 near, f32 far) { Mat4 mat; MatZero(&mat); - glm_ortho(left, right, bottom, top, near, far, mat.glm_mat.ptr); + + static if(NativeTarget) + { + glm_ortho(left, right, bottom, top, near, far, mat.glm_mat.ptr); + } + else assert(false, "Not implemented for platform"); + return mat; } @@ -906,7 +1315,13 @@ LookAt(Vec3 eye, Vec3 center, Vec3 up) { Mat4 result; MatZero(&result); - glm_lookat(eye.v.ptr, center.v.ptr, up.v.ptr, result.glm_mat.ptr); + + static if(NativeTarget) + { + glm_lookat(eye.v.ptr, center.v.ptr, up.v.ptr, result.glm_mat.ptr); + } + else assert(false, "Not implemented for platform"); + return result; } @@ -937,34 +1352,38 @@ pragma(inline) void CrossN(Vec3 a, Vec3 b, Vec3* dst) { Cross(a, b, dst); - glm_vec3_normalize(dst.v.ptr); + + static if(NativeTarget) + { + glm_vec3_normalize(dst.v.ptr); + } + else assert(false, "Not implemented for platform"); } pragma(inline) void Cross(Vec3 a, Vec3 b, Vec3* dst) { - glm_vec3_cross(a.v.ptr, b.v.ptr, dst.v.ptr); + static if(NativeTarget) + { + glm_vec3_cross(a.v.ptr, b.v.ptr, dst.v.ptr); + } + else assert(false, "Not implemented for platform"); } pragma(inline) void MatZero(Mat4* mat) { - auto v = &mat.vec; - asm - { - mov R8, v; - xorps XMM0, XMM0; - movups mat.vec.offsetof[R8]+00, XMM0; - movups mat.vec.offsetof[R8]+16, XMM0; - movups mat.vec.offsetof[R8]+32, XMM0; - movups mat.vec.offsetof[R8]+48, XMM0; - } + mat.v[] = 0.0; } pragma(inline) void Translate(Mat4* mat, Vec3 vec) { - glm_translate(mat.glm_mat.ptr, vec.v.ptr); + static if(NativeTarget) + { + glm_translate(mat.glm_mat.ptr, vec.v.ptr); + } + else assert(false, "Not implemented for platform"); } pragma(inline) Mat4 @@ -972,7 +1391,13 @@ Inverse(Mat4 mat) { Mat4 res; MatZero(&res); - glm_mat4_inv(mat.glm_mat.ptr, res.glm_mat.ptr); + + static if(NativeTarget) + { + glm_mat4_inv(mat.glm_mat.ptr, res.glm_mat.ptr); + } + else assert(false, "Not implemented for platform"); + return res; } @@ -1006,14 +1431,26 @@ Remap(T)(T v, T in_min, T in_max, T out_min, T out_max) template IsVector(T) { - import std.traits : isInstanceOf; +// import std.traits : isInstanceOf; enum bool IsVector = isInstanceOf!(Vector, T); } T -Clamp(T)(T value, T low, T high) if(IsVector!(T)) +Clamp(T)(T value, T min, T max) if(isNumeric!(T)) { - mixin(GenerateLoop!("value.v[@] = clamp(value.v[@], low.v[@], high.v[@]);", T.v.length)); + assert(min <= max, "Clamp assertion failed: min is higher than max"); + T x = value < min ? min : value; + return x > max ? max : x; +} + +T +Clamp(T)(T value, T min, T max) if(IsVector!(T)) +{ + static foreach(i; 0 .. value._N) + { + value.v[i] = Clamp(value.v[i], min.v[i], max.v[i]); + } + return value; } @@ -1023,11 +1460,6 @@ version(DLIB_TEST) unittest enum FLOAT_MIN = -f32.max; /* - import core.stdc.stdio; - import core.stdc.stdlib; - import core.stdc.time; - import std.range : take; - import std.algorithm.iteration : sum; void PrintMatrix(Mat4 mat) { @@ -1264,10 +1696,20 @@ version(DLIB_TEST) unittest { Vec2 v0 = 50U; - assert(fabsf(v0.x-50.0) < 0.0009 && fabsf(v0.y-50.0) < 0.0009); + assert(Abs(v0.x-50.0) < 0.0009 && Abs(v0.y-50.0) < 0.0009); U8Vec2 v1 = U8Vec2(55U); assert(v1.x == 55 && v1.y == 55); } + + { + assert(Ceil(10.5) == 11.0); + assert(Floor(100.33) == 100.0); + assert(Abs(-500) == 500); + assert(Abs(-500.0) == 500.0); + + assert(Clamp(55, 10, 40) == 40); + assert(Clamp(-20, -5, 55) == -5); + } } diff --git a/package.d b/package.d index a3144c6..0c15127 100644 --- a/package.d +++ b/package.d @@ -9,3 +9,11 @@ public import dlib.platform; public import dlib.aliases; public import dlib.fonts; public import dlib.assets; + +version(DLIB_TEST) +{ + version(WebAssembly) + { + void _start() { main() } + } +} diff --git a/platform.d b/platform.d index 79bce1f..e314c37 100644 --- a/platform.d +++ b/platform.d @@ -1,6 +1,10 @@ module dlib.platform; import dlib.aliases; + +static if(NativeTarget) +{ + import dlib.alloc : Reset; import dlib.alloc; import dlib.util; @@ -23,117 +27,121 @@ const WINDOW_EDGE_BUFFER = 50; enum Input : u32 { None, - Backspace = 0x08, - Tab = 0x09, - Enter = 0x0A, - Escape = 0x1B, - Space = 0x20, + Backspace = 0x08, + Tab = 0x09, + Enter = 0x0A, + Escape = 0x1B, + Space = 0x20, Exclamation = 0x21, DoubleQuote = 0x22, - Hash = 0x23, - Dollar = 0x24, - Percent = 0x25, - Ampersand = 0x26, + Hash = 0x23, + Dollar = 0x24, + Percent = 0x25, + Ampersand = 0x26, SingleQuote = 0x27, - LeftParen = 0x28, + LeftParen = 0x28, RightParent = 0x29, - Asterisk = 0x2A, - Plus = 0x2B, - Comma = 0x2C, - Minus = 0x2D, - Period = 0x2E, - Slash = 0x2F, - Zero = 0x30, - One = 0x31, - Two = 0x32, - Three = 0x33, - Four = 0x34, - Five = 0x35, - Six = 0x36, - Seven = 0x37, - Eight = 0x38, - Nine = 0x39, - Colon = 0x3A, - Semicolon = 0x3B, - LessThan = 0x3C, - Equals = 0x3D, + Asterisk = 0x2A, + Plus = 0x2B, + Comma = 0x2C, + Minus = 0x2D, + Period = 0x2E, + Slash = 0x2F, + Zero = 0x30, + One = 0x31, + Two = 0x32, + Three = 0x33, + Four = 0x34, + Five = 0x35, + Six = 0x36, + Seven = 0x37, + Eight = 0x38, + Nine = 0x39, + Colon = 0x3A, + Semicolon = 0x3B, + LessThan = 0x3C, + Equals = 0x3D, GreaterThan = 0x3E, - Question = 0x3F, - At = 0x40, + Question = 0x3F, + At = 0x40, A = 0x41, B = 0x42, C = 0x43, D = 0x44, E = 0x45, F = 0x46, G = 0x47, H = 0x48, I = 0x49, J = 0x4A, K = 0x4B, L = 0x4C, M = 0x4D, N = 0x4E, O = 0x4F, P = 0x50, Q = 0x51, R = 0x52, S = 0x53, T = 0x54, U = 0x55, V = 0x56, W = 0x57, X = 0x58, Y = 0x59, Z = 0x5A, LeftBracket = 0x5B, - BackSlash = 0x5C, + BackSlash = 0x5C, RightBracket = 0x5D, - Caret = 0x5E, - Underscore = 0x5F, - Grave = 0x60, + Caret = 0x5E, + Underscore = 0x5F, + Grave = 0x60, a = I.A+32, b = I.B+32, c = I.C+32, d = I.D+32, e = I.E+32, f = I.F+32, g = I.G+32, h = I.H+32, i = I.I+32, j = I.J+32, k = I.K+32, l = I.L+32, m = I.M+32, n = I.N+32, o = I.O+32, p = I.P+32, q = I.Q+32, r = I.R+32, s = I.S+32, t = I.T+32, u = I.U+32, v = I.V+32, w = I.W+32, x = I.X+32, y = I.Y+32, z = I.Z+32, - LeftBrace = 0x7B, + LeftBrace = 0x7B, VerticalBar = 0x7C, - RightBrace = 0x7D, - Tilde = 0x7E, - Delete = 0x7F, + RightBrace = 0x7D, + Tilde = 0x7E, + Delete = 0x7F, - F1 = 0x150, - F2 = 0x151, - F3 = 0x152, - F4 = 0x153, - F5 = 0x154, - F6 = 0x155, - F7 = 0x156, - F8 = 0x157, - F9 = 0x158, + F1 = 0x150, + F2 = 0x151, + F3 = 0x152, + F4 = 0x153, + F5 = 0x154, + F6 = 0x155, + F7 = 0x156, + F8 = 0x157, + F9 = 0x158, F10 = 0x159, F11 = 0x15A, F12 = 0x15B, + PrintScreen = 0x10C, - ScrollLock = 0x10D, - Pause = 0x10E, - Insert = 0x10F, - Home = 0x110, - End = 0x111, - PageUp = 0x112, - PageDown = 0x113, - NumLock = 0x114, + ScrollLock = 0x10D, + Pause = 0x10E, + Insert = 0x10F, + Home = 0x110, + End = 0x111, + PageUp = 0x112, + PageDown = 0x113, - NumEnter = 0x10A, + NumLock = 0x114, + NumEnter = 0x10A, NumAsterisk = 0x12A, - NumPlus = 0x12B, - NumMinus = 0x12D, - NumPeriod = 0x12E, - NumSlash = 0x12F, - NumZero = 0x130, - NumOne = 0x131, - NumTwo = 0x132, - NumThree = 0x133, - NumFour = 0x134, - NumFive = 0x135, - NumSix = 0x136, - NumSeven = 0x137, - NumEight = 0x138, - NumNine = 0x139, + NumPlus = 0x12B, + NumMinus = 0x12D, + NumPeriod = 0x12E, + NumSlash = 0x12F, + NumZero = 0x130, + NumOne = 0x131, + NumTwo = 0x132, + NumThree = 0x133, + NumFour = 0x134, + NumFive = 0x135, + NumSix = 0x136, + NumSeven = 0x137, + NumEight = 0x138, + NumNine = 0x139, - LeftCtrl = 0x13A, - RightCtrl = 0x13B, - LeftShift = 0x13C, + LeftCtrl = 0x13A, + RightCtrl = 0x13B, + LeftShift = 0x13C, RightShift = 0x13D, - LeftSuper = 0x13E, + LeftSuper = 0x13E, RightSuper = 0x13F, - LeftAlt = 0x140, - RightAlt = 0x141, - CapsLock = 0x142, + LeftAlt = 0x140, + RightAlt = 0x141, + CapsLock = 0x142, - Left = 0x143, + Left = 0x143, Right = 0x144, - Up = 0x145, - Down = 0x146, + Up = 0x145, + Down = 0x146, // Mouse MouseMotion = 0x147, - - LeftClick = 0x148, MiddleClick = 0x149, RightClick = 0x14A, ScrollUp = 0x14B, ScrollDown = 0x14C, + LeftClick = 0x148, + MiddleClick = 0x149, + RightClick = 0x14A, + ScrollUp = 0x14B, + ScrollDown = 0x14C, }; alias I = Input; @@ -204,7 +212,7 @@ GetEvents(PlatformWindow* window) { Lock(&window.input_mutex); - Inputs* inputs = &window.inputs[window.input_idx]; + Inputs* inputs = &window.inputs[window.input_idx]; window.input_idx = (window.input_idx + 1) % 2; ResetInputs(&window.inputs[window.input_idx]); @@ -381,17 +389,17 @@ import core.sys.linux.sys.inotify; import core.sys.linux.fcntl; import core.sys.posix.sys.time : timespec, timeval, gettimeofday; import core.sys.posix.unistd; -import core.sys.posix.pthread : PThread = pthread_t, - PThreadCond = pthread_cond_t, - PThreadMutex = pthread_mutex_t, - PThreadMutexInit = pthread_mutex_init, - PThreadCondInit = pthread_cond_init, - PThreadCreate = pthread_create, - PThreadMutexLock = pthread_mutex_lock, - PThreadCondWait = pthread_cond_wait, - PThreadMutexUnlock = pthread_mutex_unlock, - PThreadCondSignal = pthread_cond_signal, - PThreadExit = pthread_exit, +import core.sys.posix.pthread : PThread = pthread_t, + PThreadCond = pthread_cond_t, + PThreadMutex = pthread_mutex_t, + PThreadMutexInit = pthread_mutex_init, + PThreadCondInit = pthread_cond_init, + PThreadCreate = pthread_create, + PThreadMutexLock = pthread_mutex_lock, + PThreadCondWait = pthread_cond_wait, + PThreadMutexUnlock = pthread_mutex_unlock, + PThreadCondSignal = pthread_cond_signal, + PThreadExit = pthread_exit, PThreadCondTimedWait = pthread_cond_timedwait; import core.stdc.string : strlen; @@ -584,32 +592,29 @@ PushMotion(Inputs* inputs, i32 rel_x, i32 rel_y, i32 x, i32 y) struct PlatformWindow { - Atom[Atoms.max] atoms; - Display* display; - Window window; - Window root_window; - i32 screen_id; - // xcb_connection_t* conn; - // xcb_screen_t* screen; - // xcb_window_t window; - u32 w; - u32 h; - i32 mouse_prev_x; - i32 mouse_prev_y; - bool locked_cursor; - bool close; - Modifier modifier; + Atom[Atoms.max] atoms; + Display* display; + Window window; + Window root_window; + i32 screen_id; + u32 w; + u32 h; + i32 mouse_prev_x; + i32 mouse_prev_y; + bool locked_cursor; + bool close; + Modifier modifier; - SysThread thread; - MessageQueue msg_queue; - u32 input_idx; - Inputs[2] inputs; - TicketMut input_mutex; + SysThread thread; + MessageQueue msg_queue; + u32 input_idx; + Inputs[2] inputs; + TicketMut input_mutex; - Mut cb_msg_mut; - TicketMut cb_mut; - Selection[CBM.max] selections; - version(linux) u32 cb_transfer_size; + Mut cb_msg_mut; + TicketMut cb_mut; + Selection[CBM.max] selections; + version(linux) u32 cb_transfer_size; }; struct Library @@ -1977,3 +1982,62 @@ version(DLIB_TEST) unittest } +pragma(inline) u64 +RDTSC() +{ + union u64_split + { + u64 full; + struct + { + u32 lower; + u32 upper; + }; + }; + + u64_split val; + + u64_split* valp = &val; + asm + { + cpuid; + rdtsc; + mov R8, valp; + mov valp.upper.offsetof[R8], EDX; + mov valp.lower.offsetof[R8], EAX; + } + + return val.full; +} + +pragma(inline) u64 +OSTimeFreq() +{ + u64 freq; + + version (linux) + { + u64 freq = 1000000; + } + else static assert(false, NO_IMPL); + + return freq; +} + +pragma(inline) u64 +OSTime() +{ + version(linux) + { + import core.sys.linux.sys.time; + timeval value; + gettimeofday(&value, null); + + u64 time = OSTimeFreq() * cast(u64)(value.tv_sec) + cast(u64)(value.tv_usec); + } + else static assert(false, NO_IMPL); + + return time; +} + +} diff --git a/test.sh b/test.sh index a8e7d53..f0bc1fc 100755 --- a/test.sh +++ b/test.sh @@ -2,11 +2,16 @@ name="Test_Runner" +flags="-P-I/usr/include/freetype2 -L-lfreetype" +if [ "$1" == "wasm" ]; then + flags="-mtriple=wasm32-unknown-unknown-wasm --Xcc=-DBUILD_WASM -Iexternal/arsd-webassembly" +fi + /bin/bash ./build.sh build -ldc2 platform.d fonts.d aliases.d math.d util.d alloc.d assets.d external/xxhash/xxhash.d build/libcglm.a -d-version=DLIB_TEST -Xcc=-mno-sse -P-I/usr/include/freetype2 -L-lfreetype --main --unittest -g --of=$name -verrors=8 +ldc2 platform.d fonts.d aliases.d math.d util.d alloc.d assets.d build/libxxhash.a build/libcglm.a build/libprintf.a $flags -d-version=DLIB_TEST --main --unittest -g --of=$name -verrors=50 rm $name.o ./$name -rm $name +#rm $name diff --git a/util.d b/util.d index d93cfd5..40a5acb 100644 --- a/util.d +++ b/util.d @@ -3,35 +3,44 @@ module dlib.util; import dlib.aliases; import dlib.alloc; -import xxhash3; import dlibincludes; -import std.stdio : write, writeln, writef, writefln, stderr; -import std.conv; -import std.string; -import std.traits; +//import std.traits; -import core.stdc.string : memset; -import core.simd; +static if(NativeTarget) +{ + import std.format : sformat; + import std.stdio : write, writeln, writef, writefln, stderr; +} +else +{ -enum bool StringType(T) = (is(T: string) || is(T: u8[]) || is(T: char[]) || is(T: const(char)[])); +} + +enum bool StringType(T) = (is(T: string) || is(T: u8[]) || is(T: char[]) || is(T: const(char)[])); pragma(inline) void Int3() { - debug asm + static if(NativeTarget) { - db 0xCC; + debug asm + { + db 0xCC; + } } } pragma(inline) void Pause() { - asm + static if(NativeTarget) { - rep; - nop; + asm + { + rep; + nop; + } } } @@ -69,37 +78,37 @@ CastArr(T, U)(U[] input_array) return output_array; } -void -Debugf(Args...)(string fmt, Args args) -{ - debug Logf(fmt, args, "[DEBUG]: "); -} - void Logf(string prefix = "INFO ", Args...)(string fmt, Args args) { - try + static if(NativeTarget) { - writef("[%s]: ", prefix); - writefln(fmt, args); - } - catch (Exception e) - { - assert(false, "Incompatible format type"); + try + { + writef("[%s]: ", prefix); + writefln(fmt, args); + } + catch (Exception e) + { + assert(false, "Incompatible format type"); + } } } void Logif(Args...)(string fmt, Args args, string func = __FUNCTION__) { - try + static if(NativeTarget) { - writef("FN: [%s] ", func); - Logf(fmt, args); - } - catch (Exception e) - { - assert(false, "Incompatible format type"); + try + { + writef("FN: [%s] ", func); + Logf(fmt, args); + } + catch (Exception e) + { + assert(false, "Incompatible format type"); + } } } @@ -115,24 +124,10 @@ Errf(Args...)(string fmt, Args args) Logf!("ERROR", Args)(fmt, args); } -static string -AssignStr(T, alias p)() if(StringType!(T)) +void +Debugf(Args...)(string fmt, Args args) { - import std.format : format; - - enum string id = __traits(identifier, p); - string result = ""; - static if(is(T == string)) - { - result = id; - } - else static if(StringType!(T)) - { - result = format("Str(%s)", id); - } - else static assert(false, "Unknown type for AssignItem"); - - return result; + debug Logf(fmt, args, "[DEBUG]: "); } string @@ -146,13 +141,13 @@ Scratchf(Args...)(string fmt, Args args) void Log(string str) { - writeln(str); + static if(NativeTarget) writeln(str); } void Log(char* str) { - writeln(str); + static if(NativeTarget) writeln(str); } @nogc u64 @@ -203,7 +198,7 @@ GetFilePath(string file_name) { string result = file_name; - for(u64 i = file_name.length-1; i64(i) >= 0; i -= 1) + for(usize i = file_name.length-1; i64(i) >= 0; i -= 1) { version(Windows) { @@ -272,7 +267,7 @@ ConcatInPlace(T)(T* list, T* to_concat) list.last = to_concat.last; } - memset(to_concat, 0, T.sizeof); + MemSet(to_concat, 0, T.sizeof); } } @@ -477,7 +472,7 @@ struct KVPair(K, V) struct Result(V) { - V value; + V value; bool ok; } @@ -487,10 +482,10 @@ struct HashTable(K, V) LinkedList!(P) free_lists; LinkedList!(P)[] lists; - P* nil; + P* nil; Arena arena; - u64 node_count; - u64 list_count; + u64 node_count; + u64 list_count; void opIndexAssign(V value, K key) { @@ -640,7 +635,7 @@ Delete(K, V)(HashTable!(K, V)* ht, K key) result.ok = true; result.value = node.value; - memset(&node.value, 0, node.value.sizeof); + MemSet(&node.value, 0, node.value.sizeof); SLLPush(&ht.free_lists, node, ht.nil); @@ -658,110 +653,28 @@ const u64 HASH_SEED = 5995; pragma(inline) u64 Hash(T)(T[] value) { - return xxh3_64bits_withSeed(value.ptr, (T.sizeof * value.length) / u8.sizeof, HASH_SEED); + return XXH3_64bits_withSeed(value.ptr, (T.sizeof * value.length) / u8.sizeof, HASH_SEED); } pragma(inline) u64 Hash(T)(T* value) { - return xxh3_64bits_withSeed(value, T.sizeof / u8.sizeof, HASH_SEED); + return XXH3_64bits_withSeed(value, T.sizeof / u8.sizeof, HASH_SEED); } pragma(inline) u64 Hash(string str) { - return xxh3_64bits_withSeed(str.ptr, str.length, HASH_SEED); -} - -pragma(inline) u64 -Hash(void* ptr_1, u64 len_1, void* ptr_2, u64 len_2) -{ - XXH3_state_t xxh; - XXH3_INITSTATE(&xxh); - xxh3_64bits_reset_withSeed(&xxh, HASH_SEED); - xxh3_64bits_update(&xxh, ptr_1, len_1); - xxh3_64bits_update(&xxh, ptr_2, len_2); - - return xxh3_64bits_digest(&xxh); -} - -pragma(inline) u64 -Hash(T, U)(T value_1, U value_2) if(isArray!(T) && isArray!(U)) -{ - return Hash(value_1.ptr, value_1.length*T.sizeof, value_2.ptr, value_2.length*U.sizeof); -} - -pragma(inline) u64 -Hash(T, U)(T value_1, U value_2) if(isArray!(T) && !isArray!(U)) -{ - return Hash(value_1.ptr, value_1.length*value_1[0].sizeof, &value_2, U.sizeof); -} - -pragma(inline) u64 -Hash(T, U)(T value_1, U value_2) if(!isArray!(T) && !isArray!(U)) -{ - return Hash(&value_1, T.sizeof, &value_2, U.sizeof); -} - -pragma(inline) u64 -RDTSC() -{ - union u64_split - { - u64 full; - struct - { - u32 lower; - u32 upper; - }; - }; - - u64_split val; - u64_split* valp = &val; - asm - { - cpuid; - rdtsc; - mov R8, valp; - mov valp.upper.offsetof[R8], EDX; - mov valp.lower.offsetof[R8], EAX; - } - - return val.full; -} - -pragma(inline) u64 -OSTimeFreq() -{ - version (linux) - { - u64 freq = 1000000; - } - - return freq; -} - -pragma(inline) u64 -OSTime() -{ - version(linux) - { - import core.sys.linux.sys.time; - timeval value; - gettimeofday(&value, null); - - u64 time = OSTimeFreq() * cast(u64)(value.tv_sec) + cast(u64)(value.tv_usec); - } - - return time; + return XXH3_64bits_withSeed(str.ptr, str.length, HASH_SEED); } // TODO: probably needs improvement/testing struct IntervalTimer { - u64 cpu_freq; - u64 interval; - u64 prev; + Timer base; + u64 interval; + + alias base this; } IntervalTimer @@ -773,35 +686,9 @@ CreateFPSTimer(u64 fps) IntervalTimer CreateTimer(u64 ms) { - IntervalTimer timer; + IntervalTimer timer = { base: CreateTimer() }; - u64 ms_to_wait = 50; - - u64 os_freq = OSTimeFreq(); - u64 cpu_start = RDTSC(); - u64 os_start = OSTime(); - u64 os_end = 0; - u64 os_elapsed = 0; - - u64 os_wait_time = os_freq * ms_to_wait / 1000; - - while (os_elapsed < os_wait_time) - { - os_end = OSTime(); - os_elapsed = os_end - os_start; - } - - u64 cpu_end = RDTSC(); - u64 cpu_elapsed = cpu_end - cpu_start; - u64 cpu_freq = 0; - if(os_elapsed) - { - cpu_freq = os_freq * cpu_elapsed / os_elapsed; - } - - timer.cpu_freq = cpu_freq; - timer.interval = cpu_freq*(ms/1000); - timer.prev = RDTSC(); + timer.interval = timer.cpu_freq * (ms/1000); return timer; } @@ -809,18 +696,33 @@ CreateTimer(u64 ms) void ResetTimer(IntervalTimer* t) { - t.prev = RDTSC(); + static if(NativeTarget) + { + t.prev = RDTSC(); + } + else + { + assert(false, NO_IMPL); + } } pragma(inline) bool CheckTimer(IntervalTimer* t) { bool result = false; - u64 time = RDTSC(); - if(time - t.prev > t.interval) + + static if(NativeTarget) { - result = true; - t.prev = time; + u64 time = RDTSC(); + if(time - t.prev > t.interval) + { + result = true; + t.prev = time; + } + } + else + { + assert(false, NO_IMPL); } return result; @@ -835,34 +737,43 @@ struct Timer Timer CreateTimer() { - u64 ms_to_wait = 50; - - u64 os_freq = OSTimeFreq(); - u64 cpu_start = RDTSC(); - u64 os_start = OSTime(); - u64 os_end = 0; - u64 os_elapsed = 0; - - u64 os_wait_time = os_freq * ms_to_wait / 1000; - - while (os_elapsed < os_wait_time) + static if(NativeTarget) { - os_end = OSTime(); - os_elapsed = os_end - os_start; - } + u64 ms_to_wait = 50; - u64 cpu_end = RDTSC(); - u64 cpu_elapsed = cpu_end - cpu_start; - u64 cpu_freq = 0; - if(os_elapsed) + u64 os_freq = OSTimeFreq(); + u64 cpu_start = RDTSC(); + u64 os_start = OSTime(); + u64 os_end = 0; + u64 os_elapsed = 0; + + u64 os_wait_time = os_freq * ms_to_wait / 1000; + + while (os_elapsed < os_wait_time) + { + os_end = OSTime(); + os_elapsed = os_end - os_start; + } + + u64 cpu_end = RDTSC(); + u64 cpu_elapsed = cpu_end - cpu_start; + u64 cpu_freq = 0; + if(os_elapsed) + { + cpu_freq = os_freq * cpu_elapsed / os_elapsed; + } + + Timer timer = { + cpu_freq: cpu_freq, + prev: RDTSC(), + }; + } + else { - cpu_freq = os_freq * cpu_elapsed / os_elapsed; - } + Timer timer; - Timer timer = { - cpu_freq: cpu_freq, - prev: RDTSC(), - }; + assert(false, NO_IMPL); + } return timer; } @@ -870,92 +781,132 @@ CreateTimer() pragma(inline) f32 DeltaTime(Timer* t) { - u64 time = RDTSC(); - u64 step = time - t.prev; - t.prev = time; + u64 time, step; + + static if(NativeTarget) + { + time = RDTSC(); + step = time - t.prev; + t.prev = time; + } + else + { + assert(false, NO_IMPL); + } + return cast(f32)(step) / cast(f32)(t.cpu_freq); } static string -IntToStr(int n) nothrow pure @safe +Replace(string target, u8 find, string replace) { - string result; + assert(__ctfe, "Function can only be run at compile time"); - static immutable string[] table = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"]; - if(n < table.length) + u8[1024] result = '\0'; + + usize count; + foreach(ch; target) { - result = table[n]; - } - else - { - result = to!string(n); + if(ch == find) + { + for(usize i = 0; i < replace.length; i += 1) + { + result[count++] = replace[i]; + } + } + else + { + result[count++] = ch; + } } - return result; + return Str(result[0 .. count]); } static string -GenerateLoop(string format_string, int N)() nothrow pure @safe +Str(T)(T v) if(isIntegral!(T)) { - string result; - for (int i = 0; i < N; i++) + assert(__ctfe); + + if(v == 0) { - result ~= format_string.replace("@", IntToStr(i)); + return "0"; } - return result; + + u8[] slice = new u8[32]; + + enum base = 10; + i32 i = 30; + + for(; v && i; i--, v /= base) + { + slice[i] = r"0123456789"[v%base]; + } + + return Str(slice[i+1 .. $]); } void -MemCpy(void* dst_p, void* src_p, u64 length) +MemSet(void* dst_p, i32 ch, u64 count) @nogc nothrow +{ + u8[] slice = (cast(u8*)dst_p)[0 .. cast(usize)count]; + slice[] = cast(u8)(ch&0xFF); +} + +void +MemCpy(void* dst_p, void* src_p, usize length) { u8* dst = cast(u8*)dst_p; u8* src = cast(u8*)src_p; - u64 remaining = length; - if(remaining >= 64) + usize remaining = length; + version(X86_64) { - for(u64 i = 0; i + 64 < length; i += 64) + if(remaining >= 64) { - asm + for(u64 i = 0; i + 64 < length; i += 64) { - mov R8, src; - mov R9, dst; + asm + { + mov R8, src; + mov R9, dst; - add R8, i; - movdqu XMM0, [R8+00]; - movdqu XMM1, [R8+16]; - movdqu XMM2, [R8+32]; - movdqu XMM3, [R8+48]; - - add R9, i; - movups [R9+00], XMM0; - movups [R9+16], XMM1; - movups [R9+32], XMM2; - movups [R9+48], XMM3; + add R8, i; + movdqu XMM0, [R8+00]; + movdqu XMM1, [R8+16]; + movdqu XMM2, [R8+32]; + movdqu XMM3, [R8+48]; - sub remaining, 64; + add R9, i; + movups [R9+00], XMM0; + movups [R9+16], XMM1; + movups [R9+32], XMM2; + movups [R9+48], XMM3; + + sub remaining, 64; + } } } - } - if(remaining >= 32) - { - for(u64 i = length - remaining; i + 32 < length; i += 32) + if(remaining >= 32) { - asm + for(u64 i = length - remaining; i + 32 < length; i += 32) { - mov R8, src; - mov R9, dst; + asm + { + mov R8, src; + mov R9, dst; - add R8, i; - movdqu XMM0, [R8+00]; - movdqu XMM1, [R8+16]; + add R8, i; + movdqu XMM0, [R8+00]; + movdqu XMM1, [R8+16]; - add R9, i; - movdqu [R9+00], XMM0; - movdqu [R9+16], XMM1; + add R9, i; + movdqu [R9+00], XMM0; + movdqu [R9+16], XMM1; - sub remaining, 32; + sub remaining, 32; + } } } } @@ -966,11 +917,15 @@ MemCpy(void* dst_p, void* src_p, u64 length) } } -u8[] -Embed(string file_name) +void +Assign(T, Args...)(T slice, Args args) if(isArray!(T)) { - import std.file; - return cast(u8[])read(file_name); + assert(slice.length == Args.length); + + static foreach(i; 0 .. Args.length) + { + slice[i] = cast(typeof(slice[0]))args[i]; + } } version(DLIB_TEST) unittest @@ -1089,7 +1044,8 @@ version(DLIB_TEST) unittest assert(list.first != null && list.last != null); - TestDLList(&list, [0, 1, 2, 3, 4]); + u32[5] test1 = [0, 1, 2, 3, 4]; + TestDLList(&list, test1); u32 count = 0; u32[3] res1 = [0, 2, 4]; @@ -1116,7 +1072,6 @@ version(DLIB_TEST) unittest } { // MemCpy - import std.conv; u8[777] bytes; u8[777] test_bytes; @@ -1145,7 +1100,6 @@ version(DLIB_TEST) unittest { if(v != bytes[count]) { - Logf("Failed %d %d %d", i, v, bytes[count]); assert(false); } @@ -1193,9 +1147,9 @@ version(DLIB_TEST) unittest u64 val_1 = 555555; u32 val_2 = 33333; - u64 v1 = Hash(arr_1, arr_2); - u64 v2 = Hash(arr_1, val_1); - u64 v3 = Hash(val_1, val_2); + u64 v1 = Hash(arr_1); + u64 v2 = Hash(arr_2); + u64 v3 = Hash(&val_1); assert(v1 > 0); assert(v2 > 0); @@ -1204,7 +1158,7 @@ version(DLIB_TEST) unittest { // Casts u8[] arr = CastStr!(u8)("Test"); - char[] char_arr = ['a', 'b']; + char[2] char_arr = ['a', 'b']; arr = CastArr!(u8)(char_arr); } @@ -1213,4 +1167,33 @@ version(DLIB_TEST) unittest string str = Scratchf("%s testing %s", 55, "testing"); assert(str == "55 testing testing"); } + + { + u64[555] slice = 53321; + + MemSet(slice.ptr, 0, u64.sizeof*slice.length); + + for(u64 i = 0; i < slice.length; i += 1) + { + assert(slice[i] == 0); + } + + MemSet(slice.ptr, 5, u64.sizeof*slice.length); + + u64 cmp = cast(u64)( + cast(u64)(5) << 56 | + cast(u64)(5) << 48 | + cast(u64)(5) << 40 | + cast(u64)(5) << 32 | + cast(u64)(5) << 24 | + cast(u64)(5) << 16 | + cast(u64)(5) << 8 | + cast(u64)(5) << 0 + ); + + for(u64 i = 0; i < slice.length; i += 1) + { + assert(slice[i] == cmp); + } + } } diff --git a/wasm/wasm.js b/wasm/wasm.js new file mode 100644 index 0000000..e69de29